WeKnora 问答实现

3 阅读7分钟

提问时,WeKnora 提供两种运行时:快速问答与智能推理。快速问答是固定顺序的事件插件链;智能推理是 ReAct 循环,由模型通过 Function Calling 选用工具。产品上没有单独的「计划模式」「循环模式」——计划、循环次数是智能推理里的工具或上限。两条路径共用同一套 SSE 事件。

快速问答:各步骤做什么

无知识库且未开网页搜索时,流水线很短:可选加载历史 → 流式生成。否则动态拼成完整 RAG 链:

// 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:

  1. Think:系统提示、历史、当前用户轮 + 已注册工具的 Function Definition → 对话模型(开启 function calling)。
  2. Analyze:若自然停止(stop / end_turn 等)且 无 tool_calls,本轮文本即最终答案,结束外层循环。没有名为 final_answer 的工具。
  3. Act:若有 tool_calls,按名从注册表执行(串行或并行由配置决定),写成 tool 消息。
  4. Observe:结果追加进 messages,进入 下一轮 LLM;同时可做上下文压缩(见上下文管理)。
// 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 / 技能执行器都实现):

// 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 上下文管理