AI Agent 教程

第5章:内置工具 —— Agent 的十八般武艺

一句话:掌握所有内置工具的用法,让 Agent 真正能干活。

本章目标

前置知识


5.1 工具总览

Agent 的工具箱

上一章我们学了 query() 函数,知道了怎么"启动"Agent。但光启动没用,就像你雇了一个人,他只会说话不会动手 —— 你得给他工具。

Claude Agent SDK 自带了 11 个内置工具,每一个都有明确的分工。我们先来看个全景:

工具 干什么的 生活中的比方
Read 读取文件内容 翻开一本书来看
Write 创建或覆盖文件 写一封信
Edit 精确修改文件的某一部分 用橡皮擦改作业上的一个字
Bash 执行 Shell 命令 亲自去操作电脑
Glob 按文件名模式搜索文件 在书架上找所有带"历史"二字的书
Grep 按内容搜索文件 在一堆书里找哪本提到了"量子力学"
WebSearch 搜索互联网 打开百度/Google 搜一下
WebFetch 抓取网页内容 把一个网页保存下来阅读
Task 启动一个子 Agent 干活 把任务委派给同事去做
AskUserQuestion 向用户提问 拿不准的时候问一下老板
Skill 调用已注册的技能 查一下操作手册

工具分四大类

为了方便记忆,我把这 11 个工具分成四类:

文件操作类:Read、Write、Edit         ← 读写改文件
搜索类:    Glob、Grep               ← 找文件、搜内容
执行类:    Bash                     ← 执行命令
网络类:    WebSearch、WebFetch      ← 上网搜索和抓取
协作类:    Task、AskUserQuestion、Skill  ← 和人/其他Agent交互

接下来,我们一类一类地详细讲。


5.2 文件操作三件套:Read、Write、Edit

文件操作是 Agent 最常用的能力。就像一个秘书,最基本的技能就是能看文件、写文件、改文件。

5.2.1 Read —— 读取文件

一句话:让 Agent 读取一个文件的内容。

适用场景

代码示例:让 Agent 读取 package.json 并解释内容

// 文件:read-demo.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  for await (const message of query({
    prompt: "请读取当前目录下的 package.json 文件,然后用大白话告诉我这个项目是干什么的、用了哪些依赖",
    options: {
      allowedTools: ["Read"],           // 只给 Read 工具
      permissionMode: "bypassPermissions",
      maxTurns: 3
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use") {
          console.log(`[工具调用] ${block.name}(${JSON.stringify(block.input)})`);
        }
      }
    }
    if (message.type === "result" && message.subtype === "success") {
      console.log("\n--- 分析完成 ---");
      console.log("最终结果:", message.result);
    }
  }
}

main().catch(console.error);

Agent 调用 Read 工具时,实际传的参数长这样:

{
  "file_path": "/home/user/my-project/package.json",
  "limit": 2000
}

Read 的参数说明:

参数 类型 说明
file_path string 文件的绝对路径
offset number(可选) 从第几行开始读(文件太大时用)
limit number(可选) 最多读多少行(默认 2000)

注意事项

5.2.2 Write —— 创建文件

一句话:让 Agent 创建一个新文件,或者完全覆盖一个已有文件。

适用场景

代码示例:让 Agent 生成一个 .gitignore 文件

// 文件:write-demo.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  for await (const message of query({
    prompt: "请为一个 Node.js + TypeScript 项目创建一个 .gitignore 文件,包含常见的忽略规则",
    options: {
      allowedTools: ["Write"],          // 只给 Write 工具
      permissionMode: "bypassPermissions",
      maxTurns: 3
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use") {
          console.log(`[创建文件] ${block.input.file_path}`);
          // 文件内容太长的话只显示前几行
          const preview = block.input.content.split("\n").slice(0, 5).join("\n");
          console.log(`  内容预览:\n  ${preview}\n  ...`);
        }
      }
    }
    if (message.type === "result" && message.subtype === "success") {
      console.log("\n--- 文件创建完成 ---");
    }
  }
}

main().catch(console.error);

Agent 调用 Write 工具时,实际传的参数长这样:

{
  "file_path": "/home/user/my-project/.gitignore",
  "content": "node_modules/\ndist/\n.env\n*.log\n..."
}

Write 的参数说明:

参数 类型 说明
file_path string 文件的绝对路径
content string 要写入的完整内容

注意事项

5.2.3 Edit —— 精确修改文件

一句话:让 Agent 只修改文件中的一小部分,不动其他内容。

适用场景

代码示例:让 Agent 把函数名从 getData 改成 fetchUserData

// 文件:edit-demo.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  for await (const message of query({
    prompt: `请读取 src/api.ts 文件,然后把里面的函数 getData 重命名为 fetchUserData。
注意:要把所有引用到这个函数名的地方都改掉,不能遗漏。`,
    options: {
      allowedTools: ["Read", "Edit"],   // Read + Edit 配合使用
      permissionMode: "bypassPermissions",
      maxTurns: 10
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use" && block.name === "Edit") {
          console.log(`[编辑文件] ${block.input.file_path}`);
          console.log(`  替换: "${block.input.old_string}"`);
          console.log(`  为:   "${block.input.new_string}"`);
        }
      }
    }
  }
}

main().catch(console.error);

Agent 调用 Edit 工具时,实际传的参数长这样:

{
  "file_path": "/home/user/my-project/src/api.ts",
  "old_string": "function getData(",
  "new_string": "function fetchUserData(",
  "replace_all": false
}

Edit 的参数说明:

参数 类型 说明
file_path string 文件的绝对路径
old_string string 要被替换的原始文本
new_string string 替换后的新文本
replace_all boolean(可选) 是否替换所有匹配项(默认 false)

注意事项

Write vs Edit:什么时候用哪个?

这是很多人搞混的地方,给你画个决策树:

你要改文件?
  │
  ├── 文件不存在,需要从头创建 → 用 Write
  │
  ├── 文件存在,但你要完全重写 → 用 Write
  │
  └── 文件存在,你只想改其中几行 → 用 Edit ✓

打个比方

实际场景对比

场景 用 Write 用 Edit
创建一个新的 index.ts 文件
修复代码里的一个 Bug
根据模板生成整个配置文件
把版本号从 1.0.0 改成 1.1.0
在文件开头加一行 import 语句

5.3 搜索工具:Glob 和 Grep

搜索是 Agent 的"眼睛"。在动手之前,Agent 需要先了解项目的文件结构和代码内容。Glob 和 Grep 就是帮它"看清楚"的两个工具。

5.3.1 Glob —— 按文件名搜索

一句话:根据文件名的模式(pattern)找到匹配的文件列表。

生活中的比方:你在图书馆的书架上找"所有书名带'Python'的书"。你不需要翻开每本书看内容,只看书脊上的书名就行。Glob 就干这个 —— 只看文件名,不看文件内容。

常用的 Glob 模式:

模式 匹配什么 说人话
**/*.ts 所有目录下的 .ts 文件 "找出所有 TypeScript 文件"
**/*.test.ts 所有目录下的 .test.ts 文件 "找出所有测试文件"
src/**/*.tsx src 目录下的所有 .tsx 文件 "找出 src 里的 React 组件"
**/package.json 所有目录下的 package.json "找出所有子项目的配置"
*.md 当前目录下的 .md 文件 "找出根目录的 Markdown 文件"
src/{utils,helpers}/**/*.ts src/utils 和 src/helpers 下的 .ts 文件 "找出工具类代码"

模式语法速查:

符号 含义 示例
* 匹配任意字符(不含路径分隔符) *.ts 匹配 app.ts,不匹配 src/app.ts
** 匹配任意层级目录 **/*.ts 匹配 src/utils/helper.ts
? 匹配单个字符 file?.ts 匹配 file1.tsfileA.ts
{a,b} 匹配 a 或 b *.{ts,js} 匹配 .ts.js 文件
[abc] 匹配方括号中的任一字符 file[123].ts 匹配 file1.ts

代码示例:让 Agent 找出项目中所有的测试文件

// 文件:glob-demo.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  for await (const message of query({
    prompt: "请用 Glob 工具找出当前项目中所有的测试文件(.test.ts 和 .spec.ts),然后告诉我一共有多少个,都在哪些目录下",
    options: {
      allowedTools: ["Glob"],
      permissionMode: "bypassPermissions",
      maxTurns: 3
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use") {
          console.log(`[Glob 搜索] 模式: ${block.input.pattern}`);
          if (block.input.path) {
            console.log(`  目录: ${block.input.path}`);
          }
        }
      }
    }
  }
}

main().catch(console.error);

Agent 调用 Glob 时传的参数:

{
  "pattern": "**/*.{test,spec}.ts",
  "path": "/home/user/my-project"
}

Glob 的参数说明:

参数 类型 说明
pattern string Glob 模式
path string(可选) 搜索的根目录,默认为当前工作目录

5.3.2 Grep —— 按文件内容搜索

一句话:在文件内容中搜索匹配的文本或正则表达式。

生活中的比方:Glob 是看书名找书,Grep 就是翻开每本书看里面有没有你要找的那句话。它比 Glob 慢(因为要读内容),但能找到更精确的东西。

代码示例:找出项目中所有用到 console.log 的地方

// 文件:grep-demo.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  for await (const message of query({
    prompt: "请搜索当前项目中所有包含 console.log 的代码行,告诉我哪些文件、哪些行用了 console.log",
    options: {
      allowedTools: ["Grep"],
      permissionMode: "bypassPermissions",
      maxTurns: 3
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use") {
          console.log(`[Grep 搜索] 模式: ${block.input.pattern}`);
          if (block.input.glob) {
            console.log(`  文件过滤: ${block.input.glob}`);
          }
        }
      }
    }
  }
}

main().catch(console.error);

Agent 调用 Grep 时传的参数:

{
  "pattern": "console\\.log",
  "glob": "*.{ts,js,tsx,jsx}",
  "output_mode": "content",
  "-n": true
}

Grep 的参数说明:

参数 类型 说明
pattern string 搜索的正则表达式
path string(可选) 搜索目录
glob string(可选) 过滤文件名(只搜特定类型的文件)
output_mode string(可选) "content" 显示匹配行,"files_with_matches" 只显示文件名,"count" 显示数量
-i boolean(可选) 是否忽略大小写
-n boolean(可选) 是否显示行号
-C number(可选) 显示匹配行前后各 N 行的上下文
head_limit number(可选) 限制返回的结果数量

高级用法 —— 用正则表达式搜索:

// 找所有 TODO 和 FIXME 注释
const todoSearch = query({
  prompt: "找出项目中所有的 TODO 和 FIXME 注释,按文件分组列出",
  options: {
    allowedTools: ["Grep"],
    permissionMode: "bypassPermissions",
    maxTurns: 3
  }
});

// Agent 会用类似这样的正则:
// pattern: "(TODO|FIXME)\\s*[::]"

Glob vs Grep:什么时候用哪个?

你要找什么?
  │
  ├── 找特定类型/名字的文件 → 用 Glob
  │   例:找所有 .env 文件
  │   例:找所有测试文件
  │   例:看看 src 目录下有哪些 .ts 文件
  │
  └── 找文件里面的特定内容 → 用 Grep
      例:哪些文件用了 console.log
      例:哪里调用了 getUserById 函数
      例:找所有包含 "deprecated" 的注释

一起用效果更好:Agent 经常先用 Glob 缩小范围(找到相关文件),再用 Grep 精确定位(在这些文件里搜内容)。


5.4 命令执行:Bash

Bash —— Agent 的终极武器

一句话:让 Agent 在终端中执行 Shell 命令。

Bash 是所有工具中最强大也最危险的一个。理论上,Agent 可以通过 Bash 执行任何命令行操作 —— 安装软件包、运行测试、启动服务、管理 Git、操作数据库... 几乎无所不能。

适用场景

代码示例:让 Agent 安装依赖、检查 Git 状态、运行测试

// 文件:bash-demo.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  for await (const message of query({
    prompt: `请按以下步骤操作:
1. 检查当前 Node.js 版本
2. 运行 npm install 安装依赖
3. 查看 git log 最近3条提交
4. 运行 npm test 看看测试能不能通过
每一步都告诉我结果。`,
    options: {
      allowedTools: ["Bash"],
      permissionMode: "bypassPermissions",
      maxTurns: 10
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use" && block.name === "Bash") {
          console.log(`\n$ ${block.input.command}`);
        }
      }
    }
    if (message.type === "result" && message.subtype === "success") {
      console.log("\n--- 所有步骤完成 ---");
      console.log("花费:", message.total_cost_usd.toFixed(4), "美元");
    }
  }
}

main().catch(console.error);

Agent 调用 Bash 时传的参数:

{
  "command": "npm test",
  "description": "Run project test suite",
  "timeout": 120000
}

Bash 的参数说明:

参数 类型 说明
command string 要执行的 Shell 命令
description string(可选) 命令的描述(方便审计和日志)
timeout number(可选) 超时时间(毫秒),默认 120000(2分钟)

安全考虑:Bash 是把双刃剑

Bash 很强大,但也很危险。先看看它能干什么坏事:

危险等级    命令示例                      后果
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
☠️ 致命     rm -rf /                     删掉整个系统
☠️ 致命     curl ... | bash              下载执行恶意脚本
🔴 高危     git push --force             覆盖远程仓库
🔴 高危     npm publish                  发布包到公开仓库
🟡 中危     npm install <未知包>          可能安装恶意依赖
🟢 安全     ls, cat, git status          只读操作,无害
🟢 安全     node --version               查看信息,无害

安全原则:

  1. 不要轻易给 Bash 工具:如果 Agent 只需要读文件,给 Read 就够了,不需要 Bash
  2. 用权限模式控制:用 permissionMode: "default" 而不是 "bypassPermissions",这样 Agent 每次执行命令前都会问你
  3. 限制命令范围:在 system prompt 里明确告诉 Agent 哪些命令可以执行
  4. 监控输出:通过 for await 实时查看 Agent 在执行什么命令

安全版代码示例:

// 安全版:用 default 权限模式,Agent 每次执行 Bash 前会请求许可
for await (const message of query({
  prompt: "帮我检查项目的测试覆盖率",
  options: {
    allowedTools: ["Bash", "Read"],
    permissionMode: "default",      // 关键:不用 bypassPermissions
    maxTurns: 5,
    systemPrompt: `你是一个代码质量检查助手。
规则:
- 只允许执行以下命令:npm test, npx jest --coverage, git status, git log
- 绝对不能执行 rm, curl, wget, pip install 等命令
- 如果需要执行其他命令,先告诉用户你想干什么`
  }
})) {
  // ... 处理消息
}

5.5 网络工具:WebSearch 和 WebFetch

Agent 不只能操作本地文件,还能上网。WebSearch 和 WebFetch 就是 Agent 的"浏览器"。

5.5.1 WebSearch —— 搜索互联网

一句话:让 Agent 在互联网上搜索信息,就像你用 Google 一样。

适用场景

代码示例:让 Agent 搜索一个技术问题的解决方案

// 文件:websearch-demo.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  for await (const message of query({
    prompt: `我在用 TypeScript 时遇到了这个错误:
"Type 'string' is not assignable to type 'number'"
请搜索一下这个错误的常见原因和解决方法。`,
    options: {
      allowedTools: ["WebSearch"],
      permissionMode: "bypassPermissions",
      maxTurns: 3
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use") {
          console.log(`[搜索] ${block.input.query}`);
        }
      }
    }
  }
}

main().catch(console.error);

Agent 调用 WebSearch 时传的参数:

{
  "query": "TypeScript Type string is not assignable to type number fix",
  "allowed_domains": ["stackoverflow.com", "github.com"]
}

WebSearch 的参数说明:

参数 类型 说明
query string 搜索关键词
allowed_domains string[](可选) 只在这些网站中搜索
blocked_domains string[](可选) 排除这些网站

返回结果:搜索结果会以标题、链接、摘要的形式返回给 Agent,Agent 再整理成人类可读的回答。

5.5.2 WebFetch —— 抓取网页内容

一句话:让 Agent 打开一个网页,把内容下载下来阅读。

适用场景

代码示例:让 Agent 抓取一个 API 文档页面

// 文件:webfetch-demo.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  for await (const message of query({
    prompt: `请访问 https://docs.anthropic.com/en/docs/build-with-claude/tool-use 这个页面,
然后给我总结一下 Claude 的 Tool Use 功能是怎么工作的。`,
    options: {
      allowedTools: ["WebFetch"],
      permissionMode: "bypassPermissions",
      maxTurns: 3
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use") {
          console.log(`[抓取] ${block.input.url}`);
          console.log(`  提示: ${block.input.prompt}`);
        }
      }
    }
  }
}

main().catch(console.error);

Agent 调用 WebFetch 时传的参数:

{
  "url": "https://docs.anthropic.com/en/docs/build-with-claude/tool-use",
  "prompt": "Summarize how Claude's Tool Use feature works"
}

WebFetch 的参数说明:

参数 类型 说明
url string 要抓取的网页 URL
prompt string 告诉 AI 从这个页面提取什么信息

WebFetch 的特点

WebSearch vs WebFetch:什么时候用哪个?

你要干什么?
  │
  ├── 不知道答案在哪,需要搜索 → WebSearch
  │   例:"Node.js 怎么实现文件上传"
  │   例:"React 19 有什么新特性"
  │
  └── 知道具体网址,要看内容 → WebFetch
      例:"帮我看看这个 API 文档说了什么"
      例:"帮我抓取这个 JSON 数据接口"

组合使用:先用 WebSearch 搜到相关链接,再用 WebFetch 打开链接看详细内容。这是最常见的使用模式。


5.6 任务委派:Task

Task —— 让子 Agent 帮忙干活

一句话:主 Agent 可以派一个子 Agent(Subagent)出去干活,子 Agent 有自己独立的上下文,干完活把结果带回来。

打个比方:你是项目经理(主 Agent),遇到一个需要深入调研的问题,你不想自己花时间去查,就指派一个同事(子 Agent)去调研。同事调研完了写一份报告给你,你拿着报告继续做后面的事。

适用场景

代码示例:主 Agent 让子 Agent 去调研代码结构

// 文件:task-demo.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  for await (const message of query({
    prompt: `你是一个项目技术负责人。请完成以下工作:
1. 先用 Task 工具派一个子任务去调研项目的代码结构(有哪些目录、主要文件是什么)
2. 收到调研结果后,写一份简短的项目结构说明文档

注意:子任务只需要做调研,不需要写文档。写文档是你自己的事。`,
    options: {
      allowedTools: ["Task", "Write", "Read", "Glob"],
      permissionMode: "bypassPermissions",
      maxTurns: 10
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use" && block.name === "Task") {
          console.log(`\n[派出子任务] ${block.input.prompt?.substring(0, 80)}...`);
        }
      }
    }
  }
}

main().catch(console.error);

Task 的工作流程:

主 Agent 运行中
    │
    ├── 遇到需要委派的子任务
    │
    ├── 调用 Task 工具,传入子任务的 prompt
    │
    ├── 子 Agent 启动(有自己独立的上下文)
    │   ├── 子 Agent 思考
    │   ├── 子 Agent 使用工具
    │   ├── 子 Agent 得到结果
    │   └── 子 Agent 完成
    │
    ├── 子 Agent 的结果返回给主 Agent
    │
    └── 主 Agent 继续处理

Task 的关键特点


5.7 用户交互:AskUserQuestion

AskUserQuestion —— 拿不准就问

一句话:当 Agent 遇到不确定的决策时,可以暂停下来问用户怎么办。

适用场景

代码示例:Agent 在帮你创建项目时,问你要用什么框架

// 文件:ask-user-demo.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  for await (const message of query({
    prompt: `请帮我初始化一个新的 Web 前端项目。
在开始之前,你需要问我以下问题:
1. 用什么框架(React / Vue / Svelte)?
2. 需要 TypeScript 吗?
3. 项目名是什么?
然后根据我的回答来创建项目。`,
    options: {
      allowedTools: ["AskUserQuestion", "Bash", "Write"],
      permissionMode: "bypassPermissions",
      maxTurns: 15
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use" && block.name === "AskUserQuestion") {
          console.log(`\n❓ Agent 在问你: ${block.input.question}`);
          if (block.input.options) {
            console.log("   选项:", block.input.options.join(" / "));
          }
        }
      }
    }
  }
}

main().catch(console.error);

AskUserQuestion 的参数:

参数 类型 说明
question string 要问用户的问题
options string[](可选) 提供给用户的选项列表

注意:在使用 permissionMode: "bypassPermissions" 时,AskUserQuestion 工具的行为可能不同。在自动化场景中,通常不给这个工具,因为没有人能回答问题。


5.8 工具选择的智慧

Agent 是怎么决定用哪个工具的?

你有没有想过一个问题:我们给了 Agent 一堆工具,它怎么知道什么时候该用哪个?

答案是:靠工具的描述(description)

当你调用 query() 时,SDK 会把所有可用工具的信息(名字、描述、参数)发给 Claude 模型。Claude 通过理解这些描述,来判断当前任务需要哪个工具。

这就好比你面前有一个工具箱,里面有螺丝刀、锤子、扳手、钳子。你要拧螺丝,不用谁告诉你 —— 你看到螺丝刀的样子就知道该用它。Agent 也是类似的逻辑,只不过它是通过"阅读描述"来"认识"工具的。

一个具体的例子

假设你给了 Agent 三个工具:Read、Glob、Grep。然后你说:

"帮我找出项目中所有定义了 async function 的文件"

Agent 会怎么想?

Agent 的思考过程:

1. 用户要找"包含特定内容的文件"
2. 我有三个工具:
   - Read:读取一个具体文件的内容 → 但我不知道要读哪个文件
   - Glob:按文件名模式找文件 → 能找到所有 .ts 文件,但不知道里面有没有 async function
   - Grep:在文件内容中搜索 → 正好!我可以搜索 "async function" 这个模式
3. 决定:先用 Grep 搜索包含 "async function" 的文件

Agent 就会调用 Grep,传入 pattern: "async function"

限制工具会改变 Agent 的行为

这是一个非常重要的概念:当你限制了 Agent 能用的工具,Agent 会自适应地改变策略。

例子:找出项目中所有的 TODO 注释

可用工具 Agent 的策略
Grep, Read 直接用 Grep 搜 "TODO",一步到位
Read, Glob 先用 Glob 找所有代码文件,再逐个 Read 查找 TODO
Bash 执行 grep -rn "TODO" .
只有 Read 一个个猜文件路径然后 Read... 效率极低

看到了吗?同样的任务,不同的工具组合,Agent 会采用完全不同的策略。这就是为什么选对工具很重要。

工具描述对 Agent 决策的影响

工具描述写得好不好,直接影响 Agent 用得对不对。这对自定义工具尤其重要(第10章会详细讲)。

好的描述 vs 差的描述:

差的描述:
  name: "search"
  description: "搜索东西"
  → Agent 不知道搜什么、怎么搜

好的描述:
  name: "grep"
  description: "在文件内容中搜索匹配正则表达式的文本行,支持按文件类型过滤"
  → Agent 明确知道:搜文件内容、用正则、能过滤文件类型

最小权限原则

这是安全领域的经典原则,放在工具管理上同样适用:

只给 Agent 它需要的工具,不多给一个。

为什么?

  1. 安全:工具越少,Agent 能"闯祸"的范围越小
  2. 效率:工具越少,Agent 选择工具时越不容易犹豫或选错
  3. 可预测:工具有限时,Agent 的行为更容易预测和控制

打个比方:你让一个新来的实习生帮你整理文档。你会给他一支笔和一沓纸(Read + Write),而不是给他你的管理员密码和服务器 root 权限(全部工具)。


5.9 allowedTools 配置技巧

allowedTools 是控制 Agent 能力边界的核心参数。这一节我们来看怎么用好它。

基本用法

// 方式1:明确列出每个工具
options: {
  allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"]
}

// 方式2:不设置 allowedTools → Agent 获得所有内置工具
options: {
  // 没有 allowedTools 字段
}

// 方式3:空数组 → Agent 没有任何工具(纯聊天模式)
options: {
  allowedTools: []
}

MCP 工具的通配符写法

如果你接入了 MCP 服务器(第11章会详细讲),可以用通配符来允许某个 MCP 服务的所有工具:

options: {
  allowedTools: [
    "Read", "Write",                    // 内置工具
    "mcp__github__*",                   // GitHub MCP 的所有工具
    "mcp__database__query",             // 数据库 MCP 的 query 工具
    "mcp__slack__send_message"          // Slack MCP 的发消息工具
  ]
}

通配符规则

常用工具组合模板

根据不同场景,这里是几套经过实践验证的工具组合:

1. 代码审查员(只读,不改任何东西)

const reviewerTools = ["Read", "Glob", "Grep"];

用途:阅读代码、分析结构、找问题。不能修改文件,不能执行命令。最安全。

2. 代码编写员(能读能写,能编译运行)

const writerTools = ["Read", "Write", "Edit", "Bash", "Glob", "Grep"];

用途:写代码、改代码、运行测试、安装依赖。这是最常用的"全功能开发"工具集。

3. 研究员(能搜索,能保存结果)

const researcherTools = ["WebSearch", "WebFetch", "Read", "Write"];

用途:上网搜索信息、抓取网页内容、把结果写到文件里。不需要 Bash 和 Edit。

4. 分析师(能搜能看,不能改不能跑)

const analystTools = ["Read", "Glob", "Grep", "WebSearch"];

用途:分析项目代码、搜索相关资料。不能修改文件,不能执行命令。

5. 团队领导(能指挥子 Agent)

const leaderTools = ["Task", "Read", "Write", "Glob", "Grep"];

用途:把任务分配给子 Agent,收集结果,整合报告。

6. 全能选手(啥都能干,慎用)

const fullPowerTools = [
  "Read", "Write", "Edit", "Bash",
  "Glob", "Grep",
  "WebSearch", "WebFetch",
  "Task"
];

用途:没有限制。适合你完全信任 Agent 的场景,比如自己开发调试时。

动态工具控制

有时候你需要根据运行时的状态来决定给什么工具。比如:普通用户只能用只读工具,管理员可以用全部工具。

// 根据用户角色动态决定工具
function getToolsForRole(role: string): string[] {
  switch (role) {
    case "viewer":
      return ["Read", "Glob", "Grep"];
    case "editor":
      return ["Read", "Write", "Edit", "Glob", "Grep"];
    case "admin":
      return ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebSearch", "WebFetch"];
    default:
      return ["Read", "Glob", "Grep"]; // 默认最小权限
  }
}

// 使用
const userRole = getUserRole(); // 假设这个函数返回用户角色
for await (const message of query({
  prompt: "帮我看看这个项目的代码",
  options: {
    allowedTools: getToolsForRole(userRole),
    permissionMode: userRole === "admin" ? "bypassPermissions" : "default"
  }
})) {
  // ...
}

5.10 Skill 工具

Skill —— 调用预定义的技能

一句话:让 Agent 调用一个预先定义好的技能(Skill),Skill 是一组预设的指令和工具组合。

这个工具的详细用法会在第14章展开。这里只做简单介绍。

打个比方:你让一个新员工去"做代码审查"。如果他不知道代码审查的流程,你可能要一步一步教他。但如果公司有一本《代码审查操作手册》,你只需要说"按手册来"就行了。Skill 就是这个"操作手册"—— 预先定义好的一套操作流程。

代码示例:

for await (const message of query({
  prompt: "请使用代码审查技能来检查 src/api.ts 文件",
  options: {
    allowedTools: ["Skill", "Read", "Glob", "Grep"],
    permissionMode: "bypassPermissions",
    maxTurns: 10
  }
})) {
  // Agent 会自动调用 Skill 工具来加载代码审查的预定义流程
  // ...
}

动手练习

学了这么多工具,是骡子是马得拉出来遛遛。下面四个练习,从简单到复杂,每个都是完整可运行的代码。

练习1:项目初始化 Agent(Write + Bash)

目标:创建一个 Agent,让它帮你初始化一个完整的 Node.js + TypeScript 项目。

// 文件:exercise-1-project-init.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  console.log("=== 项目初始化 Agent ===\n");

  for await (const message of query({
    prompt: `请帮我在当前目录下创建一个名为 my-app 的 Node.js + TypeScript 项目。要求:
1. 创建 package.json(名字 my-app,版本 1.0.0,包含 typescript 和 ts-node 作为 devDependencies)
2. 创建 tsconfig.json(target ES2020,module commonjs,strict 模式)
3. 创建 src/index.ts(写一个简单的 Hello World 程序)
4. 创建 .gitignore(忽略 node_modules, dist, .env)
5. 运行 npm install 安装依赖
6. 运行 npx ts-node src/index.ts 验证能正常执行

每完成一步就告诉我。`,
    options: {
      allowedTools: ["Write", "Bash", "Read"],
      permissionMode: "bypassPermissions",
      maxTurns: 15
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use") {
          if (block.name === "Write") {
            console.log(`\n[创建文件] ${block.input.file_path}`);
          }
          if (block.name === "Bash") {
            console.log(`\n[执行命令] $ ${block.input.command}`);
          }
        }
      }
    }
    if (message.type === "result") {
      if (message.subtype === "success") {
        console.log("\n=== 项目初始化完成 ===");
        console.log(`花费: ${message.total_cost_usd.toFixed(4)} 美元`);
        console.log(`回合数: ${message.num_turns}`);
      } else {
        console.error("\n=== 初始化失败 ===");
        if ("errors" in message) {
          console.error("错误:", message.errors);
        }
      }
    }
  }
}

main().catch(console.error);

运行方式:

npx ts-node exercise-1-project-init.ts

预期效果:Agent 会依次创建 4 个文件,安装依赖,运行验证,最终你会在当前目录下看到一个完整的 my-app 项目。


练习2:代码结构分析 Agent(Read + Glob + Grep)

目标:创建一个只读 Agent,分析项目的代码结构并生成一个树形图。

// 文件:exercise-2-code-analyzer.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  console.log("=== 代码结构分析 Agent ===\n");

  const projectPath = process.argv[2] || ".";

  for await (const message of query({
    prompt: `请分析目录 ${projectPath} 的代码结构,完成以下任务:

1. 用 Glob 找出所有源代码文件(.ts, .js, .tsx, .jsx, .py, .go 等)
2. 统计每种文件类型的数量
3. 用 Grep 找出关键信息:
   - 有多少个 export 的函数/类
   - 有多少个 TODO/FIXME 注释
   - 有没有 console.log(可能需要清理的调试代码)
4. 生成一份结构分析报告,包含:
   - 项目目录树(用 ASCII art 画出来)
   - 文件类型统计
   - 关键信息汇总
   - 建议改进点`,
    options: {
      allowedTools: ["Read", "Glob", "Grep"],   // 注意:只有只读工具!
      permissionMode: "bypassPermissions",
      maxTurns: 20
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use") {
          switch (block.name) {
            case "Glob":
              console.log(`[搜索文件] 模式: ${block.input.pattern}`);
              break;
            case "Grep":
              console.log(`[搜索内容] 模式: ${block.input.pattern}`);
              break;
            case "Read":
              console.log(`[读取文件] ${block.input.file_path}`);
              break;
          }
        }
      }
    }
    if (message.type === "result" && message.subtype === "success") {
      console.log("\n=== 分析完成 ===");
      console.log(`共使用 ${message.num_turns} 个回合`);
    }
  }
}

main().catch(console.error);

运行方式:

# 分析当前目录
npx ts-node exercise-2-code-analyzer.ts

# 分析指定目录
npx ts-node exercise-2-code-analyzer.ts /path/to/your/project

预期效果:Agent 会列出目录树、统计文件类型、找出 TODO 和 console.log,生成一份完整的分析报告。注意它没有 Write 和 Bash,所以只能分析不能修改 —— 这就是只读 Agent 的安全性。


练习3:技术调研 Agent(WebSearch + WebFetch + Write)

目标:创建一个研究 Agent,搜索两个 npm 包的信息并生成对比报告。

// 文件:exercise-3-research-agent.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  console.log("=== 技术调研 Agent ===\n");

  const package1 = process.argv[2] || "express";
  const package2 = process.argv[3] || "fastify";

  for await (const message of query({
    prompt: `请帮我对比 npm 包 "${package1}" 和 "${package2}"。

研究步骤:
1. 用 WebSearch 搜索这两个包的最新信息(star 数、下载量、最后更新时间)
2. 用 WebFetch 分别访问它们的 npm 页面(https://www.npmjs.com/package/包名),获取详细信息
3. 生成一份对比报告并保存到 comparison-report.md 文件

对比报告需要包含:
- 基本信息对比表(版本、大小、许可证、维护状态)
- 性能对比
- 生态系统和社区活跃度
- 适用场景建议
- 我的选择建议(根据不同需求给出推荐)`,
    options: {
      allowedTools: ["WebSearch", "WebFetch", "Write"],
      permissionMode: "bypassPermissions",
      maxTurns: 15
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use") {
          switch (block.name) {
            case "WebSearch":
              console.log(`\n[搜索] ${block.input.query}`);
              break;
            case "WebFetch":
              console.log(`\n[抓取] ${block.input.url}`);
              break;
            case "Write":
              console.log(`\n[保存报告] ${block.input.file_path}`);
              break;
          }
        }
      }
    }
    if (message.type === "result" && message.subtype === "success") {
      console.log("\n=== 调研完成 ===");
      console.log(`报告已保存,花费: ${message.total_cost_usd.toFixed(4)} 美元`);
    }
  }
}

main().catch(console.error);

运行方式:

# 对比 express 和 fastify(默认)
npx ts-node exercise-3-research-agent.ts

# 对比其他包
npx ts-node exercise-3-research-agent.ts axios fetch
npx ts-node exercise-3-research-agent.ts prisma typeorm

预期效果:Agent 会搜索两个包的信息,抓取 npm 页面详情,然后生成一份 Markdown 格式的对比报告并保存到文件。


练习4:只读安全 Agent(Read + Glob + Grep)

目标:创建一个绝对安全的只读 Agent,让它分析一个项目的代码质量。证明即使没有 Bash 和 Write,Agent 也能做很多有用的事。

// 文件:exercise-4-readonly-agent.ts
import { query } from "@anthropic-ai/claude-code";

async function main() {
  console.log("=== 只读安全 Agent(代码质量分析)===\n");
  console.log("注意:这个 Agent 只有 Read、Glob、Grep 三个工具");
  console.log("它不能修改任何文件,不能执行任何命令\n");

  for await (const message of query({
    prompt: `你是一个代码质量分析专家。请对当前项目进行全面的代码质量审查。

请按以下步骤分析:

第一步 - 项目概览:
- 用 Glob 找出所有源代码文件,了解项目规模
- 读取 package.json 或类似配置文件,了解项目基本信息

第二步 - 代码质量检查:
- 用 Grep 搜索潜在问题:
  * any 类型的使用(TypeScript 项目)
  * console.log 调试语句
  * TODO/FIXME/HACK 注释
  * 硬编码的 URL 或密钥
  * 空的 catch 块
- 抽样读取几个重要文件,检查:
  * 函数长度是否合理
  * 命名是否清晰
  * 是否有注释

第三步 - 生成报告:
以纯文本形式输出一份代码质量报告,包含:
- 项目概览(文件数量、代码量估算)
- 发现的问题列表(按严重程度排序)
- 代码质量评分(1-10分)
- 改进建议

注意:你没有 Write 工具,不能保存文件。直接在回复中输出报告即可。`,
    options: {
      allowedTools: ["Read", "Glob", "Grep"],   // 严格只读!
      permissionMode: "bypassPermissions",
      maxTurns: 25
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
        if (block.type === "tool_use") {
          const toolEmoji: Record<string, string> = {
            "Glob": "[搜索文件]",
            "Grep": "[搜索内容]",
            "Read": "[读取文件]"
          };
          const prefix = toolEmoji[block.name] || `[${block.name}]`;
          if (block.name === "Glob") {
            console.log(`${prefix} 模式: ${block.input.pattern}`);
          } else if (block.name === "Grep") {
            console.log(`${prefix} 关键词: ${block.input.pattern}`);
          } else if (block.name === "Read") {
            const filename = block.input.file_path.split("/").pop();
            console.log(`${prefix} ${filename}`);
          }
        }
      }
    }
    if (message.type === "result") {
      if (message.subtype === "success") {
        console.log("\n=== 分析完成 ===");
        console.log(`回合数: ${message.num_turns}`);
        console.log(`花费: ${message.total_cost_usd.toFixed(4)} 美元`);
        console.log("\n最终结果:");
        console.log(message.result);
      } else {
        console.error("\n=== 分析失败 ===");
        console.error("类型:", message.subtype);
      }
    }
  }
}

main().catch(console.error);

运行方式:

# 在你想分析的项目目录中运行
cd /path/to/your/project
npx ts-node /path/to/exercise-4-readonly-agent.ts

预期效果:Agent 会进行全面的代码质量分析,找出潜在问题,给出评分和建议。整个过程中它只会读取和搜索文件,绝对不会修改任何东西 —— 这就是最小权限原则的实际应用。

思考题:如果这个 Agent 尝试调用 Write 或 Bash 会怎样?答案是 SDK 会直接拒绝,因为这些工具不在 allowedTools 列表里。Agent 会发现工具不可用,然后自动调整策略(比如把报告直接输出在回复中,而不是写到文件里)。


本章小结

知识点回顾

我们学了 11 个内置工具,按四大类来回顾:

文件操作类:

搜索类:

执行类:

网络类:

协作类:

核心原则

  1. 最小权限:只给 Agent 需要的工具,不多给
  2. 场景匹配:不同任务用不同的工具组合
  3. 安全第一:Bash 工具要谨慎使用,配合权限模式
  4. 工具描述决定选择:Agent 靠工具描述来判断用哪个

常用工具组合速查

场景 工具组合
只读分析 Read + Glob + Grep
代码开发 Read + Write + Edit + Bash + Glob + Grep
网络调研 WebSearch + WebFetch + Read + Write
多Agent协作 Task + Read + Write + Glob + Grep
纯聊天 [](空数组)

下一章预告

现在你知道了 Agent 的十八般武艺,但问题来了:给了 Agent 这么多能力,怎么保证它不乱来?

第6章《权限控制 —— 给 Agent 划安全红线》会详细讲解:

学完第5章的工具 + 第6章的权限,你就真正掌握了 Agent 的"能力"和"边界"。

← 上一章4. query() 函数