第 6 章 · 工具定义与执行
本章目标:用
AgentTool与 TypeBox 定义工具,理解 Agent 的工具执行循环与并行/串行两种执行模式。
6.1 工具是 Agent 的手脚
LLM 本身只会生成文本;工具让它能读文件、查数据库、调 API。pi 的工具协议分两层:
pi-ai层的Tool:纯声明(name/description/parameters),告诉模型有哪些工具;pi-agent-core层的AgentTool:声明 +execute()实现,Agent 循环会自动调用。
6.2 用 TypeBox 定义参数 Schema
TypeBox 提供类型安全的 JSON Schema,且可序列化为纯 JSON:
typescript
import { Type } from "typebox";
import type { AgentTool } from "@earendil-works/pi-agent-core";
import fs from "node:fs/promises";
// 一个完整的 AgentTool 示例
const readFileTool: AgentTool = {
name: "read_file",
label: "Read File", // UI 显示名(可选)
description: "读取指定路径文件的内容", // 给模型看的说明,务必清晰
parameters: Type.Object({
// 每个字段的 description 都会成为模型理解的依据
path: Type.String({ description: "文件的绝对或相对路径" }),
}),
execute: async (toolCallId, params, signal, onUpdate) => {
const content = await fs.readFile(params.path, "utf-8");
// 可选:向 UI 流式上报进度
onUpdate?.({
content: [{ type: "text", text: "读取完成" }],
details: {},
});
// 返回内容块数组 + 任意 details 附加数据
return {
content: [{ type: "text", text: content }],
details: { path: params.path, size: content.length },
};
},
};
// 挂载到 Agent 上
agent.state.tools = [readFileTool];description 是提示工程
模型的工具选择几乎完全依赖 description 与参数描述。写得越具体(何时该用、何时不该用、单位与格式约定),调用越准确。
6.3 工具失败:抛错,不要返回错误文本
typescript
execute: async (toolCallId, params) => {
if (!fs.existsSync(params.path)) {
// ✅ 正确:抛出异常 —— Agent 会捕获并作为 isError:true 的结果告知模型
throw new Error(`File not found: ${params.path}`);
}
// ❌ 错误做法:返回 { content: [{type:'text',text:'错误:文件不存在'}] }
// 模型无法区分成功与失败,可能基于错误内容继续推理
}抛出的错误由运行时统一转换为带 isError: true 的 toolResult 消息——模型能看到错误原因并自行重试或换路。
6.4 执行循环:Agent 如何决定调用
text
用户消息 → LLM 生成 assistant 消息(含 toolCall 请求)
→ 运行时校验参数 → 执行工具 → 生成 toolResult 消息
→ 回填历史 → 再次调用 LLM
→ 若仍有 toolCall 则继续循环,直到纯文本回答你不需要写任何循环代码——Agent.prompt() 内部自动完成。想手动控制每一环可用低层 agentLoop()(第 20 章实战演示)。
6.5 并行 vs 串行执行模式
typescript
const agent = new Agent({
// 全局默认:parallel(同一批工具并发跑)
toolExecution: "parallel",
initialState: { systemPrompt: "...", model },
streamFn: models.streamSimple.bind(models),
});
// 也可以在单个工具上覆盖
const exclusiveTool: AgentTool = {
name: "deploy",
executionMode: "sequential", // 强制整批按顺序执行
// ...
};规则要点:
- parallel(默认):预检顺序执行、允许的工具并发执行,完成事件按完成先后发出,但落库的 toolResult 仍按模型给出的原始顺序;
- 批次内只要有一个工具标记
sequential,整批退化为串行; beforeToolCall钩子可在参数校验后拦截危险调用(第 13 章结合权限展开)。
6.6 本章小结
AgentTool = Tool 声明 + execute(),参数用 TypeBox schema 描述;- description 写清楚是提升调用准确率的第一手段;
- 工具失败要 throw,运行时会转成 isError 结果反馈给模型;
- 默认 parallel 并发执行,per-tool
executionMode:"sequential"可强制整批串行。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. 工具执行失败时,推荐的错误处理方式是?
2. 全局 toolExecution 为 parallel 时,批次里有一个工具设了 executionMode:"sequential",结果是?
3. 工具的 parameters 字段使用什么库来定义?
4. execute 的 onUpdate 参数有什么用?
🛠️ 动手实践
- 实现一个
list_dir工具(列出目录内容),description 里写清「仅接受相对路径」,观察模型是否遵守。 - 故意让工具对不存在路径 throw,观察终端里模型收到错误后的重试行为。
- 定义两个慢工具(各 sleep 2 秒),分别在 global parallel 与两工具都 sequential 下测量总耗时差异。
会定义工具了,下一章第 7 章看流式层面工具调用是如何逐步生成的。