- 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>
66 lines
3.6 KiB
Python
66 lines
3.6 KiB
Python
"""chat 라우터 프로토콜 (대화형 /chat, P7 슬라이스)."""
|
|
|
|
from typing import List, Optional
|
|
|
|
from pydantic import Field
|
|
|
|
from common.models.gmodel import Req_WebPacketProtocol, Res_WebPacketProtocol
|
|
|
|
|
|
class Req_Chat(Req_WebPacketProtocol):
|
|
"""대화 한 턴. session_id 없으면 새 협상 시작. tenant 는 헤더로만.
|
|
|
|
협상 컨텍스트(rq_type/목표가/앵커링가/품목가/매출액/유통코드/파트너 유형)는 요청에 싣지
|
|
않는다 — 세션 시작 시 agent 가 DB 에서 1회 조회해 확정한다(NegotiationContextLoader):
|
|
negotiation.sessions(qt_type·target_price·anchoring_price), partner.items(price),
|
|
partner.suppliers(total_revenue), quotation.quotations(supplier_type), 상품별 협력사 수.
|
|
행이 없으면(데모/테스트 직접 호출) 기본값 폴백.
|
|
가격 수용률은 세션 내 라운드별 제시가로 매 턴 동적 계산: max(0, (첫 제시가−현재가)/첫 제시가).
|
|
"""
|
|
|
|
session_id: Optional[str] = Field(None, description="없으면 새 세션 생성. 운영 경로는 negotiation.sessions.session_id 를 그대로 사용")
|
|
user_input: Optional[str] = Field(None, description="버튼 선택 텍스트 또는 가격(price 모드)")
|
|
# ① desync 감지: backend 가 보는 직전 봇 step(내부 step 또는 표시 step). 없으면 검사 생략.
|
|
# agent 는 자기 세션 step 을 정답으로 보고 진행하되, 불일치 시 경고 로깅하고 응답에 desynced 를 실어
|
|
# backend/front 가 agent 응답의 step/client_step 으로 리싱크하게 한다.
|
|
client_step: Optional[str] = Field(None, description="backend 가 본 직전 봇 step (desync 감지용)")
|
|
|
|
|
|
class Res_Chat(Res_WebPacketProtocol):
|
|
session_id: Optional[str] = None
|
|
step: Optional[str] = None
|
|
client_step: Optional[str] = None
|
|
script: Optional[str] = None
|
|
input_mode: Optional[str] = None
|
|
input_options: Optional[List[str]] = None
|
|
chat_end: bool = False
|
|
outcome: Optional[str] = None
|
|
# ① step desync 신호: 요청 client_step 이 agent 세션 step 과 달랐음을 알린다(agent step 이 정답).
|
|
desynced: bool = False
|
|
# 가격협상 턴에서 선택된 협상 카드 + 학습 메타
|
|
card_id: Optional[str] = None
|
|
# ⑦ 표현 계약: 가격협상 턴은 카드 스크립트를 script 로, 협상지표 게이지를 indicator_value 로 내려보낸다.
|
|
# backend/front 가 이미 indicator/bot_chat_type 패스스루·게이지 렌더 준비 완료.
|
|
bot_chat_type: Optional[str] = None # 가격협상="indicator", 종료폼=summaryRSP/CM 등. 일반 텍스트는 None.
|
|
indicator_value: Optional[float] = None # 협상 성공률 1~99 (가격협상 턴)
|
|
indicator_range: Optional[str] = None # PZ1|PZ2|PZ3 (가격 구간)
|
|
policy: Optional[str] = None
|
|
q_value: Optional[float] = None
|
|
updated_q: Optional[float] = None
|
|
visit_count: Optional[int] = None
|
|
reward_total: Optional[float] = None
|
|
# 성공 확정 이후 턴(협상완료 요약·협상종료)에 내려주는 합의가. 와일드카드 1% 인하 수락 등
|
|
# 유저가 직접 입력하지 않은 가격으로 타결될 수 있어, backend 요약/입찰가는 이 값을 최우선 사용한다.
|
|
settled_price: Optional[int] = None
|
|
|
|
|
|
class Res_ChatSession(Res_WebPacketProtocol):
|
|
"""① 세션 상태 조회 응답. backend 가 타임아웃/재진입 시 정합을 맞출 때 사용."""
|
|
|
|
session_id: Optional[str] = None
|
|
step: Optional[str] = None
|
|
client_step: Optional[str] = None
|
|
rq_type: Optional[str] = None
|
|
ended: bool = False
|
|
found: bool = False
|