Claude Code 工具调用机制详解
掘金文章对 Claude Code 中
tool_use的完整执行链路做源码级拆解:从queryLoop判断是否需要 follow-up,到工具分批调度、权限检查、Hook、执行、结果映射、后台任务通知、取消机制,再到 Deferred Tools 的规模化加载设计。
基本信息
- 原始标题:Claude Code 工具调用机制详解
- 来源:掘金
- 原始 URL:https://juejin.cn/post/7658251444873314323
- 采集方式:Telegram bot prefetch(无需再次抓取)
- 知识分类:AI 编程开发 / Claude Code / Agent 工具调用机制
- 相关页面:Claude Code、Claude Code 工具调用机制、Deferred Tools、Function Calling、MCP 模型上下文协议、AI Agent 智能体、AI编程开发
核心观点
-
Claude Code 的 agentic turn 由
queryLoop驱动,是否继续循环只看本轮 assistant 消息里是否出现tool_use。query()把控制权交给queryLoop,后者在每轮执行“调用模型 → 判断 tool_use → 执行工具 → 决定继续或结束”。核心开关是needsFollowUp:没有工具调用就进入收尾判断,有工具调用就执行工具并把结果喂回下一轮。 -
工具调用不是简单
Promise.all,而是先按并发安全性分批,再在批次内并发或串行执行。partitionToolCalls会先解析每个工具 input,再调用工具自己的isConcurrencySafe(parsedInput.data)。只有“连续出现且都声明并发安全”的工具会合并进同一批;一旦 input 解析失败或并发判定抛错,就保守当作不可并发。批次之间严格串行,批次内并发默认上限为 10,可由CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY调整。 -
单个工具调用生命周期由
runToolUse串起:PreToolUse hooks → 权限决策 → 工具执行 → 结果映射 → PostToolUse hooks → 收尾信号。Hook 可以提前 stop、改写 input、给出权限建议或标记preventContinuation;权限层会先处理硬 deny/ask,再看工具自身checkPermissions,最后才考虑 bypass 或 always allow;工具结果会映射成tool_result,必要时持久化到文件,再追加 Hook 附件消息或hook_stopped_continuation。 -
Hook allow 不是越权放行,deny/ask 规则和工具自身安全检查仍能覆盖它。文章特别强调
resolveHookPermissionDecision的语义:Hook 返回allow只表示跳过交互确认,但还要跑规则检查;如果命中 deny、ask 或工具自己的安全检查,仍会阻止执行。这解释了 Claude Code 工具调用里“自动化”和“权限护栏”的真实边界。 -
工具结果过大时走持久化预览,而不是简单截断。单个结果超过阈值(通常是工具声明上限与 50,000 字符的较小值)会写入
tool-results/{id}.json|txt,返回给模型的是预览片段和文件路径;如果一条消息里多个工具结果合计超过约 200,000 字符,也会挑最大的新鲜结果持久化。这避免了并发工具同时返回大结果时撑爆上下文。 -
后台任务不是阻塞等待,而是通过 task-notification 队列旁路回到对话。Bash、PowerShell、AgentTool 支持后台执行:调用方很快拿到
backgroundTaskId,命令或子 agent 继续跑。后台任务完成后塞进进程级队列;如果当时queryLoop仍在运行,会在本轮注入通知;如果已经空闲,REPL 组件的队列处理器会主动发起一轮新 query。 -
中断与取消依赖 Abort Controller 的父子层级和反向冒泡,默认大多数工具不可被用户中途砍掉。工具调用有 query 级根 controller、批次级 sibling controller、单工具 controller 三层。父级 abort 会级联到子级;子级 abort 也会在非
sibling_error情况下冒泡到根级,修复权限拒绝类场景无法终止整轮的问题。工具级interruptBehavior默认是block,目前文章核对到唯一声明cancel的是 SleepTool。 -
Deferred Tools 解决 MCP 工具过多导致上下文膨胀的问题,本质是“客户端先过滤,模型按需搜索,再在下一轮加载完整工具 schema”。工具是否被延迟由客户端本地判定和历史扫描决定:未被发现的 deferred 工具不会进入本次请求;模型先调用 SearchExtraToolsTool 搜索,结果以文本形式写入对话;下一次请求构造时,客户端从历史中解析出已发现工具名,再把对应完整定义加入工具列表。
-
这篇文章的价值在于把“Claude Code 能调用工具”拆成了一个可工程化理解的运行时系统。它不是把模型能力神秘化,而是把调度、权限、Hook、结果存储、后台任务、取消和工具规模化加载逐层拆开,让开发者能判断一个 Agent harness 是否具备生产级可控性。
实操内容保留
并发分批核心逻辑
原文展示 partitionToolCalls 的关键代码,体现“并发安全由工具显式声明、失败时保守串行”的原则:
return toolUseMessages.reduce((acc: Batch[], toolUse) => {
const tool = findToolByName(toolUseContext.options.tools, toolUse.name)
const parsedInput = tool?.inputSchema.safeParse(toolUse.input)
const isConcurrencySafe = parsedInput?.success
? (() => {
try {
return Boolean(tool?.isConcurrencySafe(parsedInput.data))
} catch {
return false // 抛错时保守当作不可并发
}
})()
: false
if (isConcurrencySafe && acc[acc.length - 1]?.isConcurrencySafe) {
acc[acc.length - 1]!.blocks.push(toolUse) // 连续可并发的合并进同一批
} else {
acc.push({ isConcurrencySafe, blocks: [toolUse] }) // 否则单独成批
}
return acc
}, [])批次内并发与串行调度
let currentContext = toolUseContext
for (const { isConcurrencySafe, blocks } of partitionToolCalls(toolUseMessages, currentContext)) {
if (isConcurrencySafe) {
// 并发批次:runToolsConcurrently
for await (const update of runToolsConcurrently(blocks, assistantMessages, canUseTool, currentContext)) {
yield { message: update.message, newContext: currentContext }
}
} else {
// 串行批次:runToolsSerially
for await (const update of runToolsSerially(blocks, assistantMessages, canUseTool, currentContext)) {
yield { message: update.message, newContext: currentContext }
}
}
}权限决策顺序
原文把 hasPermissionsToUseToolInner 简化为三段:先硬边界,再宽松放行,最后兜底 ask。关键点是 bypass 只在硬 deny/ask 和工具自身安全检查之后才生效。
// 1. 硬边界检查:命中即 return,即便 bypass 模式也拦截
const denyRule = getDenyRuleForTool(ctx, tool)
if (denyRule) return { behavior: 'deny', ... }
const askRule = getAskRuleForTool(ctx, tool)
if (askRule && !canSandboxAutoAllow) return { behavior: 'ask', ... }
const toolPermissionResult = await tool.checkPermissions(input, context)
if (toolPermissionResult.behavior === 'deny') return toolPermissionResult
if (tool.requiresUserInteraction?.() && toolPermissionResult.behavior === 'ask') return toolPermissionResult
if (toolPermissionResult.behavior === 'ask' && toolPermissionResult.decisionReason?.rule?.ruleBehavior === 'ask') return toolPermissionResult
if (toolPermissionResult.behavior === 'ask' && toolPermissionResult.decisionReason?.type === 'safetyCheck') return toolPermissionResult
// 2. 宽松放行:只有上面全部没命中,才会执行到这里
if (shouldBypassPermissions) return { behavior: 'allow', decisionReason: { type: 'mode' } }
if (toolAlwaysAllowedRule(ctx, tool)) return { behavior: 'allow', decisionReason: { type: 'rule' } }
// 3. 兜底
return { ...toolPermissionResult, behavior: 'ask' }后台任务触发条件
if (run_in_background === true && !isBackgroundTasksDisabled) {
const shellId = await spawnBackgroundTask()
return { stdout: '', stderr: '', code: 0, interrupted: false, backgroundTaskId: shellId }
}AgentTool 的后台条件不止显式参数:
run_in_background === true || selectedAgent.background === true || isCoordinator || forceAsyncDeferred Tools 模式配置
ENABLE_SEARCH_EXTRA_TOOLS 模式
auto / auto:1-99 tst-auto
true / auto:0 tst
false / auto:100 standard
(未设置) tst ← 默认值关键概念
- Claude Code 工具调用机制:Claude Code 内部从
tool_use出现到工具结果回传的完整运行时机制,包括调度、权限、Hook、执行、结果存储、后台任务和取消。 - Deferred Tools:当 MCP/外部工具数量过多时,将工具 schema 延迟加载的机制,先让模型搜索工具,再在后续请求中加载完整定义。
- Function Calling:更通用的模型工具调用机制,Claude Code 的
tool_use可以看作 Agent harness 中的一种具体实现。 - MCP 模型上下文协议:Deferred Tools 主要缓解 MCP 工具过多时的上下文占用,MCP 负责标准化外部能力接入。
- AI Agent 智能体:本文提供了 Agent “观察-行动-再观察”循环中“行动”部分的工程级实现剖面。
与其他素材的关联
- 与 2026-06-29-27-hidden-claude-code-features 互补:后者讲 Claude Code 使用技巧和心智模型,本文讲工具执行运行时。
- 与 2026-07-05-juejin-context-engineering-harness 互补:后者讲 Prompt → Context → Harness 的生产化框架,本文给出 Claude Code 这个 harness 内部如何调度工具、校验权限、处理结果。
- 与 2026-07-01-woshipm-multi-agent-coding-pipeline 互补:后者讲多 agent 协作流水线的组织方法,本文解释 AgentTool 后台执行、通知队列和工具级取消为何会影响多 agent 编排体验。
- 与 2026-05-13-ai-agent-productivity-20x 的“Agent harness + MCP + Skill + memory”体系相连:本文展示 harness 层如何把工具能力变成可控的执行链路。
原文精彩摘录
queryLoop用while (true)驱动一次 agentic turn:每次迭代做“调用模型 → 判断要不要执行工具 → (执行工具)→ 决定继续循环还是结束”。核心判断点只有一个变量:needsFollowUp——本轮 assistant 消息里是否出现了tool_use内容块。
批次之间严格串行——外层是一个普通
for...of循环,必须等上一批(不管并发还是串行)完全跑完,才会开始下一批。
Hook 返回
allow不是直接放行,还会再跑一次checkRuleBasedPermissions——如果规则检查出deny或“内容级 ask”,会覆盖 hook 的 allow;只有规则检查也放行,才真正跳过用户确认。
Deferred tools 机制让这些工具先只以“名字”轻量出现,模型需要时再主动检索、展开完整 schema。关键点:deferred 工具“该不该出现在这次请求里”,是客户端本地过滤决定的,不是服务端决定的。