3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LangGraphってなんだ?〜AIクリエイティブエージェント開発の最適解を完全理解〜

Posted at

この記事の対象読者

  • Pythonの基本文法(関数、クラス、デコレータ)を理解している方
  • LLM(大規模言語モデル)を使ったアプリ開発に興味がある方
  • AIエージェントを使って複雑なクリエイティブワークフローを構築したい方

この記事で得られること

  • LangGraphの核心概念と他のフレームワークとの違いの理解
  • クリエイティブエージェントの設計パターンと実装方法
  • 開発・本番・テスト環境向けの設定テンプレート
  • 実際に動作するマルチエージェントワークフローのコード

この記事で扱わないこと

  • LLM API(OpenAI, Claude等)の基本的な使い方
  • Pythonの環境構築方法
  • LangChainの詳細な解説(必要最低限のみ触れます)

1. LangGraphとの出会い

「このAIワークフロー、なんか思い通りに動かないんだよな...」

CrewAIで簡単なマルチエージェントシステムを組んでいたとき、私はそう感じた。アイデア出し→執筆→編集→校正という流れを作りたかっただけなのに、状態管理がブラックボックスすぎて、どこで何が起きているのかさっぱりわからない。

そんなとき出会ったのがLangGraphだった。

LangGraphは、LangChain社が開発した「グラフベースのエージェントオーケストレーションフレームワーク」だ。人間で言えば、プロジェクトマネージャーが各メンバーの作業状況を常に把握しながら、次に誰が何をすべきかを指示する——そんなイメージだ。

最初にLangGraphのグラフ構造を見たとき、「ああ、これが欲しかった」と膝を打った。状態が可視化され、フローが明示的で、どこで何が起きているか一目瞭然。今回は、その感動をお伝えしたい。

ここまでで、LangGraphがどんなものか、なんとなくイメージできただろうか。次は、この技術で使われる基本用語を押さえていこう。

2. 前提知識の確認

本題に入る前に、この記事で使う用語を整理しておく。

2.1 AIエージェントとは

AIエージェントとは、LLM(Large Language Model、大規模言語モデル)を使って自律的にタスクを実行するプログラムのことだ。単なるチャットボットとの違いは、「ツール」を使えること。Web検索したり、コードを実行したり、外部APIを叩いたりできる。

2.2 オーケストレーションとは

オーケストレーションとは、複数のエージェントやタスクを協調させて動かすことを指す。オーケストラの指揮者が各楽器を統率するように、AIシステムでも誰かが「次はお前の番だ」と指示を出す必要がある。

2.3 状態管理(ステートマネジメント)とは

状態管理とは、ワークフローの「今どこにいるか」「これまで何をしたか」を追跡する仕組みだ。人間のプロジェクトでも、タスクの進捗を管理するのと同じ発想だ。

2.4 MCP(Model Context Protocol)とは

MCPとは、Anthropic社が提唱し、現在はLinux Foundationが管理するLLMとツール間の標準接続プロトコルだ。月間9700万以上のダウンロードがあり、Claude、OpenAI、Googleなど主要プレイヤーが採用している業界標準となっている。

2.5 HITL(Human-in-the-Loop)とは

HITLとは、AIワークフローに人間の確認・介入を組み込む設計のことだ。「ここで一旦止めて、人間がチェックしてから次に進む」というポイントを設定できる。

これらの用語が押さえられたら、次に進もう。

3. LangGraphが生まれた背景

3.1 LangChainの限界とLangGraphの誕生

LangGraphは2024年初頭、LangChain社によって独立したライブラリとしてリリースされた。LangChainは直線的なワークフロー(DAG: Directed Acyclic Graph、有向非巡回グラフ)には強かったが、「ループ」や「条件分岐」を含む複雑なエージェント動作には限界があった。

現実のエージェントは、単純に「A→B→C」と進むわけではない。「Aをやって、結果が悪ければAに戻る」「BとCを並行で走らせる」といった複雑なフローが必要になる。LangGraphはこの課題を解決するために生まれた。

3.2 2025年現在の重要性

2025年の今、AIエージェント開発は「研究」から「本番運用」のフェーズに移行している。Uber、LinkedIn、Replit、Elastic、Klarnaといった大企業がLangGraphを本番環境で採用している。

GitHub Stars 21,000以上、月間数百万ダウンロードという数字が、その支持を物語っている。特にクリエイティブ領域では、コンテンツ生成→レビュー→修正というイテレーティブなワークフローが不可欠であり、LangGraphの「サイクル(ループ)対応」が真価を発揮する。

背景がわかったところで、抽象的な概念から順に、具体的な仕組みを見ていこう。

4. LangGraphの基本概念

4.1 グラフ構造:ノードとエッジ

LangGraphの核心は「有向グラフ」だ。ワークフローを「ノード」(処理単位)と「エッジ」(接続)で表現する。

[アイデア出し] → [執筆] → [レビュー] → [完成]
                    ↑           ↓
                    └───[修正]←─┘

この構造により、「レビューで問題があれば修正に回し、修正が終わったら再度レビューする」というループが自然に表現できる。

4.2 ステート(状態):共有されるコンテキスト

LangGraphの特徴的な機能がTypedDictベースの明示的な状態管理だ。

from typing import TypedDict, Annotated
from langgraph.graph import add_messages

class CreativeState(TypedDict):
    """クリエイティブワークフローの状態定義"""
    topic: str                                    # テーマ
    draft: str                                    # 下書き
    feedback: str                                 # フィードバック
    final_content: str                            # 最終成果物
    revision_count: Annotated[int, lambda x, y: y]  # 修正回数
    messages: Annotated[list, add_messages]       # メッセージ履歴

この状態が全ノード間で共有され、各ノードは状態を読み取り・更新しながら処理を進める。

4.3 条件分岐:動的なルーティング

LangGraphの強力な機能が「条件付きエッジ」だ。実行時の状態に応じて、次にどのノードに進むかを動的に決定できる。

def should_continue(state: CreativeState) -> str:
    """レビュー結果に基づいてルーティングを決定"""
    if "approved" in state["feedback"].lower():
        return "finalize"
    elif state["revision_count"] >= 3:
        return "human_review"  # 3回修正しても通らなければ人間に委ねる
    else:
        return "revise"

この仕組みにより、「品質が基準に達するまで自動でイテレーション」「手に負えなければ人間にエスカレーション」という現実的なワークフローが実現する。

4.4 チェックポイント:永続化と復旧

LangGraphには組み込みのチェックポイント機能がある。これにより、処理の途中で失敗しても、最後のチェックポイントから再開できる。長時間実行されるクリエイティブワークフローでは必須の機能だ。

基本概念が理解できたところで、これらの抽象的な概念を具体的なコードで実装していこう。

5. 実際に使ってみよう

5.1 環境構築

# 必要なパッケージのインストール
pip install langgraph langchain langchain-openai python-dotenv

# オプション: LangSmith(デバッグ・可視化用)
pip install langsmith

5.2 設定ファイルの準備

以下の3種類の設定ファイルを用意している。用途に応じて選択してほしい。

開発環境用(config.yaml)

# config.yaml - 開発環境用(このままコピーして使える)
# LangGraphクリエイティブエージェントの設定

llm:
  provider: "openai"
  model: "gpt-4o-mini"        # 開発時はコスト重視
  temperature: 0.7            # クリエイティブタスクなのでやや高め
  max_tokens: 2000

workflow:
  max_revisions: 3            # 最大修正回数
  timeout_seconds: 120        # ノードごとのタイムアウト
  checkpoint_enabled: true

logging:
  level: "DEBUG"              # 開発時は詳細ログ
  output: "console"

langsmith:
  enabled: true               # 開発時はトレース有効
  project: "creative-agent-dev"

本番環境用(config.production.yaml)

# config.production.yaml - 本番環境用(このままコピーして使える)
# 高品質・高信頼性向けの設定

llm:
  provider: "openai"
  model: "gpt-4o"             # 本番は高品質モデル
  temperature: 0.5            # 安定性重視
  max_tokens: 4000
  retry_count: 3
  retry_delay: 1.0

workflow:
  max_revisions: 5            # 品質担保のため多めに
  timeout_seconds: 300
  checkpoint_enabled: true
  checkpoint_backend: "postgres"  # 本番はDB永続化

logging:
  level: "INFO"
  output: "file"
  file_path: "/var/log/creative-agent/app.log"

langsmith:
  enabled: true
  project: "creative-agent-prod"

monitoring:
  metrics_enabled: true
  alert_on_failure: true

テスト環境用(config.test.yaml)

# config.test.yaml - テスト/CI用(このままコピーして使える)
# 高速・低コスト・再現性重視の設定

llm:
  provider: "openai"
  model: "gpt-4o-mini"        # コスト削減
  temperature: 0.0            # 再現性のため固定
  max_tokens: 500             # テストは短い出力で十分
  mock_enabled: true          # モックモード(APIを叩かない)

workflow:
  max_revisions: 1            # テストは1回で十分
  timeout_seconds: 30
  checkpoint_enabled: false   # テストでは不要

logging:
  level: "WARNING"            # ノイズ軽減
  output: "console"

langsmith:
  enabled: false              # CIではトレース不要

test:
  seed: 42                    # 再現性のための乱数シード
  sample_size: 3              # 小さいサンプルで高速化

デバッグ環境用(config.debug.yaml)

# config.debug.yaml - デバッグ特化(このままコピーして使える)
# 問題調査時の詳細情報取得向け

llm:
  provider: "openai"
  model: "gpt-4o-mini"
  temperature: 0.0            # 再現性確保
  max_tokens: 2000

workflow:
  max_revisions: 2
  timeout_seconds: 600        # デバッグ時は長めに
  checkpoint_enabled: true
  checkpoint_backend: "sqlite"
  step_by_step: true          # ステップ実行モード

logging:
  level: "DEBUG"
  output: "both"              # コンソールとファイル両方
  file_path: "./debug.log"
  include_state: true         # 状態の詳細を含める
  include_timestamps: true

langsmith:
  enabled: true
  project: "creative-agent-debug"
  trace_all: true             # 全ての呼び出しをトレース

5.3 基本的な使い方

"""
LangGraph クリエイティブエージェント - 基本実装
使い方: python creative_agent.py
必要なパッケージ: pip install langgraph langchain langchain-openai python-dotenv
"""
import os
from typing import TypedDict, Annotated, Literal
from dotenv import load_dotenv

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver

# 環境変数の読み込み
load_dotenv()


# =============================================================================
# 状態定義
# =============================================================================
class CreativeState(TypedDict):
    """クリエイティブワークフローの状態"""
    topic: str              # 執筆テーマ
    draft: str              # 下書き
    feedback: str           # レビューフィードバック
    final_content: str      # 最終成果物
    revision_count: int     # 修正回数


# =============================================================================
# ノード定義
# =============================================================================
def ideation_node(state: CreativeState) -> dict:
    """アイデア出しノード: テーマを受け取り、構成案を作成"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.8)
    
    messages = [
        SystemMessage(content="あなたは創造的なコンテンツプランナーです。"),
        HumanMessage(content=f"""
        テーマ「{state['topic']}」について、魅力的な記事の構成案を作成してください。
        
        以下を含めてください:
        - キャッチーなタイトル案
        - 3-5つの主要セクション
        - 各セクションの要点
        """)
    ]
    
    response = llm.invoke(messages)
    return {"draft": response.content, "revision_count": 0}


def writing_node(state: CreativeState) -> dict:
    """執筆ノード: 構成案をもとに本文を執筆"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
    
    messages = [
        SystemMessage(content="あなたはプロのテクニカルライターです。"),
        HumanMessage(content=f"""
        以下の構成案をもとに、読みやすい技術記事を執筆してください。
        
        構成案:
        {state['draft']}
        
        要件:
        - 各セクションは具体的な例を含める
        - 専門用語には簡潔な説明を付ける
        - 読者に語りかける文体で
        """)
    ]
    
    response = llm.invoke(messages)
    return {"draft": response.content}


def review_node(state: CreativeState) -> dict:
    """レビューノード: 執筆された内容を評価"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
    
    messages = [
        SystemMessage(content="あなたは厳格な編集者です。"),
        HumanMessage(content=f"""
        以下の記事をレビューしてください。
        
        記事:
        {state['draft']}
        
        以下の観点で評価し、フィードバックを提供してください:
        1. 論理的な構成
        2. 読みやすさ
        3. 具体例の適切さ
        4. 専門用語の説明
        
        問題がなければ「APPROVED」、修正が必要なら具体的な改善点を記載してください。
        """)
    ]
    
    response = llm.invoke(messages)
    return {"feedback": response.content}


def revision_node(state: CreativeState) -> dict:
    """修正ノード: フィードバックを反映して修正"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.5)
    
    messages = [
        SystemMessage(content="あなたはプロのテクニカルライターです。"),
        HumanMessage(content=f"""
        以下のフィードバックを反映して、記事を修正してください。
        
        現在の記事:
        {state['draft']}
        
        フィードバック:
        {state['feedback']}
        
        修正版を出力してください。
        """)
    ]
    
    response = llm.invoke(messages)
    return {
        "draft": response.content,
        "revision_count": state["revision_count"] + 1
    }


def finalize_node(state: CreativeState) -> dict:
    """完成ノード: 最終成果物として確定"""
    return {"final_content": state["draft"]}


# =============================================================================
# ルーティング関数
# =============================================================================
def should_continue(state: CreativeState) -> Literal["revision", "finalize"]:
    """レビュー結果に基づいてルーティングを決定"""
    feedback = state.get("feedback", "").lower()
    revision_count = state.get("revision_count", 0)
    
    # APPROVEDならば完成へ
    if "approved" in feedback:
        return "finalize"
    
    # 3回修正してもダメなら諦めて完成へ
    if revision_count >= 3:
        print(f"[WARNING] 修正上限({revision_count}回)に達しました。強制完了します。")
        return "finalize"
    
    # それ以外は修正へ
    return "revision"


# =============================================================================
# グラフ構築
# =============================================================================
def build_creative_graph():
    """クリエイティブワークフローのグラフを構築"""
    
    # StateGraphを初期化
    workflow = StateGraph(CreativeState)
    
    # ノードを追加
    workflow.add_node("ideation", ideation_node)
    workflow.add_node("writing", writing_node)
    workflow.add_node("review", review_node)
    workflow.add_node("revision", revision_node)
    workflow.add_node("finalize", finalize_node)
    
    # エッジを追加
    workflow.add_edge("ideation", "writing")
    workflow.add_edge("writing", "review")
    
    # 条件付きエッジ: レビュー後のルーティング
    workflow.add_conditional_edges(
        "review",
        should_continue,
        {
            "revision": "revision",
            "finalize": "finalize"
        }
    )
    
    # 修正後は再度レビューへ
    workflow.add_edge("revision", "review")
    
    # 完成したら終了
    workflow.add_edge("finalize", END)
    
    # エントリーポイントを設定
    workflow.set_entry_point("ideation")
    
    # チェックポイント(メモリ永続化)を設定してコンパイル
    memory = MemorySaver()
    return workflow.compile(checkpointer=memory)


# =============================================================================
# 実行
# =============================================================================
def main():
    """メイン実行関数"""
    # グラフを構築
    graph = build_creative_graph()
    
    # 初期状態を設定
    initial_state = {
        "topic": "LangGraphでAIクリエイティブエージェントを作る方法",
        "draft": "",
        "feedback": "",
        "final_content": "",
        "revision_count": 0
    }
    
    # スレッドIDを設定(チェックポイント用)
    config = {"configurable": {"thread_id": "creative-session-001"}}
    
    print("=" * 60)
    print("クリエイティブエージェント起動")
    print("=" * 60)
    print(f"テーマ: {initial_state['topic']}")
    print("-" * 60)
    
    # グラフを実行
    final_state = graph.invoke(initial_state, config)
    
    print("\n" + "=" * 60)
    print("完成コンテンツ")
    print("=" * 60)
    print(final_state["final_content"])
    print("-" * 60)
    print(f"修正回数: {final_state['revision_count']}")


if __name__ == "__main__":
    main()

5.4 実行結果

上記のコードを実行すると、以下のような出力が得られる:

============================================================
クリエイティブエージェント起動
============================================================
テーマ: LangGraphでAIクリエイティブエージェントを作る方法
------------------------------------------------------------

============================================================
完成コンテンツ
============================================================
# LangGraphでAIクリエイティブエージェントを作る方法

## はじめに
AIエージェント開発の世界で、LangGraphは今最も注目されている...

[以下、生成されたコンテンツが出力される]

------------------------------------------------------------
修正回数: 1

5.5 よくあるエラーと対処法

エラー 原因 対処法
ImportError: cannot import name 'StateGraph' langgraphのバージョンが古い pip install -U langgraph で最新版に更新
openai.AuthenticationError APIキー未設定または無効 .envファイルにOPENAI_API_KEY=sk-xxxを設定
RecursionError: maximum recursion depth exceeded ルーティングループが無限に続いている should_continue関数で必ず終了条件を設定
KeyError: 'draft' 状態の初期値が未設定 initial_stateで全フィールドを初期化
TypeError: 'NoneType' object is not subscriptable ノードがNoneを返している ノード関数で必ずdictreturnする

基本的な使い方をマスターしたので、次は応用例を見ていこう。

6. ユースケース別ガイド

6.1 ユースケース1: ブログ記事自動生成システム

  • 想定読者: テックブログを運営しているエンジニア
  • 推奨構成: アイデア → リサーチ → 執筆 → SEO最適化 → 公開
  • サンプルコード:
"""
ブログ記事自動生成エージェント
想定: 週次でテックブログを更新したいエンジニア向け
"""
from typing import TypedDict
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage


class BlogState(TypedDict):
    topic: str
    keywords: list[str]
    outline: str
    content: str
    seo_optimized: str


def research_node(state: BlogState) -> dict:
    """キーワードリサーチを行う"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
    response = llm.invoke([
        SystemMessage(content="あなたはSEOスペシャリストです。"),
        HumanMessage(content=f"""
        テーマ「{state['topic']}」に関連する検索キーワードを5つ提案してください。
        JSON配列形式で出力: ["keyword1", "keyword2", ...]
        """)
    ])
    # 簡易的なパース(本番ではstructured outputを使う)
    import json
    try:
        keywords = json.loads(response.content)
    except:
        keywords = [state['topic']]
    return {"keywords": keywords}


def outline_node(state: BlogState) -> dict:
    """記事の構成案を作成"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
    response = llm.invoke([
        SystemMessage(content="あなたはコンテンツストラテジストです。"),
        HumanMessage(content=f"""
        テーマ: {state['topic']}
        キーワード: {', '.join(state['keywords'])}
        
        SEOに強い記事構成を作成してください。
        """)
    ])
    return {"outline": response.content}


def write_node(state: BlogState) -> dict:
    """本文を執筆"""
    llm = ChatOpenAI(model="gpt-4o", temperature=0.7)  # 高品質モデル
    response = llm.invoke([
        SystemMessage(content="あなたはテクニカルライターです。"),
        HumanMessage(content=f"""
        以下の構成で記事を執筆してください。
        
        構成:
        {state['outline']}
        
        キーワードを自然に含めてください: {', '.join(state['keywords'])}
        """)
    ])
    return {"content": response.content}


def seo_optimize_node(state: BlogState) -> dict:
    """SEO最適化"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
    response = llm.invoke([
        SystemMessage(content="あなたはSEOエディターです。"),
        HumanMessage(content=f"""
        以下の記事をSEO最適化してください。
        
        記事:
        {state['content']}
        
        最適化ポイント:
        - メタディスクリプションを追加
        - 見出しタグの最適化
        - 内部リンクの提案
        """)
    ])
    return {"seo_optimized": response.content}


def build_blog_graph():
    workflow = StateGraph(BlogState)
    
    workflow.add_node("research", research_node)
    workflow.add_node("outline", outline_node)
    workflow.add_node("write", write_node)
    workflow.add_node("seo_optimize", seo_optimize_node)
    
    workflow.add_edge("research", "outline")
    workflow.add_edge("outline", "write")
    workflow.add_edge("write", "seo_optimize")
    workflow.add_edge("seo_optimize", END)
    
    workflow.set_entry_point("research")
    
    return workflow.compile()


# 使用例
if __name__ == "__main__":
    graph = build_blog_graph()
    result = graph.invoke({
        "topic": "Pythonの非同期処理入門",
        "keywords": [],
        "outline": "",
        "content": "",
        "seo_optimized": ""
    })
    print(result["seo_optimized"])

6.2 ユースケース2: マルチエージェント脚本生成

  • 想定読者: 動画コンテンツクリエイター
  • 推奨構成: プロット → キャラクター設計 → シーン執筆 → ダイアログ生成
  • サンプルコード:
"""
マルチエージェント脚本生成システム
想定: YouTube動画や短編アニメの脚本を自動生成したいクリエイター向け
"""
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage


class ScriptState(TypedDict):
    premise: str           # 前提・設定
    genre: str             # ジャンル
    plot: str              # プロット
    characters: str        # キャラクター設定
    scenes: list[str]      # シーン一覧
    dialogue: str          # ダイアログ
    final_script: str      # 最終脚本


def genre_detector(state: ScriptState) -> dict:
    """ジャンルを自動検出"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
    response = llm.invoke([
        SystemMessage(content="入力から最適なジャンルを1つ選んでください。"),
        HumanMessage(content=f"""
        前提: {state['premise']}
        
        選択肢: comedy, drama, action, fantasy, sci-fi, horror
        ジャンル名のみ出力:
        """)
    ])
    return {"genre": response.content.strip().lower()}


def plot_generator(state: ScriptState) -> dict:
    """プロットを生成"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.8)
    response = llm.invoke([
        SystemMessage(content=f"あなたは{state['genre']}ジャンルの脚本家です。"),
        HumanMessage(content=f"""
        前提: {state['premise']}
        
        3幕構成のプロットを作成してください:
        - 第1幕: 設定と導入
        - 第2幕: 対立と展開
        - 第3幕: クライマックスと解決
        """)
    ])
    return {"plot": response.content}


def character_designer(state: ScriptState) -> dict:
    """キャラクターを設計"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
    response = llm.invoke([
        SystemMessage(content="あなたはキャラクターデザイナーです。"),
        HumanMessage(content=f"""
        プロット: {state['plot']}
        
        主要キャラクター(3名まで)を設計してください:
        - 名前
        - 性格
        - 動機
        - 特徴的な話し方
        """)
    ])
    return {"characters": response.content}


def scene_writer(state: ScriptState) -> dict:
    """シーンを執筆"""
    llm = ChatOpenAI(model="gpt-4o", temperature=0.7)
    response = llm.invoke([
        SystemMessage(content="あなたはシーン構成の専門家です。"),
        HumanMessage(content=f"""
        プロット: {state['plot']}
        キャラクター: {state['characters']}
        
        5つのキーシーンを作成してください。
        各シーンには場所、時間、登場人物、アクションを含めてください。
        """)
    ])
    scenes = response.content.split("\n\n")
    return {"scenes": scenes}


def dialogue_writer(state: ScriptState) -> dict:
    """ダイアログを生成"""
    llm = ChatOpenAI(model="gpt-4o", temperature=0.8)
    scenes_text = "\n\n".join(state['scenes'])
    response = llm.invoke([
        SystemMessage(content="あなたはダイアログライターです。自然で魅力的な会話を書きます。"),
        HumanMessage(content=f"""
        キャラクター: {state['characters']}
        シーン: {scenes_text}
        
        各シーンのダイアログを脚本形式で書いてください。
        キャラクターの個性が出るように注意してください。
        """)
    ])
    return {"dialogue": response.content}


def script_finalizer(state: ScriptState) -> dict:
    """最終脚本にまとめる"""
    final = f"""
# 脚本: {state['premise']}
## ジャンル: {state['genre']}

---

## プロット
{state['plot']}

---

## キャラクター
{state['characters']}

---

## 本編
{state['dialogue']}
"""
    return {"final_script": final}


def build_script_graph():
    workflow = StateGraph(ScriptState)
    
    workflow.add_node("detect_genre", genre_detector)
    workflow.add_node("generate_plot", plot_generator)
    workflow.add_node("design_characters", character_designer)
    workflow.add_node("write_scenes", scene_writer)
    workflow.add_node("write_dialogue", dialogue_writer)
    workflow.add_node("finalize", script_finalizer)
    
    # 直線的なフロー
    workflow.add_edge("detect_genre", "generate_plot")
    workflow.add_edge("generate_plot", "design_characters")
    workflow.add_edge("design_characters", "write_scenes")
    workflow.add_edge("write_scenes", "write_dialogue")
    workflow.add_edge("write_dialogue", "finalize")
    workflow.add_edge("finalize", END)
    
    workflow.set_entry_point("detect_genre")
    
    return workflow.compile()


# 使用例
if __name__ == "__main__":
    graph = build_script_graph()
    result = graph.invoke({
        "premise": "迷子になったドラゴンが家族を見つける、子供向けファンタジー",
        "genre": "",
        "plot": "",
        "characters": "",
        "scenes": [],
        "dialogue": "",
        "final_script": ""
    })
    print(result["final_script"])

6.3 ユースケース3: レビュー→修正ループ付きコピーライティング

  • 想定読者: マーケター、広告運用担当者
  • 推奨構成: ドラフト → 評価 → 修正(ループ)→ 完成
  • サンプルコード:
"""
レビューループ付きコピーライティングエージェント
想定: 広告コピーやランディングページの文言を繰り返し改善したいマーケター向け
"""
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage


class CopyState(TypedDict):
    product: str          # 商品・サービス名
    target_audience: str  # ターゲット層
    copy: str             # 現在のコピー
    score: int            # 評価スコア (0-100)
    feedback: str         # 改善フィードバック
    iteration: int        # イテレーション回数
    final_copy: str       # 最終コピー


def draft_writer(state: CopyState) -> dict:
    """初稿を作成"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.8)
    response = llm.invoke([
        SystemMessage(content="あなたは一流のコピーライターです。"),
        HumanMessage(content=f"""
        商品: {state['product']}
        ターゲット: {state['target_audience']}
        
        心に刺さる広告コピーを3パターン作成してください。
        各パターンは:
        - ヘッドライン(20文字以内)
        - ボディコピー(100文字以内)
        - CTA(行動喚起)
        """)
    ])
    return {"copy": response.content, "iteration": 0}


def evaluator(state: CopyState) -> dict:
    """コピーを評価"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.2)
    response = llm.invoke([
        SystemMessage(content="""
        あなたは広告効果の専門家です。
        コピーを0-100のスコアで評価し、改善点を指摘してください。
        
        出力形式:
        SCORE: [数字]
        FEEDBACK: [改善点]
        """),
        HumanMessage(content=f"""
        商品: {state['product']}
        ターゲット: {state['target_audience']}
        
        評価対象コピー:
        {state['copy']}
        """)
    ])
    
    # スコアを抽出(簡易パース)
    content = response.content
    score = 70  # デフォルト
    feedback = content
    
    if "SCORE:" in content:
        try:
            score_line = [l for l in content.split("\n") if "SCORE:" in l][0]
            score = int(''.join(filter(str.isdigit, score_line)))
        except:
            pass
    
    if "FEEDBACK:" in content:
        feedback = content.split("FEEDBACK:")[-1].strip()
    
    return {"score": score, "feedback": feedback}


def reviser(state: CopyState) -> dict:
    """フィードバックを反映して修正"""
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.6)
    response = llm.invoke([
        SystemMessage(content="あなたはコピーライターです。フィードバックを反映して改善してください。"),
        HumanMessage(content=f"""
        現在のコピー:
        {state['copy']}
        
        フィードバック:
        {state['feedback']}
        
        改善版を出力してください。
        """)
    ])
    return {"copy": response.content, "iteration": state["iteration"] + 1}


def finalizer(state: CopyState) -> dict:
    """最終コピーとして確定"""
    return {"final_copy": state["copy"]}


def should_revise(state: CopyState) -> Literal["revise", "finalize"]:
    """修正が必要か判定"""
    # スコア80以上、または3回修正したら完了
    if state["score"] >= 80:
        print(f"[OK] スコア {state['score']} 達成。完了します。")
        return "finalize"
    
    if state["iteration"] >= 3:
        print(f"[WARN] 修正上限に達しました。スコア {state['score']} で完了します。")
        return "finalize"
    
    print(f"[INFO] スコア {state['score']}。修正を続けます。({state['iteration'] + 1}回目)")
    return "revise"


def build_copy_graph():
    workflow = StateGraph(CopyState)
    
    workflow.add_node("draft", draft_writer)
    workflow.add_node("evaluate", evaluator)
    workflow.add_node("revise", reviser)
    workflow.add_node("finalize", finalizer)
    
    workflow.add_edge("draft", "evaluate")
    
    workflow.add_conditional_edges(
        "evaluate",
        should_revise,
        {
            "revise": "revise",
            "finalize": "finalize"
        }
    )
    
    workflow.add_edge("revise", "evaluate")  # 修正後は再評価
    workflow.add_edge("finalize", END)
    
    workflow.set_entry_point("draft")
    
    return workflow.compile()


# 使用例
if __name__ == "__main__":
    graph = build_copy_graph()
    result = graph.invoke({
        "product": "AI搭載スマート家計簿アプリ",
        "target_audience": "20-30代の共働き夫婦",
        "copy": "",
        "score": 0,
        "feedback": "",
        "iteration": 0,
        "final_copy": ""
    })
    print("\n=== 最終コピー ===")
    print(result["final_copy"])
    print(f"\n最終スコア: {result['score']}, イテレーション: {result['iteration']}")

ユースケースが把握できたところで、この記事を読んだ後の学習パスを確認しよう。

7. 学習ロードマップ

この記事を読んだ後、次のステップとして以下をおすすめする。

初級者向け(まずはここから)

  1. LangChain Academy - Introduction to LangGraph: 無料のオンラインコースで基礎を固める
  2. LangGraph公式クイックスタート: 最小限のコードで動くエージェントを作る
  3. LangGraph Templates: 事前構築されたテンプレートから学ぶ

中級者向け(実践に進む)

  1. LangGraph Guides: ストリーミング、永続化、デザインパターンなど
  2. LangSmith Integration: デバッグとオブザーバビリティを導入
  3. MCP対応の実装: 外部ツール連携を標準化

上級者向け(さらに深く)

  1. LangGraph Platform: 本番デプロイと運用
  2. Human-in-the-Loop設計: 人間の介入を組み込む高度な設計
  3. LangGraph GitHub: ソースコードを読んで内部実装を理解

8. 他のフレームワークとの比較

最後に、主要なAIエージェントフレームワークとLangGraphを比較しておこう。

機能 LangGraph CrewAI AutoGen Google ADK OpenAI SDK
マルチエージェント
状態管理 ◎ 明示的 ○ 暗黙的
Human-in-the-loop
MCP対応
ビジュアルUI △ Studio ◎ Studio ◎ Designer ×
学習曲線
コミュニティ ◎ 21k+ stars ○ 25k+ ○ 40k+ ○ 10k+ ○ 15k+

LangGraphの強みは「明示的な状態管理」と「グラフベースの制御フロー」だ。CrewAIは簡単に始められるが、複雑なワークフローでは制御が難しくなる。AutoGenは研究用途には良いが本番運用には課題がある。LangGraphは「本番で使えるエージェント」を作るための最適解だと言える。

9. まとめ

この記事では、LangGraphについて以下を解説した:

  1. LangGraphはグラフベースのエージェントオーケストレーションフレームワークである
  2. 明示的な状態管理とサイクル対応が、クリエイティブワークフローに最適
  3. 条件分岐、チェックポイント、Human-in-the-loopなど本番運用に必要な機能が揃っている

私の所感

正直に言うと、LangGraphは学習曲線がやや高い。CrewAIのように「ロールを定義してGo」とはいかない。しかし、その分だけ「何が起きているか」が明確になる。

クリエイティブワークフローは、単純な直線フローでは済まない。「品質が基準に達するまでループ」「人間のレビューを待って再開」——こうした要件に、LangGraphは真正面から応えてくれる。

もしあなたが「本番で使えるAIクリエイティブエージェント」を作りたいなら、LangGraphは最有力の選択肢だ。この記事がその第一歩になれば幸いである。


参考文献

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?