2026-07-26 07:25:24 +08:00
|
|
|
|
"""FastGPT client and chat backend dependency injection."""
|
2026-01-08 16:22:25 +08:00
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
|
|
from fastapi import FastAPI
|
|
|
|
|
|
from fastgpt_client import AsyncChatClient
|
2026-10-19 14:29:31 +08:00
|
|
|
|
from ..agent.graph import build_graph
|
2026-07-26 07:25:24 +08:00
|
|
|
|
from ..backends.chat import ChatBackend
|
|
|
|
|
|
from ..backends.fastgpt import FastGPTBackend
|
2026-10-19 14:29:31 +08:00
|
|
|
|
from ..backends.langgraph import LangGraphBackend
|
2026-10-22 20:50:10 +08:00
|
|
|
|
from ..services.app_service import AccidentAppService
|
|
|
|
|
|
from ..services.repository import InMemorySessionRepository
|
2026-01-08 16:22:25 +08:00
|
|
|
|
from .config import Config
|
|
|
|
|
|
|
2026-07-28 08:41:34 +08:00
|
|
|
|
|
2026-01-08 16:22:25 +08:00
|
|
|
|
_fastgpt_client: AsyncChatClient | None = None
|
2026-07-26 07:25:24 +08:00
|
|
|
|
_chat_backend: ChatBackend | None = None
|
2026-10-20 14:53:46 +08:00
|
|
|
|
_langgraph_graph = None
|
2026-10-22 20:50:10 +08:00
|
|
|
|
_app_service: AccidentAppService | None = None
|
2026-01-08 16:22:25 +08:00
|
|
|
|
|
2026-10-22 22:31:28 +08:00
|
|
|
|
# 后端超时/重试 SLA(两后端对齐;契约测试防止再漂移)。
|
|
|
|
|
|
# FastGPT 侧:客户端每次请求总超时 + 重试次数;
|
|
|
|
|
|
# LangGraph 侧:LLM 每次调用总超时 + 重试次数(退避为指数式,
|
|
|
|
|
|
# 与 FastGPT 固定 1s 不同——总尝试次数与单次超时可比,见
|
|
|
|
|
|
# test/backends/test_sla_alignment.py)。
|
|
|
|
|
|
FASTGPT_TIMEOUT_SECONDS = 60.0
|
|
|
|
|
|
FASTGPT_MAX_RETRIES = 3
|
|
|
|
|
|
|
2026-01-08 16:22:25 +08:00
|
|
|
|
|
|
|
|
|
|
@asynccontextmanager
|
|
|
|
|
|
async def lifespan(app: FastAPI):
|
2026-10-19 14:29:31 +08:00
|
|
|
|
"""Manage chat backend resource lifecycle."""
|
2026-07-26 07:25:24 +08:00
|
|
|
|
global _chat_backend, _fastgpt_client
|
2026-01-08 16:22:25 +08:00
|
|
|
|
Config.validate()
|
2026-07-28 08:41:34 +08:00
|
|
|
|
|
2026-10-19 14:29:31 +08:00
|
|
|
|
if Config.AGENT_BACKEND == "langgraph":
|
|
|
|
|
|
# 作业 2:LangGraph fake 模式不要求 FastGPT 密钥。
|
|
|
|
|
|
# TODO(作业 2): 若语音仍启用,需在此单独初始化并校验 FastGPT 配置。
|
2026-10-20 14:53:46 +08:00
|
|
|
|
from ..agent.llm import build_llm_service
|
|
|
|
|
|
|
2026-10-22 20:50:10 +08:00
|
|
|
|
global _langgraph_graph, _app_service
|
2026-10-21 15:11:16 +08:00
|
|
|
|
_langgraph_graph = build_graph()
|
|
|
|
|
|
_chat_backend = LangGraphBackend(_langgraph_graph, llm=build_llm_service())
|
2026-10-22 20:50:10 +08:00
|
|
|
|
# 作业 6:应用服务 + 内存 repository(作业 7 换 PostgreSQL)。
|
|
|
|
|
|
_app_service = AccidentAppService(
|
|
|
|
|
|
_chat_backend, InMemorySessionRepository()
|
|
|
|
|
|
)
|
2026-10-19 14:29:31 +08:00
|
|
|
|
else:
|
|
|
|
|
|
_fastgpt_client = AsyncChatClient(
|
|
|
|
|
|
api_key=Config.FASTGPT_API_KEY,
|
|
|
|
|
|
base_url=Config.FASTGPT_BASE_URL,
|
2026-10-22 22:31:28 +08:00
|
|
|
|
timeout=FASTGPT_TIMEOUT_SECONDS,
|
|
|
|
|
|
max_retries=FASTGPT_MAX_RETRIES,
|
2026-10-19 14:29:31 +08:00
|
|
|
|
retry_delay=1.0,
|
|
|
|
|
|
enable_logging=Config.DEBUG,
|
|
|
|
|
|
)
|
|
|
|
|
|
await _fastgpt_client.__aenter__()
|
|
|
|
|
|
_chat_backend = FastGPTBackend(_fastgpt_client)
|
2026-07-26 07:25:24 +08:00
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
yield
|
|
|
|
|
|
finally:
|
|
|
|
|
|
_chat_backend = None
|
2026-10-20 14:53:46 +08:00
|
|
|
|
_langgraph_graph = None
|
2026-10-22 20:50:10 +08:00
|
|
|
|
_app_service = None
|
2026-07-26 07:25:24 +08:00
|
|
|
|
if _fastgpt_client:
|
|
|
|
|
|
await _fastgpt_client.__aexit__(None, None, None)
|
|
|
|
|
|
_fastgpt_client = None
|
2026-01-08 16:22:25 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-10-20 14:53:46 +08:00
|
|
|
|
async def delete_chat_session(thread_id: str) -> None:
|
|
|
|
|
|
"""langgraph 模式下删除会话:清理图 checkpoint(对照计划作业 6 的
|
|
|
|
|
|
完整语义——业务字段、事件、路由持久化清理在接入 repository 后补齐)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
if _langgraph_graph is None:
|
|
|
|
|
|
raise RuntimeError("LangGraph backend not initialized")
|
|
|
|
|
|
checkpointer = getattr(_langgraph_graph, "checkpointer", None)
|
|
|
|
|
|
if checkpointer is not None and hasattr(checkpointer, "delete_thread"):
|
|
|
|
|
|
checkpointer.delete_thread(thread_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-08 16:22:25 +08:00
|
|
|
|
def get_fastgpt_client() -> AsyncChatClient:
|
2026-07-28 10:38:01 +08:00
|
|
|
|
"""Get the initialized FastGPT client."""
|
2026-01-08 16:22:25 +08:00
|
|
|
|
if _fastgpt_client is None:
|
|
|
|
|
|
raise RuntimeError("FastGPT client not initialized")
|
|
|
|
|
|
return _fastgpt_client
|
2026-07-26 07:25:24 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-10-22 20:50:10 +08:00
|
|
|
|
def get_app_service() -> AccidentAppService:
|
|
|
|
|
|
"""Get the initialized application service(langgraph 模式)。"""
|
|
|
|
|
|
if _app_service is None:
|
|
|
|
|
|
raise RuntimeError("Application service not initialized")
|
|
|
|
|
|
return _app_service
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-26 07:25:24 +08:00
|
|
|
|
def get_chat_backend() -> ChatBackend:
|
|
|
|
|
|
"""Get the backend-neutral chat service."""
|
|
|
|
|
|
if _chat_backend is None:
|
|
|
|
|
|
raise RuntimeError("Chat backend not initialized")
|
|
|
|
|
|
return _chat_backend
|