第 4 章:Function Calling 与工具
本章解决的问题
Agent 能调用工具,是因为模型、Schema、注册表和执行管线配合工作。这一章讲清工具调用链路。
交互图解:工具执行、权限与批准流程 · 查看图解导读
大白话理解
Function Calling 不是模型真的执行了函数,而是模型输出一个结构化请求:
text
我要调用工具 X
参数是 Y真正执行函数的是我们的运行时。执行完后,前端和后端要把结果重新发给模型。
必须掌握的概念
1. Tool Schema
Tool Schema 描述工具的:
- 名称
- 用途
- 参数类型
- 是否必填
模型根据 Schema 知道什么时候可以调用它,以及参数怎么写。
2. Tool Definition
一个工具通常包含:
namedescriptionparametersexecute- 可选的结果渲染逻辑
3. Tool Registry
Registry 保存当前 Agent 可见的工具。通过 registry,模型看到的 Schema 和实际执行能力保持一致。
4. Tool Pipeline
工具执行不是直接调函数,而是经过多个阶段:
text
模型产生 tool call
-> 解析参数
-> 权限检查
-> 执行
-> 结果规范化
-> 写入上下文DeepSeek Harness 对应实现
DeepSeek Harness 的 ToolRuntime 是工具注册和执行管线。
核心事件:
tools/pre-execute:允许、拒绝或请求批准。tools/execute:包装执行生命周期,例如超时和指标。tools/post-execute:接受、替换或阻止结果。tools/result:观察最终结果,不能修改。
这个顺序说明:工具调用不能简单写成 switch,它需要策略层。
进一步阅读:
packages/core/tools/src/index.tsdocs/cookbook/adding-a-tool.md
代码示例
简化 Tool Definition
ts
interface ToolDefinition {
name: string
description: string
parameters: Record<string, unknown>
execute(args: unknown): Promise<unknown>
}
const nowTool: ToolDefinition = {
name: 'get_current_time',
description: 'Return the current server time.',
parameters: {
type: 'object',
properties: {},
required: [],
},
async execute() {
return new Date().toISOString()
},
}简化 Tool Registry
ts
class ToolRegistry {
private tools = new Map<string, ToolDefinition>()
register(tool: ToolDefinition): void {
if (this.tools.has(tool.name)) {
throw new Error(`tool "${tool.name}" is already registered`)
}
this.tools.set(tool.name, tool)
}
schemas() {
return [...this.tools.values()].map(({ name, description, parameters }) => ({
name,
description,
parameters,
}))
}
async execute(name: string, args: unknown): Promise<unknown> {
const tool = this.tools.get(name)
if (!tool) throw new Error(`unknown tool "${name}"`)
return tool.execute(args)
}
}工具结果回传
ts
interface ToolCallMessage {
role: 'assistant'
toolCalls: Array<{ id: string; name: string; args: unknown }>
}
interface ToolResultMessage {
role: 'tool'
callId: string
name: string
result: unknown
}前端通常不需要手写这套类型,但理解它有助于设计工具状态 UI。
面试怎么问
问:Function Calling 是模型真的执行函数吗?
回答要点:
- 不是。
- 模型只输出结构化的工具调用请求。
- 真正执行函数的是应用运行时。
问:工具参数为什么要用 JSON Schema?
回答要点:
- 帮助模型生成正确参数。
- 运行时可以做校验。
- 前端可以展示参数和错误。
- 日志和回放需要稳定的数据结构。
问:工具执行失败后应该怎么处理?
回答要点:
- 不要把原始异常直接吞掉。
- 把结构化错误回传给模型。
- 保留错误信息给 UI 和审计。
- 是否继续由 Agent Loop 或策略决定。
自测题
- Tool Schema 包含哪些核心信息?
tools/pre-execute和tools/post-execute的职责有什么区别?- 为什么工具调用要经过注册表?
- 工具执行失败时,前端应展示什么?
本章小结
Function Calling 的核心不是“调接口”,而是“模型生成意图,运行时安全执行,结果回填上下文”。前端要展示这个过程的中间状态。
进一步阅读:
packages/core/tools/src/types.tsdocs/subsystems/tools.md