健康診断データをもとにAIが週間の食事スケジュールを生成するシステムに、レシピ機能を追加しました。
単に外部レシピAPIを呼ぶのではなく、MastraのSubagentとWorkflowを活用してLLMがレシピを生成する構成をとっています。
本記事では、そのアーキテクチャと実装の工夫を紹介します。
背景と課題
既存システムでは、健康診断データと個人プロフィールをもとにAIが週間の食事・運動スケジュールを自動生成していました。
健康診断データ + 個人プロフィール
↓
週間スケジュール自動生成
↓
献立メニュー一覧を表示
スケジュール上にメニュー名は表示されますが、ユーザーが実際に料理しようとしたとき、次の情報が不足していました。
- どんな食材が必要か
- 具体的な調理手順
- 食材のコストはどのくらいか
解決方針
スケジュール上のメニューをクリックすると、そのメニューの食材選定・分量・コスト・調理手順を提供するレシピ機能を実装しました。
SubagentとWorkflowとは
Subagentとは
特定の役割に特化した独立したAIエージェントです。1つの大きなエージェントにすべてを任せるのではなく、役割ごとに専門エージェントを分けることで、精度と責任範囲を明確にできます。
独立したエージェントとして設計されているため、呼び出し元を選びません。
Recipe Workflow → Chef Agent を呼び出す(レシピページ)
Health Check Agent → Chef Agent を呼び出す(チャット画面)
別の新規 Workflow → Nutritionist Agent を呼び出す(例:食事記録の栄養分析)
今後機能を拡張する際も、既存のSubagentをそのまま組み合わせるだけで対応できます。
Workflowとは
複数の処理ステップを順序立てて実行する仕組みです。前のステップの出力を次のステップの入力として渡すことで、複数のSubagentやツールを連携させた複雑な処理を構造的に管理できます。
Subagent設計:2つの専門エージェント
| エージェント | 役割 | 生成する情報 |
|---|---|---|
| Chef Agent | プロの料理人として食材・調理手順・コツを生成 | 食材一覧・分量・コスト・手順・YouTube検索キーワード |
| Nutritionist Agent | 管理栄養士として栄養バランスと健康アドバイスを生成 | カロリー・PFC・健康診断データに基づく個別アドバイス |
Subagentが呼ばれる2つの経路
① Health Check Agentから呼ぶ(チャット画面)
Health Check Agentの agents プロパティにSubagentを登録することで、チャット画面からも呼び出せます。
ただし、登録するだけでは呼ばれません。 LLMはあくまで instructions に書かれた内容をもとに判断するため、「どのような質問のときに呼ぶか」をinstructionsに明記する必要があります。
agents に登録 → 「このSubagentが使える状態にする」
instructions に記述 → 「この状況ではこのSubagentを呼びなさい」とLLMに指示する
export const healthAgent = new Agent({
tools: {
healthCheckTool,
healthTrendChartTool,
scheduleGeneratorTool,
healthContextTool,
},
// SubagentとしてChef / Nutritionistを登録
agents: { chefAgent, nutritionistAgent },
instructions: `
...
## レシピ・料理に関する質問への対応
ユーザーが料理・レシピについて質問した場合は chefAgent を呼び出す。
栄養・カロリーについて質問した場合は nutritionistAgent を呼び出す。
`,
});
これにより、チャット画面で「焼き魚の作り方を教えて」と聞くとHealth Check AgentがChef Agentを呼び出します。
② Recipe Workflowから呼ぶ(レシピページ)
スケジュール画面のメニュー名をクリックすると、Recipe Workflowがステップに沿ってSubagentを順番に呼び出します。
① Chef Agent
メニュー名 → 食材・手順・コスト・YouTube検索キーワード を生成
↓
② Health Context Tool
ユーザーの健康診断データをDBから取得
↓
③ Nutritionist Agent
食材 × 健康診断データ → 栄養情報・個別アドバイスを生成
↓
④ レシピページへ表示
import { createStep, createWorkflow } from '@mastra/core/workflows';
// Step 1: Chef Agentを呼ぶステップ
const chefStep = createStep({
id: 'chef-analysis',
inputSchema: z.object({ menuName: z.string() }),
outputSchema: z.object({
ingredients: z.array(z.string()),
steps: z.array(z.string()),
// ...
}),
execute: async ({ inputData, mastra }) => {
const agent = mastra?.getAgent('chefAgent'); // Subagentを取得して呼び出す
// ...
},
});
// Step 2: Nutritionist Agentを呼ぶステップ(Step 1の出力を自動で受け取る)
const nutritionistStep = createStep({
id: 'nutritionist-analysis',
execute: async ({ inputData, mastra }) => {
const agent = mastra?.getAgent('nutritionistAgent');
// ...
},
});
// .then() でチェーンして順番に実行
const recipeDetailWorkflow = createWorkflow({
id: 'recipe-detail-workflow',
inputSchema: z.object({ menuName: z.string() }),
})
.then(chefStep)
.then(nutritionistStep);
recipeDetailWorkflow.commit();
各ステップの inputSchema / outputSchema をZodで定義することで、ステップ間のデータの受け渡しが型安全になります。
レシピページの機能
メニュー名をクリックすると表示されるレシピページは、以下の情報を提供します。
- 栄養情報・合計カロリー
- 食材・量・コスト・調理手順
- 調理動画
- 食材のまとめ購入
4つの実装上の工夫
① 健康診断データとの連携
Nutritionist Agentは、ユーザーの健康診断結果を参照したうえでアドバイスを生成します。
アドバイス文中に登場する健康診断項目(LDL・中性脂肪・血糖値など)は太字でハイライトされ、どの検査値に関連する内容かが一目でわかります。
例:「中性脂肪値が基準値を超えているため、この料理の揚げ油の量を控えることをお勧めします。」
② 調理動画の検索キーワード工夫
課題:AIが生成した独創的なメニュー名(例:「地中海風フムス」)をそのまま検索しても、YouTubeでヒットしないケースがある。
対策:Chef AgentがYouTube検索用キーワード(searchKeyword)を同時に生成します。
| メニュー名 | searchKeyword |
|---|---|
| 地中海風フムス | ひよこ豆 フムス 作り方 |
| 鶏むね肉のハーブソテー | 鶏むね肉 ソテー 作り方 |
| 切り干し大根の煮物 | 切り干し大根 煮物 だし 作り方 |
フォーマット:主食材(1〜2つ)+ 調理法 +「作り方」
動画取得方法の選定にあたり、いくつかのサービスを比較しました。
| サービス | 公開API | 費用 | 採否と理由 |
|---|---|---|---|
| クラシル | なし | — | 公開APIなし、取得不可 |
| 楽天レシピ | あり | 無料 | 料理名での直接検索が不可 |
| Spoonacular | あり | 有料 | 英語のみ対応 |
| YouTube Data API | あり | 無料枠あり | APIキーの取得が必要 |
| YouTube 検索リンク | 不要 | 完全無料 | ✅ 採用 |
APIキー不要・完全無料のYouTube検索リンクを採用しました。
③ LLMコスト削減:ブラウザキャッシュ
同じメニュー名のレシピは毎回同じ内容が生成されます。初回生成結果を localStorage にキャッシュし、2回目以降はLLMを呼ばずに即時表示します。
const CACHE_KEY = `recipe_cache_${menuName}`;
// キャッシュがあれば即表示(LLM呼び出しなし)
const cached = localStorage.getItem(CACHE_KEY);
if (cached) {
setRecipe(JSON.parse(cached));
return;
}
// キャッシュなし → LLM呼び出し → 結果を保存
const response = await fetch('/api/recipe-detail', { ... });
const json = await response.json();
localStorage.setItem(CACHE_KEY, JSON.stringify(json.data));
| 初回 | 2回目以降 | |
|---|---|---|
| 表示までの時間 | 約15〜30秒 | 0秒(即時表示) |
| LLM APIコスト | 発生 | 発生しない |
④ 食材のまとめ購入(楽天市場)
食材リストのチェックボックスで選択した食材を楽天市場で検索し、購入ページを開きます。
購入APIの選定:
| サービス | 公開API | 費用 | 採否と理由 |
|---|---|---|---|
| Amazon PA-API | あり | 無料 | アソシエイト承認・売上実績が必要 |
| Amazon Fresh | なし | — | 公開APIなし |
| イトーヨーカドー/イオン | なし | — | 公開APIなし |
| 楽天市場 | あり | 無料 | ✅ 採用:無料・即日利用可能 |
商品の絞り込みには以下のパラメータを使用しています。
| パラメータ | 値 | 目的 |
|---|---|---|
genreId |
100227(食品) |
食品以外を除外 |
maxPrice |
食材コスト + ¥1,000 | 高額商品を除外 |
hits |
3 | 標準順トップ3を取得 |
まとめ
MastraのWorkflowとSubagentを活用し、健康診断データと連携したレシピ機能を実装しました。
| ポイント | 工夫内容 |
|---|---|
| Subagentの活用 | Chef AgentとNutritionist Agentを専門エージェントとして分離。役割ごとに精度を高め、他のWorkflowやHealth Check Agentからも再利用可能 |
| Workflowによる順次実行 |
createWorkflow().then().then() でステップを定義し、Chef → Health Context → Nutritionistの順に実行。前ステップの出力が次ステップへ自動で渡される |
| 健康診断データとの連携 | Nutritionist Agentがユーザーの検査値を参照してアドバイスを生成。アドバイス内の健康診断項目名を太字ハイライトで強調 |
| 動画リンクの検索キーワード工夫 | AIが生成した独創的なメニュー名をそのまま検索してもYouTubeでヒットしないため、Chef Agentが「主食材+調理法+作り方」形式の searchKeyword を同時生成 |
| localStorageによるLLMコスト削減 | 同じレシピは内容が変わらないため、初回生成結果をブラウザにキャッシュ。2回目以降はLLMを呼ばずに即時表示 |
| 楽天市場でのまとめ購入 | 食品カテゴリ絞り込み(genreId: 100227)+食材コスト基準の maxPrice フィルタで精度を確保 |
最後まで読んでいただきありがとうございました!
MastraのSubagentとWorkflowは、複雑な処理を役割ごとに整理しながら組み立てられるので、思ったより気持ちよく書けました。同じようなことを試してみたい方の参考になれば嬉しいです。フィードバックや質問があればコメントお待ちしています!

