"""Anonymous customer-service Agent HTTP endpoints.""" from __future__ import annotations import json from fastapi import APIRouter, Request from fastapi.responses import JSONResponse, StreamingResponse from service.customer_agent.chat import QueryTooLongError from agent.customer_agent.session import SessionOwnershipError from utils.request_id import get_request_id, new_request_id from utils.response import fail, success router = APIRouter() def _runtime(request: Request): runtime = getattr(request.app.state, "customer_agent_runtime", None) if runtime is None: raise RuntimeError("customer agent runtime is not configured") return runtime @router.post("/session/create") async def create_session(request: Request): runtime = _runtime(request) session_id = await runtime.session_service.create_session() return success({"session_id": session_id, "customer_id": None}) @router.get("/chat") async def chat(request: Request, session_id: str, query: str): runtime = _runtime(request) trace_id = request.headers.get("X-Trace-Id") or get_request_id() or new_request_id() try: await runtime.session_service.verify_session_ownership(session_id) retry_after = await runtime.session_service.consume_chat_quota(session_id) if retry_after is not None: response = fail(429, "请求过于频繁", {"retry_after": retry_after}) return JSONResponse( status_code=429, headers={"Retry-After": str(retry_after)}, content=response.model_dump(), ) result = await runtime.agent.handle(session_id, query, trace_id=trace_id) except SessionOwnershipError as exc: return JSONResponse( status_code=exc.code, content=fail(exc.code, exc.message).model_dump(), ) except QueryTooLongError as exc: return JSONResponse( status_code=400, content=fail(400, str(exc)).model_dump(), ) async def events(): yield f"data: {json.dumps(result, ensure_ascii=False)}\n\n" return StreamingResponse( events(), media_type="text/event-stream", headers={"X-Trace-Id": trace_id} ) @router.post("/session/end") async def end_session(request: Request, body: dict): runtime = _runtime(request) session_id = body.get("session_id", "") try: await runtime.session_service.verify_session_ownership(session_id) except SessionOwnershipError as exc: return JSONResponse( status_code=exc.code, content=fail(exc.code, exc.message).model_dump(), ) if hasattr(runtime.redis, "delete"): await runtime.redis.delete( f"session:{session_id}", f"session:{session_id}:messages", f"rate:limit:anon:{session_id}:chat", ) return success({"session_id": session_id, "archived": False})