news 2026/10/24 5:25:49

深入 Claude Code 核心:Agent 主循环 query() 的 AsyncGenerator 工作原理解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入 Claude Code 核心:Agent 主循环 query() 的 AsyncGenerator 工作原理解析

【免费下载链接】claude-code-sourcemap

claude-code full original source code from source maps

项目地址:https://gitcode.com/gh_mirrors/cla/claude-code-sourcemap
点击查看免费下载

本文带你深入 Claude Code 的完整原始源码(claude-code-sourcemap,基于 source map 还原的 0.2.8 版本),用通俗易懂的方式解析其 Agent 主循环核心函数query()如何借助 TypeScript 的 AsyncGenerator 实现"模型调用 → 工具执行 → 递归追问"的自动循环。无需深厚背景,看完你就懂 AI 编程 Agent 是怎么"自己干活"的。

一张图看懂 Agent 主循环

先用一张流程概览,把 src/query.ts 里query()的主循环逻辑浓缩成 5 个环节:

用户消息 │ ▼ ① 组装系统提示词 ──► ② 调用模型(querySonnet) │ │ │ ▼ │ ③ 从回复里提取 tool_use 块 │ │ │ 没有工具调用?──► 结束(本轮对话完成) │ │ 有 ▼ ▼ ⑤ 把 [历史 + 助手消息 + 工具结果] 递归传回 query() ◄── ④ 权限校验 + 执行工具

这个循环就是所有"Agentic Coding"工具的灵魂:模型想干什么就调什么工具,工具结果再喂回给模型,直到它认为任务完成、不再请求工具为止。

什么是 AsyncGenerator?为什么 Agent 框架偏爱它

AsyncGenerator(异步生成器)是 JavaScript/TypeScript 的一个语言特性:函数用async function*声明,就能一边"暂停"一边"吐出"数据。对 Agent 框架来说它几乎是完美方案,原因有三:

  • 流式推送:模型每回复一条消息、工具每产出一个进度,就能立刻yield给界面,终端不用干等;
  • 天然挂起等待用户:执行写文件这类危险操作前,权限检查可以在生成器内部"卡住"等用户点确认,其余代码无需轮询;
  • 递归无栈风险:query()通过yield*递归调用自己(src/query.ts#L234-L241),因为每层都是生成器而非常规函数调用栈,"模型 ↔ 工具"可以无限来回而不爆栈。

query()的函数签名非常直观,返回类型就是AsyncGenerator<Message, void>(src/query.ts#L124-L134):

参数作用
messages迄今为止的完整对话历史
systemPrompt+context系统提示词与附加上下文,由formatSystemPromptWithContext合并
canUseTool权限钩子,决定"这个工具让不让用"
toolUseContext运行环境:工具列表、中止信号、思考 token 上限等

它吐出的Message共有三种形态(src/query.ts#L35-L66):

类型含义用途
UserMessage工具执行结果(tool_result)拼回对话历史,喂给下一轮模型
AssistantMessage模型回复,含costUSD成本与耗时展示给用户
ProgressMessage工具执行中的中间进度仅供 UI 实时展示,不进入对话历史

四步拆解:一次 query() 的完整旅程

第 1 步:请求模型

query()内部定义了getAssistantResponse(),它调用 querySonnet(),把归一化后的消息、系统提示词、思考 token 上限和工具列表发给 Anthropic API。注意一个细节:请求会被 VCR(录像回放中间件)包一层,方便离线测试。

第 2 步:判断"是否还要用工具"

模型回复后,代码直接过滤出所有tool_use类型的 content 块(src/query.ts#L171-L178):

源码注释特意提醒:stop_reason === 'tool_use'不可靠,不能总被正确设置——所以官方选择"看实际内容"而不是"看状态码",这是实战踩坑经验的直接体现。

如果没有任何tool_use,函数直接return,本轮结束。

第 3 步:权限校验 + 执行工具

每个tool_use都会走 checkPermissionsAndCallTool() 的完整流水线:

  1. Schema 校验:用 Zod 验证模型生成的入参。源码里有一句幽默的注释——"surprisingly, the model is not great at generating valid input"(出乎意料地,模型生成合法入参的能力并不好),校验失败会把InputValidationError作为错误结果返回给模型,让它自我修正;
  2. 业务校验:各工具自己的validateInput();
  3. 权限询问:调用canUseTool,它背后是 useCanUseTool 钩子,会弹出终端确认框;用户按 Esc 取消时会触发abortController.abort(),一次性中止所有挂起的工具调用;
  4. 真正执行:tool.call()本身也是一个异步生成器,result表示最终结果,progress表示中间进度,两者分别包装成UserMessage和ProgressMessage向外吐出。

第 4 步:排序结果,递归再问

所有工具执行完后,toolResults会按与tool_use请求相同的顺序排序(API 要求 tool_result 与 tool_use 一一对应,src/query.ts#L223-L232),然后执行最精妙的一行:

yield* await query([历史, 助手消息, 工具结果...], ...)

yield*把子生成器的每一条输出原样转发给上层,对话历史不断累积,循环就这样一轮一轮推进,直到模型交卷。

并行与串行:MAX_TOOL_USE_CONCURRENCY 的策略

模型常常一条回复里同时请求多个工具。query()的策略是(src/query.ts#L182-L216):

  • 全部是只读工具(如文件读取、grep 搜索)→ 走runToolsConcurrently并行执行,上限由 MAX_TOOL_USE_CONCURRENCY = 10 控制;
  • 只要有一个非只读工具(如改文件、跑 shell)→ 降级为runToolsSerially串行执行,避免并发改坏状态。

并行能力来自 utils/generators.ts 的 all():它用Promise.race从一批生成器中"谁先吐数据就先转发谁",实现带并发上限的合流,是 AsyncGenerator 编程的教科书级用法。

顺带一提,Agent 可用工具全集在 src/tools.ts 中注册,包括BashTool、FileEditTool、GrepTool、AgentTool(子 Agent)等十余种。

消息流如何驱动终端 UI

生成器是"发动机",而 REPL 界面 是"仪表盘"。用户提交消息后,onQuery只做一件事:

for await (const message of query(...)) { setMessages(old => [...old, message]) // 每来一条就渲染一条 }

模型回复、工具进度、执行结果逐条到达,React 状态逐条追加——你在终端里看到的"实时打字 + 工具执行动画",本质上就是这个for await循环在消费 AsyncGenerator。这也解释了为什么 Claude Code 支持随时 Esc 中断:中止信号贯穿整条消息链,任何一环都能立刻"刹车"。

源码速查表:跟着 Agent 主循环读代码

想了解什么去哪里看
Agent 主循环与递归逻辑src/query.ts
工具并发合流all()src/utils/generators.ts
模型 API 请求封装src/services/claude.ts
权限确认钩子src/hooks/useCanUseTool.ts
消息类型定义src/utils/messages.tsx
工具注册表src/tools.ts
UI 消费端 REPLsrc/screens/REPL.tsx

小结

Claude Code 的query()用一个不到 120 行的 AsyncGenerator 递归函数,搭起了整个 Agent 的骨架:生成器负责流式产出消息,递归负责"模型—工具"无限循环,yield*负责层层透传,AbortController负责人类随时叫停。理解了这套模式,你再看任何 AI Agent 框架的 ReAct 循环、工具调用管线,都会发现殊途同归。

💡 延伸建议:先通读query()全文(约 516 行),再对照all()与checkPermissionsAndCallTool(),是掌握 Agent 主循环源码的最佳阅读路径。

【免费下载链接】claude-code-sourcemap

claude-code full original source code from source maps

项目地址:https://gitcode.com/gh_mirrors/cla/claude-code-sourcemap
点击查看免费下载

相关推荐

上一篇:ASO Skills App Preview视频脚本:30秒抓住用户的逐拍结构
下一篇:LightCraft命令行与JSON控制协议:139个命令实现照片处理全流程自动化

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 5:25:33

webgis 技能要求

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 5:25:25

LiveAutoRecord 跨平台直播录制完整教程:从安装到多频道自动录制

LiveAutoRecord 跨平台直播录制完整教程&#xff1a;从安装到多频道自动录制 【免费下载链接】LiveAutoRecord 基于 Electron 的多平台直播自动录制软件 项目地址: https://gitcode.com/GitHub_Trending/li/LiveAutoRecord LiveAutoRecord 是一款开源的跨平台直播自动录…

作者头像 李华
网站建设 2026/10/24 5:24:43

金蚕蛊留在业务系统里的伤口,ABAP 如何识别持续消耗型故障

一张销售订单今天保存成功,明天运行的交货作业却把它挑了出来,报一次错。作业次日照常执行,同一张订单又报一次错。过了几天,业务人员看到的仍然只是订单暂时无法交货,技术团队看到的却是不断累积的失败记录、重复调用和人工处理时间。这种故障很适合借《仙剑奇侠传 1》里…

作者头像 李华
网站建设 2026/10/24 5:24:42

什么是GEO?别再把它当成SEO的升级版了

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 5:23:45

【FDE系列】阶段2:Day 39:Linux 综合实战 — 让服务无人值守

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华