「TypeScript で AI エージェントを書こう」と思った瞬間、選択肢が多すぎて手が止まる方は少なくないのではないでしょうか。Vercel AI SDK を直接叩く案、LangChain.js を組む案、Google 純正の ADK(TypeScript 版)を試す案 ── どれもそれぞれの正解があります。
私はここ数ヶ月で全部触ってみた上で、個人開発と本番投入の両方を兼ねるユースケースでは Mastra に落ち着きました。Vercel AI SDK の薄さを保ったまま、エージェント・ツール・メモリ・ワークフローを統一された API で扱える設計が、個人開発の速度感に合うからです。
ここではMastra と Gemini API を組み合わせて、ローカル開発から Cloudflare Workers へのデプロイまでを一気通貫で動かす手順を、実際にハマった落とし穴つきでまとめます。
なぜ Mastra か — Vercel AI SDK / LangChain.js との違い
Mastra を一言で表すと「Vercel AI SDK をベースに、エージェント開発で必要なものだけを薄く乗せたフレームワーク」です。Vercel AI SDK の generateText や streamText をそのまま中で使っているので、すでに AI SDK を触ったことがある方なら学習コストはほぼゼロです。
私が比較していて感じた違いは、おおむね次のとおりです。
- Vercel AI SDK 単体: モデルを呼ぶだけなら最小。ただしエージェントのループ・メモリ・ワークフローは自分で組む必要があり、コードがすぐに膨らみます。
- LangChain.js: 機能は豊富ですが、抽象化の層が深く、デバッグ時にどこでプロンプトが組み立てられているのかが追いにくいことが多いです。
- Mastra: AI SDK の関数ラッパーに近く、
createAgent/createTool/Workflowといった最小限の構造を提供します。mastra devがローカルでトレースを可視化してくれるので、エージェントのデバッグが明確に楽になります。
Vercel AI SDK と Next.js で Gemini を使う基礎構成は別の記事 で解説していますので、AI SDK 自体を触ったことがない方はそちらから読むとスムーズです。
プロジェクトをセットアップする
Mastra は create-mastra で雛形を作るのが最速です。Node.js 20 以上を前提にしてください。
# 雛形生成(対話式:プロジェクト名 / コンポーネント / 例 などを聞かれる)
npx create-mastra@latest my-gemini-agent
cd my-gemini-agent
# Gemini を使うので Google プロバイダを追加
npm install @ai-sdk/google.env には Gemini の API キーを置きます。これを忘れるとローカル起動時に 401 ではなく「モデルが見つからない」風のエラーが出るので、最初に確認しておくとハマりません。
# .env
GOOGLE_GENERATIVE_AI_API_KEY=YOUR_GEMINI_API_KEYAPI キーは Google AI Studio から発行できます。本番では Cloudflare Workers の Secrets や Vercel の環境変数に移すので、ここではローカル用の値を入れるだけで十分です。
Gemini 2.5 Flash で最初のエージェントを動かす
エージェントは「モデル + 指示 + ツール」で構成されます。まずはツールなしで、指示だけを与えた最小エージェントを作ってみます。
// src/mastra/agents/assistant.ts
import { Agent } from "@mastra/core/agent";
import { google } from "@ai-sdk/google";
export const assistantAgent = new Agent({
name: "AssistantAgent",
instructions: `
あなたは技術記事の編集アシスタントです。
- 日本語で回答してください
- 不明な点は推測ではなく「確認が必要」と返してください
- コード例を求められたら必ず TypeScript で書いてください
`,
model: google("gemini-2.5-flash"),
});Mastra インスタンスに登録します。
// src/mastra/index.ts
import { Mastra } from "@mastra/core";
import { assistantAgent } from "./agents/assistant";
export const mastra = new Mastra({
agents: { assistantAgent },
});これで npx mastra dev を叩くと、ブラウザでエージェントとチャットできるダッシュボードが立ち上がります。プロンプトに対する応答だけでなく、内部のリクエスト・トークン数・各ステップの所要時間まで確認できるので、初期チューニングが非常にやりやすいです。
「Gemini 2.5 Flash と Pro のどちらを選ぶか」で迷うこともあるかもしれません。私は開発初期は Flash、品質を詰める段階で Pro に切り替える方針を取っています。コストとレイテンシのバランスでは Flash が明確に扱いやすく、エージェント特有の試行錯誤回数とよく合います。
ツールを足してエージェントに「行動」させる
ただ会話するだけのエージェントは AI SDK 単体でも書けます。Mastra の真価は、ツールを宣言的に書ける点にあります。例として、外部の天気 API を叩くツールを追加します。
// src/mastra/tools/weather.ts
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
export const weatherTool = createTool({
id: "get-weather",
description: "指定した都市の現在の天気を取得します",
inputSchema: z.object({
city: z.string().describe("都市名(例: Tokyo, New York)"),
}),
outputSchema: z.object({
temperature: z.number(),
description: z.string(),
}),
execute: async ({ context }) => {
const res = await fetch(
`https://wttr.in/${encodeURIComponent(context.city)}?format=j1`,
);
if (!res.ok) {
throw new Error(`Weather API failed: ${res.status}`);
}
const data = await res.json();
return {
temperature: Number(data.current_condition[0].temp_C),
description: data.current_condition[0].weatherDesc[0].value,
};
},
});エージェントに渡します。
import { weatherTool } from "../tools/weather";
export const assistantAgent = new Agent({
name: "AssistantAgent",
instructions: "...(前述)",
model: google("gemini-2.5-flash"),
tools: { weatherTool },
});ここで重要なのが inputSchema を Zod でしっかり書くことです。Gemini の Function Calling は型情報からツール呼び出しを判断するため、description を曖昧にすると「ツールを呼ぶべき場面でも呼ばない」「呼ぶ必要がない場面で呼ぶ」が頻発します。私は最初これをサボって何度もハマりました。
メモリで会話の文脈を保持する
短い対話なら毎回プロンプトに履歴を含めれば済みますが、会話が長引くと Gemini の入力上限とコストの両方を圧迫します。Mastra の Memory を使うと、ストレージ層を切り替えるだけで会話履歴を永続化できます。
// src/mastra/memory.ts
import { Memory } from "@mastra/memory";
import { LibSQLStore } from "@mastra/libsql";
export const memory = new Memory({
storage: new LibSQLStore({
url: "file:./mastra.db", // 本番は Turso / Cloudflare D1 に切り替え
}),
options: {
lastMessages: 20, // 直近 20 件をプロンプトに含める
semanticRecall: false, // 必要なら true にして埋め込み検索を有効化
},
});export const assistantAgent = new Agent({
// ...
memory,
});ローカルでは SQLite ファイルで動きますが、Cloudflare Workers にデプロイする場合はファイルシステムが使えないので、Turso(LibSQL のホスト版)か Cloudflare D1 に切り替える設計にしておくと、移行が楽です。
会話履歴の保存と検索の設計をもっと深掘りしたい方には、LangChain.js + Gemini で本番運用するエージェントの記事 も合わせて読むと、メモリ層の選び方の比較になります。
mastra dev のトレース機能で詰める
npx mastra dev はチャット UI 以上のものを提供します。エージェントが内部で何回 LLM を呼び、各呼び出しでどのツールが使われ、トークンがどれだけ消費されたかが、ステップごとに可視化されます。
私が初心者の方によくおすすめしているのは、次の流れです。
- ツールを 1 つ追加するごとに
mastra devで実際に呼ばせ、どのプロンプトでツールが選択されているかを確認します - プロンプトを変えるたびに、ツール選択が壊れていないかをトレースで検証します
- 想定外のツール呼び出しがあったら、
descriptionを書き直して再実行します
エージェント開発は「指示文 → 実行 → ログ確認 → 指示文修正」のループに尽きます。Mastra の良さは、このループを 1 画面で完結させられる点です。
Cloudflare Workers にデプロイするときの罠
ローカルで動いた後、Cloudflare Workers にデプロイしようとして引っかかった点をまとめます。
@mastra/libsqlは Cloudflare Workers で動かない: ファイルシステムを前提とするため、本番では@mastra/cloudflare-d1か Turso クライアントに切り替えます。- Node.js 互換フラグが必要:
wrangler.tomlにcompatibility_flags = ["nodejs_compat"]を入れないと、@ai-sdk/google内部のBuffer依存で落ちます。 - 環境変数の渡し方: Workers では
process.envではなく、ハンドラに渡されるenvオブジェクトから読みます。Mastra の Agent 初期化をfetchハンドラ内に移すか、globalThis経由で初期化時にキーを注入する設計にしてください。
Hono と Cloudflare Workers で Gemini をエッジ実行する構成 は、Mastra のエージェントを Workers に乗せる際の API ゲートウェイ層としても応用できます。
全体を振り返って
ここまで動かしてみて感じるのは、Mastra は「AI SDK の薄さを失わずに、本番で必要な土台だけを足してくれる」フレームワークだということです。LangChain.js のような網羅性はありませんが、その分コードを読み解く時間が短く、TypeScript エンジニアが「自分で触っている感覚」を保ったまま開発できます。
次の一歩として私がおすすめしたいのは、Workflow を使った多段処理の実装です。たとえば「ユーザー質問 → リサーチエージェント → 要約エージェント → SNS 投稿用整形」のような分岐つきフローを Mastra のワークフローで書くと、責務が明確になりエージェントごとのプロンプトもシンプルに保てます。今日試したシングルエージェントが動いたら、次はぜひワークフローに踏み込んでみてください。