o2o-negosium-original/agent/tests/test_h5_harness.py
hbyang 1682481b01 [feat] agent: 협상 고도화 — LLM 표현/이해층 + 카드 전술 실행계층 + ktcommerce 정리
카드 재설계("멘트 카드 → 전술 카드"):
- tactics.py 신설: 카드번호→전술(카운터 산식) 레지스트리, min(counter,target) 클램프
- 카운터 수락=즉시 타결(pending_counter_price 일반화, 구 offer_1pct 흡수)
- 목표가 초과 타결 금지(성공스텝 진입 가드) + "카드 소진=실패" 폐지→종결 국면
- 선택형 와일드카드(WC-*) 발동 + card.wild_cards 멘트 DB 어댑터

LLM 계층:
- Phase 2 표현층 ScriptNaturalizer(카드 멘트 자연화, 마커·치환자·숫자 보존 검증)
- Phase 3 이해층 InputInterpreter(자유발화 NLU→기대입력, 한국어 가격 파서)
- OPENAI_API_KEY env override(server_configs) + 전역 자격증명 게이트

결정 스택(Phase 1):
- 협상 규칙 데이터화(negotiation.wildcard_*_ratio/max_counter_rounds)
- 선택카드 우선순위 prior(UCB 방문수 감쇠, Q-table 오염 없음)

버그픽스:
- 인하율 음수 표기 제거 + 인상/동일/인하 구분(discount_phrase)
- 자연화 강조마커 보존(볼드/색 소실 시 원본 폴백)
- 카드 시드 가격변수(prev_partner_price·target_mid_price·middle_price 등) 치환

정리:
- ktcommerce 테넌트 삭제 + 테스트 21파일 imarketkorea/_base 로 마이그레이션
- 실 LLM 호출 차단 conftest 가드

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:37:36 +09:00

60 lines
2.3 KiB
Python

"""H5 검증 — 학습 검증 하네스 (PoC 본체).
핵심 판정(계획서 G): 학습형(qtable_ucb)이 random/static 대비 성과 우상향.
- 평균보상 우위(95%CI 분리) + '좋은 카드' 적중 우위 → 학습 루프 유효.
- 시뮬레이터 결정론(동일 seed 재현).
- 멀티테넌트 분리 실행(--tenant).
"""
import pytest
from eval_harness.buyer import HeuristicBuyer, Scenario, make_card_effectiveness
from eval_harness.runner import run
from eval_harness.simulator import run_episode
from negotiation.qtable.domain.model.q_table import QTable
from negotiation.policies.qtable_policy import UCBQTablePolicy
from tenancy.config import RewardConfig, StateConfig
def test_simulator_deterministic():
eff = make_card_effectiveness(9, seed=1)
scn = Scenario(anchor_price=800, target_price=1000)
sc, rc = StateConfig(), RewardConfig()
def one():
b = HeuristicBuyer(eff, seed=7)
p = UCBQTablePolicy(QTable(sc.state_space_size, 9))
return run_episode(p, b, scn, sc, rc, 9, learn=False).total_reward
assert one() == one() # 동일 seed → 동일 결과
def test_card_effectiveness_has_good_cards():
eff = make_card_effectiveness(9, seed=42, n_good=3)
good = [a for a, e in eff.items() if e >= 0.7]
assert len(good) >= 3 # 효과 좋은 카드 존재 → 학습 대상 신호
@pytest.mark.parametrize("tenant", ["_base", "imarketkorea"])
def test_learning_beats_baseline(tenant):
report = run("configs/exp_default.yaml", tenant)
pols = report["policies"]
q = pols["qtable_ucb"]
rnd = pols["random"]
# 평균보상 우위 + 95%CI 비중첩
assert q["mean_reward"] > rnd["mean_reward"]
assert q["mean_reward"] - q["reward_ci95"] > rnd["mean_reward"] + rnd["reward_ci95"]
# 좋은 카드 적중 우위 (학습으로 카드 우열 파악)
assert q["good_card_hit_rate"] >= rnd["good_card_hit_rate"] + 0.2
assert q["good_card_hit_rate"] >= 0.7
# 종합 판정
assert report["verdict"]["pass"] is True
def test_static_does_not_learn():
report = run("configs/exp_default.yaml", "imarketkorea")
static = report["policies"]["static"]
# 정적 정책은 항상 고정 카드 → 좋은카드 적중 학습 없음(우연 일치만)
assert static["good_card_hit_rate"] <= report["policies"]["qtable_ucb"]["good_card_hit_rate"]