124 lines
4.0 KiB
Python
124 lines
4.0 KiB
Python
"""Logged-in client Agent HTTP endpoints.
|
|||
|
|
|
||
|
|
Authentication and customer-owned session enforcement are added in C2.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
|
||
|
|
from fastapi import APIRouter, Depends, Request
|
||
|
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||
|
|
|
||
|
|
from agent.client_agent.session import SessionOwnershipError
|
||
|
|
from api.deps import require_customer
|
||
|
|
from model.sys_user import SysUser
|
||
|
|
from service.customer_agent.chat import QueryTooLongError
|
||
|
|
from utils.request_id import get_request_id, new_request_id
|
||
|
|
from utils.response import fail, success
|
||
|
|
|
||
|
|
|
||
|
|
router = APIRouter()
|
||
|
|
|
||
|
|
|
||
|
|
def _runtime(request: Request):
|
||
|
|
"""Return the client Agent runtime installed by the application lifespan."""
|
||
|
|
runtime = getattr(request.app.state, "client_agent_runtime", None)
|
||
|
|
if runtime is None:
|
||
|
|
raise RuntimeError("client agent runtime is not configured")
|
||
|
|
return runtime
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/session/create")
|
||
|
|
async def create_session(
|
||
|
|
request: Request,
|
||
|
|
user: SysUser = Depends(require_customer),
|
||
|
|
):
|
||
|
|
"""Create a client Agent session during the architecture bootstrap phase."""
|
||
|
|
runtime = _runtime(request)
|
||
|
|
session_id = await runtime.session_service.create_session(user.id)
|
||
|
|
return success({"session_id": session_id, "customer_id": user.id})
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/chat")
|
||
|
|
async def chat(
|
||
|
|
request: Request,
|
||
|
|
body: dict,
|
||
|
|
user: SysUser = Depends(require_customer),
|
||
|
|
):
|
||
|
|
"""Reuse the existing customer Agent chat orchestration."""
|
||
|
|
session_id = body.get("session_id", "")
|
||
|
|
query = body.get("query", "")
|
||
|
|
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, customer_id=user.id
|
||
|
|
)
|
||
|
|
retry_after = await runtime.session_service.consume_chat_quota(
|
||
|
|
session_id, customer_id=user.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,
|
||
|
|
customer_id=user.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,
|
||
|
|
user: SysUser = Depends(require_customer),
|
||
|
|
):
|
||
|
|
"""Close the client Agent session during the architecture bootstrap phase."""
|
||
|
|
runtime = _runtime(request)
|
||
|
|
session_id = body.get("session_id", "")
|
||
|
|
try:
|
||
|
|
await runtime.session_service.verify_session_ownership(
|
||
|
|
session_id, customer_id=user.id
|
||
|
|
)
|
||
|
|
except SessionOwnershipError as exc:
|
||
|
|
return JSONResponse(
|
||
|
|
status_code=exc.code,
|
||
|
|
content=fail(exc.code, exc.message).model_dump(),
|
||
|
|
)
|
||
|
|
warnings = await runtime.memory_service.close_session(
|
||
|
|
customer_id=user.id,
|
||
|
|
session_id=session_id,
|
||
|
|
)
|
||
|
|
if warnings:
|
||
|
|
return success({"session_id": session_id, "archived": False, "warnings": warnings})
|
||
|
|
if hasattr(runtime.redis, "delete"):
|
||
|
|
await runtime.redis.delete(
|
||
|
|
f"session:{session_id}",
|
||
|
|
f"session:{session_id}:messages",
|
||
|
|
f"rate:limit:client:{user.id}:{session_id}:chat",
|
||
|
|
)
|
||
|
|
return success({"session_id": session_id, "archived": True, "warnings": []})
|