第二十五篇:Multi-turn Reasoning多轮推理,复杂问题的拆解艺术
🤯 第二十五篇:Multi-turn Reasoning多轮推理,复杂问题的拆解艺术
作者:Claude Code源码探索者 | 深度解析AI Agent的思考链
当你对Claude Code说"帮我重构这个项目并写出测试"时,它不是一次性给你答案,而是像人类专家一样——先理解问题、再拆解步骤、然后一步步执行。中途遇到障碍还会回头调整策略。这种"边想边做、错了就改"的能力,就是Multi-turn Reasoning(多轮推理)。
这篇文章,我带你扒开Claude Code的思维链源码,看看它到底是怎么做到的。
一、为什么多轮推理是AI Agent的核心能力
传统AI回答问题,像查字典——你问,它答,一问一答,干干净净。
但真实世界的任务不是这样的:
用户:帮我把后端从Java迁到Go,同时保留所有API兼容性和测试覆盖率
这个任务至少有这几个层次:
- 理解需求边界 — 什么叫"API兼容"?REST还是gRPC?
- 信息收集 — 先看看现有项目结构、测试覆盖率
- 制定计划 — 按什么顺序迁移?模块依赖图怎么画?
- 执行验证 — 每迁移一个模块,同步跑测试
- 调整策略 — 遇到不兼容的库,临时找替代方案
- 收尾确认 — 集成测试、性能对比
这不是一次性问答能搞定的。这需要多轮内部推理 + 外部执行交替进行。
Claude Code的核心设计哲学,就是围绕这个能力构建的。
二、Claude Code的推理循环:ReAct模式的深度实现
Claude Code内部运行的是一个改良版的 ReAct(Reasoning + Acting)循环:
输入任务
↓
[推理阶段] 分析问题 → 拆解步骤 → 制定计划
↓
[执行阶段] 执行工具(读文件/运行命令/调用API)
↓
[评估阶段] 检查结果 → 判断是否达到目标
↓
[调整阶段] 若失败 → 调整计划重新推理
↓
继续下一轮,直到任务完成
这在Claude Code源码中,核心体现在以下几个模块:
2.1 推理入口:Loop机制
src/loop.ts(或类似文件)是整个Agent的主循环。它负责:
- 接收用户的高层次指令
- 生成推理步骤(Thought)
- 决定下一步行动(Action)
- 评估执行结果(Observation)
典型代码结构类似这样:
async function runLoop(task: Task) {
let state = await initializeState(task);
let iteration = 0;
const maxIterations = 100;
while (!state.isComplete && iteration < maxIterations) {
// 推理阶段:分析当前状态,决定下一步
const thought = await reasoningEngine.think(state);
// 记录推理过程(上下文累积)
state.addThought(thought);
// 执行阶段:根据推理结果执行工具
const result = await executor.execute(thought.action);
// 评估阶段:检查结果
const evaluation = evaluator.evaluate(result, state.goal);
if (evaluation.success) {
state.markComplete();
} else if (evaluation.adjustmentNeeded) {
// 调整策略,保留之前经验
state.adjustPlan(evaluation.feedback);
}
iteration++;
}
return state.finalResponse();
}
关键点:不是一次性把问题喂给LLM,而是迭代式地让LLM在每一步都能看到"我做了什么"+“结果是什么”+“我接下来要做什么”。
2.2 上下文构建:每轮推理的输入来自哪里
Claude Code在每轮推理时,并不是只把"当前任务"和"最近一次结果"传给LLM。它构建的上下文包含:
| 内容 | 来源 | 作用 |
|---|---|---|
| 系统提示词 | src/prompts/system.md |
定义Agent角色和行为规范 |
| 当前任务描述 | 用户输入 | 明确要解决的核心问题 |
| 已执行的步骤历史 | state.thoughtHistory |
LLM知道"做到了哪一步" |
| 最新工具返回结果 | 工具执行层 | LLM能看到执行反馈 |
| 相关代码上下文 | 文件读取工具 | 决策时有足够的背景信息 |
| 约束条件 | 配置文件 | 边界条件、安全规则 |
这种渐进式上下文构建(Progressive Context Building),是实现可靠多轮推理的技术基础。
三、复杂问题的拆解策略
Claude Code不是简单地把任务切成"步骤1、步骤2、步骤3"。它的拆解策略要复杂得多。
3.1 三层拆解模型
第一层:任务理解(Task Decomposition)
把用户指令拆成可执行的子任务。用自然语言规划,而不是用固定模板:
原始任务:重构用户认证模块支持OAuth2
↓
子任务:
1. 分析现有认证流程,识别依赖点
2. 设计OAuth2集成方案(选Provider:Google/GitHub/自定义)
3. 实现OAuth2回调处理逻辑
4. 修改登录入口,做渐进式切换
5. 写OAuth2相关测试用例
6. 更新文档
第二层:依赖分析(Dependency Analysis)
不是所有子任务都能并行。Claude Code会分析哪个任务依赖哪个结果:
子任务3(OAuth2回调)依赖子任务2(方案设计)的结论
子任务5(测试)依赖子任务3(实现)
子任务4(入口修改)依赖子任务3完成
这样就能生成最优执行顺序,最大化并行度。
第三层:动态调整(Dynamic Adjustment)
执行过程中遇到意外怎么办?比如:
用户任务:把项目迁移到Go
↓
执行中遇到:某个底层库没有Go版本,只有Rust版本
↓
Claude Code不是报错退出,而是:
1. 识别问题(依赖库缺失)
2. 评估选项(A.找替代Go库 B. 用cgo包装Rust库 C. 建议用户换方案)
3. 执行调整(选B,并在代码中标注技术债)
4. 继续执行
这就是多轮推理中的"元认知"能力 — 不仅在解决问题,还在监控自己的解决方案是否合理。
3.2 回溯机制(Backtracking)
Claude Code不是一条道走到黑的。如果某一步的结果和预期不符,它会回溯并尝试替代方案。
典型场景:
步骤3:执行数据库迁移脚本
→ 预期:所有表结构更新成功
→ 实际:某字段与新结构不兼容,报错
→ 推理:问题出在历史遗留字段,需要先清理数据
→ 回溯:回到步骤2,增加数据清理步骤
→ 重试:清理 → 再次迁移 → 成功
这种能力在代码生成中尤为重要 — 生成一段代码、运行测试、发现失败、调整代码、再运行 — 这本身就是多轮推理在实际编程场景中的应用。
四、Token窗口管理与推理效率
多轮推理有一个天然的敌人:上下文长度。
每轮推理都会在上下文中累积历史。如果不做管理,很快就会把200K的Token窗口塞满。
Claude Code的解决方案:
4.1 滚动摘要(Rolling Summary)
对于太长的历史,Claude Code会周期性生成摘要,把多轮细节"压缩"成语义等价但更短的形式:
原始历史(5000 tokens):
"步骤1:读取了main.go,识别到3个认证相关函数...
步骤2:找到了auth/login.go和auth/session.go...
步骤3:分析了两个文件的依赖关系...
..."
压缩后(200 tokens):
"已分析认证模块结构:2个核心文件,涉及3个函数。
依赖关系:session.go → login.go → main.go。
计划:下一步实现OAuth2回调。"
4.2 优先级保留
不是所有历史都一样重要。Claude Code会智能判断:
- 高优先级保留:最近执行的结果、当前任务的目标、关键决策理由
- 可压缩:重复的失败尝试、已过时的中间状态
- 可丢弃:工具调用参数详情(除非出错)
五、真实的"边想边做"场景
光说不练假把式。以下是一个真实的多轮推理执行轨迹(简化版):
用户:帮我把这段Python代码重构,用上TypeScript的类型系统
[第1轮推理]
Thought:我需要先理解这段Python代码的结构和类型使用情况
Action:read_file(path="src/processor.py")
Result:代码有300行,使用了4个类,若干动态类型
[第2轮推理]
Thought:代码结构清楚了,有几个地方的动态类型会影响迁移
需要特别注意:第87行的dict、第142行的list comprehension
Action:read_file(path="src/processor.py", highlight="87,142")
Result:确认两处动态类型,需要定义接口
[第3轮推理]
Thought:现在要设计TS类型方案。4个类 → 4个接口
动态dict → TypedDict
动态list → 具体类型数组
Action:write_file(path="src/types.ts", content="interface ProcessorConfig...")
Result:类型文件写入成功
[第4轮推理]
Thought:现在可以逐个类进行迁移。先从最简单的Processor类开始
Action:write_file(path="src/Processor.ts", content="export class Processor...")
Result:类文件写入
[第5轮推理]
Thought:运行TypeScript编译检查结果
Action:exec(cmd="npx tsc --noEmit")
Result:3个类型错误(两处是遗漏的any,一处是循环引用)
[第6轮推理]
Thought:有错误,先修复循环引用问题,再补全遗漏的any
Action:edit_file(path="src/Processor.ts", fix="add type annotation")
Result:循环引用解决
[第7轮推理]
Thought:再运行一次检查
Action:exec(cmd="npx tsc --noEmit")
Result:全部通过 ✅
[第8轮推理]
Thought:现在运行测试确保重构没有破坏功能
Action:exec(cmd="npm test")
Result:测试全部通过 ✅
最终状态:任务完成
注意这个轨迹中的来回调整(第5轮发现问题 → 第6轮修复 → 第7轮验证)。这不是预设的执行计划,而是实时推理的产物。
六、多轮推理的工程难点
说了这么多优点,也要看看工程实现上的难点:
6.1 推理成本控制
每轮推理都要调用一次LLM API。复杂任务可能有几十轮,成本会急剧上升。
Claude Code的策略:用更小的模型处理"执行层"推理(判断工具参数、简单决策),只在关键节点(制定计划、复杂调整)调用主力模型。
6.2 状态一致性
当多轮推理跨越很长时间时(比如用户离开了几小时再回来),状态可能会过时。
Claude Code会在每次恢复会话时,先用状态同步步骤确认:项目当前状态 + 上次执行到了哪一步 + 是否需要重新初始化环境。
6.3 无限循环检测
最危险的情况:Agent陷入"思考-执行-失败-再思考-再执行-再失败"的死循环。
Claude Code有循环检测机制:如果连续N轮执行相同的操作(且都失败),会强制终止并向用户报告"我卡住了"。
七、给开发者的启示
理解了Claude Code的多轮推理机制后,对我们有什么实际帮助?
1. 写提示词时,给它"思考空间"
❌ 差的指令:帮我写一个排序算法
✅ 好的指令:帮我写一个排序算法,要求:1)支持多种数据类型 2)在大数据量时有优化 3)有完整的单元测试 4)如果某类型不支持请说明原因
后者给了Claude Code多轮推理的方向,它会先分析数据类型,再设计算法,再写测试。
2. 遇到它卡住时,给它"提示"而不是"答案"
❌:你直接用QuickSort实现
✅:注意数据中有大量重复值,选择排序算法时考虑这种情况
后者保留了它的推理空间,同时引导了方向。
3. 利用它的"边做边调整"能力
不要等想到了所有细节才让它执行。给它一个不完美的起点,让它边做边调整,往往比你想好再让它做效果更好。
总结
Claude Code的Multi-turn Reasoning,不是简单的"循环调用LLM",而是一套完整的推理-执行-评估-调整工程体系:
| 模块 | 核心职责 |
|---|---|
| Loop循环 | 驱动整个推理过程 |
| 推理引擎 | 理解问题、制定计划 |
| 执行器 | 调用工具、收集结果 |
| 评估器 | 判断是否成功、是否需要调整 |
| 状态管理 | 累积上下文、压缩历史、跨轮持久化 |
理解了这些,你就知道为什么Claude Code能做到"你只说一句话,它能干一整天"了。
下一篇预告:「Post Sampling Hooks 采样后钩子 — 自定义扩展点」。讲讲Claude Code在模型输出之后、返回给你之前,都做了哪些"后处理"操作,以及如何通过Hooks定制这些行为。
往期回顾:
- 第24篇:我肝了500行源码,发现Claude Code执行Shell命令的「黑科技」
- 第23篇:AppState状态管理 — 10万行代码的全局状态如何有序运转
- 第22篇:Tasks系统 — 异步任务管理器的设计与实现
更多推荐


所有评论(0)