o2o-negosium-original/agent/tests/test_agent_integration.py
hbyang ed175c5b65 [feat] agent: Req_Chat 슬림화 — 협상 컨텍스트를 DB 조회로 전환 + CRUD 계층 도입
- Req_Chat 을 session_id/user_input/client_step 3필드로 축소 — rq_type·목표가·앵커·품목가·
  매출액·유통코드·파트너 유형·수용률 필드 전부 제거
- NegotiationContextLoader 신설: 세션 시작 시 공유 DB 1회 조회로 컨텍스트 확정
  · rq_type = sessions.qt_type ({1,3}→재협상 / {2,4}→재견적)
  · anchor = sessions.anchoring_price(박제) — NULL 이면 무할인 폴백 anchor=target (v1.2 정책 승계)
  · 매출액 = suppliers.total_revenue(KTC 미러), 유통코드 = quotations.supplier_type 매핑
  · 파트너 유형 = 상품별 distinct supplier 수 → PartnerType enum(0=NONE/1=SINGLE/2=MULTIPLE)
- 가격 수용률은 세션 내 동적 계산: max(0, (첫 제시가−현재가)/첫 제시가)
- DB 쿼리를 backend crud 패턴으로 분리: INegoContextCRUD(ABC)+NegoContextCRUD,
  IChatSessionRepository 인터페이스 추가 (테스트 더블 주입 가능)
- 와일드카드 1% 수락 시 합의가=offer_1pct 반영 + Res_Chat.settled_price 신설 —
  backend 요약/입찰가가 이를 최우선 사용 (19,800원 수락이 20,000원으로 기록되던 버그 수정)
- backend: agent 전송 바디 3필드로 축소, 앵커/파트너 조회 메서드 제거,
  test_anchoring_chat 을 새 구조로 재작업(박제 소비/폴백 검증은 agent 테스트로 이관)
- 데모 페이지(/demo·negotiation_demo.html) 제거 — 컨텍스트 주입 경로 폐지로 무의미
- 테스트: agent 83/83, backend 57/57 (컨텍스트 로더 실데이터 왕복 4종 + CRUD 더블 검증 포함)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 09:44:21 +09:00

71 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""AGENT_INTEGRATION.md 규약 검증 (backend ↔ agent).
4-1 session_id honoring: backend 가 보낸 session_id(=negotiation.sessions.session_id)를
새 uuid 발급 없이 그대로 세션 키로 사용.
4-4 tenant 키 = company_id(uuid): 미등록 company_id 는 _base 자동 온보딩(cold-start) 으로 수용.
"""
import os
import pytest
from router.v1.chat.protocol import Req_Chat
from services.chat_service import ChatService, reset_sessions
from negotiation.chat.service.chat_session_repository import ChatSessionRepository
from tenancy.config_loader import TenantConfigLoader
from tenancy.registry import TenantEngineRegistry
_TENANTS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "tenants")
# backend 가 보내는 값 형식: session_id = negotiation.sessions.session_id(uuid), X-Tenant-ID = company_id(uuid)
BACKEND_SESSION_ID = "11111111-1111-1111-1111-111111111111"
COMPANY_ID = "00000000-0000-0000-0000-000000000099"
def _reg():
return TenantEngineRegistry(loader=TenantConfigLoader(tenants_dir=_TENANTS_DIR, cache_ttl_seconds=0))
@pytest.mark.asyncio
async def test_4_1_honors_backend_session_id(db_engine):
reset_sessions()
eng = await _reg().get_engine("ktcommerce")
svc = ChatService()
# 첫 턴: backend 의 session_id 를 그대로 키로 써야 함 (새 uuid 발급 X)
# 컨텍스트는 DB 조회(행 없음 → 기본값 폴백: target=10000, anchor=9900)
r = await svc.chat(eng, Req_Chat(session_id=BACKEND_SESSION_ID))
assert r.session_id == BACKEND_SESSION_ID
assert r.step == "서비스안내"
# 같은 session_id 로 이어가면 그 세션이 DB 에서 복원됨
r2 = await svc.chat(eng, Req_Chat(session_id=BACKEND_SESSION_ID, user_input="확인"))
assert r2.session_id == BACKEND_SESSION_ID and r2.step == "담당자확인"
# learning.chat_sessions 에 backend 의 session_id 그대로 저장됨
saved = await ChatSessionRepository(eng.company_id).get(BACKEND_SESSION_ID)
assert saved is not None and saved.session_id == BACKEND_SESSION_ID
@pytest.mark.asyncio
async def test_4_4_company_id_auto_onboard():
# 미등록 company_id → _base 자동 온보딩 (전용 tenant.yaml 없이도 엔진 생성)
eng = await _reg().get_engine(COMPANY_ID)
assert eng.tenant_id == COMPANY_ID
assert eng.company_id == COMPANY_ID # 학습/세션이 이 company_id 로 격리
assert eng.action_space_size == 9 # base 기본 카드(162×9 정합)
assert eng.state_space_size == 162
@pytest.mark.asyncio
async def test_4_4_company_id_chat_end_to_end(db_engine):
reset_sessions()
eng = await _reg().get_engine(COMPANY_ID) # 자동 온보딩 테넌트
svc = ChatService()
r = await svc.chat(eng, Req_Chat(session_id=BACKEND_SESSION_ID))
assert r.session_id == BACKEND_SESSION_ID and r.step == "서비스안내"
# 카드선택 턴까지 진행 → company_id 스코프로 학습 기록
for ui in ["확인", "예", "확인", "11000", "예"]:
r = await svc.chat(eng, Req_Chat(session_id=BACKEND_SESSION_ID, user_input=ui))
assert r.step == "가격협상" and r.card_id is not None