o2o-negosium-original/AGENT_INTEGRATION.md
민헌 dcce82a630 feat(backend): 협상 채팅(chat) API + agent 위임
- /v1/negotiation/sessions/{id}/chat/{init,messages,send} 추가
- agent(9500) 위임 어댑터(IAgentClient) + mock(use_mock) 격리 → agent 미연동 시 1402 graceful degrade
- negotiation.chats 메시지 영속화(meta JSONB) + 종료 시 세션 입찰/거부 확정(단일 트랜잭션)
- 동시전송 가드(유저 메시지 pre-claim/CHAT_IN_PROGRESS) + 실패 시 롤백, 마감/만료 분기, init 만료 정리
- ChatSender enum, chat 에러코드(1400~1403), chats ORM 모델, AgentConfig
- 테스트 10건(test_chat.py), AGENT_INTEGRATION.md 연동 규약 문서

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:24:57 +09:00

4.1 KiB

Chat 연동 규약 (backend ↔ agent)

대상: agent(9500) 담당자. backend(9300)가 채팅 한 턴을 agent POST /v1/chat 으로 위임한다. backend/frontend 개발은 이 규약을 가정하고 완료했고, 현재는 AgentConfig.use_mock=true 로 내장 mock 을 쓴다. agent 가 준비되면 아래 항목을 맞춘 뒤 backend config.local.toml[AgentConfig] use_mock=false 로 전환하면 된다.

1. 호출 흐름

프론트(5173) → backend(9300) /v1/negotiation/sessions/{id}/chat/send → agent(9500) POST /v1/chat
  • 인증·소유권·견적마감·가격범위 검증, 말풍선 영속화(negotiation.chats), 종료 시 세션 입찰확정은 backend 책임.
  • 협상 로직(스텝 전이·카드선택·학습)은 agent 책임. backend 는 agent 응답을 그대로 말풍선으로 저장/전달한다.

2. backend → agent 요청 (POST /v1/chat)

{
  "session_id": "<negotiation.sessions.session_id>",
  "rq_type": "재협상 | 재견적",
  "user_input": "<버튼 텍스트 또는 가격문자열, 첫 턴(오프닝)은 null>",
  "target_price": 100000,
  "anchor_price": 99000
}

헤더: X-Tenant-ID: <견적(갑) 회사 company_id>

3. agent → backend 응답 (Res_Chat) ↔ 프론트 ChatMessage 매핑

agent 필드 backend/프론트
session_id 세션 키
step step
client_step display_step
script script (말풍선 텍스트)
input_mode next_input_mode (confirm·yes_no·percent·price·delivery_type)
input_options next_input_type (버튼 라벨 배열)
chat_end chat_end
outcome "success"=협상완료(DONE)+입찰가 확정 / 그 외=협상거부(REJECTED)
card_id (저장만, 표시 범위 외)

4. agent 쪽에서 맞춰줘야 하는 항목 ⚠️

  1. session_id honoring — 첫 턴에 backend 가 보낸 session_id(우리 negotiation.sessions.session_id)를 새 uuid 발급 없이 그대로 세션 키로 사용해야 한다.
    • 현재 agent/services/chat_service.py 는 새 세션 생성 시 session_id=str(uuid.uuid4()) 로 무시한다 → req.session_id 우선 사용하도록 수정 필요.
    • 이미 learning.experience_logs.session_idnegotiation.sessions 를 가리키도록 설계돼 있어 agent 입장에서도 올바른 방향.
  2. tenant 헤더 — backend 가 X-Tenant-ID = company_id 로 보낸다. agent 의 TenantMiddleware 가 이 키로 엔진 해석.
  3. 신규 세션 컨텍스트target_price/anchor_price/rq_type 를 backend 가 견적 데이터로 채워 보낸다(기본값 의존 X).
  4. tenant_id 정밀 해석(backend 측 TODO와 짝) — 현재 backend 는 X-Tenant-ID 를 빈 값으로 보낸다. 정확히는 견적 작성자(갑) 회사 company_id 여야 하며, quotation.user_id → company.users.company_id 조회로 채울 예정. agent 가 기대하는 tenant 키 형식(company_id uuid 문자열 / _base)을 확정해주면 backend 가 맞춘다.
  5. (범위 외) indicator / summary / reject — 이번 범위 미포함. agent 응답에 협상지표·최종요약·거부폼이 생기면 backend ChatMessage 의 예약 필드(indicator_value/bot_chat_type/summary)로 확장 협의.

5. mock → 실제 전환 체크리스트

  • backend config.local.toml[AgentConfig] use_mock=false (전환 완료 — agent 미기동 시 1402 로 graceful degrade 확인)
  • backend httpx 의존 설치(requirements.txt 반영됨)
  • 위 4-1 ~ 4-3 반영 (agent 측)
  • tenant_id 해석(4-4) 합의 후 backend chat_service._agent_contexttenant_id 채우기
  • agent(9500) 기동 후 양 서버 라이브 E2E

로컬에서 agent 없이 mock 으로 개발하려면 환경변수로 덮는다: AGENT_USE_MOCK=true

6. 참고 (backend 구현 위치)

  • agent 어댑터: backend/services/agent_client.py (IAgentClient / Http / Mock)
  • 오케스트레이션: backend/services/chat_service.py
  • 계약(프로토콜): backend/router/v1/negotiation/chat_protocol.py
  • 엔드포인트: backend/router/v1/negotiation/chat.py