06 · 实战:读-改-跑-验闭环
用完整的工具链跑通一次从搜索到验证的闭环。展示 agent 如何组合使用多种工具完成真实开发任务。
从单个工具到完整工作流
前五节分别实现了搜索、文件读写、命令执行和 Git 查看工具。每个工具单独看都不复杂,但 agent 的真正力量在于组合——用多个工具串联完成一个完整的开发任务。
这一节通过 agent 自己跑一个真实任务,展示它怎么组合使用这些工具,完成"读-改-跑-验"的完整闭环。
完整工具注册表
先看一下这一章构建的完整工具集:
// src/tools/index.ts
import type { Tool } from "../types";
import type { TodoManager } from "../todo";
import { readFileTool } from "./read-file";
import { searchTool } from "./search";
import { globTool } from "./glob";
import { writeFileTool } from "./write-file";
import { patchFileTool } from "./patch-file";
import { runCommandTool } from "./run-command";
import { gitStatusTool } from "./git-status";
import { gitDiffTool } from "./git-diff";
import { createTodosTool } from "./create-todos";
import { updateTodoTool } from "./update-todo";
/** 基础工具集(搜索和文件读写) */
export const tools: Tool[] = [
searchTool,
globTool,
readFileTool,
writeFileTool,
patchFileTool,
runCommandTool,
gitStatusTool,
gitDiffTool,
];
/**
* 创建完整的工具集,包含基础工具和计划管理工具
*/
export function createAllTools(
todoManager: TodoManager,
baseTools: Tool[] = tools,
): Tool[] {
return [
...baseTools,
createTodosTool(todoManager),
updateTodoTool(todoManager),
];
}八个基础工具加两个计划工具,按功能分层:
| 层次 | 工具 | 作用 |
|---|---|---|
| 搜索 | search, glob | 找到要操作的文件 |
| 读写 | read_file, write_file, patch_file | 读取和修改文件内容 |
| 执行 | run_command | 运行命令验证结果 |
| 查看 | git_status, git_diff | 检查变更影响 |
| 计划 | create_todos, update_todo | 管理任务步骤 |
给 agent 一个真实任务
> 在 src/tools/search.ts 中添加一个 countOnly 参数,当设置为 true 时只返回匹配数量而不返回匹配内容。然后写对应的测试验证。这个任务要求 agent 修改现有代码并写测试——典型的"读-改-跑-验"闭环。
第一步:模型创建计划
模型判断这是复杂任务,调用 create_todos:
工具调用: create_todos({
items: [
"在 searchTool 中添加 countOnly 参数定义和执行逻辑",
"创建测试验证功能"
]
})
--- 执行计划 ---
[ ] #1 在 searchTool 中添加 countOnly 参数定义和执行逻辑
[ ] #2 创建测试验证功能
----------------两步计划,清晰明了。
第二步:读取现有代码,添加参数
模型标记步骤 1 为 running,然后读取 search.ts 的当前内容:
工具调用: update_todo({ id: "1", status: "running" })
工具调用: read_file({ path: "src/tools/search.ts" })模型看到 search.ts 的完整代码后,用 patch_file 添加了 countOnly 参数:
工具调用: patch_file({
path: "src/tools/search.ts",
old_content: "maxResults: ...",
new_content: "maxResults: ...,\ncountOnly: {\n type: \"boolean\",\n ...\n}"
})接着修改执行逻辑——当 countOnly=true 时用 rg --count 替代 rg --line-number。
第三步:写测试验证
模型标记步骤 1 完成,步骤 2 开始:
工具调用: update_todo({ id: "1", status: "completed" })
工具调用: update_todo({ id: "2", status: "running" })
工具调用: write_file({ path: "test/tools/search.test.ts", content: "..." })模型创建了一个完整的测试文件,包含 7 个测试用例:正常搜索、countOnly 基本行为、不同关键词统计、未找到匹配等。
第四步:运行测试,确认通过
工具调用: run_command({ command: "npx vitest run" })
stdout:
Test Files 13 passed (13)
Tests 77 passed (77)全部通过。模型标记步骤 2 完成。
实际运行统计
--- 执行计划 ---
[x] #1 在 searchTool 中添加 countOnly 参数定义和执行逻辑
[x] #2 创建测试验证功能
----------------
[模型调用: 14次 | 工具调用: 17次]14 次模型调用,17 次工具调用。模型在一个循环中完成了搜索文件、读取代码、编写修改、创建测试、运行验证的全过程。
工具调用时序
把实际运行整理成调用时序表:
| 步骤 | 工具 | 目的 |
|---|---|---|
| 1 | create_todos | 制定 2 步计划 |
| 2 | read_file | 读取 search.ts 现有代码 |
| 3 | patch_file | 添加 countOnly 参数定义 |
| 4 | patch_file | 修改执行逻辑支持 countOnly |
| 5 | write_file | 创建测试文件 |
| 6 | run_command | 运行测试验证 |
| 7 | update_todo ×4 | 标记步骤状态 |
每次工具调用都是一轮 ReAct 循环:模型看到上一次的结果,决定下一步做什么。
"读-改-跑-验"四阶段模式
这个例子展现了一个通用的 agent 工作模式:
读(Read):搜索和读取现有代码,理解项目结构和上下文。agent 先读取文件内容——不会凭空猜测代码结构。
改(Modify):用 patch_file 修改现有代码,用 write_file 创建新文件。agent 优先使用 patch_file,只改需要改的部分。
跑(Run):用 run_command 运行测试验证修改。测试全部通过才标记步骤完成。
验(Verify):agent 确认改动范围和测试结果。在这个例子中,77 个测试全部通过,修改只涉及 search.ts 和新增测试文件。
这四个阶段不是严格的线性流程——agent 可能需要多次循环。比如跑测试失败了,agent 会回到"读"阶段查看错误信息,再"改"修复 bug,再"跑"重新验证。这正是 ReAct 模式的优势:Reason-Analyze-Act 的每一轮都在积累信息。
简单任务不走计划
对比一下:如果问"找出所有 .ts 文件的路径",模型判断这是简单问题,直接用 glob 工具回答,不创建计划:
> 列出所有 .ts 文件的路径
[模型调用: 2次 | 工具调用: 2次]2 次模型调用就够了。系统提示词中的这段引导让模型能区分两种情况:
对于复杂任务,先用 create_todos 工具创建执行计划,然后按步骤执行。
...
对于简单问题,可以直接使用搜索和读文件工具回答,不需要创建计划。这一章的代码结构
完成第 3 章后,项目结构变为:
mini-coding-agent/
├── src/
│ ├── types.ts # 类型定义
│ ├── model.ts # 模型层(不变)
│ ├── todo.ts # Todo 管理器(不变)
│ ├── agent.ts # Agent Loop(升级:扩展系统提示词)
│ ├── main.ts # CLI 入口(不变)
│ └── tools/
│ ├── index.ts # 工具注册(新增 createAllTools)
│ ├── search.ts # 搜索工具(不变)
│ ├── glob.ts # 按模式列文件(新增)
│ ├── read-file.ts # 读文件(不变)
│ ├── write-file.ts # 写文件(新增)
│ ├── patch-file.ts # 精确修改(新增)
│ ├── run-command.ts # 执行命令(新增)
│ ├── git-status.ts # Git 状态(新增)
│ ├── git-diff.ts # Git 变更(新增)
│ ├── create-todos.ts # 创建计划(不变)
│ └── update-todo.ts # 更新步骤(不变)
├── test/
│ ├── agent.test.ts
│ ├── model.test.ts
│ ├── todo.test.ts
│ └── tools/
│ ├── search.test.ts
│ ├── glob.test.ts
│ ├── read-file.test.ts
│ ├── write-file.test.ts
│ ├── patch-file.test.ts
│ ├── run-command.test.ts
│ ├── git-status.test.ts
│ ├── git-diff.test.ts
│ ├── create-todos.test.ts
│ └── update-todo.test.ts
├── package.json
└── tsconfig.json第三章总结
这一章构建了完整的工具系统,从理解 Tool Calling 的本质到实现八个具体工具:
01 · Tool Calling 的本质:理解了工具是 agent 能力的真实落点。每个工具定义包含 name、description、parameters、execute 四个字段。
02 · 搜索工具:search 在文件内容中找关键词,glob 按路径模式列文件。两种搜索方式互补。
03 · 文件工具:read_file 读取、write_file 创建覆写、patch_file 精确修改。patch 的四重安全机制确保修改的安全性。
04 · 命令工具:run_command 让 agent 执行终端命令。超时限制、输出截断、stdout/stderr 分离是三个关键设计。
05 · Git 工具:git_status 看全貌,git_diff 看详情。只读设计,写操作留给权限系统。
06 · 实战闭环:用"读-改-跑-验"四阶段跑通完整工作流,展示工具组合的真正力量。
最终的工具注册表:
/** 基础工具集 */
export const tools: Tool[] = [
searchTool, /** 全文搜索 */
globTool, /** 按模式列文件 */
readFileTool, /** 读取文件 */
writeFileTool, /** 创建/覆写文件 */
patchFileTool, /** 精确修改文件 */
runCommandTool, /** 执行命令 */
gitStatusTool, /** 查看 Git 状态 */
gitDiffTool, /** 查看 Git 变更 */
];八个工具,各司其职,组合起来形成完整的开发能力。
下一章:权限系统
agent 现在有了强大的工具——能搜索、读写文件、执行命令、查看 Git 变更。但这些能力都"裸奔"着,没有任何安全控制。run_command 可以执行任何命令,write_file 可以写入任何路径。
第四章会构建权限系统:哪些操作可以直接执行,哪些需要用户确认,哪些应该直接拒绝。这是让 agent 从"能用"走向"敢用"的关键一步。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。