Skip to content

Tool:把模型不能可靠完成的事交给代码

问题场景

模型擅长理解语言和组织答案,但不擅长可靠地访问数据库、计算精确结果、调用 API 或读取本地文件。Tool 的作用是把这些能力变成可验证的函数,并用 schema 告诉模型如何调用。

工具的设计原则

一个好工具应该:

  • 只做一件清楚的事。
  • 输入输出有 schema。
  • 执行函数可以单独测试。
  • 错误信息能帮助 Agent 修正参数。
  • 不把密钥、用户隐私或复杂业务流程藏在 prompt 里。

Mastra 映射

Mastra 的 createTool() 用来定义可被 Agent 执行的工具。官方参考文档展示的模式是:iddescriptioninputSchemaoutputSchemaexecute

ts
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 文档里有两个很容易被忽略的能力:toModelOutputtransform。它们解决的是同一个问题:工具返回的数据,给模型看的、给 UI 看的、给业务代码保存的,往往不是同一份。

层级目标典型内容
Tool 原始返回值给应用代码使用完整对象、ID、metadata、调试字段
toModelOutput给模型继续推理精简文本、必要图片、少量结构化摘要
transform给 UI 和 transcript 展示安全摘要、脱敏参数、用户能看懂的状态

在 Study Agent 中,searchCourseTool 可以返回完整资料 ID 和 score,但模型只需要标题和摘要。真实项目中如果工具返回数据库原始行、外部 API 响应或敏感字段,不要直接把完整对象交给模型。

ts
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() 时可以用 toolChoiceactiveTools 控制工具使用。

ts
await studyAgent.generate('只搜索资料,不生成计划', {
  toolChoice: 'required',
  activeTools: ['searchCourseTool'],
})

常见用法:

控制方式适合场景
activeTools按用户套餐、页面功能或任务阶段限制可用工具
toolChoice: 'required'当前请求必须调用工具,例如必须先查资料
不显式限制开放式聊天,让 Agent 自行判断

如果工具很多,不要一次全部挂给 Agent。先按任务阶段限制 activeTools,再用 Workflow 或 Supervisor 拆分职责。

工具名和 stream 事件

Mastra stream 中的 toolName 由注册到 Agent 时的对象 key 决定,不一定等于工具内部的 id

ts
tools: { searchCourseTool }
// stream 里看到 toolName: "searchCourseTool"

tools: { [searchCourseTool.id]: searchCourseTool }
// stream 里看到 toolName: "search-course"

教程中建议保持对象 key 稳定,并在 eval cases 里按同一名称检查工具调用。否则 UI、trace 和测试脚本会出现「工具明明调用了,但名字对不上」的问题。

Vibe coding 提示词

text
请为 Mastra Agent 写一个工具 searchCourseTool。
要求:
- 使用 createTool 和 zod。
- 输入为 { query: string; topK?: number }。
- 输出为 { hits: Array<{ id: string; title: string; excerpt: string; score: number }> }。
- 工具只搜索本地 courseMaterials 数组,不调用网络。
- execute 里要处理空 query,返回空 hits。

验证方式

运行工具调用 Demo:

bash
npm run demo:02

观察输出里是否包含:

  • Agent 决定调用哪个工具。
  • 工具收到的参数。
  • 工具结果如何进入最终回答。
  • 如果接入 stream,toolName 是否和 UI / eval 脚本预期一致。
  • 如果工具返回大对象,模型看到的内容是否经过 toModelOutput 精简。

常见错误

错误后果修复
schema 字段和 execute 参数不一致运行时报 undefined让 schema、类型和实现保持同名
工具描述像注释而不是能力说明模型不调用或乱调用写清“什么时候用它”
工具承担多步业务流程难以复用拆成 Tool + Workflow
工具直接吞掉错误Agent 看不到失败原因返回可解释错误或抛出明确异常
工具把原始数据全交给模型上下文浪费或泄露字段toModelOutput 精简
UI 直接展示原始工具 payload用户看到内部字段或敏感信息transform 做安全展示