Mastraを使って実務でエージェントを作成するとき、toolsとサブエージェントの使い分けに改めて疑問を持ったので備忘録です。
-
ToolとSub-agentは何が違うか - 「オーケストレーター -> サブエージェント -> Toolでデータ取得」だけで十分では?
- なのに、なぜ
Toolの中でさらにAgentを呼ぶ設計があるのか?
などです。
先に結論
まず、次の3行で判断できます。
- 「説明文」を返したいなら
Sub-agent。
例: 「この市場の売上変化を考察して」 - 「固定JSON」を返したいなら
Tool。
例: 「この条件でExcel出力したい」 - 「自然文を解釈する推論」は必要だが「返却は固定JSON」にしたいなら
Toolの中でAgentを呼ぶ。
例: 自然文の抽出依頼を、{ market, categoryId, fromYm, toYm }に変換して返す
もう少し短く言うと次のようにもいえます。
-
Sub-agent: 考える役(自然文に強い) -
Tool: 約束を守る役(型保証に強い)
前提
Mastraでは、親が複数の専門エージェントに委譲する設計は Supervisor Agents として説明されています。
この前提を押さえると、次の役割分担が見やすくなります。
-
Supervisor Agent: どこに委譲するかを決める司令塔 -
Sub-agent: 専門タスクを処理する担当 -
Tool: 外部実行 + 入出力整形を担う実行境界
つまり、親エージェント視点では、toolsもagentsも「呼び出し先」になります。
責務について
-
agentsに登録されたSub-agentへ委譲できる -
toolsに登録されたToolを実行できる
見た目は似ていますが、設計責務は違います。
-
Sub-agent: 推論結果を作る -
Tool: 呼び出し契約を保証する
つまり、Sub-agentだといつも異なる不定形の自然文が帰ってきますが、
Toolは決JSONなどのまった型で返してくれます。
この分離ができると、プロンプト依存の不安定性を局所化できます。
Tool と Sub-agent の実務的な使い分け
Tool を選ぶ場面
私はこのような用途で使い分けています。
- API / DB / 検索基盤 / MCP など、外部システムに問い合わせてデータ取得したい
- 通知送信、チケット作成、ワークフロー起動など、外部アクションを実行したい
- LLMに外部機能を安全に呼ばせるため、入力制約(
inputSchema)を明示したい - 返却値を後段(フロントや別処理)が確実に使えるよう、出力形式(
outputSchema)を固定したい
Sub-agent を選ぶ場面
- 分析・比較・考察など、自然文での説明や解釈が主目的
- 単発処理ではなく、文脈や履歴を踏まえた判断が必要
- 「何を先に調べるか」「どの順に進めるか」など、手順の計画が必要
- 途中で複数Toolを使い分けながら探索的に進める必要がある
- 返却形式の厳密さより、推論品質や説明品質を優先したい
Tool 内部で Agent を呼ぶユースケース
ここまでで「Toolは実行境界」「Sub-agentは推論担当」という役割を整理しました。
ここからは、本記事の中心テーマである「自然文の依頼を抽出条件へ変換してフロントで使う」ケースに絞って説明します。
なぜ Tool の中で Agent を呼ぶのか
具体的には、ユーザーの自然文リクエストを、フロントがそのまま扱える抽出パラメータに変換したい場面です。
下記のプロダクトは、自然言語で抽出条件を入力しダウンロードできるエージェントサービスです。
Excelダウンロード自体はエージェントではなくAPIで行っていて、AIにリクエストパラメータを作ってもらう必要があります。
しかし、ユーザーに見せる自然文は表示、実際のリクエストパラメータは非表示にする必要があるため、
フロントの動作として、必ず決まった方で返してほしいです。
{
"message": "商品検索ダウンロード条件として処理しますね。少々お待ちください。楽天市場の「化粧水・ローション」カテゴリでの商品Excel抽出条件を作成しました!",
"params": [
{
"market": "market_a",
"categoryId": "12345",
"fromYm": "2026-05",
"toYm": "2026-05",
"minPrice": "1000"
}
]
}
この場面で Sub-agent だけに任せると出力形式が揺れるリスクが残るため、入口を Tool にして inputSchema で入力を制約し、内部で Parser Agent に自然文理解をさせ、最後に outputSchema で検証して返す構成にします。
これにより、言い換え理解や補完といった推論力は活かしつつ、呼び出し元には安定した契約を返せるため、フロントが自然文パースを持つ必要がなくなります。
Tool が返すレスポンス例(型を固定する)
たとえば、build_export_params のようなToolなら、成功時も失敗時も同じ形で返します。
{
"message": "商品検索ダウンロード条件として処理しますね。少々お待ちください。楽天市場の「化粧水・ローション」カテゴリでの商品Excel抽出条件を作成しました!",
"params": [
{
"market": "market_a",
"categoryId": "12345",
"fromYm": "2026-05",
"toYm": "2026-05",
"minPrice": "1000"
}
]
}
{
"message": "条件を特定できませんでした。市場・カテゴリ・期間を具体化してください。",
"params": []
}
このように返却型を固定すると、フロント側は params.length の判定だけで次の処理へ進めるため、実装とテストがシンプルになります。
この設計はMastraの思想と仕組みに沿っている
Mastraの設計は、役割分離して組み合わせる考え方と相性が良いです。
Supervisor Agents は「どこに委譲するか」の判断を担い、Tool は外部実行や入出力契約の境界を担います。
そのうえで、公式の Calling Agents 例が示すように、Toolの execute からランタイム経由で別Agentを呼ぶ構成も可能で、今回はこれを利用しています。
つまり「推論はAgent、契約保証はTool」という分担を1つの処理チェーンに落とし込めるのがポイントです。
実装面でも、structuredOutput を使ってAgentの返却をスキーマに寄せ、最終的にToolで検証して返す流れにすると、Supervisor配下の複数経路でもインターフェースを揃えやすくなります。
匿名化した最小構成サンプル
以下は「司令塔 + 分析Sub-agent + 条件抽出Tool(内部でParser Agentを呼ぶ)」の最小例です。
条件抽出Tool(内部でAgent呼び出し)
import { createTool } from '@mastra/core/tools';
import { z } from 'zod';
const exportConditionSchema = z.object({
market: z.enum(['market_a', 'market_b', 'market_c']),
categoryId: z.string().min(1),
fromYm: z.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
toYm: z.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/),
minPrice: z.string().optional(),
maxPrice: z.string().optional(),
});
const buildExportParamsOutputSchema = z.object({
message: z.string().min(1),
params: z.array(exportConditionSchema),
});
export const buildExportParamsTool = createTool({
id: 'build_export_params',
description:
'自然文の抽出依頼を、フロントが利用できるエクスポート条件へ変換する。',
inputSchema: z.object({
prompt: z.string().trim().min(1),
}),
outputSchema: buildExportParamsOutputSchema,
execute: async ({ context, mastra }) => {
const parserAgent = mastra.getAgent('condition-parser-agent');
if (!parserAgent) {
return {
message: '条件変換エージェントが見つかりませんでした。',
params: [],
};
}
const result = await parserAgent.generate(context.prompt, {
structuredOutput: {
schema: buildExportParamsOutputSchema,
// 必要なら jsonPromptInjection などを追加
},
});
const parsed = buildExportParamsOutputSchema.safeParse(result.object);
if (!parsed.success) {
return {
message: '条件を特定できませんでした。市場・カテゴリ・期間を具体化してください。',
params: [],
};
}
return parsed.data;
},
});
司令塔(Supervisor)から使い分け
import { Agent } from '@mastra/core/agent';
export const coordinatorAgent = new Agent({
name: 'coordinator-agent',
instructions: `
あなたは司令塔です。
- 分析や考察が目的なら analysis-agent に委譲
- 条件作成やエクスポート準備が目的なら build_export_params を実行
`,
tools: {
buildExportParams: buildExportParamsTool,
},
agents: {
analysisAgent,
},
model: yourModel,
});
analysisAgent 側には、委譲条件がわかる description を設定しておく前提です。
この構成にすると、
- 「説明して」系は
analysisAgent - 「抽出条件を作って」系は
buildExportParamsTool
と役割が衝突しにくくなります。
今回のExcel出力ユースケースに改めて当てはめる
ユーザー要件「自然文から条件抽出し、フロントでExcel出力」に対しては、次の分業が実用的です。
- 司令塔が意図を判定
分析か条件作成かを判断 - 条件作成なら
Toolを呼ぶ
Toolが内部Parser Agentを使って推論 -
Toolが構造化結果を返す
失敗時も同一スキーマで返す - フロントは
paramsをそのままエクスポート処理へ渡す
ここで大事なのは、フロントが自然文に依存しないように、AIが返す形式を固定化させることです。
これにより、UIの実装が単純になり、バグも減らせます。
まとめ
Sub-agent と Tool はどちらが上位という関係ではなく、責務が違います。
-
Sub-agent: 推論と説明 -
Tool: 契約と実行境界
そして、両方の長所を使うのが Tool 内部で Agent を呼ぶ設計です。
特に「自然文を構造化してフロントで再利用する」要件で効果が出ます。
参考リンク(Mastra公式)
- Agents Overview: https://mastra.ai/docs/agents/overview
- Supervisor Agents: https://mastra.ai/docs/agents/supervisor-agents
- Agent Networks(Deprecated): https://mastra.ai/docs/agents/networks
- Using Tools: https://mastra.ai/docs/agents/tools
- Structured Output: https://mastra.ai/docs/agents/structured-output
