# -*- coding: utf-8 -*- """ 对话式数据解读接口 —— 统一入口,SSE 流式 根据用户意图自动路由到不同智能体: - 需风量计算/管控平台查询 → needq_calc_agent - 配风计划审查(上传PDF) → 审查管线 - 数据解读/其他 → dialog_agent(默认) """ from fastapi import APIRouter, File, Form, UploadFile from fastapi.responses import StreamingResponse from api.sse_core import sse_event_generator from api.intent import classify_intent from agents.vent_agent import get_dialog_agent from agents.needq_agent import get_needq_calc_agent from agents.review_agent import stream_review from db.chat_store import get_messages, create_session, update_session_title router = APIRouter() @router.post("/chat") async def chat( message: str = Form(...), session_id: str | None = Form(default=None), thread_id: str | None = Form(default=None), file: UploadFile | None = File(default=None), ): """统一对话接口(SSE 流式)—— 支持文本对话和 PDF 配风计划审查。 根据用户意图自动路由到不同智能体: - 需风量计算/管控平台查询 → needq_calc_agent - 配风计划审查(上传PDF) → 审查管线(form-reviewer + data-checker + calc-verifier) - 数据解读/其他 → dialog_agent(默认) 请求体: multipart/form-data - message: 用户消息(必填) - session_id: 可选,会话ID - thread_id: 可选,LangGraph 线程ID - file: 可选,附件(PDF/文档等) 响应: SSE 流(text/event-stream) """ # 会话管理 session_id = session_id or create_session() thread_id = thread_id or session_id config = {"configurable": {"thread_id": thread_id}} # 提取附件文件名(如有) filename = file.filename if file else None # 自动设置会话标题(取前30字) messages = get_messages(session_id) if len(messages) <= 1: title = message[:30] + ("..." if len(message) > 30 else "") update_session_title(session_id, title) # ── Agent 推理意图(含附件文件名辅助判断)── intent = await classify_intent(message, filename) # ── 按意图路由 ── if intent == "review" and file is not None: # 配风计划审查 + 有文件 → 审查管线 print(f"[路由] 意图: 配风计划审查 → stream_review (file={filename})") return StreamingResponse( stream_review(file, message, session_id), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no", "X-Session-Id": session_id, }, ) elif intent == "review" and file is None: # 审查意图但无文件 → dialog 提示用户上传 print(f"[路由] 意图: 配风计划审查 → 提示用户上传 PDF") agent = get_dialog_agent() agent_cn_name = "通风对话助手" message = f"用户想进行配风计划审查,但未上传附件。请提示用户上传配风计划PDF文件。用户原始消息:{message}" elif intent == "needq_calc": agent = get_needq_calc_agent() agent_cn_name = "需风量计算专家" print(f"[路由] 意图: 需风量计算 → needq_calc_agent") # 如有附件但非审查意图,将文件名信息追加到消息中 if filename: message = f"用户上传了文件「{filename}」。{message}" else: agent = get_dialog_agent() agent_cn_name = "通风对话助手" print(f"[路由] 意图: 数据解读 → dialog_agent") # 如有附件但非审查意图,将文件名信息追加到消息中 if filename: message = f"用户上传了文件「{filename}」。{message}" return StreamingResponse( sse_event_generator(agent, message, thread_id, session_id, config, agent_cn_name), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no", "X-Session-Id": session_id, }, )