mastra_basics: Mastra で学ぶ TypeScript AI エージェント開発¶
一言の本質: Mastra は「エージェント(LLM が流れを決める)とワークフロー(コードが流れを決める)を、同じ登録簿(Mastra インスタンス)に載る対等な部品として提供する TS フレームワーク」。
LangChain + LangGraph が2つのライブラリで担う役割を、1つの @mastra/core が最初から統合設計で持っている — この「全部入りだが薄い」感覚を10本の写経で掴む。
書籍『MastraによるAIエージェント開発・運用[実践入門]』(技術評論社、2026-07)の1〜6章・9〜11章の核概念に対応する(対応表は README.md)。
全体像¶
Mastra の部品は「Mastra インスタンス=登録簿」を中心に組み上がる。
flowchart TD
MastraCore[("Mastra インスタンス<br>アプリ全体の登録簿<br>ex03")]
subgraph AgentSide["Agent — LLM が流れを決める"]
AgentNode["Agent<br>instructions + model<br>ex01"]
ToolNode["tools: createTool<br>zod が仕様書<br>ex02"]
MemNode["memory: Memory<br>履歴・付箋・想起<br>ex05"]
SubAgents["agents: 部下エージェント<br>委譲ツール化<br>ex06"]
ProcNode["processors: 入出力の関所<br>tripwire<br>ex09"]
end
subgraph WfSide["Workflow — コードが流れを決める"]
StepNode["createStep<br>zod で配線を型付け"]
ChainNode["then / parallel / branch<br>ex03"]
SuspendNode["suspend / resume<br>HITL ex07"]
end
subgraph DataSide["データと評価"]
RagNode["MDocument + LibSQLVector<br>RAG ex08"]
EvalNode["createScorer + runEvals<br>ex10"]
end
MastraCore --> AgentNode
MastraCore --> ChainNode
AgentNode --> ToolNode
AgentNode --> MemNode
AgentNode --> SubAgents
AgentNode --> ProcNode
StepNode --> ChainNode
ChainNode --> SuspendNode
RagNode -.->|検索ツールとして| ToolNode
EvalNode -.->|golden dataset で採点| AgentNode
読む順もこの図の通り: Agent 側(ex01→02→04→05→06)と Workflow 側(ex03→07)を往復し、最後にデータ(ex08)・防御(ex09)・評価(ex10)で外堀を埋める。
使用ライブラリ・原理¶
Mastra とは何か(LangChain / LangGraph との対比)¶
| 観点 | LangChain + LangGraph (Python) | Mastra (TypeScript) |
|---|---|---|
| エージェント | create_agent() が実行ループを構築 |
new Agent({...}) クラスを直接 new |
| 決定論フロー | LangGraph の StateGraph(ノード+エッジのグラフ) |
createWorkflow().then().parallel().commit()(メソッドチェイン) |
| モデル指定 | プロバイダ SDK を import して注入 | 'openai/gpt-4o-mini' 文字列1つ(モデルルーティング。AI SDK 由来) |
| 状態永続化 | checkpointer を差す | Mastra / Memory に storage(LibSQL 等)を差す |
| ガードレール | Middleware(@before_model 等)を自分で書く |
processors に検知器クラスを並べる |
| 評価 | 別ライブラリ(Ragas 等)か素手 | @mastra/core/evals が同梱 |
ポイントは「グラフを描かずにメソッドチェインで済ませる」設計。LangGraph でノード・エッジ・State 型を書いていた作業が、Mastra では zod スキーマ付きステップの .then() 連結になる。表現力はグラフに劣る場面もあるが、TS の型推論が「前段の出力=次段の入力」を静的に検査してくれる(ex03 で inputSchema をわざと変えると tsc が落ちるのを試すとよい)。
モデルルーティング(ex01)¶
model: 'openai/gpt-4o-mini' と書くだけで、env の OPENAI_API_KEY を見て OpenAI に解決される。プロバイダ SDK の import がゼロ。Anthropic に切り替えるなら文字列を 'anthropic/claude-...' に書き換えるだけ(book 4章の「AI SDK によるマルチ LLM 対応」の土台がこれ)。local_models/ レクチャーでやった「モデルは base_url 1つで差し替え可能な部品」の TS 版と考えると早い。
エージェントとワークフローの使い分け(ex01 vs ex03)¶
判定基準は1文: 「実行経路を毎回 LLM に決めさせたいか(Agent)、コードで固定したいか(Workflow)」。
書籍6章の Deep Research が好例で、「検索→評価→レポート」という大枠は Workflow で固定し、各ステップの中身(何を検索するか)は Agent に任せる。ex03/ex07 の execute 内で mastra.getAgent() する形がその縮図。
メモリの3層(ex05)¶
| 層 | 実体 | 効く範囲 | 一言 |
|---|---|---|---|
会話履歴 (lastMessages) |
直近 N 件をそのまま注入 | 同一 thread | 「議事録をそのまま読む」 |
ワーキングメモリ (workingMemory) |
LLM が更新し続ける要約テンプレ | scope: 'resource' なら thread 跨ぎ |
「相手の名刺に書き足す付箋」 |
セマンティックリコール (semanticRecall) |
過去ログの埋め込み検索 | 同上 | 「過去の議事録を検索して該当ページだけ開く」 |
実出力で確認できたこと: thread-2(履歴ゼロの新会話)で「石川県に住んでいる」を言い当てた。これは履歴ではなく resource スコープの記憶(付箋+検索)が効いた証拠。
プロセッサ=関所、ブロック=tripwire(ex09)¶
inputProcessors / outputProcessors は LLM の前後に挟む関所。検知器(PromptInjectionDetector / ModerationProcessor / PIIDetector 等)はそれ自体が小さな LLM 判定なので、guardrails_basics/ で学んだ「確率の防御」に分類される。ブロックは例外を投げず、response.tripwire に理由が入って結果として返る(アプリ側で分岐しやすい設計)。
ファイル別の役割¶
| ファイル | 役割 |
|---|---|
ex01_agent_basics.ts |
Agent 最小構成。モデルルーティング文字列・generate()・usage の観察 |
ex02_tools.ts |
createTool。zod スキーマ=LLM への仕様書、toolCalls / toolResults の観察 |
ex03_workflow.ts |
createStep / createWorkflow / .then() / .parallel()、Mastra インスタンス経由の Agent 参照 |
ex04_streaming_structured.ts |
stream().textStream と structuredOutput(検証済み response.object) |
ex05_memory_threads.ts |
Memory 3層と thread / resource の区画。LibSQL ファイル1個で永続化 |
ex06_multi_agent.ts |
agents: {...} によるスーパーバイザー。委譲=ツール呼び出しの再帰 |
ex07_hitl_workflow.ts |
suspend() / resume()。HITL の状態機械 |
ex08_rag.ts |
チャンク→埋め込み→ベクトル保存→検索ツール化(agentic RAG)+幻覚チェック |
ex09_guardrails_processors.ts |
入力プロセッサ2枚(injection / moderation)と tripwire |
ex10_evals_scorers.ts |
決定論 scorer 2本 + runEvals バッチ評価。content 構造の落とし穴込み |
コードの挙動解説¶
ex07: suspend / resume の状態機械(HITL の心臓部)¶
ex07_hitl_workflow.ts:36-52 の承認ステップ:
const approveDraft = createStep({
id: 'approve-draft',
inputSchema: z.object({ draft: z.string() }),
outputSchema: z.object({ finalText: z.string() }),
resumeSchema: z.object({ approved: z.boolean(), comment: z.string() }), // ①
suspendSchema: z.object({ draftForReview: z.string() }), // ②
execute: async ({ inputData, resumeData, suspend }) => {
if (!resumeData) { // ③
return await suspend({ draftForReview: inputData.draft }) // ④
}
if (!resumeData.approved) {
return { finalText: `【差し戻し】${resumeData.comment}` } // ⑤
}
return { finalText: `${inputData.draft}\n(承認者コメント: ...)` }
},
})
| 行 | やってること | なぜそうする |
|---|---|---|
| ① | 人間から受け取るデータの型 | resume() 時に zod 検証される。「承認フォームのスキーマ」に相当 |
| ② | 人間に見せるデータの型 | レビュー UI が表示すべき情報(ここでは下書き)を型で宣言 |
| ③ | resumeData の有無で初回/再開を判別 |
同じ execute が2回呼ばれるのがこの API の肝。初回は undefined |
| ④ | suspend() で run 全体を停止 |
戻り値ではなく「状態遷移」。run は 'suspended' になり、プロセスが死んでも storage があれば後日再開できる |
| ⑤ | 差し戻しも「正常な出力」として返す | 却下=エラーではない。HITL の結果は常に outputSchema に載せる |
呼び出し側(ex07_hitl_workflow.ts:72-85)の実行シーケンス:
sequenceDiagram
participant App as 呼び出し側
participant Run as Workflow Run
participant Human as 人間の承認者
App->>Run: run.start(topic)
Run->>Run: write-draft 実行(LLM)
Run->>Run: approve-draft 実行 → suspend()
Run-->>App: status: 'suspended', suspended: [['approve-draft']]
App->>Human: 下書きを提示(本来は Web UI)
Human-->>App: 承認 + コメント
App->>Run: run.resume(step, resumeData)
Run->>Run: approve-draft を resumeData 付きで再実行
Run-->>App: status: 'success', result.finalText
実行結果(実出力): status: suspended → 中断中の step: [["approve-draft"]] → resume 後 status: success で承認コメント付きの本文が返った。
ex10: 「エラーは出ないが常に 0」の壊れた scorer(実際に踏んだ穴)¶
初版の scorer は run.output.map((m) => m.content).join('') と書いて 常に 0 を返した。原因は content が文字列ではないこと。probe で実構造を見ると:
run.output = [{ "role": "assistant", "content": { "format": 2, "parts": [{ "type": "text", "text": "金沢市です。" }], "content": "金沢市です。" } }]
つまり m.content はオブジェクトで、暗黙の文字列化で '[object Object]' になり includes('金沢市') が常に false。修正は ex10_evals_scorers.ts:24-33 のテキスト抽出ヘルパー:
function outputText(output: ScorerRunOutputForAgent): string {
return output
.map((message) => {
const { content } = message
if (typeof content === 'string') return content
return typeof content.content === 'string' ? content.content : ''
})
.join('')
}
これは testing.md の「green だが何も検証していないテスト」問題そのもの。scorer を書いたら、まず正解ケースで 1 が出ることを確認してから使う(scorer 自体の検証)。修正後は contains-ground-truth: 1 / is-concise: 1。
学んだこと(要点)¶
- モデルルーティング文字列だけで LLM が動く。SDK import ゼロ。プロバイダ差し替え=文字列書き換え(書籍4章のマルチ LLM 対応の土台)
toolCallsに出るツール名はtools: { fxRateTool }のキー名。id(get-fx-rate)ではない方が表示された — 命名はキー側も意識する- サブエージェントは
agent-<id>という名前のツールとして親に見える(ex06 実出力:agent-researcher/agent-copywriter)。マルチエージェント=ツール呼び出しの再帰 - storage 未設定の Mastra はメモリ内フォールバックで警告を出す(ex03/ex07)。suspend した run を「数日後に再開」するには LibSQL 等の永続 storage が必須 — 警告文がそのまま本番要件を教えてくれる
- suspend/resume は「同じ execute が2回呼ばれる」モデル。
resumeDataの有無が初回/再開のフラグ - プロセッサのブロックは tripwire という結果で返る(例外ではない)。ログに出るスタックトレースは内部ログでクラッシュではない
- ハマりどころ:
op runの並列実行は認証プロンプトが競合して落ちる(authorization prompt dismissed)。ex は直列で回す - ハマりどころ: scorer の
run.output[].contentはオブジェクト(上記)。String()頼みの抽出は静かに壊れる
用語集¶
混同しやすい語の階層:
Mastra インスタンス(登録簿)
├── Agent ─ tools / memory / agents / processors / scorers を持つ
│ └── 実行 = generate() / stream()(1回の呼び出しで LLM+ツールのループが回る)
└── Workflow ─ Step を then / parallel / branch で配線
└── 実行 = createRun() → run.start()(run が状態を持つ。suspended ⇄ resume)
| 用語 | 何か | 具体例(このレクチャー) | 判定基準1文 |
|---|---|---|---|
| Agent | LLM に流れを決めさせる実行単位 | ex01 new Agent({...}) |
「実行経路が入力によって変わってよいか? Yes → Agent」 |
| Workflow | コードで流れを固定する実行単位 | ex03 createWorkflow() |
「毎回同じ順で走るべきか? Yes → Workflow」 |
| Step | Workflow の1マス。zod で入出力を型付け | ex03 createStep({...}) |
「配線の型(inputSchema/outputSchema)を持つ最小単位」 |
| Run | Workflow の1回の実行インスタンス | ex07 workflow.createRun() |
「状態(suspended 等)を持つのは Workflow ではなく Run」 |
| Tool | LLM が呼ぶと決め、TS が実行する関数 | ex02 createTool({...}) |
「zod スキーマが LLM への仕様書」 |
| thread | 会話1本の区画 | ex05 thread: 'thread-1' |
「チャット画面の1つのスレッド=1 thread」 |
| resource | ユーザー(等)の区画。thread を束ねる | ex05 resource: 'user-yamamoto' |
「thread を跨いで覚えたい主体=resource」 |
| ワーキングメモリ | LLM が更新する要約の付箋 | ex05 workingMemory |
「履歴そのものではなく、抽出された状態」 |
| セマンティックリコール | 過去ログの埋め込み検索 | ex05 semanticRecall |
「lastMessages に入らない昔の発言を意味で引く」 |
| Processor | LLM 前後の関所 | ex09 inputProcessors |
「メッセージが LLM に届く前/出た後に挟まるか?」 |
| tripwire | プロセッサがブロックした結果 | ex09 response.tripwire |
「ブロック=例外ではなく結果フィールド」 |
| Scorer | 1観点の採点関数 | ex10 createScorer |
「観点1つ=scorer 1本(合議は複数 scorer で)」 |
| runEvals | dataset × scorer の一括実行 | ex10 | 「golden dataset を回す CI ゲートの本体」 |
困りごと → 見る/使うもの クイック早見表:
| 困りごと | 見る/使うもの |
|---|---|
| LLM がツールを使ってくれない | response.toolCalls(空なら instructions と description を疑う) |
| 会話を覚えていない | thread/resource の指定漏れ → ex05 の memory: { thread, resource } |
| suspend した run を後日再開したい | Mastra に永続 storage(LibSQL 等)を設定 |
| 出力を JSON で受けたい | structuredOutput: { schema } → response.object |
| 入力攻撃を止めたい | inputProcessors + tripwire 確認 |
| プロンプト変更の回帰が怖い | runEvals を CI に(eval_basics/ の回帰ゲートと同型) |
拡張アイデア¶
- Mastra Studio を触る(書籍2.6):
npm create mastra@latestで正規プロジェクトを作り、ex の Agent/Workflow をsrc/mastra/index.tsに移植してmastra devの GUI(実行・トレース閲覧)で観察する - Langfuse 連携(書籍11.4): Mastra の observability exporter で ex06 のマルチエージェント実行を Langfuse に流し、
langfuse_basics/で見た trace ツリーと比較する - Deep Research ミニ再現(書籍6章): ex07 の HITL を「検索ワークフロー→ユーザー評価→レポート生成」の3段に拡張する(検索は Tavily か mock)
- LLM-as-a-Judge scorer(書籍11章): ex10 に
createScorer().generateScore()内で judge LLM を呼ぶ採点を足し、eval_basics/で実測した judge バイアスが Mastra でも出るか確かめる - ブランチ: ex03 に
.branch()(条件分岐)を足して flowchart との対応を確認する
既知の注意点¶
npm install時に zod v3 系を求める深い依存の peer 警告が出るが、zod 4.4.3 に dedupe されて動作に問題なし(typecheck・全実行 green)- ex03 / ex07 の「No
storageconfigured」警告は仕様(in-memory フォールバック)。学習用途では無視してよい - ex09 のブロック時スタックトレースは Mastra 内部ログ(プロセッサはワークフローとして実行されるため)。クラッシュではない
op runを複数並列で起動しない(1Password の認証プロンプトが競合)
参考¶
- 書籍: 『MastraによるAIエージェント開発・運用[実践入門]』技術評論社(2026-07)https://gihyo.jp/book/2026/978-4-297-15766-1
- Mastra 公式: https://mastra.ai/docs(Agents / Workflows / Memory / RAG / Scorers / Processors)
- 関連レクチャー:
langgraph_basics/(Python でのグラフ型)・guardrails_basics/(確率の防御 vs 構造の防御)・eval_basics/(golden dataset・judge バイアス)・langfuse_basics/(観測) - 動作確認: 2026-07-14、
@mastra/core1.50.1、Node v26.0.0、全10例 実行 green
作成: 2026-07-14 / 最終更新: 2026-07-14