第4章 推理循环算法
“Intelligence is not a single act, but an iterated loop of perception, reasoning, and action.” 4.1 问题引入:Agent 的心跳 当用户在终端输入一行自然语言指令——比如"帮我把这个函数重构成两个"——背后发生的事情远比想象中复杂。Claude Code 不会仅仅调用一次大语言模型就给出答案。相反,它启动了一个推理循环(Reasoning Loop):先理解任务,再决定调用哪个工具,然后执行工具、观察结果,之后再次推理——如此反复,直到任务完成。 这个循环就像 Agent 的心跳。每一次搏动,Agent 都在感知世界(读取工具执行结果)、思考对策(调用 LLM)、采取行动(调用工具)。心跳停止,Agent 停止。心跳失控,Agent 陷入死循环。如何设计这个循环,使其既足够灵活以应对任意复杂的任务,又足够可控以避免失控和资源浪费——这是 Agent 系统最核心的工程挑战。 在 Claude Code 的源码中,这个推理循环分布在两个关键模块中:QueryEngine(src/QueryEngine.ts)负责会话级别的生命周期管理,包括用户输入处理、系统提示词构建、会话持久化等;query(src/query.ts)负责核心推理循环的实际执行,包括 LLM 调用、工具执行、上下文压缩、异常恢复等。两者的关系可以类比为操作系统中的进程管理器与CPU 调度器——前者管理进程的创建和资源分配,后者决定每个时间片做什么。 本章将深入解析这一核心循环的算法设计。 4.2 算法思想 4.2.1 核心 Loop 算法:永动的推理引擎 推理循环的基本骨架是一个 while(true) 无限循环。乍看之下这似乎很原始,但其精妙之处在于退出条件的分布式设计——循环体内有多个出口,每个出口对应一种终止语义。 让我们先看最精简的算法骨架: 算法 4-1:推理循环核心骨架 function queryLoop(params): state ← 初始化(params) while true: // 阶段1:准备 messagesForQuery ← 预处理消息(state.messages) messagesForQuery ← 自动压缩IfNeeded(messagesForQuery) // 阶段2:调用 LLM for message in streamCallModel(messagesForQuery): yield message // 向外部流式输出 if message.type == 'assistant': 收集工具调用块(message) // 阶段3:判断是否需要继续 if 无工具调用: 执行停止钩子检查() return { reason: 'completed' } // 出口1:正常完成 // 阶段4:执行工具 toolResults ← executeTools(toolUseBlocks) yield toolResults // 阶段5:检查终止条件 if 用户中断: return { reason: 'aborted' } // 出口2:用户中断 if 超过最大轮次: return { reason: 'max_turns' } // 出口3:轮次上限 // 阶段6:组装下一轮输入 state.messages ← [...messagesForQuery, ...assistantMessages, ...toolResults] state.turnCount++ 这个算法有几个值得注意的设计决策: ...