# 14. Function Calling / Tool Use 工具调用

# 14.1 原理

Function Calling 是模型生成结构化动作的接口,Tool Use 是从定义、选择、审批、执行到结果反馈的完整生命周期。

安全执行顺序:

lookup → schema parse → semantic validate → hooks/policy  // 解析工具并依次做类型、语义、Hook 与策略校验
→ approval → sandbox → execute → normalize result        // 获批后在沙箱执行,并统一成功或错误结果结构
→ output bound → persist → feed back to model            // 控制输出大小、持久化事实,再反馈给下一次采样

Schema 校验只能保证类型正确,不能保证语义安全。例如 {command: "rm -rf ..."} 完全符合字符串 schema,仍需命令解析、权限和沙箱。

并发策略也不能只由“模型一次返回多个 ToolCall”决定:只读工具通常可并发;写文件、改变工作目录、修改共享 Context 或依赖前一步结果的调用必须串行。把并发结果加入下一轮 Context 时,应保持原 ToolCall 顺序,或通过明确的 call id 与调用对应。

# 14.2 Claude Code 源码

partitionToolCalls 根据 Tool 的 isConcurrencySafe 划分批次;并发工具产生的 Context Modifier 先按 ToolUse id 缓存,之后按原调用顺序应用,避免完成时序导致状态不确定:toolOrchestration.ts:91toolOrchestration.ts:36

执行链对 input schema、Tool 自定义验证、Hook 和 Permission 分层处理;执行成功后统一映射 ToolResult、记录大小与 OTel 事件,再跑 PostToolUse Hook:toolExecution.ts:650toolExecution.ts:798toolExecution.ts:1271

# 14.3 Codex 源码

Codex 的 ToolRouter、Registry 与 Parallel Runtime 将协议 ToolCall 路由到具体 Handler;MCP 是 Tool Handler 的一种,Shell/ApplyPatch/协作工具也共享生命周期和事件模型:router.rsparallel.rs

# 14.4 伪代码

function partition(calls):                                            // 将模型同时产生的 ToolCall 划分为安全执行批次
    batches = []                                                       // 初始化保持原始调用顺序的批次列表
    for call in calls:                                                 // 逐个检查每个调用的并发安全属性
        spec = registry[call.name]                                     // 从工具注册表读取只读性和状态影响元数据
        safe = spec.readOnly && spec.noSharedContextMutation           // 只有只读且不修改共享上下文时才视为可并行
        if safe and batches.last?.parallel:                            // 当前调用可加入紧邻的并行安全批次
            batches.last.add(call)                                    // 合并调用以降低独立执行等待时间
        else:                                                          // 写操作或批次边界必须新建有序批次
            batches.add(Batch(parallel=safe, calls=[call]))            // 创建串行或并行属性明确的新批次
    return batches                                                     // 返回供 Runtime 按顺序执行的批次序列

# 14.5 面试题

问:工具结果为什么也要 schema?

结果是下一步模型与 UI 的输入。结构化结果便于裁剪、展示、评测和错误分类;纯字符串会把业务数据、日志和错误混在一起,也更容易被 Prompt Injection 影响。

# 14.6 原理深化:Tool Use 必须经过统一、不可绕过的受控执行流程

一次工具调用应先被规范化成 call_id + tool_identity + validated_args + principal + step_snapshot,后续 Hook、权限、沙箱、审计和持久化都引用这份对象。Schema Parse 解决结构合法性,Tool Validator 解决领域约束,PreToolUse Hook 提供扩展拦截,Permission 决定主体是否可请求,Sandbox 强制资源边界;它们不是功能重复,而是处在不同信任层。

Claude Code executeToolCall 的顺序把输入校验、Hook、权限和执行分层,partitionToolCalls 又只并行声明为安全的调用,并按原 ToolUse 顺序应用 Context Modifier。这说明并发完成顺序不能决定共享状态顺序,否则相同模型输出会得到不同上下文:toolExecution.ts:614toolExecution.ts:798toolOrchestration.ts:36。Codex 也在 Step 内固定 Tool Registry/Context,再由 Router 与 Handler 处理具体协议,避免工具清单在解析与执行之间漂移。

失败必须被规范化为可决策结果:validation_error 不应重试,denied 等待用户或改变方案,sandbox_violation 表示策略与动作冲突,transient_execution_error 才可能按幂等性退避重试,unknown_after_interrupt 则要求查询外部事实或人工确认。PostToolUse Hook 只能观察或在受限 Schema 内改写结果,不能把失败伪装成成功。最终 ToolResult 必须先获得稳定 call id 并写入可恢复记录,再提供给下一次模型采样。

最后更新: 2026/8/5 22:05:21