第 3 章 · Tool Calling 与工具系统

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 次工具调用。模型在一个循环中完成了搜索文件、读取代码、编写修改、创建测试、运行验证的全过程。

工具调用时序

把实际运行整理成调用时序表:

步骤工具目的
1create_todos制定 2 步计划
2read_file读取 search.ts 现有代码
3patch_file添加 countOnly 参数定义
4patch_file修改执行逻辑支持 countOnly
5write_file创建测试文件
6run_command运行测试验证
7update_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 从"能用"走向"敢用"的关键一步。

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page