feat:客户agent以及记忆模块功能开发

This commit is contained in:
2026-09-11 22:38:15 +08:00
parent 0197ac2ef2
commit 4da4775e8c
39 changed files with 2508 additions and 14 deletions
+123
View File
@@ -0,0 +1,123 @@
"""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": []})
+7
View File
@@ -37,3 +37,10 @@ async def require_knowledge_operator(user: SysUser = Depends(get_current_user))
if user.user_type != "EMPLOYEE" or user.employee_role not in KNOWLEDGE_OPERATOR_ROLES:
raise ForbiddenError("仅运营人员可以管理知识库")
return user
async def require_customer(user: SysUser = Depends(get_current_user)) -> SysUser:
"""Allow only authenticated customer accounts to use client Agent APIs."""
if user.user_type != "CUSTOMER":
raise ForbiddenError("仅客户用户可以访问 client_agent")
return user
+2 -1
View File
@@ -3,7 +3,7 @@
"""
from fastapi import APIRouter
from api.chat import customer_agent, knowledge
from api.chat import client_agent, customer_agent, knowledge
from api.routers import product, questionnaire
from api.routers import account, auth, holdings, purchase, redeem
@@ -14,6 +14,7 @@ api_router.include_router(holdings.router, prefix="/api", tags=["持仓"])
api_router.include_router(purchase.router, prefix="/api", tags=["交易"])
api_router.include_router(redeem.router, prefix="/api", tags=["交易"])
api_router.include_router(customer_agent.router, prefix="/api/agent/customer", tags=["客服Agent"])
api_router.include_router(client_agent.router, prefix="/api/agent/client", tags=["ClientAgent"])
api_router.include_router(knowledge.router, prefix="/api/knowledge", tags=["知识库"])
api_router.include_router(product.router, prefix="/api", tags=["产品"])
api_router.include_router(questionnaire.router, prefix="/api", tags=["问卷"])