主题 12 · Agent 推理与规划#
定位:Agent 的”推理”不在代码里而在模型的 tool-calling 序列里——如何让模型自驱拆解/多跳/停止,以及推理失败时如何诊断和修复
一、通用知识#
1.1 核心概念与原理#
隐式规划 vs 显式规划#
Tool-calling loop 是隐式规划——模型在每步决策中隐含了”下一步该做什么”的计划,不需要显式生成一个 plan 文档。传统 Plan-and-Execute 是先画流程图再按图施工;tool-calling loop 是边走边看——模型每一步都在隐式地重新评估”接下来该搜什么”。
面试怎么讲:“传统 Plan-and-Execute 是先画流程图再按图施工,tool-calling loop 是边走边看——模型每一步都在隐式地重新评估’接下来该搜什么’。后者更适合信息未知的检索场景,因为你不知道第一步搜出来什么,计划会过时。显式规划适合任务步骤可预先枚举的场景,比如’去 5 个数据源各拉一份数据然后合并’——这种并行分发比串行 tool-calling 效率高。但 RAG 检索本质上是探索式的,隐式规划是更好的选择。“
Agent 推理范式演进#
从学术到工程,Agent 推理经历了五代范式演进:
- ReAct(Yao 2022):最基础的 Thought→Action→Observation 循环。模型先输出思考过程,再决定动作,观察结果后继续。优势是可解释性强,劣势是串行执行、每步都要输出 Thought 增加 token 开销。
- Plan-and-Execute(Wang 2023):先让模型生成完整计划(步骤列表),再逐步执行。适合可分解的确定性任务,但计划容易因中间结果变化而过时。
- Reflexion(Shinn 2023):在执行后加一步自我反思——评估结果质量、总结失败原因、写入记忆。下次重试时模型能”吸取教训”。代价是每次反思增加一轮 LLM 调用。
- LATS(Zhou 2023):树搜索多路径——每一步生成多个候选 action,用评估函数打分选最优路径。最强但也最贵,适合高价值低容错场景。
- Tool-Calling Loop(Anthropic 2024 实践派):给模型有限工具集 + 迭代上限,模型自驱决定拆解/多跳/停止。核心理念是”约束工具集而非约束步骤”,让模型在安全边界内自主推理。
面试怎么讲:“五代范式的演进方向是从规则驱动走向模型自驱。ReAct 是基础循环,Plan-Execute 加了前置规划,Reflexion 加了后置反思,LATS 加了多路搜索。但工程上 Anthropic 那篇 Building Effective Agents 给出了最务实的建议:别搞复杂框架,给模型最小工具集 + 迭代上限,让它自己决定怎么走。这正是我在 DocMind 里的实践——4 个只读工具 + executeCode 沙箱计算工具 + 4 轮上限 + 空转检测,模型自驱搜索,代码只管兜底。“
CoT 在 Agent 中的角色#
Chain-of-Thought 提高模型推理质量,但在 tool-calling 场景中 CoT 是隐式的——模型的”思考”体现在它选择什么工具、传什么参数、何时停止。你看它第 1 轮搜了”用户认证模块”,第 2 轮搜了”张三 入职时间”——这个工具调用序列本身就是 CoT,只是没有用自然语言写出来。
面试怎么讲:“CoT 在 tool-calling 场景是隐式的——模型的思考体现在工具调用序列里,而不是显式的 Thought 输出。强制让模型在每步写 Thought 会增加 token 成本,但对工具选择帮助有限——它该选 searchDocs 还是 webSearch,靠的是工具描述和上下文,不是靠它先写一段’我觉得应该搜知识库’。我们在 trace 中看模型的推理质量,看的就是工具调用参数序列,而不是让模型输出思维过程。“
温度控制与推理稳定性#
Agent 推理场景下温度(temperature)设置直接影响推理稳定性。高温度增加随机性——同一个问题两次跑可能选不同的工具、传不同的参数。低温度(接近 0)让工具选择更确定,但可能降低探索性。工程实践中 Agent 的推理部分通常用 temperature=0(确定性选择),生成答案的部分可以适当提高温度增加表达多样性。
面试怎么讲:“Agent 推理部分用 temperature=0——你不希望同一个问题两次跑选了不同的工具。我们的 agentic 循环 temperature 设的就是 0.0,保证工具选择的确定性。但最终答案生成阶段可以用稍高的温度,让表达更自然。两个阶段用不同的 ChatOptions,温度分开控制。“
推理失败五种模式#
实际运行中模型推理会出现以下典型失败模式:
- 空转(同义词重搜):搜”Docker 部署指南”无果,又搜”Docker 部署教程”、“Docker 安装部署步骤”——三轮同义表达搜同一来源
- 幻觉工具(调用不存在的工具):模型尝试调用 prompt 中未定义的工具,如自己编造一个
getDocumentList - 参数错误(类型或格式不对):比如 topK 传了字符串 “five” 而非数字 5,或 query 传了空字符串
- 推理漂移(越搜越偏离原始问题):多跳查询中每一步都离原始问题远一点,最后搜的内容和问题毫无关系
- 过早停止(还没搜全就停了):对比类问题只搜了一半主题就停止,因为第一个主题的结果里顺带提到了第二个
面试怎么讲:“推理失败不看代码看 trace——五种模式里,空转和过早停止最常见,我们都在 DocMind 里遇到过并修复了。空转靠 zero-yield detection 检测 + prompt 引导双管齐下;过早停止靠确定性后处理保证多焦点问题路由到 agentic 循环,给模型多轮机会分别搜。幻觉工具在 Spring AI 的 schema 校验里就被拦了。推理漂移靠 max_iterations 硬上限兜底。“
1.2 业界主流方案对比(表格形式)#
| 模式 | 核心论文 | 机制 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|---|
| ReAct | Yao et al. 2022 | Thought→Action→Observation 循环 | 可解释、实现简单 | 串行慢、Thought 增加 token | 简单工具场景 |
| Plan-Execute | Wang et al. 2023 | 先生成计划再逐步执行 | 可并行、任务分解清晰 | 计划过时、需要中间重规划 | 可预先分解的确定性任务 |
| Reflexion | Shinn et al. 2023 | 执行后自我反思 + 记忆写入 | 能从失败中学习 | 每次反思增加一轮 LLM 调用 | 试错式任务(代码生成等) |
| LATS | Zhou et al. 2023 | 树搜索多路径 + 评估函数 | 全局最优、回溯能力 | 成本极高(N 条路径 × M 步) | 高价值低容错场景 |
| Tool-Calling Loop | Anthropic 2024 | 最小工具集 + 迭代上限 + 模型自驱 | 工程简洁、适配真实场景 | 依赖模型能力、缺少显式规划 | RAG 检索等探索式任务 |
1.3 关键论文与技术要点#
- ReAct(Yao et al., ICLR 2023):首次系统性地将推理和行动结合,证明了 Thought→Action→Observation 循环在知识密集型任务上优于纯 CoT
- Plan-and-Execute(Wang et al., 2023):提出先规划后执行的两阶段 Agent 架构,适合可分解任务但在动态环境中计划容易失效
- Reflexion(Shinn et al., NeurIPS 2023):引入 verbal self-reflection 机制,让 Agent 在失败后把经验写入记忆,下次执行时避免同样错误
- LATS(Zhou et al., 2023):将蒙特卡洛树搜索引入 LLM Agent,多路径探索 + 回溯,在复杂推理任务上达到 SOTA
- Building Effective Agents(Anthropic, 2024):实践指南——核心观点”约束工具集而非约束步骤”,推荐 tool-calling loop 模式
推理工程三原则:
- 最小工具集降低决策复杂度——20 个工具模型选不过来,3 个工具选择准确率显著提升。Anthropic 的实践指南也强调这一点:工具数量和模型决策错误率正相关。DocMind 只暴露 4 个只读工具(searchDocs / keywordSearch / webSearch / recall_memory)+ executeCode 沙箱计算工具(默认关),用白名单硬排除 store_memory 和 kb_meta
- 空转检测比 prompt 约束更可靠——“不要换近义词重搜”模型不一定遵守,代码层 zero-yield detection 是硬检测。prompt 是概率性约束,代码是确定性约束——安全关键路径上只能靠后者
- prompt 引导策略而非约束步骤——“先搜 KB”是策略建议,不是”第一步必须搜 KB”的硬规则。约束步骤会让模型变成”按剧本演出”,失去根据实际情况调整的能力
1.4 常见面试问答#
Q1:ReAct vs Plan-Execute 怎么选?
面试怎么讲:“看任务是否可预先分解。如果任务步骤确定且步骤间无数据依赖——比如’去 5 个数据源各拉一份数据’,Plan-Execute 可以并行分发,效率更高。但如果任务是探索式的——比如 RAG 检索,你不知道第一步搜出来什么、第二步该搜什么,那 Plan 写了也会过时。这时候 tool-calling loop(一种改良的 ReAct)更合适,模型每一步基于实际结果决定下一步。”
Q2:隐式规划 vs 显式规划哪个更适合 RAG?
面试怎么讲:“隐式。RAG 检索的核心特点是信息未知——你不知道知识库里有什么、搜出来质量如何。显式规划假设你能预先枚举所有步骤,但检索场景里第一步搜到什么决定了第二步该搜什么。比如多跳问题’某模块负责人的入职时间’,你得先搜到负责人是谁,才知道下一步搜谁的入职时间——这个’谁’是运行时才知道的,没法提前 plan。”
Q3:模型推理’跑偏’怎么办?
面试怎么讲:“三层防线。第一层 prompt 引导:在 SYS_PROMPT 里写策略而非约束步骤,比如’资料齐全后立即停止’。第二层代码护栏:max_iterations 硬上限防止无限循环,zero-yield detection 检测空转。第三层异常回退:整个 agentic 循环异常或空结果时,回退一次性检索——保证请求永不因推理失败而中断。核心原则是 prompt 是软约束、代码是硬兜底。”
Q4:CoT 要不要强制要求模型输出?
面试怎么讲:“tool-calling 场景不需要。强制输出 Thought 增加 token 成本但对工具选择帮助有限。模型的推理体现在工具调用序列本身——选了什么工具、传了什么参数、在哪一步停止。我们通过 Langfuse trace 中的 tool_call 参数序列来评估推理质量,比看模型自己写的 Thought 更客观。”
Q5:推理深度怎么控制?
面试怎么讲:“三级控制。硬上限 max_iterations=4 防止 token 爆炸;空轮早停——当一轮工具调用没有带来任何新 chunk 且已有累积资料时提前 break,避免无意义的重搜;软引导——prompt 写’用尽量少的轮数’而不是告诉模型具体上限。关键发现:告诉模型’你最多调用 4 次’会产生锚定效应——简单问题也跑满 4 轮。改成’用尽量少的轮数’后平均迭代从 3.2 轮降到 1.8 轮。”
Q6:如何评估 Agent 推理质量?
面试怎么讲:“三个维度。正确性看 answer accuracy——golden set 52 条标注答案,自动评分。效率看 iteration count——简单题应 ≤ 2 轮,对比题应 ≥ 2 轮,异常值人工复查 trace。一致性看 cross-run variance——同一问题跑 5 次,迭代轮数和工具选择应该基本一致。如果方差大说明模型推理不稳定,需要调整 prompt 或 temperature。我们的 agentic 循环 temperature=0,所以工具选择的一致性很高。”
Q7(补充):tool-calling loop 和 ReAct 有什么本质区别?
面试怎么讲:“ReAct 要求模型在每步输出显式的 Thought(自然语言推理过程),再基于 Thought 决定 Action。tool-calling loop 跳过了 Thought 输出——模型直接输出 tool_call(工具名 + 参数 JSON),推理过程是隐式的。区别在于:ReAct 的 Thought 增加了可解释性但也增加了 token 成本和延迟;tool-calling loop 更高效但推理过程不透明——需要靠 trace 中的工具调用序列来事后分析推理质量。对于 RAG 检索场景,我们选择了效率优先——反正要看 trace 调试,Thought 输出的可解释性价值不大。“
二、DocMind 实践#
30 秒口述版#
“DocMind 的 agentic 循环是隐式规划——模型自己决定拆解、多跳、补 Web、停止,但需要 prompt 策略引导 + 代码护栏兜底。三个关键设计:SYS_PROMPT 5 条策略引导而非约束步骤、空轮早停 zero-yield detection、不暴露预算防锚定效应。开发过程中诊断修复了三个典型推理失败:多跳推理时模型没提取中间事实、同义词空转三轮无增益、对比类问题只搜了一半就停止。每个都是通过 Langfuse trace 中工具调用参数的回溯发现的——推理失败不看代码看 trace。”
“设计哲学是’模型自驱 + 代码兜底’:分类器只做粗粒度路由(SIMPLE→one-shot / COMPLEX→agentic),不塞 needDecompose / multiHop 等过细字段——这些判断分类器做不准,交给模型运行时自决。代码侧只管兜底:max_iterations 防无限循环、zero-yield 防空转、异常回退防中断。“
详细展开#
1. Agentic 循环的手动控环机制#
DocMind 的 agentic 循环核心在 AgenticSearchOrchestrator.search():用 Spring AI 的 internalToolExecutionEnabled(false) 关闭框架内部的自动工具执行循环(Spring AI 的内部循环没有迭代上限),改由代码手动控环——每轮调 toolCallingManager.executeToolCalls() 执行工具,然后把结果拼回 prompt 继续下一轮。
循环的每一轮做四件事:(1) 调 LLM 拿到 ChatResponse;(2) 检查 resp.hasToolCalls()——模型不再请求工具说明资料已齐,break;(3) 通过 emitAgenticStep() 发射 SSE agentic 事件,让前端实时展示当前轮的工具调用;(4) 执行工具后把 ToolExecutionResult.conversationHistory() 拼回 Prompt 进入下一轮。整个循环用 AgentToolContext(ThreadLocal)累积多轮的 chunk 并集,循环结束后统一做去重 → Cross-Encoder rerank → MMR → 压缩 → CRAG 评分。
面试怎么讲:“Spring AI 的默认行为是 LLM 返回 tool_call 后自动执行并把结果喂回去,直到模型不再请求工具为止。但这个内部循环没有迭代上限——模型如果空转,它会无限循环下去。所以我们用
internalToolExecutionEnabled(false)把自动循环关掉,改为手动 for 循环控制,每轮执行完检查 early stop 条件。循环外面套了一层 OTel 的agentic_searchspan,迭代次数、early_stop 原因都作为 span 属性记录,在 Langfuse 里能直接看到。“
2. SYS_PROMPT 5 条策略引导#
SYS_PROMPT 不约束具体步骤,而是给出 5 条策略让模型自主决策:
- 先用 searchDocs 检索——优先 KB 是策略建议,不是”第一步必须搜 KB”
- 多焦点分别检索——“对比 A 和 B”要对每个焦点分别搜,不要合成一句模糊查询
- 多跳先查中间事实——“X 的负责人在哪年入职”要先搜负责人是谁,拿到后再用它构造下一跳查询
- 搜空即止不换近义词——某来源返回空或不相关时,换思路或停止,不要换个近义说法重搜同一来源
- 不暴露预算——“用尽量少的轮数”而非”你最多调用 4 次”
另外 SYS_PROMPT 用了明确的角色约束:“你的唯一职责是调用工具把资料检索齐全,你不负责作答。“这防止模型在检索阶段就开始生成长篇答案——答案生成是后面的 prompt assembly + LLM generation 阶段的事。
面试怎么讲:“策略引导而非步骤约束是核心设计哲学。‘先搜 KB’是建议不是命令——模型看到用户问的是时效性问题,自己会判断先搜 Web。‘多焦点分别检索’是告诉模型拆解策略,但具体怎么拆、拆几步、先拆哪个,由模型根据实际结果自主决定。角色约束也很重要——明确说’不负责作答’,避免模型在检索阶段输出长篇答案浪费 token。“
3. 空轮早停(Zero-Yield Detection)#
每轮工具调用后,通过 chunksSeen 跟踪累积 chunk 数量。如果一轮工具调用后 roundYield == 0(没有新增 chunk)且 chunksSeen > 0(已有累积资料),说明模型在空转——提前 break 并在 OTel span 上标记 rag.agentic.early_stop=empty_round。
注意 early stop 的条件是 roundYield == 0 && now > 0——只有”已有资料但本轮没新增”才触发。如果 now == 0(从头到尾什么都没搜到),不触发 early stop,让模型继续尝试(换工具、换 query),直到 max_iterations 耗尽后由调用方(DocMindAgent.agenticWithFallback)回退一次性检索。这个区分很重要:空转 ≠ 搜不到——搜不到应该给模型更多机会,空转应该尽早止损。
该特性可通过配置 agentic.early_stop_on_empty_round 动态开关,便于灰度验证和 A/B 测试。
面试怎么讲:“空转检测是硬护栏——prompt 里写了’不要换近义词重搜’,但模型不一定遵守。代码层的 zero-yield detection 是不可绕过的:chunksSeen 不增长就 break,不管模型觉得自己在做什么。这比 prompt 约束可靠得多。但要注意区分’空转’和’搜不到’——已有资料但本轮无增益是空转,一直搜不到则继续给机会。“
4. 推理失败诊断与修复(三个真实案例)#
案例一:多跳推理失败——中间事实未提取
- 现象:用户问”负责用户认证模块的开发者是哪一年入职的”
- trace 回溯:第 1 轮
searchDocs("用户认证模块 开发者")搜到含”张三负责用户认证模块”的 chunk(chunksSeen: 0→3);第 2 轮searchDocs("用户认证模块 入职时间")而非searchDocs("张三 入职时间")(chunksSeen: 3→3,零增益触发 early stop) - 根因:模型没有从返回结果中提取中间事实”张三”作为下一跳 query,而是直接用原始问题的关键词组合
- 修复:SYS_PROMPT 多跳策略加强为”先检索中间事实,拿到后再用它(而非原始问题的关键词)构造下一跳查询”
- 效果:修复后模型在第 2 轮正确构造了
searchDocs("张三 入职时间"),成功搜到入职信息 - 教训:多跳推理的难点不在”知道要多跳”,而在”从上一步结果中提取正确的中间实体”——prompt 必须明确引导这个提取动作
案例二:同义词空转——三轮无增益
- 现象:模型搜”Docker 部署指南”得到 3 条 chunk,又搜”Docker 部署教程”、“Docker 安装部署步骤”——三轮同义表达搜同一来源
- trace 回溯:
chunksSeen三轮无增长(3→3→3),第 3 轮触发 early stop - 根因:向量检索对同义改写不敏感——“部署指南”和”部署教程”搜出来的是同一批 chunk。模型以为换个说法能搜到新东西,实际上是无效重试
- 修复:SYS_PROMPT 加入”某来源一旦返回空或不相关,不要换个近义说法把同一来源再搜一遍——要么改用另一种来源(如 webSearch),要么直接结束”
- 残留问题:虽然第 3 轮被 early stop 拦住了,但前 2 轮的 LLM 调用已经浪费了。这是 prompt 软约束的局限——不能保证 100% 杜绝
- 教训:prompt 引导能降低空转频率但无法消除,代码层 zero-yield detection 是必要的安全网
案例三:过早停止——对比类问题只搜了一半
- 现象:用户问”Redis 和 Memcached 的区别”,模型只搜了”Redis”就停止(1 轮)
- trace 回溯:唯一一轮
searchDocs("Redis")返回的 chunk 里顺带提到了 Memcached。roundYield > 0所以 early stop 没触发,iterations=1模型自己判断资料够了 - 根因:查询被 QueryUnderstanding 判为 SIMPLE → 走了 one-shot 路径(RULE_PLANNER),只搜一次。即使走了 agentic 循环,模型也可能因为第一轮 roundYield > 0 就认为”够了”
- 修复:
applyDeterministicSignals将含”对比/分别/vs/区别”的查询从 SIMPLE 升级为 MEDIUM,确保路由到 agentic 循环,且 SYS_PROMPT 的”多焦点分别检索”策略引导模型对每个主题分别搜 - 效果:修复后”Redis 和 Memcached 的区别”进入 agentic 循环,模型分两轮分别搜 Redis 和 Memcached,答案双视角覆盖
- 教训:过早停止靠 prompt 不够——需要从路由层保证多焦点问题有足够轮数,这是确定性后处理(regex)的价值所在
诊断方法总结:trace 中的工具调用参数序列是”X 光片”
三个案例共同的诊断方法:打开 Langfuse trace,找到 agentic_search span,展开每轮 tool_call 的参数。不需要 debug 代码,不需要打断点——工具调用参数序列本身就是模型推理过程的完整记录。案例一看的是第 2 轮 query 参数(是否用了中间事实),案例二看的是 chunksSeen 属性三轮不变,案例三看的是只有 1 轮就停了且 iterations=1。
修复手段分两层:能在 prompt 策略层解决的用 prompt(案例一、二),需要确定性保证的用代码(案例三的 regex 升级)。原则是先试 prompt 引导,不够用时加代码护栏——prompt 改起来快、迭代成本低,但模型不一定遵守;代码护栏可靠但维护成本高、灵活性差。
面试怎么讲:“三个案例都不是看代码 debug 出来的,而是看 Langfuse trace 中工具调用参数序列发现的。这说明 Agent 的调试范式和传统后端不同——你不是在调代码逻辑,你是在调模型的决策序列。trace 中每轮 tool_call 的 name + args + 返回 chunk 数就是模型推理的’X 光片’。“
5. 锚定效应与预算隐藏#
改前在 prompt 中写”你最多调用 4 次工具”,trace 显示简单问题也跑满 4 轮。改后换成”用尽量少的轮数完成检索”,平均迭代从 3.2 轮降到 1.8 轮。buildSeed() 方法注释中明确记录了这个设计决策:代码注释写道”不向模型暴露轮数上限——告知预算会把模型锚定到用满 N 轮”。
面试怎么讲:“这是一个有量化数据的行为经济学发现。告诉模型’你最多调用 4 次’,模型会觉得’还有预算不如再搜搜’,简单事实题也跑满 4 轮。改成’用尽量少的轮数’后简单题 1 轮就停了。上限还是 4——但由代码侧的 for 循环强制,模型不知道自己有多少预算。这降低了 44% 的 LLM 调用成本。“
6. 三路路径决策与 Agentic 循环的入口条件#
Agentic 循环不是所有查询的入口——DocMindAgent.routePath() 做三路分发:
- SELECTED_DOC:用户选了特定文档 + 问的是”总结/概述”类问题 → 跳过搜索,直接读 chunk
- RULE_PLANNER:SIMPLE 且非歧义 → 一次性检索,省一轮 agentic 决策的 LLM 调用
- AGENTIC:COMPLEX / MEDIUM / 歧义 / 多焦点 → 进 agentic 循环,模型自驱搜索
关键设计:拆解 / 多跳的判定不在路由层——路由只看 complexity + ambiguity 决定走哪条路,具体怎么拆解、要不要多跳,由 agentic 循环内的模型运行时自决。这避免了在分类器中塞入 needDecompose / multiHop 等过细的字段——这些字段在实践中准确率低,不如让模型自己根据检索结果决定。
面试怎么讲:“我们有意不在分类器里做’是否需要拆解’的判断——这种判断分类器做不准。我们只做一个粗粒度的路由:简单走 one-shot,复杂走 agentic。进了 agentic 循环后,要不要拆解、怎么拆、要不要多跳,都由模型根据实际检索结果在运行时决定。分类器只负责开门,模型自己决定怎么走。“
7. Agentic 循环的收尾管道#
循环结束后进入 finalize():把多轮工具调用累积的 chunk 并集做统一收尾。这一步很关键——agentic 循环中每轮工具调用返回的 chunk 来源不同(KB 向量 vs Web 搜索),分数体系不可比(余弦相似度 vs BM25 vs Web 原始分)。需要用 Cross-Encoder rerank 统一标尺后才能做有意义的排序和截断。
收尾流程:防御拷贝(RetrievedChunk.copyAll)→ 按稳定 key 去重 → Cross-Encoder rerank(统一标尺)→ MMR 多样性 → token 预算压缩 → CRAG 三档评分。最后按来源拆分统计 kbChunks / webChunks,写入持久化日志和 SSE 事件,方便在前端 agent trace 面板中直接看出”KB 0 条 / Web N 条 / grade=LOW”这类模式。
收尾管道还有一个关键设计:needsFallback 只在 compressed.isEmpty() 时置位——即真正零证据才走兜底模板。CRAG LOW 但 compressed 非空(典型场景:KB 半命中或仅联网搜到内容、rerank 分偏低)不再置位,交由 assembleLowConfidence 模板带证据作答——避免丢弃 KB/Web 证据退回基座模型的参数化记忆产生时效性幻觉。
面试怎么讲:“agentic 循环结束后有一个统一收尾管道——多轮工具调用累积的 chunk 来源不同、分数体系不可比,必须用 Cross-Encoder rerank 统一标尺后才能做有意义的截断。然后 CRAG 评分决定后续走哪个 prompt 模板:HIGH/AMBIGUOUS 走标准模板,LOW 且有证据走 low_confidence 模板(带证据但提示相关性低),真·零证据走 fallback 模板(不喂 chunk)。“
代码锚点#
| 类/方法 | 路径 | 职责 |
|---|---|---|
AgenticSearchOrchestrator.search() | agent/supervisor/AgenticSearchOrchestrator.java | agentic 循环入口 + OTel 父 span + 手动控环 |
AgenticSearchOrchestrator (SYS_PROMPT) | 同上 | 5 条策略引导 + 只读工具角色约束 |
AgenticSearchOrchestrator.emitAgenticStep() | 同上 | 每轮 SSE agentic 事件发射(iteration + tool_calls) |
AgenticSearchOrchestrator.buildAgenticLog() | 同上 | 持久化日志:selectedTools/iterations/cragGrade/kbChunks/webChunks |
StageEmitter.agenticStep() | agent/emit/StageEmitter.java | SSE agentic 事件统一出口 |
QueryUnderstandingService.applyDeterministicSignals() | service/rag/QueryUnderstandingService.java | 确定性后处理:STRONG_COMPLEX_PATTERN regex 升级 complexity |
RetrievalGrader.grade() | service/rag/RetrievalGrader.java | CRAG 三档评分(HIGH/AMBIGUOUS/LOW) |
PromptAssembler.assembleLowConfidence() | service/rag/PromptAssembler.java | CRAG LOW 有证据时带证据作答模板 |
量化数据#
| 指标 | 数值 | 基线 | 来源 |
|---|---|---|---|
| 最大迭代轮数 | 4 | — | agentic.max_iterations 配置 |
| 实测平均迭代 | ~1.8 轮 | 改前 ~3.2 轮(暴露预算时) | Langfuse 统计 |
| 空转检测准确率 | 100%(zero-yield) | — | chunksSeen 跟踪机制 |
| 多焦点 regex 覆盖 | 命中后 SIMPLE→MEDIUM→路由到 AGENTIC | 原来 SIMPLE 走 one-shot | STRONG_COMPLEX_PATTERN |
| OTel span 类型 | 4 种(agentic_search/cross_encoder_rerank/crag_grading/worker) | — | tracer.spanBuilder |
| SSE 事件类型 | 8 种(understanding/routing/agentic/code_exec/retrieval/rerank/grader/done) | — | StageEmitter |
| CRAG 评分档位 | 3 档(HIGH/AMBIGUOUS/LOW) | — | RetrievalGrader |
| 预算隐藏成本节省 | ~44%(3.2→1.8 轮) | — | Langfuse 统计 |
| 工具白名单 | 4 读 + executeCode(searchDocs/keywordSearch/webSearch/recall_memory + executeCode 沙箱计算,默认关) | — | ALLOWED_TOOLS Set |
| 循环内温度 | 0.0(确定性工具选择) | — | OpenAiChatOptions.temperature(0.0) |
| 并行工具调用 | 开启(parallelToolCalls=true) | — | 模型自判哪些可并行 |
| Self-Reflection 收益 | 忠实度 +0.000 / 延迟 +956ms | — | 评测 V4(已删除) |
三、追问应对#
面试官想听到的信号#
- 理解隐式规划的本质和适用场景——不是所有场景都需要显式 Plan,检索场景的信息未知性决定了隐式规划更合适
- trace 驱动调试思维——不是看代码逻辑而是看模型的工具调用参数序列,这是诊断推理失败的唯一有效方法
- 知道何时用 prompt 引导、何时用代码护栏——prompt 是软约束适合策略引导,代码是硬护栏适合安全兜底
- 推理失败模式的实战诊断经验——不只知道理论上有哪些失败模式,而是实际遇到过、在 trace 中定位过、修复过
- 粗粒度路由 + 模型自驱的设计哲学——不在分类器里塞过细字段(needDecompose / multiHop),只做 SIMPLE vs COMPLEX 的粗分流,让模型在运行时自主决策
- 量化思维——锚定效应有数据(3.2→1.8 轮),Reflexion 删除有数据(+0.000 忠实度 / 956ms 延迟),不是靠感觉做决策
追问预判与应答#
Q1:隐式规划够用吗?不需要显式 Plan-and-Execute?
“RAG 检索场景够用。因为检索结果不可预知——你不知道第一步搜出来什么,提前 plan 没意义。但如果任务是’生成报告需要查 5 个固定数据源’这种可预先枚举的,Plan-Execute 并行效率更高。关键判断标准是任务的确定性:步骤确定用 Plan,步骤依赖中间结果用 tool-calling loop。”
Q2:空转检测为什么不只靠 prompt?
“prompt 是软约束——‘不要换近义词重搜’模型不一定遵守,尤其是小模型(我们用 qwen-plus)。代码层 zero-yield detection 是硬检测:
chunksSeen不增长就 break,模型无法绕过。实测也证明了:加了 prompt 引导后空转从’必然发生’降到了’偶尔发生’,但没降到零;加了代码层检测后才真正消除了空转带来的 token 浪费。”
Q3:多跳失败具体怎么通过 trace 发现的?
“Langfuse trace 中每轮 tool_call 有完整参数。第 1 轮
searchDocs(\"用户认证模块 开发者\")返回含’张三负责’的 chunk,第 2 轮参数是searchDocs(\"用户认证模块 入职时间\")而不是searchDocs(\"张三 入职时间\")——模型没有从返回结果中提取中间事实’张三’作为下一跳 query。一看参数就知道问题出在哪——这就是 trace 驱动调试的核心:工具调用参数序列是 Agent 推理的’X 光片’。”
Q4:不暴露预算的锚定效应,有量化数据吗?
“改前平均 3.2 轮/查询,改后 1.8 轮。下降主要来自简单题——改前告诉模型’最多 4 次’,简单事实题也用满 4 轮(每轮都觉得’还有预算不如再搜搜’)。改后用’尽量少的轮数’,简单题 1 轮就停了。复杂题迭代轮数没怎么变(本来就需要多轮)。整体 LLM 调用成本降低约 44%。”
Q5:如何做推理质量的回归测试?
“52 条 golden set 中标注了’预期检索轮数’——简单事实题标注 ≤ 2 轮,对比类标注 ≥ 2 轮。跑评测后对比实际轮数 vs 预期,异常值人工复查 trace。比如某条简单题突然跑了 4 轮——回看 trace 发现是空转,说明 prompt 变更可能引入了回退。这本质上是用迭代轮数作为推理效率的 proxy 指标。”
Q6:为什么不用 Reflexion 让模型自己反思?
“DocMind 最早有 Self-Reflection——首轮评测 V4 忠实度 +0.000 零提升,但反思延迟 956ms 占总延迟 29%。根因是反思只检查’答案是否忠于来源’,但真正的质量问题出在检索阶段(搜不到/搜偏了),不在生成阶段。后来用 CitationParser 覆盖率(纯 Java 0ms)替代反思的质量检测功能——不靠 LLM 判答案质量,靠解析引用标记计算覆盖率。成本从 956ms 降到 0ms,检测能力反而更强。”
Q7:确定性后处理(regex 升级)为什么不全交给 LLM?
“小模型 qwen-plus 在 complexity 判断上会低估多焦点——‘对比 A 和 B’被判为 SIMPLE。这不是 prompt 能修的,是模型能力边界问题。regex 是确定性兜底:看到’对比/分别/vs/区别’等强信号就把 complexity 升级为 MEDIUM,保证路由到 agentic 循环。
STRONG_COMPLEX_PATTERN的正则很保守——只覆盖高确定性的强信号词,避免误升级。这是’模型能力不够、规则来补’的经典模式。”
Q8:agentic 循环异常了怎么办?
“整个 agentic 循环用 try-catch 包裹,异常或空结果时回退一次性检索(
agenticWithFallback)。回退路径用RetrievalPlanner规则引擎选工具 +SupervisorAgent.oneShotRetrieval并行 Worker 分发——和 RULE_PLANNER 模式走的是同一条路。关键原则:请求永不因 agentic 失败而中断,用户无感知地降级到 one-shot。降级事件在 trace 里有记录(routing_fallback SSE 事件),排障时可回溯。“
反击引导#
- “推理诊断依赖的可观测性体系——OTel span + SSE 事件 + 持久化 trace——是工程化的另一个故事,包括 span 设计、Langfuse Score 推送、BusinessRootSpanSampler 白名单采样。”(→ Story 06 线上反馈闭环)
- “空转护栏和 max_iterations 属于Agent 安全约束的硬约束层——工具白名单怎么实现、kbIds 怎么防越权、store_memory 怎么被排除出 agentic 循环,完整的安全边界设计在安全篇展开。”(→ Story 13)
- “多焦点 regex 升级是查询理解的确定性后处理——分类器的完整设计包括意图/复杂度/歧义度/scope 四维分类、Tier-0 规则快路径 + Tier-1 LLM 合并路由、以及 applyDeterministicSignals 的正则兜底设计。”(→ Story 02 Query 改写优化)
- “收尾管道中 Cross-Encoder rerank 跨 KB/Web 统一标尺、MMR 多样性、CRAG 三档评分的具体策略——这些属于检索召回与重排优化。”(→ Story 03/04)
- “工具描述怎么写、@Tool 注解怎么用、白名单怎么实现——这些属于工具设计与 Function Calling 的范畴。”(→ Story 09)
- “CRAG LOW 有证据走 assembleLowConfidence 而非 assembleFallback——可信生成的完整模板体系在生成篇展开。”(→ Story 05)