5.1 工具抽象层设计

模型: claude-opus-4-6 (anthropic/claude-opus-4-6) 生成日期: 2025-02-17


工具(Tool)是 AI Agent 与外部世界交互的桥梁。没有工具,LLM 只能生成文本;有了工具,LLM 可以读写文件、执行命令、搜索代码——成为真正的"Agent"。

5.1.1 Tool.Info 接口

所有工具都实现统一的 Tool.Info 接口(tool/tool.ts):

export interface Info<
  Parameters extends z.ZodType = z.ZodType,
  M extends Metadata = Metadata
> {
  id: string                          // 工具的唯一标识符(如 "bash", "edit", "read")
  init: (ctx?: InitContext) => Promise<{
    description: string               // 工具的功能描述(发送给 LLM)
    parameters: Parameters            // 参数的 Zod Schema
    execute(                           // 执行函数
      args: z.infer<Parameters>,
      ctx: Context,
    ): Promise<{
      title: string                   // 执行结果的简短标题
      metadata: M                     // 元数据(如是否被截断)
      output: string                  // 输出文本
      attachments?: MessageV2.FilePart[]  // 文件附件
    }>
    formatValidationError?(error: z.ZodError): string  // 自定义参数错误信息
  }>
}

关键设计决策

  1. 两阶段初始化init() 是异步的,允许工具在初始化时执行异步操作(如检测环境、加载配置)。返回的对象包含 descriptionparameters——这些信息会被发送给 LLM,让它知道这个工具能做什么、需要什么参数。

  2. InitContext 携带 Agent 信息:工具可以根据调用它的 Agent 调整行为。例如,description 中可以替换占位符来包含当前目录等上下文信息。

5.1.2 Tool.Context 上下文

每次工具执行时,都会收到一个 Context 对象:

ctx.ask() 的重要性:这是工具与权限系统的接口。在执行敏感操作之前(如写文件、执行命令),工具必须调用 ctx.ask() 请求权限。如果权限规则是 "ask",这会暂停工具执行,等待用户确认。

5.1.3 Tool.define() 工厂函数

Tool.define() 是创建工具的标准方式:

Tool.define() 自动为每个工具添加了两层保护:

  • 参数校验:使用 Zod Schema 验证 LLM 传入的参数是否合法

  • 输出截断:防止工具输出过大占满上下文窗口

5.1.4 工具输出截断机制

Truncatetool/truncation.ts)是一个重要的保护机制:

衍生概念:为什么需要截断?

LLM 的上下文窗口是有限的。如果一个 read 工具返回了一个 10,000 行的文件,这可能直接消耗掉大部分上下文空间,导致后续对话无法继续。

截断策略:当输出超过 2000 行或 50KB 时,将完整输出写入临时文件,只将截断后的内容返回给 LLM,并附上提示:"输出已截断,完整内容保存在 [文件路径]"。

LLM 收到截断提示后,可以使用 read 工具按需读取完整文件的特定部分——这比一次性加载整个文件要高效得多。

截断后的临时文件有定时清理机制(7 天过期):

Last updated