Skip to content
签到签到

第 4 章:Function Calling 与工具

本章解决的问题

Agent 能调用工具,是因为模型、Schema、注册表和执行管线配合工作。这一章讲清工具调用链路。

交互图解:工具执行、权限与批准流程 · 查看图解导读

大白话理解

Function Calling 不是模型真的执行了函数,而是模型输出一个结构化请求:

text
我要调用工具 X
参数是 Y

真正执行函数的是我们的运行时。执行完后,前端和后端要把结果重新发给模型。

必须掌握的概念

1. Tool Schema

Tool Schema 描述工具的:

  • 名称
  • 用途
  • 参数类型
  • 是否必填

模型根据 Schema 知道什么时候可以调用它,以及参数怎么写。

2. Tool Definition

一个工具通常包含:

  • name
  • description
  • parameters
  • execute
  • 可选的结果渲染逻辑

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.ts
  • docs/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 或策略决定。

自测题

  1. Tool Schema 包含哪些核心信息?
  2. tools/pre-executetools/post-execute 的职责有什么区别?
  3. 为什么工具调用要经过注册表?
  4. 工具执行失败时,前端应展示什么?

本章小结

Function Calling 的核心不是“调接口”,而是“模型生成意图,运行时安全执行,结果回填上下文”。前端要展示这个过程的中间状态。

进一步阅读:

  • packages/core/tools/src/types.ts
  • docs/subsystems/tools.md