这份文档不再按“模块设计”讲,而是按你最关心的方式讲:
当前项目里,主要有两条链路:
shudao-chat-py 的聊天链路shudao-aichat 的结构化意图识别 / 报告链路shudao-chat-py更像:
shudao-aichat更像:
shudao-chat-py:用户问题一步步会去哪儿这里先讲最容易理解的一条:shudao-chat-py 的问答链。
入口代码:
用户在前端输入一个问题,比如:
高处作业安全防护有哪些要求?
这个问题会被传到:
这一步拿到的内容是:
message,也就是用户原始问题business_type,决定这是 AI 问答、PPT、大纲还是其他业务如果 business_type == 0,表示走普通 AI 问答链路。
下一步:
message 传给意图识别函数 QwenService.intent_recognition代码位置:
这一层会做的事是:
{userMessage}传进去的是:
返回的不是最终答案,而是:
下一步:
代码位置:
这一层会:
model=self.intent_modelapi_url=self.intent_api_url传出去的是:
messages=[{"role":"user","content":"意图识别 Prompt"}]返回回来的是:
下一步:
代码位置:
这一层会做:
json.loads() 解析intent 和 intent_typeintent_type这里希望返回的是类似这样的结果:
{
"intent_type": "query_knowledge_base",
"confidence": 0.9,
"search_queries": ["高处作业安全防护要求"],
"response": ""
}
或者:
{
"intent_type": "greeting",
"confidence": 0.95,
"search_queries": [],
"response": "您好!我是蜀安AI助手,很高兴为您服务。"
}
如果解析失败,返回的是:
{
"intent_type": "general_chat",
"confidence": 0.5,
"reason": "无法解析JSON",
"response": ""
}
下一步:
intent_type 判断接下来去哪儿intent_type 判断下一步去哪儿代码位置:
这里分三种典型情况。
greeting(问候)比如:
返回的内容通常是:
response下一步:
response 作为结果返回给前端faq(常见问题)比如:
返回的内容通常是:
下一步:
query_knowledge_base(知识库查询)比如:
返回的是:
response 一般为空下一步:
代码位置:
这一层会做:
query=用户原问题n_results=top_krag_context传出去的是:
返回回来的是:
rag_context下一步:
rag_context 和用户问题一起传给最终回答 Prompt代码位置:
这一层会把这些内容组合起来:
messagerag_context返回的不是最终回答,而是:
下一步:
代码位置:
这一层传出去的是:
返回回来的是:
如果模型输出里包含 <think>...</think>,说明它把思考过程和正式回答一起吐出来了。
下一步:
<think>,先拆分再二次总结代码位置:
这一层会:
<think> 里的原始思考内容返回回来的是:
thinking_summaryanswer_text下一步:
代码位置:
最终返回给前端的一般是:
responsereplycontentmessage这些字段本质上都在装“同一份最终回答”。
入口代码:
这条链和上面很像,只是“最后返回”不是一次性返回整段文本,而是边生成边推送。
和上面的非流式问答基本一样:
intent_type代码位置:
传出去的是:
返回回来的是:
chunk下一步:
chunk,边通过 SSE 推给前端<think>,先拦住做摘要代码位置:
这一层会:
<think>返回给前端的是:
最后一步:
[DONE] 结束流入口代码:
这条最重要,但也是最容易误解的一条。
这条链路里:
代码位置:
传进来的是:
messageai_conversation_idbusiness_typeonline_search_content下一步:
代码位置:
返回的是:
conv_id下一步:
代码位置:
返回的是:
user_msg.idai_msg.id下一步:
代码位置:
返回的是:
ai_conversation_idai_message_id下一步:
代码位置:
传出去的是:
message返回的是:
rag_context下一步:
代码位置:
返回的是:
history_context下一步:
message + rag_context + history_context (+ online_search_content) 一起传给最终回答 Prompt代码位置:
传给主模型的是:
返回的是:
下一步:
<think>,先做摘要再继续回答代码位置:
返回给前端的是:
下一步:
代码位置:
写回的是:
AIMessage.content = full_response最后一步:
[DONE]代码位置:
shudao-aichat:用户问题一步步会去哪儿这条链不是普通聊天链,而是“结构化意图识别 + 报告主流程”的链。
入口代码:
传进来的字段定义在:
包括:
user_questionconversation_historyenable_online_model下一步:
代码位置:
这一层会把这些信息塞进 Prompt:
返回的是:
下一步:
代码位置:
这一层不是返回答案,而是给模型增加限制:
下一步:
代码位置:
传出去的是:
返回的是:
理想情况下,这个文本应该是一个合法 JSON。
下一步:
代码位置:
如果第一次返回的不是合法 JSON:
返回的仍然是:
下一步:
代码位置:
这一层返回的是一个结构化字典,可能包含:
is_professional_questionrouteneed_offline_modelorigin_questionkeywordsfallback_keywordsintent_scenecompany_namecompany_aliasessummaryreasoning_summary下一步:
thinking_contentreasoning_summary 加工成前端可展示摘要代码位置:
这一层返回的是:
thinking_content它不是原始推理链,而是安全可展示的摘要。
下一步:
代码位置:
返回的是:
比如:
下一步:
代码位置:
最终返回给上层的是:
is_professional_questionroutekeywordsfallback_keywordsintent_scenecompany_namecompany_aliasessummarythinking_content下一步:
代码位置:
调用点:
传进去的是:
返回的是:
IntentAnalyzeResponse下一步:
route 和 is_professional_question 分支online_only(仅在线)代码位置:
下一步:
online_then_offline(先在线后离线)代码位置:
下一步:
代码位置:
下一步:
internal_query(内部查询)代码位置:
下一步:
如果只保留最简版,可以这样记:
shudao-chat-py用户问题
-> 聊天路由
-> 意图识别小模型
-> 返回 intent_type
-> 如果需要查库,就去 RAG
-> 把问题 + RAG结果 传给主模型
-> 主模型生成最终回答
-> 如果有 think,再做思考摘要
-> 返回前端
shudao-chat-py 主聊天接口 stream/chat-with-db用户问题
-> 创建/复用会话
-> 写 user 消息
-> 写 ai 占位消息
-> 直接去 RAG
-> 拼历史上下文
-> 传给主模型流式生成
-> 边输出边写回数据库
shudao-aichat用户问题
-> 结构化意图识别
-> 返回 route / keywords / scene / summary
-> 报告主流程读取这些字段
-> 决定下一步走在线、离线、内部查询还是直接结束
如果你问的是:
那最直接的答案就是:
shudao-chat-pyshudao-aichat