AIに記事を書かせるたびに「なんか薄い」「トーンがバラバラ」と感じていませんか?原因は1つのプロンプトに分析・構成・執筆を詰め込みすぎているからです。Claudeを3つの専門エージェントに分割するだけで、品質は劇的に安定します。
フェーズ1:なぜ単発プロンプトでは品質が安定しないのか
単発プロンプトの構造的欠陥
「このトピックで2000字の記事を書いて」——多くの方が最初に試みるのがこのアプローチです。しかし、この方法には構造的な欠陥があります。
1回のプロンプトに「読者分析」「競合調査」「見出し設計」「本文執筆」をすべて詰め込むと、AIは各タスクに割ける思考リソース(コンテキストウィンドウ内の処理容量)が分散してしまいます。人間に例えるなら、「企画書を作りながら同時にプレゼン資料も仕上げて、その場で発表もして」と言われるようなものです。どれも中途半端になるのは当然です。
具体的には、以下の3つの問題が発生します。
- 思考深度の浅さ:読者ペルソナの分析が雑になり、「初心者向け」「IT担当者向け」程度の曖昧な定義しか生成されない
- 論理の飛躍:見出し設計と本文執筆を同時に考えるため、セクション間のつながりが弱くなる
- トーンの揺れ:同じ記事内でも、前半は丁寧語、後半はカジュアルになるなど一貫性が崩れる
マルチエージェント連鎖が解決する3つの問題
マルチエージェント(複数のAIエージェントを連携させる手法)アーキテクチャでは、各エージェントが1つのタスクに集中できます。これにより、以下の3つの問題が解決されます。
①論理の一貫性:分析エージェントが「読者は月5万円以上のSaaSを導入検討している中小企業のIT担当者で、ROI計算に悩んでいる」という詳細なペルソナを出力します。構成エージェントはそのJSONを受け取り、ROI計算に特化した見出し設計を行います。執筆エージェントはその構成に沿って本文を書くため、記事全体で一本の論理軸が通ります。
②体験ベースの深み:執筆エージェントに「このペルソナが実際に経験する失敗シナリオを体験談として盛り込め」と指示できます。単発プロンプトでは「分析しながら体験談も考えて」となって薄くなりがちですが、専任エージェントなら執筆だけに集中できます。
③トーンの再現性:執筆エージェントのシステムプロンプトにトーン定義を固定することで、どのトピックでも同じ文体・語り口で生成できます。複数記事を量産するメディア運営者にとって、これは特に重要なメリットです。
実際の失敗例との比較
同じトピック「Claude APIでチャットボットを作る方法」で比較してみましょう。
【単発プロンプト出力の例】
Claude APIを使ったチャットボットの作り方を解説します。まずAPIキーを取得し、Pythonでanthropicライブラリをインストールします。次にclient.messages.createを呼び出します。これで簡単にチャットボットが作れます。
【3エージェント連鎖出力の例】
「APIキーの設定でつまずいた」「レート制限エラーが出て本番運用できない」——Claude APIでチャットボットを作ろうとした開発者の7割が、最初の1週間でこの2つの壁にぶつかります。本記事では、環境変数の安全な管理からエラーハンドリングの実装まで、実際のプロダクション環境で動かすための設計を解説します。
同じトピックでも、読者の課題を深く分析してから執筆した記事は、冒頭から「自分のことを書いてくれている」と感じさせる引力があります。これが3エージェント連鎖の力です。
フェーズ2:3エージェント連鎖パイプラインの全体設計
各エージェントの役割定義
パイプライン(データが流れる処理の連鎖)全体を設計する前に、各エージェントの責務を明確にします。責務の境界が曖昧だと、エージェント間でタスクが重複したり、重要な処理が抜け落ちたりします。
エージェント①:分析エージェント(Analyst)
- 入力:トピック文字列(例:「Claude APIでチャットボットを作る方法」)
- 処理:読者ペルソナの詳細定義、検索意図の分類、競合記事との差別化ポイント抽出、ターゲットキーワードリストの生成
- 出力:
AnalysisResult型のJSON
エージェント②:構成エージェント(Architect)
- 入力:分析エージェントの出力JSON
- 処理:SEO(検索エンジン最適化)を考慮した見出し設計、各セクションのkey_points定義、論理フローの検証、想定文字数の配分
- 出力:
StructureResult型のJSON
エージェント③:執筆エージェント(Writer)
- 入力:分析JSON+構成JSON+トーン指定
- 処理:本文マークダウン生成、体験談・具体例の注入、CTA(行動喚起)の設計
- 出力:完成した記事本文(Markdown形式)
エージェント間のデータ受け渡し設計
マルチエージェント設計で最も重要なのが、エージェント間のデータ受け渡し方法です。ハナが推奨するのは「JSON構造化渡し」パターンです。
各エージェントの出力をJSON形式で構造化し、次エージェントのプロンプトにjson.dumps()でシリアライズ(データをテキスト形式に変換する処理)して渡します。この設計には3つのメリットがあります。
- 検証可能性:各エージェントの出力をファイルに保存して人間がレビューできる
- 再実行性:途中でエラーが出ても、保存済みJSONから再開できる
- 拡張性:エージェントを追加するとき、JSONスキーマを拡張するだけで対応できる
全体アーキテクチャの流れ
パイプライン全体のデータフローを整理すると、以下のようになります。
↓
【分析エージェント】
Claude 3.5 Sonnet → AnalysisResult JSON
↓(JSON丸ごと渡し)
【構成エージェント】
Claude 3.5 Sonnet → StructureResult JSON
↓(Analysis + Structure 両方渡し)
【執筆エージェント】
Claude 3.5 Sonnet → 記事本文 Markdown
↓
ArticleResult(全データ統合)→ ファイル保存
注目すべきは、執筆エージェントに「分析JSON」と「構成JSON」の両方を渡している点です。執筆エージェントは構成に従いながらも、分析で得た読者ペルソナや検索意図を常に参照できるため、読者にとって「自分のために書かれた記事」と感じられる文章が生成されます。
参考として、より複雑なマルチエージェント設計についてはClaude Codeマルチエージェント開発の実践ガイド|並列開発・タスク分割・同期設計 | 株式会社StartLinkも参照してみてください。並列処理や同期設計まで踏み込んだ実践的な内容が解説されています。
フェーズ3:Pythonによる実装手順(コード全公開)
ここからは実際のコードを見ていきましょう。環境構築から3エージェントの実装、パイプラインの統合まで、ステップごとに解説します。コードはすべてコピー&ペーストで動作するよう設計しています。
前提環境
- Python 3.10以上
- Anthropic APIキー(Anthropic Consoleで取得)
- pip(Pythonパッケージマネージャー)
まず必要なライブラリをインストールします。
pip install anthropic python-dotenv
プロジェクトのルートディレクトリに.envファイルを作成し、APIキーを設定します。
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxx
ステップ1:環境設定と型定義
必要なライブラリをインポートし、エージェント間で受け渡すデータ構造をTypedDictで定義します。.envファイルにANTHROPIC_API_KEYを設定してください。
import os
import json
import anthropic
from typing import TypedDict
from dotenv import load_dotenv
load_dotenv()
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
MODEL = "claude-3-5-sonnet-20241022"
class AnalysisResult(TypedDict):
topic: str
persona: str
search_intent: str
differentiation_points: list[str]
target_keywords: list[str]
class StructureResult(TypedDict):
title: str
headings: list[dict]
key_points: list[str]
estimated_word_count: int
class ArticleResult(TypedDict):
analysis: AnalysisResult
structure: StructureResult
content: str
tone: str
TypedDict(型付き辞書)を使うことで、各エージェントの入出力データ構造が明確になり、IDEの補完も効くようになります。チームで開発する場合は特に、このような型定義が品質を安定させる土台になります。
ステップ2:分析エージェント関数の実装
トピック文字列を受け取り、読者ペルソナ・検索意図・差別化ポイントをJSON形式で返すrun_analysis_agent()を実装します。Claude 3.5 Sonnetに対してJSONのみを返すよう明示的に指示します。
def run_analysis_agent(topic: str) -> AnalysisResult:
prompt = f"""あなたはコンテンツ戦略の専門家です。
以下のトピックについて詳細な分析を行い、必ず純粋なJSONのみを返してください。
トピック: {topic}
返すJSONの形式:
{{
"topic": "トピック名",
"persona": "ターゲット読者の詳細なペルソナ(年齢・職業・課題・スキルレベル)",
"search_intent": "このトピックを検索する読者の主な意図と目的",
"differentiation_points": [
"他の記事と差別化できるポイント1",
"他の記事と差別化できるポイント2",
"他の記事と差別化できるポイント3"
],
"target_keywords": [
"メインキーワード",
"関連キーワード1",
"関連キーワード2",
"ロングテールキーワード1"
]
}}
JSONのみ返してください。説明文や前置きは不要です。"""
message = client.messages.create(
model=MODEL,
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
)
response_text = message.content[0].text.strip()
if response_text.startswith("```"):
response_text = response_text.split("```")[1]
if response_text.startswith("json"):
response_text = response_text[4:]
result: AnalysisResult = json.loads(response_text)
return result
ポイントは、プロンプトの末尾に「JSONのみ返してください。説明文や前置きは不要です。」と明示していることです。これを省略すると、Claudeが「以下の分析結果をお届けします:」のような前置きを付けてしまい、json.loads()がエラーを起こします。また、コードブロック(“`json)で返ってきた場合の除去処理も実装しています。
ステップ3:構成エージェント関数の実装
分析JSONを受け取り、SEO最適化された見出し構成と各セクションのkey_pointsリストを生成するrun_structure_agent()を実装します。前エージェントの出力を完全にコンテキストとして渡すことで一貫性を確保します。
def run_structure_agent(analysis: AnalysisResult) -> StructureResult:
prompt = f"""あなたはSEOとコンテンツ構成の専門家です。
以下の分析結果をもとに、記事の構成を設計してください。
分析結果:
{json.dumps(analysis, ensure_ascii=False, indent=2)}
返すJSONの形式:
{{
"title": "SEO最適化されたH1タイトル(40〜60文字)",
"headings": [
{{
"level": 2,
"text": "H2見出しテキスト",
"key_points": ["このセクションで伝えるポイント1", "ポイント2"],
"word_count": 300
}},
{{
"level": 3,
"text": "H3見出しテキスト(H2の子要素)",
"key_points": ["ポイント1"],
"word_count": 200
}}
],
"key_points": [
"記事全体を通じて読者に伝える最重要メッセージ1",
"最重要メッセージ2",
"最重要メッセージ3"
],
"estimated_word_count": 2000
}}
H2見出しを4〜6個、必要に応じてH3を含めてください。JSONのみ返してください。"""
message = client.messages.create(
model=MODEL,
max_tokens=2048,
messages=[{"role": "user", "content": prompt}]
)
response_text = message.content[0].text.strip()
if response_text.startswith("```"):
response_text = response_text.split("```")[1]
if response_text.startswith("json"):
response_text = response_text[4:]
result: StructureResult = json.loads(response_text)
return result
json.dumps(analysis, ensure_ascii=False, indent=2)で分析結果を整形して渡している点に注目してください。ensure_ascii=Falseを指定しないと日本語が文字コードに変換されてしまい、Claudeが読みにくくなります。indent=2で整形することで、Claudeがより正確に構造を解釈できます。
ステップ4:執筆エージェント関数の実装
構成JSONとトーン指定を受け取り、本文マークダウンを生成するrun_writing_agent()を実装します。分析・構成の両方をコンテキストとして渡し、体験ベースの深みある文章を生成させます。
def run_writing_agent(
analysis: AnalysisResult,
structure: StructureResult,
tone: str = "実践的・技術者向け"
) -> str:
headings_text = json.dumps(structure["headings"], ensure_ascii=False, indent=2)
key_points_text = "\n".join(f"- {p}" for p in structure["key_points"])
prompt = f"""あなたは経験豊富な技術ライターです。
以下の情報をもとに、完全な記事本文をMarkdown形式で執筆してください。
【記事タイトル】
{structure["title"]}
【ターゲット読者】
{analysis["persona"]}
【検索意図】
{analysis["search_intent"]}
【差別化ポイント】
{chr(10).join(f"- {p}" for p in analysis["differentiation_points"])}
【見出し構成】
{headings_text}
【記事全体のキーメッセージ】
{key_points_text}
【執筆トーン】
{tone}
【執筆指示】
- 各H2・H3見出しのkey_pointsを必ずカバーすること
- 具体的な数値・事例・失敗談を含めること
- 読者が「自分のことを書いてくれている」と感じる体験ベースの表現を使うこと
- 最後にCTA(行動喚起)を含めること
- 目標文字数: {structure["estimated_word_count"]}字
Markdown形式で本文のみ出力してください。"""
message = client.messages.create(
model=MODEL,
max_tokens=4096,
messages=[{"role": "user", "content": prompt}]
)
return message.content[0].text
ステップ5:パイプライン統合とエラーハンドリング
3つのエージェントをチェーン(連鎖)させるmain関数を実装します。途中結果をJSONファイルに保存することで、エラー発生時に途中から再実行できます。
import datetime
def save_intermediate(data: dict, filename: str) -> None:
"""中間結果をJSONファイルに保存する"""
with open(filename, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"✅ 保存完了: {filename}")
def run_pipeline(topic: str, tone: str = "実践的・技術者向け") -> ArticleResult:
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
print(f"🔍 分析エージェント起動中... トピック: {topic}")
try:
analysis = run_analysis_agent(topic)
save_intermediate(analysis, f"output_{timestamp}_1_analysis.json")
except Exception as e:
print(f"❌ 分析エージェントエラー: {e}")
raise
print("📐 構成エージェント起動中...")
try:
structure = run_structure_agent(analysis)
save_intermediate(structure, f"output_{timestamp}_2_structure.json")
except Exception as e:
print(f"❌ 構成エージェントエラー: {e}")
raise
print("✍️ 執筆エージェント起動中...")
try:
content = run_writing_agent(analysis, structure, tone)
except Exception as e:
print(f"❌ 執筆エージェントエラー: {e}")
raise
result: ArticleResult = {
"analysis": analysis,
"structure": structure,
"content": content,
"tone": tone
}
save_intermediate(result, f"output_{timestamp}_final.json")
# マークダウンファイルとして本文を保存
with open(f"output_{timestamp}_article.md", "w", encoding="utf-8") as f:
f.write(content)
print(f"📄 記事本文を保存: output_{timestamp}_article.md")
return result
if __name__ == "__main__":
result = run_pipeline(
topic="Claude APIでマルチエージェントシステムを構築する方法",
tone="実践的・IT担当者向け・です・ます調"
)
print(f"\n🎉 完了!記事タイトル: {result['structure']['title']}")
print(f"推定文字数: {result['structure']['estimated_word_count']}字")
エラーハンドリングで特に重要なのが、各エージェントの実行後に中間結果を保存していることです。分析と構成が完了した後、執筆エージェントでAPIのレート制限(一定時間内のAPI呼び出し回数制限)エラーが出た場合でも、保存済みのJSONから執筆エージェントだけを再実行できます。これにより、APIコストの無駄遣いを防げます。
マルチエージェント実装でよくあるハマりポイントについては、Claude Agent SDK でマルチエージェントを動かしてみよう ── 便利さと、いくつかのハマりポイント|Japan Digital Design, Inc.が参考になります。実際の運用で遭遇しやすいエラーパターンが詳しく解説されています。
フェーズ4:動作確認・品質チェック・応用展開
動作確認:中間出力JSONのデバッグ手順
パイプラインを初めて実行するときは、まずシンプルなトピックでテストすることをお勧めします。
# テスト実行
result = run_pipeline(
topic="Python入門:リスト内包表記の使い方",
tone="初心者向け・やさしい語り口"
)
実行後、以下の順序で中間出力を確認します。
①分析JSONの確認ポイント:
personaフィールドに年齢・職業・スキルレベルが含まれているかdifferentiation_pointsが3件以上生成されているかtarget_keywordsにロングテールキーワード(3語以上の複合キーワード)が含まれているか
②構成JSONの確認ポイント:
headingsのH2が4〜6個生成されているか- 各
key_pointsが分析結果のsearch_intentと整合しているか estimated_word_countが目標文字数(最低1500字)に達しているか
③本文マークダウンの確認ポイント:
- 冒頭にフック(読者の課題を突く一文)があるか
- 各H2セクションが構成JSONの
key_pointsをカバーしているか - 末尾にCTAが含まれているか
この記事はハナ編集部が執筆しました。

コメント