o2o-negosium-original/AGENT_INTEGRATION.md
2026-06-18 17:02:12 +09:00

82 lines
5.0 KiB
Markdown

# 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`)
```json
{
"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_id` 가 `negotiation.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)로 확장 협의.
## 4-바. agent 측 확정 회신 (4-4 tenant 키 형식) ✅
- **tenant 키 = 견적 작성자(갑) 회사 `company_id`(uuid 문자열)**. backend 가 `X-Tenant-ID` 에 그대로 넣으면 된다.
- **`_base`** 는 공유 베이스 정책 예약어(직접 보내지 말 것).
- **미등록 company_id 자동 온보딩**: 전용 `tenants/<id>/tenant.yaml` 이 없어도 agent 가 `_base` 설정으로
엔진을 만들고(기본 9카드·162 state), 첫 협상에서 **base warm-start(cold-start)** 로 학습 시작한다.
→ backend 는 회사를 agent 에 사전 등록할 필요 없이 company_id 만 보내면 된다.
- 헤더 부재 시 400, 빈 값도 미등록(400). (그 외 present company_id 는 모두 수용)
## 5. mock → 실제 전환 체크리스트
- [x] backend `config.local.toml` → `[AgentConfig] use_mock=false` (전환 완료 — agent 미기동 시 1402 로 graceful degrade 확인)
- [x] backend `httpx` 의존 설치(`requirements.txt` 반영됨)
- [x] 위 4-1 ~ 4-3 반영 (agent 측) — session_id honoring(req.session_id 그대로 사용)·tenant 헤더·신규 컨텍스트(target/anchor/rq_type) 완료
- [x] tenant_id 형식 확정(4-4) — **company_id(uuid)**, backend 는 `chat_service._agent_context` 의 `tenant_id` 를 `quotation.user_id → company.users.company_id` 로 채우면 됨
- [ ] agent(9500) 기동 후 양 서버 라이브 E2E (agent 단독 /chat 은 검증 완료)
> 로컬에서 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`