Files
ZNJJ-api-server/src/core/fastgpt_client.py
T

103 lines
3.8 KiB
Python
Raw Normal View History

"""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
from ..agent.graph import build_graph
from ..backends.chat import ChatBackend
from ..backends.fastgpt import FastGPTBackend
from ..backends.langgraph import LangGraphBackend
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
_chat_backend: ChatBackend | None = None
_langgraph_graph = None
_app_service: AccidentAppService | None = None
2026-01-08 16:22:25 +08:00
# 两后端缺省 60 秒、最多额外重试 3 次,1/2/4 秒退避。
# FastGPT SDK 的 max_retries 实为总尝试数,注入时转换为 1 + 重试次数。
# LLM 关闭 SDK 自动重试;流式发出文本后不再重试。
# LLM 预算按单次模型调用计算,FastGPT timeout 为 HTTP SDK 超时,
# 均不承诺多次模型调用的整个聊天回合具有相同总期限。
FASTGPT_TIMEOUT_SECONDS = 60.0
FASTGPT_MAX_RETRIES = 3
2026-01-08 16:22:25 +08:00
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Manage chat backend resource lifecycle."""
global _chat_backend, _fastgpt_client
2026-01-08 16:22:25 +08:00
Config.validate()
llm = None
2026-07-28 08:41:34 +08:00
if Config.AGENT_BACKEND == "langgraph":
# 作业 2:LangGraph fake 模式不要求 FastGPT 密钥。
# TODO(作业 2): 若语音仍启用,需在此单独初始化并校验 FastGPT 配置。
from ..agent.llm import build_llm_service
global _langgraph_graph, _app_service
_langgraph_graph = build_graph()
llm = build_llm_service()
_chat_backend = LangGraphBackend(_langgraph_graph, llm=llm)
# 作业 6:应用服务 + 内存 repository(作业 7 换 PostgreSQL)。
_app_service = AccidentAppService(
_chat_backend, InMemorySessionRepository()
)
else:
_fastgpt_client = AsyncChatClient(
api_key=Config.FASTGPT_API_KEY,
base_url=Config.FASTGPT_BASE_URL,
timeout=FASTGPT_TIMEOUT_SECONDS,
max_retries=FASTGPT_MAX_RETRIES + 1,
retry_delay=1.0,
enable_logging=Config.DEBUG,
)
await _fastgpt_client.__aenter__()
_chat_backend = FastGPTBackend(_fastgpt_client)
try:
yield
finally:
_chat_backend = None
_langgraph_graph = None
_app_service = None
if llm is not None and hasattr(llm, "aclose"):
await llm.aclose()
if _fastgpt_client:
await _fastgpt_client.__aexit__(None, None, None)
_fastgpt_client = None
2026-01-08 16:22:25 +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
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
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