ai-qa-api-call-chain.md 19 KB

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 stream_chat_with_db
非流式问答 POST /send_deepseek_message 一次性返回完整回答 Chat.vue、apis.js send_deepseek_message
历史(列表/详情) GET /get_history_record ai_conversation_id=0 返回会话列表;>0 返回消息列表 apis.js get_history_record
删除对话/消息 POST /delete_conversation 软删除会话或某条消息对 apis.js delete_conversation
删除历史(会话级) POST /delete_history_record 软删除会话主记录 apis.js delete_history_record
猜你想问 POST /guess_you_want 基于某条 AI 消息生成 3 个关联问题并落库 apis.js guess_you_want
在线搜索 GET /online_search?question=... Qwen 提炼关键词 → Dify 工作流 → 返回摘要 apis.js online_search
保存在线搜索结果 POST /save_online_search_result 保存到 AIMessage.search_source apis.js save_online_search_result
意图识别(独立) POST /intent_recognition 只做意图识别(可选写 DB) apis.js intent_recognition
点赞/点踩 POST /like_and_dislike 保存 AIMessage.user_feedback 并处理积分 apis.js like_and_dislike
报告兼容 SSE(Go 对齐) POST /report/complete-flow SSE:可能代理到 aichat;失败则降级本地 Chat.vue complete_flow
停止报告 SSE POST /sse/stop 停止 SSE(外部 token 代理到 aichat) stopSSEStream stop_sse
回写 AI 消息 POST /report/update-ai-message 前端将整理后的内容回写到 DB(外部 token 代理到 aichat) updateAIMessageContent update_ai_message
附件解析 POST /attachments/parse 文件上传解析(代理到 aichat) parseAttachment parse_attachment

2. 出站接口清单(shudao-chat-py → 外部服务)

目标服务 调用点 典型接口 用途
4A Auth verify_external_token POST settings.auth.api_url 校验外部 token,解析 account 信息
搜索服务(RAG) _rag_search POST settings.search.api_url 向量检索/知识库检索,拼接上下文
Qwen3(主模型) QwenService.chat、QwenService.stream_chat POST {settings.qwen3.api_url}/v1/chat/completions 主问答生成与流式输出
意图识别模型 intent_recognition POST {settings.intent.api_url}/v1/chat/completions 识别 greeting/faq/知识库查询 等
DeepSeek(备用模型) DeepSeekService.chat、stream_chat POST {settings.deepseek.api_url}/v1/chat/completions Qwen3 失败时回退
Dify 工作流(在线搜索) online_search POST settings.dify.workflow_url 关键词 → 工作流 → 摘要
aichat 服务(代理) AIChatProxy POST {settings.aichat.api_url}/* 兼容 Go 版报告 SSE、附件解析、停止、回写等

3. 认证与用户注入(所有链路的共同前置)

3.1 中间件入口

后端在 combined_middleware 内做:

  1. Authorization / token / Token 读取 token
  2. 调用 verify_token
  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 映射到本地 UserData.id(用于复用 AIConversation/AIMessage

4. 链路 A:主流式问答(POST /stream/chat-with-db

这是“边生成边显示 + 保留历史”的标准链路。

4.1 请求形态

请求体(核心字段):

{
  "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。

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/completionsstream=true
    • 若 Qwen3 上游错误或网络错误,回退到 DeepSeek 流式接口
  7. 可选:思考过程摘要(二次模型调用)
    • 若流中出现 <think>...</think> 且摘要开关启用,会调用 summarize_thinking_content
    • 该摘要本质上是一次 qwen_service.chat()(仍会触发 DeepSeek 回退逻辑)
  8. SSE 持续返回给前端
  9. 流结束后回写数据库:
    • 更新 AIMessage(type=ai).content = full_response
    • 更新 AIConversation.updated_at / business_type / content 预览
  10. SSE 返回 [DONE]

4.3 时序图(主流式问答)

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_messagebusiness_type=0

这条链路更像“同步 RPC”:一次请求直接拿到完整回答。

5.1 调用链路(AI助手分支)

实现入口见 send_deepseek_message。

  1. 前端 → POST /apiv1/send_deepseek_message
  2. 中间件鉴权,写入 request.state.user
  3. 创建/复用 AIConversation
  4. 意图识别:
    • 调用 QwenService.intent_recognition
    • 出站: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/completionsstream=false
    • Qwen3 可回退 DeepSeek(见 QwenService.chat)
  8. 可选:思考过程摘要(再次调用 qwen_service.chat()
  9. 返回 {statusCode: 200, data: { reply/content/... } }

5.2 时序图(非流式问答)

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)。

后端入口: complete_flow

6.1 两种路由策略(关键)

兼容路由会根据 token 类型决定“走 aichat 代理”还是“本地降级”:

  • 外部 token(非本地 token):优先代理到 aichat
    判断逻辑: should_proxy_to_aichat
  • 本地 token代理失败:降级为本地流式 POST /stream/chat-with-db 再转换为兼容事件流
    降级逻辑: fallback_to_local_stream

6.2 外部 token:代理到 aichat

链路:

  1. 前端 → POST /apiv1/report/complete-flow(SSE)
  2. report_compat.py 构造转发请求体,规范化 ai_conversation_id(新会话必须是 0)
  3. 调用代理: aichat_proxy.proxy_sse
  4. 后端将 aichat SSE 原样转发给前端(chunk 级别)

其中 aichat 的 base URL 来自:

  • settings.aichat.api_url(见 AIChatConfig)

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)

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

调用链路:

  1. 前端 → GET /apiv1/online_search?question=...
  2. 后端用 Qwen 提炼关键词:
    • extract_keywords
  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

调用链路:

  1. 前端提交 {ai_message_id, search_result}
  2. 后端更新 AIMessage.search_source

8. “猜你想问”链路(POST /guess_you_want

实现入口: guess_you_want

调用链路:

  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

调用链路:

  1. 前端上传文件(multipart/form-data)
  2. 后端校验 request.state.user 不为空
  3. 代理到 aichat:
    • proxy_upload
    • POST {settings.aichat.api_url}/attachments/parse
  4. 返回解析结果给前端(通常含抽取文本/attachment_id 等)

10. 停止 SSE 与回写消息(报告模式常用)

10.1 POST /sse/stop

前端调用: stopSSEStream

后端实现: stop_sse

路由策略:

  • 外部 token:代理到 aichat 的 /sse/stop
  • 本地 token:本地直接返回成功(不做真实中断)

10.2 POST /report/update-ai-message

前端调用: updateAIMessageContent

后端实现: update_ai_message

路由策略:

  • 外部 token:代理到 aichat 的 /report/update-ai-message
  • 本地 token:直接更新 AIMessage.id = ai_message_idcontent

11. 相关配置入口(定位“外部服务地址”用)

统一配置类定义在:

  • 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