乐于分享
好东西不私藏

给AI装上双手:Agent工具系统设计全解析

给AI装上双手:Agent工具系统设计全解析
第三章 · 工具系统设计与实现

上一章我们搞定了 Agent 的「循环」——让它能自己决定下一步做什么。但问题来了:如果 Agent 只有大脑没有双手,它依然什么也做不了。

这一章,我们给 Agent 装上「手」——工具系统。工具决定了 Agent 能做什么、不能做什么。设计好不好,直接决定 Agent 是「万能助手」还是「花瓶」。

📌 本文你将掌握

✅ 理解工具系统在 Agent 架构中的核心地位

✅ 设计工具定义接口(含分类、结果标准化)

✅ 实现 Tool Registry 注册中心

✅ 掌握工具描述的最佳实践

✅ 三种工具组合模式(编排 / 组合 / 管道)

01 工具是 Agent 能力的边界

先记住这个公式:

Agent 的能力 = LLM 推理能力 × 工具系统覆盖范围  推理能力(LLM)= 大脑  工具系统(Tools)= 双手  没有工具的 Agent → 只是聊天机器人  没有 LLM 的工具 → 只是普通函数

回顾第 1 章的例子:

客服 Agent = 工单界面 + 工具集(查订单、退款、发邮件)+ LLM 推理循环

Claude Code 内置的工具集长这样:

分类
工具
功能
文件操作
Read, Write, Edit, Glob, Grep
文件读写、搜索
命令执行
Bash
Shell 命令
工具管理
ToolRegistry
注册、查找、执行
权限控制
PermissionManager
权限校验、审批

Claude Code 的核心是 Registry 模式——所有工具先注册到注册中心,Agent 循环通过注册中心查找和执行。好处有四个:

① 松耦合:工具定义和 Agent 逻辑分离

② 可扩展:随时添加新工具,不改核心逻辑

③ 可审计:所有调用经过注册中心,便于日志

④ 统一执行:执行接口标准化,Agent 不关心实现

02 工具长什么样?接口设计

Anthropic API 的工具定义格式很简洁:

interface APITool {  name: string;           // 工具名(LLM 选择的依据)  description: string;    // 描述(LLM 理解用途的关键)  input_schema: {         // 参数定义(JSON Schema)type"object";    properties: Record;    required: string[];  };}

但光有这些还不够。我们在 API 格式基础上,增加了执行处理器分类信息

// 工具分类enumToolCategory {READ = "READ",         // 只读,无副作用WRITE = "WRITE",       // 写入,有副作用EXECUTE = "EXECUTE",   // 执行命令,高风险}// 完整工具定义interfaceToolDefinition {namestring;descriptionstring;categoryToolCategory;inputSchema: { type"object"properties: ...; required: ... };handler(input: any) =>Promise;  // 执行函数}// 标准化结果interfaceToolResult {successboolean;     // 是否成功  data?: unknown;       // 成功时的数据  error?: string;       // 失败时的错误信息  meta?: {              // 元信息(调试/监控)durationnumber;toolNamestring;  };}

为什么要分类? 因为不同工具的风险等级不同:

分类
示例工具
风险
审批
READ
read_file, search_code
自动放行
WRITE
write_file, send_email
需确认
EXECUTE
run_command, refund
严格审批

💡 这个分类在第 5 章权限系统中会发挥关键作用——READ 自动放行,WRITE 需确认,EXECUTE 严格审批。

03 Tool Registry:工具的「调度中心」

所有工具注册到一个中心,Agent 只跟这个中心打交道。核心结构:

classToolRegistry{private tools: Map;        // 名称 → 定义private categoryIndex: Map; // 分类 → 名称列表register(tool: ToolDefinition): void;       // 注册unregister(namestring): boolean;          // 注销get(namestring): ToolDefinition;          // 查找list(): ToolDefinition[];                   // 全部列出listByCategory(cat: ToolCategory): ...;     // 按分类列出execute(namestringinput: any): Promise;  // 执行toAPIFormat(): Tool[];                      // 转为 API 格式}

执行流程一目了然:

Agent 循环                       ToolRegistry    │                                  │    │  1. LLM 返回 tool_use            │    │  2. 提取工具名 + 参数            │    │──────────────▶  3get(name)     │    │◀──────────────  4. 返回定义      │    │──────────────▶  5execute()     │    │                    ├─ 检查参数    │    │                    ├─ 调用 handler│    │                    └─ 返回结果    │    │◀──────────────  6. 返回 ToolResult│    │  7. 注入结果到消息               │

💡 关键设计:Agent 不直接调用工具函数,而是通过 Registry 间接调用。这层间接性带来了权限控制、日志记录、结果标准化的能力。

04 工具描述:写给 LLM 看的「说明书」

工具的 

description
 不是写给人看的,是写给 LLM 看的。描述写得好不好,直接决定 LLM 能不能选对工具。

先看个反例 vs 正例:

// ❌ 坏描述 —— 太简略{  name: "search_code",  description: "搜索代码",}// ✅ 好描述 —— 说清用途、时机、限制{  name: "search_code",  description: "在项目中搜索代码。当你需要查找函数    定义、变量引用、或确认某段代码是否存在时使用。    支持按关键词和文件类型过滤。    注意:搜索范围仅限于已索引的项目文件。",}

五条写作原则,记住就行:

原则
说明
示例
说明用途
这工具做什么
"获取指定城市实时天气"
说明时机
什么时候该用
"当用户问天气、温度时"
明确参数
参数作用和格式
"city: 城市名,如'北京'"
说明限制
什么情况不能用
"不支持查询历史天气"
关联工具
提示替代方案
"如需空气质量用另一个工具"

高级技巧:用描述引导 LLM 的工作顺序:

{  name: "generate_report",  description: "生成分析报告。注意:在调用此工具前,    请确保已通过 read_file、search_code 等工具收集了    足够的信息。如果信息不足,请先收集信息。",}

这样 LLM 会先收集信息,再生成报告,而不是在信息不足时就急着输出——一句话的描述,改变 Agent 的行为模式。

05 三种工具组合模式

实际业务中,一个操作往往需要多个工具配合。比如退款 = 查订单 + 检查库存 + 创建退款 + 发邮件。怎么组合?三种模式:

简单查询:LLM → query_order("ORD-001") → 订单信息

复杂操作:LLM → process_refund → 查订单 → 检查库存 → 创建退款 → 发邮件

模式 1:编排模式(Agent 驱动)

LLM 思考:"需要退款,先查订单,再退款,最后通知"  → 第1步: query_order("ORD-001")  → 第2步: refund_order("ORD-001")  → 第3步: send_email("user@example.com""退款成功")

Agent 自己决定调哪些工具、什么顺序。最灵活,也是默认方式。

模式 2:组合模式(工具内部调工具)

LLM: process_refund("ORD-001")  ↓process_refund handler 内部:1query_order("ORD-001")     → 验证订单2check_inventory("P001")     → 更新库存3create_refund_record(...)   → 创建退款4send_email("user@...")      → 通知用户5. return { success: true }

一个「高级工具」内部调用多个「基础工具」,对 LLM 只暴露为一个工具。降低 LLM 决策负担。

模式 3:管道模式(数据流)

read_config → parse_config → validate_config → apply_config     ↓              ↓              ↓              ↓  原始 JSON     解析对象      验证结果       应用结果

一个工具的输出是下一个工具的输入,形成数据管道。适合数据处理流水线场景。

06 从 Claude Code 学到四个设计技巧

Claude Code 的 ToolRegistry 有四个值得借鉴的设计:

📁 按域分组:fileTools、bashTools、customTools,按业务领域隔离

⏳ 延迟初始化:需要时才注册,不一次性加载所有工具

🔗 工具拦截器:执行前后加 hook,统一日志/权限/监控

🛡️ 结果验证层:handler 返回任意格式 → 标准化 → 大结果截断 → 敏感信息脱敏

拦截器的代码特别优雅:

executeTool(name, input) {  preHooks.forEach(hook =>hook(name, input));   // 前置钩子const result = awaithandler(input);            // 执行  postHooks.forEach(hook =>hook(name, result));  // 后置钩子return result;}

权限检查、日志记录、性能监控——全部通过 hook 插入,不改工具本身的代码。这就是松耦合的力量。

07 三个文件,三个台阶

文件
功能
运行
01-tool-registry.ts
ToolRegistry 实现 + 6 个示例工具
npx tsx src/01-tool-registry.ts
02-business-tools.ts
客服业务工具集(查订单/退款/邮件)
npx tsx src/02-business-tools.ts
03-tool-composition.ts
工具组合模式(编排/组合/管道)
npx tsx src/03-tool-composition.ts

📦 01-tool-registry.ts — 注册中心 + 6 个示例工具

🔧 02-business-tools.ts — 真实业务场景(客服工具集)

🤖 03-tool-composition.ts — 工具组合(编排 + 组合 + 管道)

08 本章总结

工具系统架构,一张图概括:

ToolDefinition(接口定义)  ├─ name         ← LLM 选择依据  ├─ description  ← LLM 理解依据  ├─ inputSchema  ← 参数验证  ├─ category     ← 安全分类(READ/WRITE/EXECUTE  └─ handler      ← 实际执行ToolRegistry(注册中心)  ├─ register/unregister  ← 动态管理  ├─ get/list             ← 查找工具  ├─ execute              ← 统一执行入口  └─ toAPIFormat          ← 转为 API 格式ToolResult(结果标准化)  ├─ success: boolean  ← 执行状态  ├─ data/error        ← 成功/失败数据  └─ meta              ← 元信息(耗时等)

五条设计原则:

① 工具描述写给 LLM 看 — 清晰描述 = 正确的工具选择

② 分类管理 — READ/WRITE/EXECUTE 为权限系统打基础

③ 结果标准化 — 统一 ToolResult 格式简化处理逻辑

④ 注册中心模式 — 松耦合、可扩展、可审计

⑤ 组合优于单一 — 复杂业务通过工具组合实现

🎯 下一章预告:上下文管理与优化——Agent 对话越来越长,Token 怎么管?如何让 Agent 在长对话中不丢上下文、不超限制

📝 课后练习

1️⃣ 在 01-tool-registry.ts 中添加一个 delete_file 工具(WRITE 类别)

2️⃣ 在 02-business-tools.ts 中添加 cancel_order,让它内部调用 send_email

3️⃣ 用管道模式实现:read_file → parse_json → validate → apply

4️⃣ 思考:50+ 个工具时,LLM 选择准确率会下降。你有哪些策略?

👇 关注我,一起从零搭建 AI Agent

每周更新,带你从原理到实战不懂的地方随时留言,我会一一回复

觉得有帮助?点个在看 👁 你的支持是我持续更新的动力