🤯 第二十五篇:Multi-turn Reasoning多轮推理,复杂问题的拆解艺术

作者:Claude Code源码探索者 | 深度解析AI Agent的思考链


当你对Claude Code说"帮我重构这个项目并写出测试"时,它不是一次性给你答案,而是像人类专家一样——先理解问题、再拆解步骤、然后一步步执行。中途遇到障碍还会回头调整策略。这种"边想边做、错了就改"的能力,就是Multi-turn Reasoning(多轮推理)

这篇文章,我带你扒开Claude Code的思维链源码,看看它到底是怎么做到的。


一、为什么多轮推理是AI Agent的核心能力

传统AI回答问题,像查字典——你问,它答,一问一答,干干净净。

但真实世界的任务不是这样的:

用户:帮我把后端从Java迁到Go,同时保留所有API兼容性和测试覆盖率

这个任务至少有这几个层次:

  1. 理解需求边界 — 什么叫"API兼容"?REST还是gRPC?
  2. 信息收集 — 先看看现有项目结构、测试覆盖率
  3. 制定计划 — 按什么顺序迁移?模块依赖图怎么画?
  4. 执行验证 — 每迁移一个模块,同步跑测试
  5. 调整策略 — 遇到不兼容的库,临时找替代方案
  6. 收尾确认 — 集成测试、性能对比

这不是一次性问答能搞定的。这需要多轮内部推理 + 外部执行交替进行

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系统 — 异步任务管理器的设计与实现
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐