|
@@ -0,0 +1,372 @@
|
|
|
|
|
+# AI问答模块 API 调用链路(详细版)
|
|
|
|
|
+
|
|
|
|
|
+本文档从“前端发起一次 AI 问答”开始,梳理本项目 AI 问答模块涉及的 **入站接口(前端 → shudao-chat-py)** 与 **出站接口(shudao-chat-py → 外部服务)**,并按不同交互场景给出完整调用链路与时序图。
|
|
|
|
|
+
|
|
|
|
|
+## 0. 名词与范围
|
|
|
|
|
+
|
|
|
|
|
+- **前端**:`shudao-vue-frontend`
|
|
|
|
|
+- **后端**:`shudao-chat-py`(FastAPI)
|
|
|
|
|
+- **aichat**:独立服务 `shudao-aichat`(本项目通过代理/兼容路由调用)
|
|
|
|
|
+- **搜索服务**:RAG 检索服务(本项目通过 HTTP 调用)
|
|
|
|
|
+- **Dify 工作流**:在线搜索链路使用
|
|
|
|
|
+- **Qwen3**:主模型接口(OpenAI 风格 `/v1/chat/completions`)
|
|
|
|
|
+- **DeepSeek**:备用模型(Qwen3 异常时回退)
|
|
|
|
|
+- **4A Auth**:外部 token 校验接口
|
|
|
|
|
+
|
|
|
|
|
+## 1. 入站接口清单(前端 → shudao-chat-py)
|
|
|
|
|
+
|
|
|
|
|
+下表只列 AI 问答相关;其他业务接口(考试工坊/PPT/隐患识别等)不在本文范围内。
|
|
|
|
|
+
|
|
|
|
|
+| 场景 | 方法&路径(前缀 `/apiv1`) | 作用 | 前端调用点(示例) | 后端实现 |
|
|
|
|
|
+|---|---|---|---|---|
|
|
|
|
|
+| 主流式问答(写 DB) | `POST /stream/chat-with-db` | SSE 流式输出 + 落库 | [Chat.vue](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/views/Chat.vue#L2398-L2498) | [stream_chat_with_db](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1491-L1804) |
|
|
|
|
|
+| 非流式问答 | `POST /send_deepseek_message` | 一次性返回完整回答 | [Chat.vue](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/views/Chat.vue#L2512-L2630)、[apis.js](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/request/apis.js#L17-L19) | [send_deepseek_message](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L893-L1183) |
|
|
|
|
|
+| 历史(列表/详情) | `GET /get_history_record` | `ai_conversation_id=0` 返回会话列表;`>0` 返回消息列表 | [apis.js](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/request/apis.js#L38-L40) | [get_history_record](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1185-L1266) |
|
|
|
|
|
+| 删除对话/消息 | `POST /delete_conversation` | 软删除会话或某条消息对 | [apis.js](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/request/apis.js#L71-L76) | [delete_conversation](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1268-L1333) |
|
|
|
|
|
+| 删除历史(会话级) | `POST /delete_history_record` | 软删除会话主记录 | [apis.js](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/request/apis.js#L74-L76) | [delete_history_record](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1335-L1353) |
|
|
|
|
|
+| 猜你想问 | `POST /guess_you_want` | 基于某条 AI 消息生成 3 个关联问题并落库 | [apis.js](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/request/apis.js#L116-L118) | [guess_you_want](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1811-L1885) |
|
|
|
|
|
+| 在线搜索 | `GET /online_search?question=...` | Qwen 提炼关键词 → Dify 工作流 → 返回摘要 | [apis.js](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/request/apis.js#L101-L105) | [online_search](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1891-L1942) |
|
|
|
|
|
+| 保存在线搜索结果 | `POST /save_online_search_result` | 保存到 `AIMessage.search_source` | [apis.js](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/request/apis.js#L104-L106) | [save_online_search_result](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1944-L1970) |
|
|
|
|
|
+| 意图识别(独立) | `POST /intent_recognition` | 只做意图识别(可选写 DB) | [apis.js](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/request/apis.js#L107-L109) | [intent_recognition](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1976-L2072) |
|
|
|
|
|
+| 点赞/点踩 | `POST /like_and_dislike` | 保存 `AIMessage.user_feedback` 并处理积分 | [apis.js](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/request/apis.js#L58-L60) | [like_and_dislike](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/total.py#L226-L275) |
|
|
|
|
|
+| 报告兼容 SSE(Go 对齐) | `POST /report/complete-flow` | SSE:可能代理到 aichat;失败则降级本地 | [Chat.vue](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/views/Chat.vue#L3744-L3830) | [complete_flow](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/report_compat.py#L187-L234) |
|
|
|
|
|
+| 停止报告 SSE | `POST /sse/stop` | 停止 SSE(外部 token 代理到 aichat) | [stopSSEStream](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/utils/api.js#L82-L140) | [stop_sse](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/report_compat.py#L302-L337) |
|
|
|
|
|
+| 回写 AI 消息 | `POST /report/update-ai-message` | 前端将整理后的内容回写到 DB(外部 token 代理到 aichat) | [updateAIMessageContent](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/utils/api.js#L147-L205) | [update_ai_message](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/report_compat.py#L246-L299) |
|
|
|
|
|
+| 附件解析 | `POST /attachments/parse` | 文件上传解析(代理到 aichat) | [parseAttachment](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/request/apis.js#L27-L29) | [parse_attachment](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/report_compat.py#L236-L244) |
|
|
|
|
|
+
|
|
|
|
|
+## 2. 出站接口清单(shudao-chat-py → 外部服务)
|
|
|
|
|
+
|
|
|
|
|
+| 目标服务 | 调用点 | 典型接口 | 用途 |
|
|
|
|
|
+|---|---|---|---|
|
|
|
|
|
+| 4A Auth | [verify_external_token](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/utils/token.py#L175-L228) | `POST settings.auth.api_url` | 校验外部 token,解析 account 信息 |
|
|
|
|
|
+| 搜索服务(RAG) | [_rag_search](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L529-L553) | `POST settings.search.api_url` | 向量检索/知识库检索,拼接上下文 |
|
|
|
|
|
+| Qwen3(主模型) | [QwenService.chat](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/services/qwen_service.py#L117-L207)、[QwenService.stream_chat](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/services/qwen_service.py#L208-L256) | `POST {settings.qwen3.api_url}/v1/chat/completions` | 主问答生成与流式输出 |
|
|
|
|
|
+| 意图识别模型 | [intent_recognition](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/services/qwen_service.py#L73-L116) | `POST {settings.intent.api_url}/v1/chat/completions` | 识别 greeting/faq/知识库查询 等 |
|
|
|
|
|
+| DeepSeek(备用模型) | [DeepSeekService.chat](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/services/deepseek_service.py#L24-L48)、[stream_chat](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/services/deepseek_service.py#L50-L89) | `POST {settings.deepseek.api_url}/v1/chat/completions` | Qwen3 失败时回退 |
|
|
|
|
|
+| Dify 工作流(在线搜索) | [online_search](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1891-L1942) | `POST settings.dify.workflow_url` | 关键词 → 工作流 → 摘要 |
|
|
|
|
|
+| aichat 服务(代理) | [AIChatProxy](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/services/aichat_proxy.py#L13-L207) | `POST {settings.aichat.api_url}/*` | 兼容 Go 版报告 SSE、附件解析、停止、回写等 |
|
|
|
|
|
+
|
|
|
|
|
+## 3. 认证与用户注入(所有链路的共同前置)
|
|
|
|
|
+
|
|
|
|
|
+### 3.1 中间件入口
|
|
|
|
|
+
|
|
|
|
|
+后端在 [combined_middleware](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/main.py#L33-L107) 内做:
|
|
|
|
|
+
|
|
|
|
|
+1. 从 `Authorization` / `token` / `Token` 读取 token
|
|
|
|
|
+2. 调用 [verify_token](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/utils/token.py#L230-L243)
|
|
|
|
|
+3. 校验成功后写入 `request.state.user`
|
|
|
|
|
+4. 路由层统一通过 `request.state.user` 获取当前用户
|
|
|
|
|
+
|
|
|
|
|
+### 3.2 外部 token 的校验链路
|
|
|
|
|
+
|
|
|
|
|
+外部 token:
|
|
|
|
|
+
|
|
|
|
|
+1. `POST settings.auth.api_url` 验证 token 合法性
|
|
|
|
|
+2. 从返回数据取 `accountID / name / role / exp`
|
|
|
|
|
+3. 调用 [_resolve_external_user_id](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/utils/token.py#L149-L173) 映射到本地 `UserData.id`(用于复用 `AIConversation/AIMessage`)
|
|
|
|
|
+
|
|
|
|
|
+## 4. 链路 A:主流式问答(`POST /stream/chat-with-db`)
|
|
|
|
|
+
|
|
|
|
|
+这是“边生成边显示 + 保留历史”的标准链路。
|
|
|
|
|
+
|
|
|
|
|
+### 4.1 请求形态
|
|
|
|
|
+
|
|
|
|
|
+请求体(核心字段):
|
|
|
|
|
+
|
|
|
|
|
+```json
|
|
|
|
|
+{
|
|
|
|
|
+ "message": "用户问题",
|
|
|
|
|
+ "ai_conversation_id": 0,
|
|
|
|
|
+ "business_type": 0,
|
|
|
|
|
+ "online_search_content": ""
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+响应:`text/event-stream`,每条事件形态为:
|
|
|
|
|
+
|
|
|
|
|
+- 首次:`data: {"type":"initial","ai_conversation_id":123,"ai_message_id":456}\n\n`
|
|
|
|
|
+- 中间:`data: <文本chunk>\n\n`(chunk 内换行会被转义为 `\\n`)
|
|
|
|
|
+- 结束:`data: [DONE]\n\n`
|
|
|
|
|
+
|
|
|
|
|
+实现见 [stream_chat_with_db](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1491-L1804)。
|
|
|
|
|
+
|
|
|
|
|
+### 4.2 详细调用链路(含 DB 与外部 API)
|
|
|
|
|
+
|
|
|
|
|
+1. 前端 → `POST /apiv1/stream/chat-with-db`
|
|
|
|
|
+2. 中间件:
|
|
|
|
|
+ - 校验 token(本地 / 外部 4A)
|
|
|
|
|
+ - 写入 `request.state.user`
|
|
|
|
|
+3. 路由进入 `stream_chat_with_db`:
|
|
|
|
|
+ - 创建/复用 `AIConversation`
|
|
|
|
|
+ - 写入 `AIMessage(type=user)`
|
|
|
|
|
+ - 写入 `AIMessage(type=ai, content='')` 占位
|
|
|
|
|
+ - SSE 推送 `initial`(返回会话/消息 ID)
|
|
|
|
|
+4. 触发 RAG:
|
|
|
|
|
+ - `POST settings.search.api_url`,请求体 `{"query": message, "n_results": top_k}`
|
|
|
|
|
+ - 将结果拼成 `rag_context`
|
|
|
|
|
+5. 拼装 Prompt:
|
|
|
|
|
+ - `final_answer`(含 userMessage、rag_context、historyContext、可选 online_search_content)
|
|
|
|
|
+6. 调用主模型流式生成:
|
|
|
|
|
+ - `POST {settings.qwen3.api_url}/v1/chat/completions`(`stream=true`)
|
|
|
|
|
+ - 若 Qwen3 上游错误或网络错误,回退到 DeepSeek 流式接口
|
|
|
|
|
+7. 可选:思考过程摘要(二次模型调用)
|
|
|
|
|
+ - 若流中出现 `<think>...</think>` 且摘要开关启用,会调用 [summarize_thinking_content](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/utils/thinking_summary.py#L321-L397)
|
|
|
|
|
+ - 该摘要本质上是一次 `qwen_service.chat()`(仍会触发 DeepSeek 回退逻辑)
|
|
|
|
|
+8. SSE 持续返回给前端
|
|
|
|
|
+9. 流结束后回写数据库:
|
|
|
|
|
+ - 更新 `AIMessage(type=ai).content = full_response`
|
|
|
|
|
+ - 更新 `AIConversation.updated_at / business_type / content 预览`
|
|
|
|
|
+10. SSE 返回 `[DONE]`
|
|
|
|
|
+
|
|
|
|
|
+### 4.3 时序图(主流式问答)
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ participant FE as 前端
|
|
|
|
|
+ participant MW as 中间件
|
|
|
|
|
+ participant AU as 4A Auth
|
|
|
|
|
+ participant RT as chat.py:/stream/chat-with-db
|
|
|
|
|
+ participant DB as MySQL
|
|
|
|
|
+ participant SS as 搜索服务
|
|
|
|
|
+ participant QW as Qwen3
|
|
|
|
|
+ participant DS as DeepSeek(回退)
|
|
|
|
|
+
|
|
|
|
|
+ FE->>MW: POST /apiv1/stream/chat-with-db
|
|
|
|
|
+ MW->>AU: (可选) POST settings.auth.api_url
|
|
|
|
|
+ AU-->>MW: valid + account
|
|
|
|
|
+ MW-->>RT: request.state.user
|
|
|
|
|
+
|
|
|
|
|
+ RT->>DB: upsert AIConversation
|
|
|
|
|
+ RT->>DB: insert user AIMessage
|
|
|
|
|
+ RT->>DB: insert ai placeholder AIMessage
|
|
|
|
|
+ RT-->>FE: SSE data: {"type":"initial",...}
|
|
|
|
|
+
|
|
|
|
|
+ RT->>SS: POST settings.search.api_url
|
|
|
|
|
+ SS-->>RT: docs
|
|
|
|
|
+ RT->>QW: POST /v1/chat/completions (stream=true)
|
|
|
|
|
+ alt Qwen3 失败
|
|
|
|
|
+ RT->>DS: POST /v1/chat/completions (stream=true)
|
|
|
|
|
+ DS-->>RT: chunk...
|
|
|
|
|
+ else Qwen3 正常
|
|
|
|
|
+ QW-->>RT: chunk...
|
|
|
|
|
+ end
|
|
|
|
|
+ RT-->>FE: SSE chunk...
|
|
|
|
|
+ RT->>DB: update ai_message.content
|
|
|
|
|
+ RT->>DB: update conversation snapshot
|
|
|
|
|
+ RT-->>FE: SSE [DONE]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 5. 链路 B:非流式问答(`POST /send_deepseek_message`,`business_type=0`)
|
|
|
|
|
+
|
|
|
|
|
+这条链路更像“同步 RPC”:一次请求直接拿到完整回答。
|
|
|
|
|
+
|
|
|
|
|
+### 5.1 调用链路(AI问答分支)
|
|
|
|
|
+
|
|
|
|
|
+实现入口见 [send_deepseek_message](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L893-L1183)。
|
|
|
|
|
+
|
|
|
|
|
+1. 前端 → `POST /apiv1/send_deepseek_message`
|
|
|
|
|
+2. 中间件鉴权,写入 `request.state.user`
|
|
|
|
|
+3. 创建/复用 `AIConversation`
|
|
|
|
|
+4. 意图识别:
|
|
|
|
|
+ - 调用 [QwenService.intent_recognition](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/services/qwen_service.py#L73-L116)
|
|
|
|
|
+ - 出站:`POST {settings.intent.api_url}/v1/chat/completions`
|
|
|
|
|
+5. 若意图命中“知识库查询/技术咨询”,触发 RAG:
|
|
|
|
|
+ - `POST settings.search.api_url`
|
|
|
|
|
+6. 组装 `final_answer` Prompt
|
|
|
|
|
+7. 非流式生成:
|
|
|
|
|
+ - `POST {settings.qwen3.api_url}/v1/chat/completions`(`stream=false`)
|
|
|
|
|
+ - Qwen3 可回退 DeepSeek(见 [QwenService.chat](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/services/qwen_service.py#L117-L207))
|
|
|
|
|
+8. 可选:思考过程摘要(再次调用 `qwen_service.chat()`)
|
|
|
|
|
+9. 返回 `{statusCode: 200, data: { reply/content/... } }`
|
|
|
|
|
+
|
|
|
|
|
+### 5.2 时序图(非流式问答)
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ participant FE as 前端
|
|
|
|
|
+ participant MW as 中间件
|
|
|
|
|
+ participant RT as chat.py:/send_deepseek_message
|
|
|
|
|
+ participant IM as Intent模型
|
|
|
|
|
+ participant SS as 搜索服务
|
|
|
|
|
+ participant QW as Qwen3
|
|
|
|
|
+ participant DS as DeepSeek(回退)
|
|
|
|
|
+ participant DB as MySQL
|
|
|
|
|
+
|
|
|
|
|
+ FE->>MW: POST /apiv1/send_deepseek_message
|
|
|
|
|
+ MW-->>RT: request.state.user
|
|
|
|
|
+ RT->>DB: upsert AIConversation
|
|
|
|
|
+ RT->>IM: POST intent /v1/chat/completions
|
|
|
|
|
+ IM-->>RT: intent_type
|
|
|
|
|
+ opt 命中知识库查询
|
|
|
|
|
+ RT->>SS: POST settings.search.api_url
|
|
|
|
|
+ SS-->>RT: docs
|
|
|
|
|
+ end
|
|
|
|
|
+ RT->>QW: POST qwen3 /v1/chat/completions (stream=false)
|
|
|
|
|
+ alt Qwen3 失败
|
|
|
|
|
+ RT->>DS: POST deepseek /v1/chat/completions
|
|
|
|
|
+ DS-->>RT: answer
|
|
|
|
|
+ else 正常
|
|
|
|
|
+ QW-->>RT: answer
|
|
|
|
|
+ end
|
|
|
|
|
+ RT-->>FE: JSON response(一次性)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 6. 链路 C:报告兼容 SSE(`POST /report/complete-flow`)
|
|
|
|
|
+
|
|
|
|
|
+这条链路是为了对齐 Go 版本接口格式,前端在“报告生成模式”会调用它(见 [Chat.vue](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/views/Chat.vue#L3744-L3830))。
|
|
|
|
|
+
|
|
|
|
|
+后端入口: [complete_flow](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/report_compat.py#L187-L234)
|
|
|
|
|
+
|
|
|
|
|
+### 6.1 两种路由策略(关键)
|
|
|
|
|
+
|
|
|
|
|
+兼容路由会根据 token 类型决定“走 aichat 代理”还是“本地降级”:
|
|
|
|
|
+
|
|
|
|
|
+- **外部 token(非本地 token)**:优先代理到 aichat
|
|
|
|
|
+ 判断逻辑: [should_proxy_to_aichat](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/report_compat.py#L39-L46)
|
|
|
|
|
+- **本地 token** 或 **代理失败**:降级为本地流式 `POST /stream/chat-with-db` 再转换为兼容事件流
|
|
|
|
|
+ 降级逻辑: [fallback_to_local_stream](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/report_compat.py#L93-L185)
|
|
|
|
|
+
|
|
|
|
|
+### 6.2 外部 token:代理到 aichat
|
|
|
|
|
+
|
|
|
|
|
+链路:
|
|
|
|
|
+
|
|
|
|
|
+1. 前端 → `POST /apiv1/report/complete-flow`(SSE)
|
|
|
|
|
+2. `report_compat.py` 构造转发请求体,规范化 `ai_conversation_id`(新会话必须是 0)
|
|
|
|
|
+3. 调用代理: [aichat_proxy.proxy_sse](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/services/aichat_proxy.py#L32-L95)
|
|
|
|
|
+4. 后端将 aichat SSE 原样转发给前端(chunk 级别)
|
|
|
|
|
+
|
|
|
|
|
+其中 aichat 的 base URL 来自:
|
|
|
|
|
+
|
|
|
|
|
+- `settings.aichat.api_url`(见 [AIChatConfig](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/utils/config.py#L66-L69))
|
|
|
|
|
+
|
|
|
|
|
+### 6.3 本地 token:降级走本地流式并“翻译事件”
|
|
|
|
|
+
|
|
|
|
|
+链路:
|
|
|
|
|
+
|
|
|
|
|
+1. 前端 → `POST /apiv1/report/complete-flow`(SSE)
|
|
|
|
|
+2. 进入 `fallback_to_local_stream`
|
|
|
|
|
+3. 后端内部再次发起 HTTP 调用:
|
|
|
|
|
+ - `POST http://127.0.0.1:{settings.app.port}/apiv1/stream/chat-with-db`
|
|
|
|
|
+4. 从本地 SSE 中解析:
|
|
|
|
|
+ - 读取 `initial` 获取 `ai_conversation_id / ai_message_id`
|
|
|
|
|
+ - 将后续 chunk 累积为 `full_response`
|
|
|
|
|
+5. 当收到 `[DONE]` 时,输出兼容事件:
|
|
|
|
|
+ - `data: {"type":"online_answer","content": full_response, ...}\n\n`
|
|
|
|
|
+ - `data: {"type":"completed"}\n\n`
|
|
|
|
|
+
|
|
|
|
|
+### 6.4 时序图(报告兼容 SSE)
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+sequenceDiagram
|
|
|
|
|
+ participant FE as 前端
|
|
|
|
|
+ participant RC as report_compat:/report/complete-flow
|
|
|
|
|
+ participant AP as aichat_proxy
|
|
|
|
|
+ participant AC as aichat 服务
|
|
|
|
|
+ participant LC as 本地 chat-with-db
|
|
|
|
|
+
|
|
|
|
|
+ FE->>RC: POST /apiv1/report/complete-flow (SSE)
|
|
|
|
|
+ alt 外部 token
|
|
|
|
|
+ RC->>AP: proxy_sse("/report/complete-flow")
|
|
|
|
|
+ AP->>AC: POST {settings.aichat.api_url}/report/complete-flow (SSE)
|
|
|
|
|
+ AC-->>AP: SSE chunk...
|
|
|
|
|
+ AP-->>FE: SSE chunk...(原样转发)
|
|
|
|
|
+ else 本地 token 或代理失败
|
|
|
|
|
+ RC->>LC: POST http://127.0.0.1:{port}/apiv1/stream/chat-with-db (SSE)
|
|
|
|
|
+ LC-->>RC: SSE initial + chunk... + [DONE]
|
|
|
|
|
+ RC-->>FE: data: {"type":"online_answer",...}
|
|
|
|
|
+ RC-->>FE: data: {"type":"completed"}
|
|
|
|
|
+ end
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 7. 在线搜索链路(`GET /online_search` + `POST /save_online_search_result`)
|
|
|
|
|
+
|
|
|
|
|
+在线搜索用于补充回答来源或做联网摘要。
|
|
|
|
|
+
|
|
|
|
|
+### 7.1 `GET /online_search`
|
|
|
|
|
+
|
|
|
|
|
+实现入口: [online_search](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1891-L1942)
|
|
|
|
|
+
|
|
|
|
|
+调用链路:
|
|
|
|
|
+
|
|
|
|
|
+1. 前端 → `GET /apiv1/online_search?question=...`
|
|
|
|
|
+2. 后端用 Qwen 提炼关键词:
|
|
|
|
|
+ - [extract_keywords](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/services/qwen_service.py#L45-L71)
|
|
|
|
|
+3. 后端调用 Dify workflow:
|
|
|
|
|
+ - `POST settings.dify.workflow_url`
|
|
|
|
|
+ - `Authorization: Bearer settings.dify.auth_token`
|
|
|
|
|
+4. 返回 `{keywords, result}` 给前端
|
|
|
|
|
+
|
|
|
|
|
+### 7.2 `POST /save_online_search_result`
|
|
|
|
|
+
|
|
|
|
|
+实现入口: [save_online_search_result](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1944-L1970)
|
|
|
|
|
+
|
|
|
|
|
+调用链路:
|
|
|
|
|
+
|
|
|
|
|
+1. 前端提交 `{ai_message_id, search_result}`
|
|
|
|
|
+2. 后端更新 `AIMessage.search_source`
|
|
|
|
|
+
|
|
|
|
|
+## 8. “猜你想问”链路(`POST /guess_you_want`)
|
|
|
|
|
+
|
|
|
|
|
+实现入口: [guess_you_want](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/chat.py#L1811-L1885)
|
|
|
|
|
+
|
|
|
|
|
+调用链路:
|
|
|
|
|
+
|
|
|
|
|
+1. 前端提交 `{ai_message_id}`
|
|
|
|
|
+2. 后端读取该条 AI 消息内容
|
|
|
|
|
+3. 后端加载 `guess_questions` prompt 并调用 `qwen_service.chat()`
|
|
|
|
|
+4. 将结果解析为 `questions[]`,写入 `AIMessage.guess_you_want`
|
|
|
|
|
+5. 返回 `{ai_message_id, questions}`
|
|
|
|
|
+
|
|
|
|
|
+## 9. 附件解析链路(`POST /attachments/parse`)
|
|
|
|
|
+
|
|
|
|
|
+实现入口: [parse_attachment](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/report_compat.py#L236-L244)
|
|
|
|
|
+
|
|
|
|
|
+调用链路:
|
|
|
|
|
+
|
|
|
|
|
+1. 前端上传文件(multipart/form-data)
|
|
|
|
|
+2. 后端校验 `request.state.user` 不为空
|
|
|
|
|
+3. 代理到 aichat:
|
|
|
|
|
+ - [proxy_upload](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/services/aichat_proxy.py#L147-L192)
|
|
|
|
|
+ - `POST {settings.aichat.api_url}/attachments/parse`
|
|
|
|
|
+4. 返回解析结果给前端(通常含抽取文本/attachment_id 等)
|
|
|
|
|
+
|
|
|
|
|
+## 10. 停止 SSE 与回写消息(报告模式常用)
|
|
|
|
|
+
|
|
|
|
|
+### 10.1 `POST /sse/stop`
|
|
|
|
|
+
|
|
|
|
|
+前端调用: [stopSSEStream](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/utils/api.js#L82-L140)
|
|
|
|
|
+
|
|
|
|
|
+后端实现: [stop_sse](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/report_compat.py#L302-L337)
|
|
|
|
|
+
|
|
|
|
|
+路由策略:
|
|
|
|
|
+
|
|
|
|
|
+- 外部 token:代理到 aichat 的 `/sse/stop`
|
|
|
|
|
+- 本地 token:本地直接返回成功(不做真实中断)
|
|
|
|
|
+
|
|
|
|
|
+### 10.2 `POST /report/update-ai-message`
|
|
|
|
|
+
|
|
|
|
|
+前端调用: [updateAIMessageContent](file:///Users/fanhong/UGIT/shudao-main/shudao-vue-frontend/src/utils/api.js#L147-L205)
|
|
|
|
|
+
|
|
|
|
|
+后端实现: [update_ai_message](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/routers/report_compat.py#L246-L299)
|
|
|
|
|
+
|
|
|
|
|
+路由策略:
|
|
|
|
|
+
|
|
|
|
|
+- 外部 token:代理到 aichat 的 `/report/update-ai-message`
|
|
|
|
|
+- 本地 token:直接更新 `AIMessage.id = ai_message_id` 的 `content`
|
|
|
|
|
+
|
|
|
|
|
+## 11. 相关配置入口(定位“外部服务地址”用)
|
|
|
|
|
+
|
|
|
|
|
+统一配置类定义在:
|
|
|
|
|
+
|
|
|
|
|
+- [config.py](file:///Users/fanhong/UGIT/shudao-main/shudao-chat-py/utils/config.py)
|
|
|
|
|
+
|
|
|
|
|
+通常运行时值来自 `shudao-chat-py/config.yaml`(以及环境变量覆盖),关键项包括:
|
|
|
|
|
+
|
|
|
|
|
+- `auth.api_url`
|
|
|
|
|
+- `search.api_url`
|
|
|
|
|
+- `qwen3.api_url` / `qwen3.model`
|
|
|
|
|
+- `intent.api_url` / `intent.model`
|
|
|
|
|
+- `deepseek.api_url`
|
|
|
|
|
+- `dify.workflow_url` / `dify.workflow_id` / `dify.auth_token`
|
|
|
|
|
+- `aichat.api_url`
|
|
|
|
|
+
|