技術ブログ記事を自動生成するエージェントシステムを、段階的に作りながら学ぶハンズオンです。
- エージェント開発で避けて通れないタスク分割の難しさ
- プロンプトの品質がアウトプットに与える影響を数値で比較
- ツールでエージェントに「行動」させる方法
- 構造化出力でプログラムから扱えるデータを得る方法
- MCP サーバーで自作ツールを外部クライアントに公開する方法
npm installcp .env.example .env
# .env ファイルを開いて OPENAI_API_KEY を設定してください.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/>サーバー]
# OpenAI版
npm run ch1
# Bedrock版
npm run ch1:bedrock何を体験するか
- 1つのエージェントに記事全体を任せると、どんな問題が起きるか
- 同じ指示でも、毎回違う結果が返ってくる不安定さ
- 「どこを直せばいいのか」が分からない状態
# 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 }
# OpenAI版
npm run ch3
# Bedrock版
npm run ch3:bedrock何を体験するか
- 3つのパターンを同じ評価基準で比較し、スコアの違いを確認する
| パターン | instructions | リクエスト | 期待スコア |
|---|---|---|---|
| A(最悪) | 「記事を書いてください」 | 「TypeScriptについて」 | 低 |
| B(中程度) | 詳しい役割設定あり | トピックのみ | 中 |
| C(最良) | 詳しい役割設定あり | 対象読者・構成・トーン指定あり | 高 |
評価の観点
指示準拠スコア: instructions の要件をどれだけ守っているかコンテンツ品質: 構成・深さ・具体性・読者への適切さの総合評価
# 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" }
});# 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 を見て、いつ呼ぶかを自律的に判断# 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);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 |
スキーマ定義(全章で使用) |