Tool:把模型不能可靠完成的事交给代码
问题场景
模型擅长理解语言和组织答案,但不擅长可靠地访问数据库、计算精确结果、调用 API 或读取本地文件。Tool 的作用是把这些能力变成可验证的函数,并用 schema 告诉模型如何调用。
工具的设计原则
一个好工具应该:
- 只做一件清楚的事。
- 输入输出有 schema。
- 执行函数可以单独测试。
- 错误信息能帮助 Agent 修正参数。
- 不把密钥、用户隐私或复杂业务流程藏在 prompt 里。
Mastra 映射
Mastra 的 createTool() 用来定义可被 Agent 执行的工具。官方参考文档展示的模式是:id、description、inputSchema、outputSchema 和 execute。
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'
export const searchCourseTool = createTool({
id: 'search-course',
description: 'Search course notes by keyword.',
inputSchema: z.object({
query: z.string(),
}),
outputSchema: z.object({
hits: z.array(
z.object({
title: z.string(),
excerpt: z.string(),
}),
),
}),
execute: async ({ query }) => {
return {
hits: [
{
title: 'Agent Loop',
excerpt: `matched: ${query}`,
},
],
}
},
})为什么 description 很重要
模型不是直接读你的函数实现,而是根据工具描述和 schema 判断“什么时候该调用它”。description 写得越像业务能力说明,工具越容易被正确选择。
Tool 和普通函数的区别
| 维度 | 普通函数 | Mastra Tool |
|---|---|---|
| 谁调用 | 代码直接调用 | Agent 或 Workflow 调用 |
| 参数约束 | 靠 TypeScript 或运行时检查 | schema 暴露给模型和运行时 |
| 可观测性 | 需要自己打日志 | Mastra 运行时可记录工具调用 |
| 用途 | 业务实现 | 模型可用的外部能力 |
工具结果要分三层看
官方 Tools 文档里有两个很容易被忽略的能力:toModelOutput 和 transform。它们解决的是同一个问题:工具返回的数据,给模型看的、给 UI 看的、给业务代码保存的,往往不是同一份。
| 层级 | 目标 | 典型内容 |
|---|---|---|
| Tool 原始返回值 | 给应用代码使用 | 完整对象、ID、metadata、调试字段 |
toModelOutput | 给模型继续推理 | 精简文本、必要图片、少量结构化摘要 |
transform | 给 UI 和 transcript 展示 | 安全摘要、脱敏参数、用户能看懂的状态 |
在 Study Agent 中,searchCourseTool 可以返回完整资料 ID 和 score,但模型只需要标题和摘要。真实项目中如果工具返回数据库原始行、外部 API 响应或敏感字段,不要直接把完整对象交给模型。
export const searchCourseTool = createTool({
id: 'search-course',
description: 'Search course notes by keyword.',
inputSchema,
outputSchema,
execute: async ({ query }) => {
return {
hits: await searchInternalCourseIndex(query),
debug: { indexName: 'course-v1' },
}
},
toModelOutput: output => ({
type: 'content',
value: output.hits.map(hit => ({
type: 'text',
text: `${hit.title}: ${hit.excerpt}`,
})),
}),
})教学项目没有默认使用 toModelOutput,是为了让初学阶段能直接看到完整结果。正式项目应尽早设计这层边界。
控制工具选择
官方文档说明,调用 .generate() 或 .stream() 时可以用 toolChoice 和 activeTools 控制工具使用。
await studyAgent.generate('只搜索资料,不生成计划', {
toolChoice: 'required',
activeTools: ['searchCourseTool'],
})常见用法:
| 控制方式 | 适合场景 |
|---|---|
activeTools | 按用户套餐、页面功能或任务阶段限制可用工具 |
toolChoice: 'required' | 当前请求必须调用工具,例如必须先查资料 |
| 不显式限制 | 开放式聊天,让 Agent 自行判断 |
如果工具很多,不要一次全部挂给 Agent。先按任务阶段限制 activeTools,再用 Workflow 或 Supervisor 拆分职责。
工具名和 stream 事件
Mastra stream 中的 toolName 由注册到 Agent 时的对象 key 决定,不一定等于工具内部的 id。
tools: { searchCourseTool }
// stream 里看到 toolName: "searchCourseTool"
tools: { [searchCourseTool.id]: searchCourseTool }
// stream 里看到 toolName: "search-course"教程中建议保持对象 key 稳定,并在 eval cases 里按同一名称检查工具调用。否则 UI、trace 和测试脚本会出现「工具明明调用了,但名字对不上」的问题。
Vibe coding 提示词
请为 Mastra Agent 写一个工具 searchCourseTool。
要求:
- 使用 createTool 和 zod。
- 输入为 { query: string; topK?: number }。
- 输出为 { hits: Array<{ id: string; title: string; excerpt: string; score: number }> }。
- 工具只搜索本地 courseMaterials 数组,不调用网络。
- execute 里要处理空 query,返回空 hits。验证方式
运行工具调用 Demo:
npm run demo:02观察输出里是否包含:
- Agent 决定调用哪个工具。
- 工具收到的参数。
- 工具结果如何进入最终回答。
- 如果接入 stream,
toolName是否和 UI / eval 脚本预期一致。 - 如果工具返回大对象,模型看到的内容是否经过
toModelOutput精简。
常见错误
| 错误 | 后果 | 修复 |
|---|---|---|
| schema 字段和 execute 参数不一致 | 运行时报 undefined | 让 schema、类型和实现保持同名 |
| 工具描述像注释而不是能力说明 | 模型不调用或乱调用 | 写清“什么时候用它” |
| 工具承担多步业务流程 | 难以复用 | 拆成 Tool + Workflow |
| 工具直接吞掉错误 | Agent 看不到失败原因 | 返回可解释错误或抛出明确异常 |
| 工具把原始数据全交给模型 | 上下文浪费或泄露字段 | 用 toModelOutput 精简 |
| UI 直接展示原始工具 payload | 用户看到内部字段或敏感信息 | 用 transform 做安全展示 |