Tool 的最小形态
import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; export const getWeatherTool = createTool({ id: 'get-weather', description: '查询指定城市当前天气', inputSchema: z.object({ city: z.string().describe('城市中文名,如"北京"'), }), outputSchema: z.object({ temp: z.number(), condition: z.string(), }), execute: async ({ context }) => { const res = await fetch(`https://api.weather.com?q=${context.city}`); const data = await res.json(); return { temp: data.temp, condition: data.condition }; }, });
execute 函数的入参
context
Zod 校验过的 input,类型自动从 inputSchema 推导,编辑器里
context. 有智能提示。runtimeContext
Agent 调用时传入的上下文容器——放 userId、traceId、tenantId 等,贯穿整个 tool 链。
mastra
Mastra 主实例。在 tool 里可以
mastra.getAgent('xxx') 调用其他 Agent(嵌套 Agent 模式)。abortSignal
上层传下来的取消信号,用来给 fetch 加 signal,避免 Agent 超时后 tool 仍在跑。
Zod:schema 就是文档
Zod 的 .describe() 会作为参数说明喂给模型,写得越清楚模型调用越准:
inputSchema: z.object({ query: z.string().describe('搜索关键词,不要含时间,时间由 from/to 指定'), from: z.string().datetime().describe('ISO8601 时间,搜索起点'), to: z.string().datetime().describe('ISO8601 时间,搜索终点'), topK: z.number().int().min(1).max(50).default(10), tag: z.enum(['news', 'blog', 'paper']).optional(), }),
技巧:描述要给"时机"
与其写「查询数据库」,不如写「当用户问及具体订单号时调用此工具」。时机性描述能显著减少模型"该调不调"或"不该调乱调"的毛病。
与其写「查询数据库」,不如写「当用户问及具体订单号时调用此工具」。时机性描述能显著减少模型"该调不调"或"不该调乱调"的毛病。
错误处理
export const payTool = createTool({ id: 'pay', description: '发起支付', inputSchema: z.object({ amount: z.number().positive() }), execute: async ({ context, abortSignal }) => { try { const res = await fetch('/pay', { method: 'POST', body: JSON.stringify({ amount: context.amount }), signal: abortSignal, }); if (!res.ok) { // 业务错误要回给 LLM 让它决策,不要 throw 原始 HTTP 错误 return { success: false, error: `支付失败:${res.status}` }; } return { success: true, txId: (await res.json()).id }; } catch (e) { return { success: false, error: (e as Error).message }; } }, });
不要让 tool throw
throw 会中断整个 Agent 执行链。把错误包装成结构化返回值交给 LLM,它通常会道歉、重试或转告用户——这是好用的 Agent 该有的韧性。
throw 会中断整个 Agent 执行链。把错误包装成结构化返回值交给 LLM,它通常会道歉、重试或转告用户——这是好用的 Agent 该有的韧性。
调用外部 API(带 secrets)
export const githubSearchTool = createTool({ id: 'github-search', description: '搜 GitHub 仓库', inputSchema: z.object({ q: z.string() }), execute: async ({ context }) => { const res = await fetch( `https://api.github.com/search/repositories?q=${context.q}`, { headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, 'User-Agent': 'mastra-agent', }, } ); const data = await res.json(); return data.items.slice(0, 5).map(r => ({ name: r.full_name, url: r.html_url, stars: r.stargazers_count, })); }, });
查数据库(Drizzle / Prisma)
import { db } from './db'; import { orders } from './schema'; import { eq } from 'drizzle-orm'; export const getOrderTool = createTool({ id: 'get-order', description: '通过订单号查订单详情', inputSchema: z.object({ orderId: z.string().regex(/^ORD\d{8}$/), }), execute: async ({ context, runtimeContext }) => { const userId = runtimeContext.get('userId'); const [row] = await db.select().from(orders) .where(eq(orders.id, context.orderId)); if (!row) return { found: false }; if (row.userId !== userId) { return { found: false, error: '非本人订单' }; } return { found: true, order: row }; }, });
安全:永远用 runtimeContext 检查权限
LLM 可以被越权提示注入"查所有订单"。Tool 内部必须拿 userId 做过滤,把权限逻辑写在工具里,不要指望 LLM 自觉。
LLM 可以被越权提示注入"查所有订单"。Tool 内部必须拿 userId 做过滤,把权限逻辑写在工具里,不要指望 LLM 自觉。
嵌套 Agent:tool 里再调 Agent
export const translateTool = createTool({ id: 'translate', description: '把英文段落翻译成中文', inputSchema: z.object({ text: z.string() }), execute: async ({ context, mastra }) => { const translator = mastra.getAgent('translator'); const { text } = await translator.generate(context.text); return { translated: text }; }, });
主 Agent 把翻译任务委托给专门的翻译 Agent。这是多 Agent 协作的最简模式——比手写 orchestrator 简单,适合小规模场景。大规模用 Workflow(下一章)。
Tool 的三种挂载方式
// 1. 静态:Agent 固定可用一批 tools new Agent({ ..., tools: { getWeatherTool, getOrderTool } }) // 2. 动态:根据运行时上下文决定可见 tools new Agent({ ..., tools: ({ runtimeContext }) => { const tier = runtimeContext.get('tier'); return tier === 'pro' ? { getWeatherTool, payTool, exportTool } : { getWeatherTool }; }, }); // 3. 调用时临时加 await agent.generate(input, { toolsets: { admin: { banUserTool } }, // 仅本次可用 });
max steps 与循环防护
await agent.generate(input, { maxSteps: 5, // 最多 5 轮 tool 调用,超出强制停 });
默认 5。复杂任务可调到 10-20。但 maxSteps 过大意味着模型容易在 tool 里"神游"——应同时增强 instructions 约束。
并发工具调用
模型(Claude、GPT-4o)支持在一个 turn 里并发返回多个 tool call。Mastra 会并发 Promise.all 执行,tool 里只要不共享状态就是安全的。
// 同一 turn 模型可能同时调: // get-weather(city: "北京") // get-weather(city: "上海") // get-weather(city: "深圳") // 三个 fetch 并行,延迟 = max 而不是 sum
Playground 里观察 tool call
发消息后展开 Trace 面板,会看到:
- 模型选择的 tool id 与入参(JSON)
- execute 执行耗时、返回值
- 下一轮模型输出(可能再调 tool 或最终回答)
- 每步 token 用量与累计花费
本章小结
createTool+ Zod 的 input/output schema = 类型安全 + LLM 可读描述- execute 收到的 context 已 Zod 校验;返回值也会被 outputSchema 过一遍
- 错误用结构化返回,别 throw;权限用 runtimeContext 做,别信 LLM
- tool 里能调 mastra.getAgent() 形成嵌套 Agent
- 静态 tools / 动态 tools / 调用时 toolsets 三种挂载方式
- Playground Trace 是调试 tool call 的利器