第5章:内置工具 —— Agent 的十八般武艺
一句话:掌握所有内置工具的用法,让 Agent 真正能干活。
本章目标
- 掌握所有内置工具的功能和用法
- 理解 Agent 选择工具的逻辑
- 学会通过
allowedTools控制 Agent 的能力边界 - 能根据不同场景搭配合适的工具组合
前置知识
- 第4章(query() 函数的基本用法)
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 读取一个文件的内容。
适用场景:
- 查看项目配置(package.json、tsconfig.json 等)
- 阅读源代码
- 读取日志文件
- 查看文档内容
代码示例:让 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) |
注意事项:
file_path必须是绝对路径,不能用相对路径- 如果文件很大(比如日志文件有几万行),Agent 会自动用
offset和limit分批读取 - 可以读取图片文件(Agent 是多模态的,能"看"图)
- 可以读取 PDF 文件(大 PDF 需要指定页码范围)
- 可以读取 Jupyter Notebook(.ipynb)
5.2.2 Write —— 创建文件
一句话:让 Agent 创建一个新文件,或者完全覆盖一个已有文件。
适用场景:
- 生成配置文件(.gitignore、tsconfig.json 等)
- 创建新的源代码文件
- 生成报告或文档
- 把处理结果保存到文件
代码示例:让 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 | 要写入的完整内容 |
注意事项:
- Write 是"全量覆盖"的 —— 如果文件已存在,会完全替换原内容
- 如果你只想改文件的一小部分,别用 Write,用下面要讲的 Edit
- 写入前 Agent 通常会先用 Read 看一下原文件内容(如果文件存在的话)
5.2.3 Edit —— 精确修改文件
一句话:让 Agent 只修改文件中的一小部分,不动其他内容。
适用场景:
- 修改函数名、变量名
- 修复 Bug(改几行代码)
- 更新配置文件中的某个值
- 在已有文件中插入新内容
代码示例:让 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) |
注意事项:
old_string必须在文件中是唯一的,否则 Edit 会失败- 如果有多处需要改,设置
replace_all: true(比如重命名变量) - Agent 使用 Edit 之前必须先用 Read 读过这个文件
Write vs Edit:什么时候用哪个?
这是很多人搞混的地方,给你画个决策树:
你要改文件?
│
├── 文件不存在,需要从头创建 → 用 Write
│
├── 文件存在,但你要完全重写 → 用 Write
│
└── 文件存在,你只想改其中几行 → 用 Edit ✓
打个比方:
- 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.ts、fileA.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、操作数据库... 几乎无所不能。
适用场景:
- 安装依赖:
npm install、pip install - 运行测试:
npm test、pytest - Git 操作:
git status、git log、git commit - 编译项目:
npm run build、tsc - 查看系统信息:
node --version、which python - 运行自定义脚本
代码示例:让 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 查看信息,无害
安全原则:
- 不要轻易给 Bash 工具:如果 Agent 只需要读文件,给 Read 就够了,不需要 Bash
- 用权限模式控制:用
permissionMode: "default"而不是"bypassPermissions",这样 Agent 每次执行命令前都会问你 - 限制命令范围:在 system prompt 里明确告诉 Agent 哪些命令可以执行
- 监控输出:通过
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 一样。
适用场景:
- 搜索技术问题的解决方案
- 查询某个库的最新版本
- 了解最新的技术趋势
- 查找 API 文档
代码示例:让 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 打开一个网页,把内容下载下来阅读。
适用场景:
- 获取 API 文档的详细内容
- 阅读某篇技术博客
- 抓取在线 JSON 数据
- 查看 npm 包的说明页
代码示例:让 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 的特点:
- 自动把 HTML 转成 Markdown,方便 Agent 阅读
- 内容太长会自动摘要
- 有 15 分钟缓存,重复访问同一页面会更快
- 不能访问需要登录的页面(GitHub 私有仓库、Google Docs 等)
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 分担
- 某个子任务需要独立的上下文空间
- 希望并行处理多个子任务
- 任务分工明确,适合"老板分活"的模式
代码示例:主 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 的关键特点:
- 子 Agent 有独立的上下文,不会占用主 Agent 的上下文空间
- 子 Agent 可以使用自己的工具集
- 子 Agent 完成后,只把结果摘要返回给主 Agent
- 这是实现多 Agent 协作的基础,第13章会详细展开
5.7 用户交互:AskUserQuestion
AskUserQuestion —— 拿不准就问
一句话:当 Agent 遇到不确定的决策时,可以暂停下来问用户怎么办。
适用场景:
- Agent 需要在多个方案中选择
- 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 它需要的工具,不多给一个。
为什么?
- 安全:工具越少,Agent 能"闯祸"的范围越小
- 效率:工具越少,Agent 选择工具时越不容易犹豫或选错
- 可预测:工具有限时,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 的发消息工具
]
}
通配符规则:
mcp__<服务名>__*:允许这个 MCP 服务的所有工具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 个内置工具,按四大类来回顾:
文件操作类:
- Read:读取文件内容,支持分页、图片、PDF
- Write:创建或完全覆盖文件
- Edit:精确替换文件中的某一部分
搜索类:
- Glob:按文件名模式找文件
- Grep:在文件内容中搜索正则表达式
执行类:
- Bash:执行 Shell 命令,功能最强大也最危险
网络类:
- WebSearch:搜索互联网
- WebFetch:抓取网页内容
协作类:
- Task:派子 Agent 去做子任务
- AskUserQuestion:拿不准时问用户
- Skill:调用预定义的技能
核心原则
- 最小权限:只给 Agent 需要的工具,不多给
- 场景匹配:不同任务用不同的工具组合
- 安全第一:Bash 工具要谨慎使用,配合权限模式
- 工具描述决定选择:Agent 靠工具描述来判断用哪个
常用工具组合速查
| 场景 | 工具组合 |
|---|---|
| 只读分析 | Read + Glob + Grep |
| 代码开发 | Read + Write + Edit + Bash + Glob + Grep |
| 网络调研 | WebSearch + WebFetch + Read + Write |
| 多Agent协作 | Task + Read + Write + Glob + Grep |
| 纯聊天 | [](空数组) |
下一章预告
现在你知道了 Agent 的十八般武艺,但问题来了:给了 Agent 这么多能力,怎么保证它不乱来?
第6章《权限控制 —— 给 Agent 划安全红线》会详细讲解:
- 四种权限模式(从最严到最松)
- 精细化的权限规则(allow / deny / ask)
- 运行时动态权限决策
- 如何在"让 Agent 能干活"和"保证安全"之间找到平衡
学完第5章的工具 + 第6章的权限,你就真正掌握了 Agent 的"能力"和"边界"。