在 Mastra 中为文件化子代理编写指令:以weather-fs/forecaster/instructions.md为例
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
导读
在 Mastra 的文件化(file-based)Agent体系中,instructions.md是定义 Agent 行为灵魂的纯文本文件:它决定模型何时调用工具、如何汇报结果、何时把任务委派给子代理。本文以 examples/agent/src/mastra/agents/weather-fs 示例中forecaster子代理的 instructions.md 为骨架,完整还原该指令文本,并结合其配套的config.ts、get_forecast工具、嵌套子代理historian以及@mastra/core的文件路由源码,讲透“如何给一个子代理写出高可用指令、它如何被父代理发现与委派、最深可以嵌套几层”。读完你将能照葫芦画瓢,在自己的 Mastra 项目中用纯文件方式组织出多层协作的 Agent 团队。
一、关联文档原文:forecaster/instructions.md的完整内容
该子代理的指令文件全文如下(原样保留):
You are a forecasting specialist. When asked for a forecast, call the `get_forecast` tool for the named city and summarize the result day by day in a short list. Call out any precipitation. If no city is named, ask which city.虽然只有四行,但它是一份结构完整、可运行的子代理指令,包含了三条关键的行为约束:
| 指令内容 | 行为含义 |
|---|---|
You are a forecasting specialist. | 角色设定(System Prompt 的身份),让模型以“预报专家”的身份回答 |
call the get_forecast tool for the named city and summarize the result day by day in a short list. Call out any precipitation. | 明确工具调用触发条件(用户要预报 → 调get_forecast)、输出格式(逐日短列表)与输出重点(突出降水) |
If no city is named, ask which city. | 兜底交互策略:参数缺失时主动追问,而不是臆造城市 |
在 Mastra 的文件化约定中,这份文件不需要任何export,目录结构本身即声明:subagents/forecaster/instructions.md就是forecaster子代理的instructions。正如 forecaster/config.ts 中的注释所说:// instructions omitted -> taken from instructions.md——即 config 中省略instructions字段时,框架自动从同名目录下的instructions.md读取。
二、instructions.md 在文件化 Agent 布局中的位置
forecaster并不是孤立存在的,它是文件化示例weather-fs的一级子代理。完整目录布局如下(来自 weather-fs/README.md):
weather-fs/ config.ts # model + config overrides (uses agentConfig() for typing) instructions.md # the agent instructions memory.ts # default-exported Memory instance tools/ get_weather.ts # default-exported tool, keyed by filename skills/ units.md # flat skill severe-weather/ SKILL.md # packaged skill references/ thresholds.md workspace/ # seed files mirrored into the agent's workspace cities.json README.md subagents/ forecaster/ # a declared subagent, same layout as an agent config.ts # MUST set a description instructions.md tools/ get_forecast.ts subagents/ historian/ # a nested subagent (depth 2) config.ts instructions.md tools/ get_climate_normals.ts从布局可以看出子代理与顶层 Agent 的关系:
- 子代理的布局与顶层 Agent 完全一致——
config.ts、instructions.md、tools/*,可选skills/、workspace/、以及自己的subagents/; - 父代理通过“委托工具”调用子代理——
forecaster目录会被组装成一个独立的Agent,以目录名forecaster为键挂到父代理的agents映射中,成为模型可见的委派工具; - 子代理可以继续嵌套——
forecaster又声明了自己的子代理historian,形成weather-fs → forecaster → historian的委派链。
三、父代理如何把任务“交给” forecaster:指令与 description 的分工
3.1 父代理指令中的委派触发条件
顶层weather-fs的 instructions.md 明确写有委派策略:
When the user asks for a multi-day forecast (or "this week", "next few days"), delegate to the `forecaster` subagent instead of answering directly.也就是说,委派行为本身也是一条指令:父模型根据用户请求判断“是否多日预报”,命中则调用名为forecaster的工具。这正是示例设计的分层意图——单日天气由get_weather直接回答,多日预报下沉给预报专家。
3.2 description:父模型决策委派的唯一依据
与顶层 Agent 不同,子代理的config.ts必须提供非空的description。forecaster的配置如下(config.ts):
import { agentConfig } from '@mastra/core/agent'; export default agentConfig({ model: 'openai/gpt-5.4-mini', description: 'Produces a multi-day weather forecast for a city.', // instructions omitted -> taken from instructions.md // tools omitted -> taken from tools/*.ts });这条description是父模型在选择是否委派时唯一能看到的描述性文本(子代理内部的instructions.md不会暴露给父模型)。在 packages/core/src/agent/fs-routing/index.ts 的源码中,这条规则被强制校验(L587-L667):
const description = child.getDescription(); if (!description || description.trim() === '') { throw new MastraError({ id: 'AGENT_FS_ROUTING_SUBAGENT_DESCRIPTION_REQUIRED', ... text: `Agent "${name}": subagent "${childId}" requires a non-empty 'description'. Set one in agents/${name}/subagents/${childId}/config.ts.`, }); }所以实践上:description要写成“一句话能说清这个子代理擅长什么”的摘要,例如Produces a multi-day weather forecast for a city.;而instructions.md则写“拿到任务后具体怎么干”的完整规程。两者一个对外(父模型选人)、一个对内(子代理执行)。
四、指令引用的工具:get_forecast 的契约定义
forecaster/instructions.md要求调用get_forecast工具。该工具在 tools/get_forecast.ts 中用createTool定义,核心是输入/输出 Schema 契约:
import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; export default createTool({ id: 'get-forecast', description: 'Fetches a multi-day weather forecast for a given city', inputSchema: z.object({ city: z.string().describe('The city to forecast'), days: z.number().int().min(1).max(7).default(3).describe('Number of days'), }), outputSchema: z.object({ city: z.string(), days: z.array( z.object({ day: z.number(), conditions: z.string(), highCelsius: z.number(), lowCelsius: z.number(), }), ), }), execute: async ({ city, days }) => { // Stubbed response — swap in a real API for production use. const conditions = ['sunny', 'partly cloudy', 'rain', 'clear']; return { city, days: Array.from({ length: days }, (_, i) => ({ day: i + 1, conditions: conditions[i % conditions.length]!, highCelsius: 22 - i, lowCelsius: 14 - i, })), }; }, });几个与指令编写直接相关的细节:
- 输入 Schema 的约束即指令的“可执行范围”:
days限定为1..7的整数、默认3,这意味着指令中不必再写“预报几天”,模型只要传city即可,缺失参数由框架默认值兜底; - 输出 Schema 定义了“day by day”的数据形状:数组中的每一项含
day、conditions、highCelsius、lowCelsius,指令中要求“summarize the result day by day in a short list”正好与这个结构对应; execute目前是桩实现:代码注释明确写着Stubbed response — swap in a real API for production use.,实际生产环境替换为真实天气 API 即可,Schema 与指令无需改动。
文件化发现机制上,工具以文件名为键注册:get_forecast.ts→ 工具名get_forecast,与指令中call the get_forecast tool完全一致,指令里的工具名必须与文件名对齐。
五、嵌套子代理:historian 与委派深度上限
forecaster还拥有自己的子代理historian(目录subagents/forecaster/subagents/historian/),用于回答“某地某月通常什么天气”这类气候问题。它的 instructions.md 遵循同样的写作模式:
You are a climate history specialist. When asked how the weather usually is somewhere, call the `get_climate_normals` tool for the named city and month and report the typical high, low, and rainy days in one short sentence. If no month is named, use the current month.其配套工具 get_climate_normals.ts 接收city与month(1..12),输出avgHighCelsius、avgLowCelsius、rainyDays,同样为桩实现(用正弦函数模拟季节温差)。其config.ts也遵循“description 必填”规则:
export default agentConfig({ model: 'openai/gpt-5.4-mini', description: 'Looks up historical climate normals (typical temperatures and rainfall) for a city and month.', });关于嵌套深度,fs-routing/index.ts 中定义:
export const MAX_FS_SUBAGENT_DEPTH = 3;即:顶层 Agent 记为 depth 0,其子代理为 depth 1,依此类推;声明超过 3 层的子代理会被忽略并发出警告(ignoring its subagents — subagents may only nest 3 levels below a top-level agent)。该上限一方面防止委派树无限膨胀,另一方面可保护assembleAgentFromFsEntry免受循环目录对象的影响。weather-fs → forecaster → historian这条链正好处于 depth 2,是“合理嵌套”的典型示范。
六、组合起来:完整委派链路与运行验证
将上面所有文件组合,就得到一条完整的分层 Agent 链路:
| 用户请求示例 | 处理链路 | 依据 |
|---|---|---|
| “what's the weather in Tokyo?” | weather-fs直接调get_weather,同时报 °C 与 °F | 顶层指令 + skills/units.md 技能 |
| “give me a 5-day forecast for London” | weather-fs委派给forecaster,后者调get_forecast逐日汇报 | 顶层指令的委派条款 + forecaster/instructions.md |
| “how is the weather in Paris usually in April?” | forecaster再委派给historian,后者调get_climate_normals | historian/instructions.md |
在仓库根目录运行即可在 Studio 中看到weather-fs与代码定义的 Agent 并列出现:
pnpm --filter ./examples/agent mastra dev需要强调的是,weather-fs是纯文件定义的 Agent:没有new Agent()调用,也没有在src/mastra/index.ts中注册任何内容,mastra dev/mastra build会自动发现并注册它——这也正是instructions.md这类文件之所以重要的前提:在文件化体系里,目录与 Markdown 本身就是声明。
七、写给子代理指令的实践要点(基于本示例的提炼)
结合forecaster与historian两份指令及其源码佐证,可以总结出编写高质量子代理指令的四条经验:
- 先定角色,再给行为:以
You are a ... specialist.开头明确专业身份,随后用“When asked for X, do Y”的条件句式把触发场景与动作绑定,减少模型误判; - 工具名必须与文件名严格一致:指令中写的
get_forecast要能在 tools/get_forecast.ts 中找到对应文件,否则模型无法调用; - 把“怎么答”写进指令:包括输出格式(逐日短列表)、重点强调(
Call out any precipitation)和缺失参数时的兜底(ask which city),这些细节决定回答质量; - 对外靠 description、对内靠 instructions:在 config.ts 中用一句话描述子代理职责供父模型决策(缺失即构建报错),把完整执行规程放进
instructions.md,两者职责分离。
结语
forecaster/instructions.md虽只有四行,却是 Mastra 文件化子代理机制的浓缩样本:它展示了指令与工具契约(get_forecast的 Zod Schema)、指令与 description 的分工、以及子代理间的多层委派(受MAX_FS_SUBAGENT_DEPTH = 3约束)。以它为模板,你可以在agents/<name>/subagents/下用纯 Markdown 与目录结构快速搭建分工明确的 Agent 团队——这也是 Mastra 文件化开发范式最直观的入门路径。更多背景可参考 weather-fs 的 README。
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考