Skip to content

snickerjp/mastra-agent-tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

66 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mastra エージェント チュートリアル

技術ブログ記事を自動生成するエージェントシステムを、段階的に作りながら学ぶハンズオンです。

このチュートリアルで学べること

  • エージェント開発で避けて通れないタスク分割の難しさ
  • プロンプトの品質がアウトプットに与える影響を数値で比較
  • ツールでエージェントに「行動」させる方法
  • 構造化出力でプログラムから扱えるデータを得る方法
  • MCP サーバーで自作ツールを外部クライアントに公開する方法

セットアップ

npm install

OpenAI版

cp .env.example .env
# .env ファイルを開いて OPENAI_API_KEY を設定してください

AWS Bedrock版(SageMaker Studio等)

.env ファイルは不要です。IAM Roleで認証されます。

使用モデル: Amazon Nova Lite(入力: $0.06/1M tokens, 出力: $0.24/1M tokens)


チュートリアルの流れ

graph LR
    A[Chapter 1<br/>単純な<br/>1エージェント] --> B[Chapter 2<br/>タスクを<br/>分割する]
    B --> C[Chapter 3<br/>プロンプトの<br/>品質を測る]
    C --> D[Chapter 4<br/>メモリで<br/>改善を重ねる]
    D --> E[Chapter 5<br/>ツールで<br/>行動する]
    E --> F[Chapter 6<br/>構造化<br/>出力]
    F --> G[Chapter 7<br/>MCP<br/>サーバー]
Loading

Chapter 1: 「全部やって」の落とし穴

# OpenAI版
npm run ch1

# Bedrock版
npm run ch1:bedrock

何を体験するか

  • 1つのエージェントに記事全体を任せると、どんな問題が起きるか
  • 同じ指示でも、毎回違う結果が返ってくる不安定さ
  • 「どこを直せばいいのか」が分からない状態

Chapter 2: タスクを分割して設計する

# OpenAI版
npm run ch2

# Bedrock版
npm run ch2:bedrock

何を体験するか

  • research → outline → write → review のワークフロー設計
  • 分割の本質的な難しさ: 「どこで切るか」より「何を渡すか(スキーマ設計)」が重要
  • 最初のステップ(research: リサーチ)の品質が、後続すべてに影響する問題
  • 間違った分割の例: 「前半を書く → 後半を書く」(文脈が途切れてしまう)

ワークフローの流れ

researchStep (リサーチ)  → { keyPoints[], targetAudience, tone }
  ↓
outlineStep (構成設計)   → { sections[], targetAudience, tone }
  ↓
writeStep (執筆)        → { draft, targetAudience }
  ↓
reviewStep (レビュー)    → { revisions[], article }

Chapter 3: プロンプトの品質を測る

# OpenAI版
npm run ch3

# Bedrock版
npm run ch3:bedrock

何を体験するか

  • 3つのパターンを同じ評価基準で比較し、スコアの違いを確認する
パターン instructions リクエスト 期待スコア
A(最悪) 「記事を書いてください」 「TypeScriptについて」
B(中程度) 詳しい役割設定あり トピックのみ
C(最良) 詳しい役割設定あり 対象読者・構成・トーン指定あり

評価の観点

  • 指示準拠スコア: instructions の要件をどれだけ守っているか
  • コンテンツ品質: 構成・深さ・具体性・読者への適切さの総合評価

Chapter 4: メモリで改善を重ねる

# OpenAI版
npm run ch4

# Bedrock版
npm run ch4:bedrock

何を体験するか

  • メモリなし: 「2番目のタイトルに決めた」 → 何の2番目か分からず的外れな回答
  • メモリあり: 同じ thread で送ると前の提案を覚えていて、「2番目」が通じる
// memory: { thread, resource } で会話を識別
await agent.generate("TypeScriptの型システムについてブログ記事を書きたい。タイトル候補を3つ提案して。", {
  memory: { thread: "session-1", resource: "user-1" }
});

// 同じ thread → 前の提案を覚えている
await agent.generate("2番目のタイトルに決めた。そのタイトルで記事の構成案(章立て)を5つ出して。", {
  memory: { thread: "session-1", resource: "user-1" }
});

Chapter 5: ツールを使うエージェント

# OpenAI版
npm run ch5

# Bedrock版
npm run ch5:bedrock

何を体験するか

  • createTool でエージェントが使える「アクション」を定義する
  • エージェントが searchTopic / getCurrentDate ツールを自律的に呼び出す
  • Chapter 1(ツールなし)と比べて、事実に基づいた記事が生成される

お題: TypeScriptの最新動向

// ツールを定義して Agent に渡すだけ
const agent = new Agent({
  tools: { getCurrentDate, searchTopic },
  // ...
});
// → LLM が description と inputSchema を見て、いつ呼ぶかを自律的に判断

Chapter 6: 構造化出力(Structured Output)

# OpenAI版
npm run ch6

# Bedrock版
npm run ch6:bedrock

何を体験するか

  • Zod スキーマで出力の型を定義し、型付きオブジェクトとして受け取る
  • article.title, article.sections[0].heading のようにプログラムでアクセスできる
  • JSON シリアライズ可能 → DB保存、API返却等の後続処理に使える

お題: TypeScriptの型システムを活用したバグ防止テクニック

// ブログ記事の出力構造を定義する Zod スキーマ
const BlogArticleSchema = z.object({
  title: z.string(),
  sections: z.array(z.object({ heading: z.string(), body: z.string() })),
  tags: z.array(z.string()),
});

const result = await agent.generate(messages, {
  structuredOutput: {
    schema: BlogArticleSchema,
  },
});

// スキーマで検証してから、型付きオブジェクトとして扱う
const article = BlogArticleSchema.parse(result.object);

Chapter 7: MCP サーバーを作る

npm run ch7

※ MCP サーバーは LLM を使わないため、OpenAI/Bedrock の区別はありません。

何を体験するか

  • Chapter 5 で作ったツールを MCP (Model Context Protocol) サーバーとして公開する
  • MCPServer + startStdio() で、Cursor / Windsurf / Claude Desktop 等から接続可能に
  • 「自分のツールを他のアプリに提供する」という MCP の基本を理解する
import { MCPServer } from "@mastra/mcp";
import { getCurrentDate, searchTopic } from "../chapter5/tools.js";

const server = new MCPServer({
  id: "blog-research-server",
  name: "Blog Research MCP Server",
  version: "1.0.0",
  tools: { getCurrentDate, searchTopic },
});

server.startStdio();
// → MCP クライアントから get-current-date / search-topic が呼べるようになる

このチュートリアルで伝えたいこと

タスク分割の本質的な難しさ

エージェント開発で「タスクを分割する」というと、多くの人は「どこで切るか」を考えます。 例えば「記事を書く」を「前半を書く → 後半を書く」のように分割するイメージです。

しかし、本当に難しいのは**「次のステップに何を渡すか」を設計すること**です。

// ❌ 悪い例: 何を渡すか曖昧
researchStep  outlineStep
// 何が渡されるか分からない

// ✅ 良い例: スキーマで明確に定義
researchStep  { keyPoints[], targetAudience, tone }  outlineStep
// 次のステップが必要とする情報が明確

スキーマ設計を間違えると、後続のステップが「必要な情報が足りない」状態になり、 いくらプロンプトを調整しても品質が上がりません。

Chapter 2で体験すること: 最初のステップ(research)の出力スキーマが不十分だと、 後続の outline → write → review すべてが影響を受けてしまう問題。


プロンプトの品質とアウトプットの関係

プログラミングでは、間違ったコードを書くと「エラー」が出ます。 しかし、エージェントに曖昧な指示を出してもエラーにはなりません

代わりに「低品質なアウトプット」が返ってきます。

// ❌ 曖昧な指示 → エラーにならないが品質が低い
"記事を書いてください"
 毎回違う構成、対象読者が不明確、トーンがバラバラ

// ✅ 明確な指示 → 安定した高品質なアウトプット
{
  topic: "TypeScript",
  targetAudience: "JavaScript経験者でTypeScriptは初めての人",
  tone: "丁寧で具体例が多い",
  structure: ["基本概念", "実践例", "よくある間違い"]
}
 期待通りの記事が生成される

この「曖昧さがエラーにならない」特性が、エージェント開発を難しくしています。

Chapter 3で体験すること: 同じエージェントでも、インプットの品質によって アウトプットのスコアが大きく変わることを数値で確認します。

ワークフローとの関係: ワークフローの inputSchema を丁寧に設計することが、 実は最良のプロンプトエンジニアリングになります。スキーマが「良いインプットを強制する装置」として機能するからです。


使用しているパッケージ

パッケージ 用途
@mastra/core Agent, createTool, createWorkflow, createStep, createScorer
@mastra/core/tools createTool(Chapter 5で使用)
@mastra/memory Memory クラス(Chapter 4で使用)
@mastra/libsql インメモリ SQLite ストレージ(Chapter 4で使用)
@mastra/core/evals createScorer など(Chapter 3で使用)
@mastra/mcp MCPServer(Chapter 7で使用)
zod スキーマ定義(全章で使用)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors