提问时,WeKnora 提供两种运行时:快速问答与智能推理。快速问答是固定顺序的事件插件链;智能推理是 ReAct 循环,由模型通过 Function Calling 选用工具。产品上没有单独的「计划模式」「循环模式」------计划、循环次数是智能推理里的工具或上限。两条路径共用同一套 SSE 事件。
快速问答:各步骤做什么
无知识库且未开网页搜索时,流水线很短:可选加载历史 → 流式生成。否则动态拼成完整 RAG 链:
go
// internal/application/service/session_knowledge_qa.go
pipeline = types.NewPipelineBuilder().
AddIf(hasHistory, types.LOAD_HISTORY).
Add(types.QUERY_UNDERSTAND).
Add(types.CHUNK_SEARCH_PARALLEL).
Add(types.CHUNK_RERANK).
AddIf(req.WebSearchEnabled, types.WEB_FETCH).
Add(types.CHUNK_MERGE).
Add(types.FILTER_TOP_K).
AddIf(chatManage.DataAnalysisEnabled, types.DATA_ANALYSIS).
Add(types.INTO_CHAT_MESSAGE).
Add(types.CHAT_COMPLETION_STREAM).
Build()
各阶段职责:
| 阶段 | 做什么 |
|---|---|
| LOAD_HISTORY | 按轮数从消息表取历史问答,拼进后续上下文。 |
| QUERY_UNDERSTAND | 一次模型调用做查询改写与意图分类(寒暄、闲聊、要检索、追问等)。意图若不需要检索,后续检索插件仍会触发,但内部直接 next(),不打向量库。有图时可用视觉模型一并出图描述。 |
| CHUNK_SEARCH_PARALLEL | 并行跑知识库混合检索(稠密 + 稀疏 + RRF,见混合检索)与可选网页搜索;图谱开启时还可并行实体检索。召回不足且打开查询扩展时,再并发变体查询补一轮。 |
| CHUNK_RERANK | 用重排模型对候选段落打分、按阈值排序并按 RerankTopK 截断;未配置重排模型则跳过。 |
| WEB_FETCH(可选) | 对网页搜索命中的 URL 拉正文,丰富引用材料。 |
| CHUNK_MERGE | 去重、历史引用弱化、子块抬父块、重叠区间合并、FAQ 答案填充等,整理成一份待进提示词的结果集。 |
| FILTER_TOP_K | 再截一刀,控制最终进模型的条数。 |
| DATA_ANALYSIS(可选) | 表格类分析开启时,走数据分析相关步骤。 |
| INTO_CHAT_MESSAGE | 按上下文模板把检索结果与用户问题拼成模型输入;引用别名在此前后处理(见回答溯源)。拼好的内容可写入 RenderedContent 供下轮历史使用。 |
| CHAT_COMPLETION_STREAM | 流式调用对话模型,经事件总线推到 SSE。 |
快速问答侧的「插件」是按事件类型注册的流水线插件 :每个插件声明自己关心的 EventType,EventManager 按流水线顺序触发;插件内决定干活还是跳过。阶段固定、行为可关,排障时能对上「卡在改写还是重排」。
智能推理:循环机制与 Function Calling
智能推理不是「只调一次模型、在一次响应里塞满所有工具」。它分两层,容易混在一起:
| 层次 | 是什么 | 次数感 |
|---|---|---|
| 外层循环(ReAct) | 每轮 一次 LLM 调用;有工具则执行并写回 messages,再开下一轮 | 由 MaxIterations 封顶(引擎常量默认约 20;智能体配置可改,校验还有更小的兜底与硬上限) |
| 单轮 Function Calling | 这一次 LLM 响应里,模型可返回 0~多个 tool_calls(可并行执行) |
跟「20」无关;20 不是「Function Calling 默认能调 20 次」 |
也就是说:多轮 = 多次 LLM 调用 ;Function Calling = 某一轮响应里选用工具的协议 。轮与轮之间靠累加的 messages 协作(上一轮的工具结果成为下一轮输入),没有单独的「计划模式」产品开关------计划类能力是可选工具(如 todo_write),循环次数是配置上限。
每轮内部大致是 Think → Analyze → Act → Observe:
- Think:系统提示、历史、当前用户轮 + 已注册工具的 Function Definition → 对话模型(开启 function calling)。
- Analyze :若自然停止(
stop/end_turn等)且 无tool_calls,本轮文本即最终答案,结束外层循环。没有名为final_answer的工具。 - Act :若有
tool_calls,按名从注册表执行(串行或并行由配置决定),写成 tool 消息。 - Observe :结果追加进 messages,进入 下一轮 LLM ;同时可做上下文压缩(见上下文管理)。
go
// internal/agent/engine.go(分析是否答完)
verdict := e.analyzeResponse(ctx, response, step, ...)
if verdict.isDone { /* FinalAnswer = 本轮文本;跳出循环 */ }
// 否则 executeToolCalls → 写回 messages → 继续下一轮 LLM
流式开场白 也是单轮内的事,与 MaxIterations 无关:模型可能先流式吐出「我去查一下」,再发出 tool_call。前端一开始可能把它当答案展示;一旦确认本轮有工具调用,这段字改判为开场白(preamble),挪进步骤树,不写入最终消息。真正答案仍是某轮「无工具、自然停止」时的纯文本。
可插拔工具各自做什么
内置工具实现同一接口,按名注册;UI / 预设里的清单与 AvailableToolDefinitions 对齐。常见几类:
| 工具 | 作用 |
|---|---|
knowledge_search |
语义检索(内部仍走混合检索 + 重排) |
grep_chunks |
关键词快速定位块 |
list_knowledge_chunks |
按文档列出分块,便于通读 |
query_knowledge_graph |
查知识图谱实体关系 |
get_document_info |
文档元数据 |
web_search / web_fetch |
网页搜索与抓取正文(开网页搜索时才挂上) |
data_schema / data_analysis / database_query |
表结构、分析与库查询 |
wiki_* |
读/搜/写 Wiki 页、源文档、问题标记等(有 Wiki 库时才保留) |
thinking / todo_write |
逐步思考、列计划;预设里常默认关掉,避免简单问答浪费 token |
read_skill / execute_skill_script |
渐进读取技能说明、在沙箱跑技能脚本 |
| MCP 工具 | 远端 MCP 服务上的工具,包装后同名注册进同一注册表(名形如 mcp_{服务}_{工具}) |
危险 MCP 可走审批门;需 OAuth 的服务可在会话中授权后再续跑。
插拔怎么设计
核心是三层:统一接口 → 注册表 → 按配置装配。
1. 工具接口(所有内置 / MCP / 技能执行器都实现):
go
// internal/types/agent.go
type Tool interface {
Name() string
Description() string
Parameters() json.RawMessage // JSON Schema,给 Function Calling
Execute(ctx context.Context, args json.RawMessage) (*ToolResult, error)
}
2. 注册表 ToolRegistry:RegisterTool(同名先到先得,防劫持)、GetFunctionDefinitions(按名排序,便于提示缓存)、ExecuteTool(校验参数后再 Execute)。引擎只依赖注册表,不依赖某个具体工具类型。
3. 装配策略(创建引擎时):
- 以智能体配置里的
AllowedTools白名单为准;空则用默认列表。不会静默塞进用户没选的工具。 - 再按能力裁剪:本轮没有知识库范围时去掉 KB/Wiki/数据分析类;没有 Wiki 库时去掉
wiki_*;开启网页搜索时才追加web_search/web_fetch。 - MCP 按
MCPSelectionMode(all/selected/none)拉服务,每个远端工具包装成实现同一Tool接口的适配器,再RegisterTool。 - 技能开启时注册
read_skill等;脚本执行受沙箱模式约束。
因此「插拔」不是改引擎主循环,而是:实现 Tool → 出现在允许列表且通过能力过滤 → 注册进表 → 模型在 Function Calling 里看见并选用 。快速问答的流水线插件是另一套插拔(按 EventType),面向固定阶段;智能推理的工具插拔面向开放动作集。两者都把「扩展点」收成接口 + 注册,而不是在核心路径里写死 if tool == xxx。
提升了什么
固定链保证日常问答路径可预期、可按阶段观测;工具注册表让多步任务、MCP、技能在同一循环里扩展,而不复制第二套 Agent 运行时。前端仍只消费一套流式事件。
好处与代价
好处
- 快速问答阶段清晰,意图不需检索时可跳过重活。
- 工具白名单 + 能力过滤,减少模型调用必然失败的工具。
- MCP / 技能与内置工具同一接口,扩展面统一。
代价
- 快速问答默认不会因首轮检索差而自主多跳(除非查询扩展等补丁)。
- 智能推理轮次多、上下文与费用更高;过渡句与最终答案的流式区分增加复杂度。
- 工具过多或计划/思考类默认打开时,简单问答也容易绕远路。
基于 WeKnora v0.7.0
上一篇:WeKnora 混合检索 | 下一篇:WeKnora 上下文管理