"""사장님 에이전트 대화 — 빌더 화면의 입구. ★ 카카오톡 웹훅이 생겨도 이 파일은 안 바뀐다. 런타임이 채널을 모르고, 웹훅은 그저 같은 `runtime.chat()` 을 부르는 두 번째 입구가 된다(docs/AGENT.md). """ from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Response from pydantic import BaseModel, Field from common.models.gmodel import UserInfo from router.v1.validator.dependencies import IsValidAccessToken from services.agent import runtime from services.agent.runtime import AgentError router = APIRouter(prefix="/v1/agent", tags=["Agent"]) _STATUS = { "PLACE_NOT_FOUND": 404, "AGENT_NOT_CONFIGURED": 409, "AGENT_UNKNOWN_TOOL": 409, "AGENT_EMPTY_MESSAGE": 400, "AGENT_MESSAGE_TOO_LONG": 400, "AGENT_CALL_FAILED": 502, } class Confirm(BaseModel): """직전 답의 확인 버튼이 그대로 돌려보내는 값. ★ 서버는 이 값을 믿지 않는다 — 도구 이름은 레지스트리에서 다시 찾고, 인자는 도구가 다시 검증한다. 확인 절차가 오히려 검증을 건너뛰는 구멍이 되면 안 된다.""" tool: str = Field(min_length=1, max_length=40) args: dict = {} class Req_Chat(BaseModel): message: str = Field(default="", max_length=runtime.MAX_MESSAGE) confirm: Confirm | None = None @router.get("/status") async def status(response: Response, user: UserInfo = Depends(IsValidAccessToken)): """대화창을 열 수 있는지. 키가 없으면 화면은 자리를 두고 입력만 죽인다.""" response.headers["Cache-Control"] = "no-store" return {"enabled": runtime.is_configured()} @router.post("/chat/{place_id}") async def chat( place_id: UUID, req: Req_Chat, response: Response, user: UserInfo = Depends(IsValidAccessToken), ): response.headers["Cache-Control"] = "no-store" response.headers["Referrer-Policy"] = "no-referrer" try: return await runtime.chat( user, str(place_id), req.message, confirm=req.confirm.model_dump() if req.confirm else None, ) except AgentError as ex: raise HTTPException(_STATUS.get(str(ex), 409), str(ex)) from ex