01 基本エージェントの作成
概要
- LLMのみで応答する最小構成のMastraエージェントを定義する。
- 目的は、以降の拡張(ツール/メモリ等)を差し込める土台を整えること。
実装場所
-
src/agents/basicAgent.ts: Agent定義とLLM設定を記述するAgent層。 -
src/index.ts: ランタイムを起動し、CLIやテストから呼べるようにするアプリ層。
実装内容
// src/agents/basicAgent.ts
import { Agent } from '@mastra/core';
import { OpenAIChatModel } from '@mastra/llms-openai';
export const basicAgent = new Agent({
name: 'basic-support',
instructions: `あなたはプロダクトサポート担当です。ユーザーの質問には正確かつ簡潔に回答してください。`,
llm: new OpenAIChatModel({
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-4o-mini',
temperature: 0.2,
}),
});
// src/index.ts
import { AgentRuntime } from '@mastra/runtime';
import { basicAgent } from './agents/basicAgent';
const runtime = new AgentRuntime({ agents: [basicAgent] });
export async function askBasicAgent(prompt: string) {
const result = await runtime.run('basic-support', {
messages: [{ role: 'user', content: prompt }],
});
return result.output;
}
if (require.main === module) {
askBasicAgent(process.argv.slice(2).join(' ') || 'Hello').then(console.log);
}
-
basicAgentはLLMのみを保持し、まだツールやメモリ機構を持たない。 -
AgentRuntimeは今後も使い回せるエントリーポイントなので、この段階で用意しておく。
ポイント解説
- MastraのAgentはLLMクライアントとシステムプロンプト(instructions)を基礎構成とする。
- ここではAPIキーを環境変数から読み、テスト時はMock LLMに差し替えられるようにファクトリを1箇所に集約している。
- まだ状態を持たないため、各リクエストはステートレスに完結する。
次の段階へのつながり
- LLM呼び出しの流れが固まったので、次はAgentにToolを登録して外部機能を呼び出せるようにする。
- 以降の拡張は
basicAgent定義を順に拡張していく形で積み重ねる。
02 ツールの追加
概要
- Agentに外部ツールを登録し、LLMが必要に応じてデータを取得できるようにする。
- ユーザーの質問に対してAPI経由の最新情報(例: 天気)を返せるようになる。
実装場所
-
src/tools/weatherTool.ts: Toolクラスを定義するTool層。 -
src/agents/basicAgent.ts: Agentにツールを登録する。 -
src/index.ts: ランタイムに渡すAgentは変わらないが、利用例をツール想定に更新する。
実装内容
// src/tools/weatherTool.ts
import { Tool, ToolExecutionContext } from '@mastra/core';
import fetch from 'node-fetch';
export class WeatherTool extends Tool {
name = 'weatherLookup';
description = '指定都市の現在の天気を返す';
async run(input: { city: string }, _ctx: ToolExecutionContext) {
const response = await fetch(
`https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${encodeURIComponent(input.city)}`
);
const data = await response.json();
return {
condition: data.current.condition.text,
temperatureC: data.current.temp_c,
};
}
}
// src/agents/basicAgent.ts (追記)
import { WeatherTool } from '../tools/weatherTool';
export const basicAgent = new Agent({
name: 'basic-support',
instructions: `...`,
llm: new OpenAIChatModel({ /* 省略 */ }),
tools: [new WeatherTool()], // ← LLMがfunction callで呼び出せるようになる
});
// src/index.ts (利用例の更新)
const runtime = new AgentRuntime({ agents: [basicAgent] });
runtime.run('basic-support', {
messages: [
{ role: 'user', content: '東京の現在の天気を教えて' },
],
});
- Toolは
nameとdescriptionでLLMに用途を説明し、runで実際の処理を行う。 - Agent構成に
tools配列を渡すだけで、Mastraが自動でfunction callプロトコルを扱う。
ポイント解説
- ツールのI/O型はLLMのfunction schemaとして利用されるため、曖昧さを避けるために
cityフィールドなどを明確にする。 - ネットワーク呼び出しはLLMトークン消費と独立しており、失敗時のリトライやタイムアウトをTool側で制御できる。
- 複数ツールを登録するときは
tools: [new WeatherTool(), new AnotherTool()]のように並べればよい。
次の段階へのつながり
- ツール呼び出しによって応答がリッチになったが、会話の文脈はまだ保持していない。
- 次はAgentMemoryを導入し、ユーザーとの継続的な会話を扱えるようにする。
03 記憶(Memory)の導入
概要
- 過去の対話履歴を保持する
AgentMemoryを追加し、会話の連続性を確保する。 - 目的は、ユーザーの要望や前提を覚えてフォローアップ質問に自然に答えること。
実装場所
-
src/memory/supportMemory.ts: Agent専用のメモリストアを定義するMemory層。 -
src/agents/basicAgent.ts: Agent設定にmemoryを紐付ける。 -
src/index.ts: ランタイム呼び出し時に会話IDを指定し、同じ会話を続けられるようにする。
実装内容
// src/memory/supportMemory.ts
import { AgentMemory } from '@mastra/memory';
export const supportMemory = new AgentMemory({
namespace: 'support-thread',
window: 12, // 直近12ターンを保持(後段で要約を差し込めるようwindow型に)
storage: {
provider: 'redis',
url: process.env.REDIS_URL!,
},
});
// src/agents/basicAgent.ts (メモリを紐付け)
import { supportMemory } from '../memory/supportMemory';
export const basicAgent = new Agent({
name: 'basic-support',
instructions: `...`,
llm: new OpenAIChatModel({ /* 省略 */ }),
tools: [new WeatherTool()],
memory: supportMemory, // ← AgentMemoryをセット
});
// src/index.ts (会話IDを指定)
export async function askBasicAgent(prompt: string, threadId = 'demo-thread') {
const result = await runtime.run('basic-support', {
threadId, // 同じthreadIdを使えばメモリが共有される
messages: [{ role: 'user', content: prompt }],
});
return result.output;
}
-
AgentMemoryはストレージ層を抽象化し、ローカルでもクラウドでも同じAPIで使える。 -
threadIdがメモリのキーになるため、マルチユーザー環境ではユーザーIDと紐付けて生成する。
ポイント解説
- window型メモリはトークン消費を一定に保ちつつ、後段で要約フローを噛ませやすい。
- MastraのメモリはLLMへの入力テンプレートに自動で挿入されるため、呼び出し側は特別な処理を追加する必要がない。
- Redisなどの外部ストアを使う場合は、接続ライフサイクルをアプリ起動時に1回だけ作ると効率的。
次の段階へのつながり
- 会話文脈を保持できたので、次は外部知識を参照するRAGを導入し、メモリ+知識ベースの両立を図る。
04 RAG連携の追加
概要
- ベクトル検索を行う
vectorQueryToolを組み込み、社内ドキュメントやFAQから根拠ある回答を生成する。 - 目的は、LLM単体では保持していない最新・正確なナレッジを参照可能にすること。
実装場所
-
src/vector/store.ts: ベクトルストアと埋め込みモデルの初期化。 -
src/tools/vectorQueryTool.ts: vectorQueryToolの定義。 -
src/agents/basicAgent.ts: AgentにRAGツールを追加。
実装内容
// src/vector/store.ts
import { VectorStore } from '@mastra/vector';
import { OpenAIEmbeddings } from '@mastra/embeddings-openai';
export const supportStore = new VectorStore({
name: 'support-knowledge-base',
embeddings: new OpenAIEmbeddings({
apiKey: process.env.OPENAI_API_KEY!,
model: 'text-embedding-3-small',
}),
});
export async function upsertSupportDocs(documents: Array<{ id: string; text: string }>) {
await supportStore.upsertMany(
documents.map((doc) => ({ id: doc.id, values: doc.text, metadata: doc }))
);
}
// src/tools/vectorQueryTool.ts
import { Tool } from '@mastra/core';
import { supportStore } from '../vector/store';
export class SupportRagTool extends Tool {
name = 'supportDocsLookup';
description = '社内ナレッジベースから関連情報を検索して要約する';
async run(input: { query: string }) {
const matches = await supportStore.similaritySearch(input.query, { k: 4 });
return matches
.map((match) => `#${match.metadata.id}\n${match.metadata.text}`)
.join('\n---\n');
}
}
// src/agents/basicAgent.ts (RAGツールを追加)
import { SupportRagTool } from '../tools/vectorQueryTool';
export const basicAgent = new Agent({
name: 'basic-support',
instructions: `...`,
llm: new OpenAIChatModel({ /* 省略 */ }),
tools: [
new WeatherTool(),
new SupportRagTool(), // ← 質問がドキュメント参照を必要とするときに使用
],
memory: supportMemory,
});
- RAGツールは検索結果をテキスト化してLLMに渡すだけでよい。引用形式や区切り文字を揃えておくとLLMが扱いやすい。
-
upsertSupportDocsはデプロイ前にバッチで呼び出すか、CIで同期する。
ポイント解説
- ベクトルストアはLLMとは別のリソースなので、更新頻度が高い場合は差分アップサート用のタスクを用意する。
-
Toolとして包むと、LLMが「社内情報が必要」と判断した時だけ呼び出されるため、毎回すべてのドキュメントを入力する必要がない。 - マッチ結果には
metadataを含められるので、回答中で参照IDを提示したい場合はLLMプロンプト内で利用する。
次の段階へのつながり
- メモリ+RAGの両立により情報量が増えるため、会話履歴を適宜要約する仕組みが必要になる。
- 次は要約ワークフローを導入し、メモリウィンドウを保ちながら長時間の会話を維持する。
05 要約ワークフローの追加
概要
- 長い会話履歴を圧縮する要約ワークフローを追加し、メモリ消費とトークンコストを抑える。
- 目的は、windowサイズを超える履歴が発生した際に自動要約を実行し、重要情報だけを残すこと。
実装場所
-
src/workflows/summarizeConversation.ts: メモリ圧縮用ワークフローを定義するWorkflow層。 -
src/memory/supportMemory.ts: 要約結果を格納するフィールドを追加。 -
src/index.ts: AgentRuntimeにワークフローを登録し、イベントをフックする。
実装内容
// src/workflows/summarizeConversation.ts
import { defineWorkflow } from '@mastra/workflows';
import { OpenAIChatModel } from '@mastra/llms-openai';
export const summarizeConversation = defineWorkflow({
id: 'conversation-summary',
trigger: 'memory.window.exceeded', // AgentMemoryのwindowを超えたタイミング
run: async ({ threadId, memory, logger }) => {
const transcript = await memory.exportThread(threadId);
const llm = new OpenAIChatModel({
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-4o-mini',
temperature: 0.1,
});
const summary = await llm.invoke([
{ role: 'system', content: '次の会話をサマリし、重要なTODO/決定のみ残してください。' },
{ role: 'user', content: transcript.join('\n') },
]);
await memory.saveSummary(threadId, summary.output);
logger.info('conversation summary stored', { threadId });
},
});
// src/memory/supportMemory.ts (サマリ格納メソッドを利用)
export const supportMemory = new AgentMemory({
namespace: 'support-thread',
window: 12,
storage: { provider: 'redis', url: process.env.REDIS_URL! },
summaryField: 'compressedSummary', // summarizeConversationで書き込むキー
});
// src/index.ts (ワークフロー登録)
import { summarizeConversation } from './workflows/summarizeConversation';
const runtime = new AgentRuntime({
agents: [basicAgent],
workflows: [summarizeConversation], // ← メモリイベントを監視
});
-
trigger: 'memory.window.exceeded'はAgentMemoryのバッファが満杯になった瞬間に発火するカスタムイベント。 -
memory.saveSummaryで保存した要約は次回のmemory.exportThreadに自動で含まれるため、LLMに与える前処理を挟む必要がない。
ポイント解説
- 要約に専用のLLMインスタンスを使うと、メインLLMをブロックせずに済む。また低コストモデルに切り替えやすい。
- ワークフローは独立したコンテキストで動作するため、ログを分けておくと監視しやすい。
- 要約ポリシーはユースケース次第で変更でき、TODO抽出や数値ダイジェストなど複数パターンを用意可能。
次の段階へのつながり
- 会話の凝縮ができたことで、Agentは長期利用に耐えられる。次は応答後に自己評価を行うReflection機構を追加し、品質を高める。
06 反省(Reflection)機構の追加
概要
- Agentが生成した応答を自己評価し、改善が必要な場合は再生成またはメモリに学習事項を残すReflectionを導入する。
- 目的は、ツールやRAGを利用した複雑な回答の品質を安定させること。
実装場所
-
src/workflows/reflectionWorkflow.ts: 応答後に走るReflectionワークフロー。 -
src/agents/basicAgent.ts: 反省結果を参照できるよう、Agent instructionsにフィードバックサマリを追加。 -
src/index.ts: RuntimeにReflectionワークフローを登録し、応答サイクルに組み込む。
実装内容
// src/workflows/reflectionWorkflow.ts
import { defineWorkflow } from '@mastra/workflows';
import { OpenAIChatModel } from '@mastra/llms-openai';
import { supportMemory } from '../memory/supportMemory';
export const reflectOnResponse = defineWorkflow({
id: 'response-reflection',
trigger: 'agent.response.completed',
run: async ({ threadId, response }) => {
const critic = new OpenAIChatModel({
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-4o-mini',
temperature: 0,
});
const critique = await critic.invoke([
{ role: 'system', content: '以下の回答の正確性・根拠・トーンを100文字以内で評価し、改善案があれば指摘してください。' },
{ role: 'user', content: response.output },
]);
await supportMemory.appendNote(threadId, {
type: 'reflection',
content: critique.output,
timestamp: new Date().toISOString(),
});
if (critique.output.includes('再回答')) {
return { action: 'retry' }; // Mastraが同じ入力で再実行する
}
},
});
// src/agents/basicAgent.ts (instructionsへ反映)
import { supportMemory } from '../memory/supportMemory';
export const basicAgent = new Agent({
name: 'basic-support',
instructions: `...\n常に最新のreflection noteを考慮して回答を改善してください。`,
llm: new OpenAIChatModel({ /* 省略 */ }),
tools: [new WeatherTool(), new SupportRagTool()],
memory: supportMemory,
});
// src/index.ts
import { reflectOnResponse } from './workflows/reflectionWorkflow';
import { summarizeConversation } from './workflows/summarizeConversation';
const runtime = new AgentRuntime({
agents: [basicAgent],
workflows: [summarizeConversation, reflectOnResponse],
});
- Reflectionワークフローは
responseペイロードを受け取り、LLMで簡易評価してからメモリに保存する。 -
return { action: 'retry' }を返すと、Mastraは同じツール呼び出しを保持したまま再生成を行うため、自己修正ループを組める。
ポイント解説
- 評価LLMは低温度で決定的なフィードバックを返す設定にする。
- 反省結果をメモリ内で
type: 'reflection'として分離すると、Agentがinstructionsで取り出しやすい。 - リトライ条件は任意のルール(信頼度スコア、禁則語検出など)に置き換えられる。
次の段階へのつながり
- Reflectionまで揃ったので、最後に全層(Agent/Tool/Memory/RAG/Workflow)の構成図とファイル一覧をまとめ、運用の全体像を共有する。
07 最終構成まとめ
概要
- これまで追加したLLM・ツール・メモリ・RAG・要約・反省を統合した完成構成を整理する。
- 目的は、運用チームが全体像を把握し、追加開発やデバッグを容易にすること。
実装場所
- Agent層:
src/agents/basicAgent.ts - Tool層:
src/tools/weatherTool.ts,src/tools/vectorQueryTool.ts - Memory層:
src/memory/supportMemory.ts - Workflow層:
src/workflows/summarizeConversation.ts,src/workflows/reflectionWorkflow.ts - エントリーポイント:
src/index.ts
実装内容
// src/index.ts(完成形)
import { AgentRuntime } from '@mastra/runtime';
import { basicAgent } from './agents/basicAgent';
import { summarizeConversation } from './workflows/summarizeConversation';
import { reflectOnResponse } from './workflows/reflectionWorkflow';
export const runtime = new AgentRuntime({
agents: [basicAgent],
workflows: [summarizeConversation, reflectOnResponse],
});
export async function askSupportAgent({ prompt, threadId }: { prompt: string; threadId: string }) {
const result = await runtime.run('basic-support', {
threadId,
messages: [{ role: 'user', content: prompt }],
});
return result.output;
}
// src/agents/basicAgent.ts(機能統合済み)
import { Agent } from '@mastra/core';
import { OpenAIChatModel } from '@mastra/llms-openai';
import { WeatherTool } from '../tools/weatherTool';
import { SupportRagTool } from '../tools/vectorQueryTool';
import { supportMemory } from '../memory/supportMemory';
export const basicAgent = new Agent({
name: 'basic-support',
instructions: `プロダクトサポート担当として正確さ・根拠・トーンを重視してください。\n反省ノートと要約を常に参照し、必要ならツールを使って確認しましょう。`,
llm: new OpenAIChatModel({
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-4o-mini',
temperature: 0.2,
}),
tools: [new WeatherTool(), new SupportRagTool()],
memory: supportMemory,
});
src/
├─ agents
│ └─ basicAgent.ts
├─ tools
│ ├─ weatherTool.ts
│ └─ vectorQueryTool.ts
├─ memory
│ └─ supportMemory.ts
├─ vector
│ └─ store.ts
├─ workflows
│ ├─ summarizeConversation.ts
│ └─ reflectionWorkflow.ts
└─ index.ts
- Agentが中心となり、Tool/Memory/Workflowが依存逆転で差し込まれている。
- 各層は単体でテストできる構成になっているため、LLMをMockしても動作確認が容易。
ポイント解説
- AgentはLLM以外をすべてDI(依存性注入)で受け取るため、テスト・環境差分・モデル切り替えに強い。
- Memory→Workflow→Agentのデータフローを明確にしておくと、トークン使用量や遅延のボトルネックを追跡しやすい。
- ツールはLLMのfunction schemaに直結するため、I/O型をzod等で検証すると安全性がさらに向上する。
次の段階へのつながり
- この完成形を基点に、権限管理(RBAC)、モニタリング、A/Bテストなど運用レベルの機能を追加できる。
- 複数エージェント連携や自動オーケストレーションを検討する場合も、本構成をテンプレートとして流用可能。