AI エージェント(Agent)という言葉が、近年急速に広まりました。しかし、その実態はしばしば曖昧で、「大規模言語モデル(LLM)を少しラップしただけ」と思われがちです。実際には、エージェントの知能性は、単なるモデルの能力ではなく、制御構造と状態管理によって支えられています。本稿では、LangChain と LangGraph の二つの主要フレームワークを軸に、エージェントの設計思想と実装パターンを技術的に整理します。
LangChain:線形パイプラインとしての LLM
LangChain は、初期の LLM アプリケーション開発において標準となったフレームワークです。その核となるのは Chain —— すなわち、一連の処理ステップを順次つなげた線形フローです。
このアプローチでは、LLM はあくまで「処理ノードの一つ」であり、全体の制御権は開発者が書いたコードにあります。たとえば、ユーザー入力の前処理 → LLM 推論 → 出力整形 の3段階フローは、以下のように表現できます:
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
# プロンプトテンプレート定義
template = ChatPromptTemplate.from_messages([("user", "{query}")])
# LLM クライアント(例:Qwen-Plus via Alibaba Cloud)
llm = ChatOpenAI(
model="qwen-plus",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
api_key="your_api_key_here"
)
# 出力パーサー
parser = StrOutputParser()
# チェイン構築:template → llm → parser
pipeline = template | llm | parser
# 実行
result = pipeline.invoke({"query": "日本の四季について簡潔に説明してください"})
print(result) # 出力例: "春は桜、夏は祭り、秋は紅葉、冬は雪…"
このチェインは堅牢ですが、柔軟性に欠けます。途中で LLM の出力が不適切だった場合、例外処理や再試行ロジックを手動で組み込む必要があります。つまり、LLM は判断せず、ただ与えられたタスクを遂行する「作業員」に過ぎません。
LangGraph:状態駆動型の反復的制御
複雑なタスクに対応するためには、静的なチェインを超えた動的な制御が必要です。LangGraph は、これを 状態(State) と グラフベースの遷移(Graph-based Transitions) で実現します。
ここでは、LLM が「指揮者」として振る舞い、現在の状態に基づいて次のアクションを選択・実行します。以下は、作家推薦とその文体でのジョーク生成という2ステップタスクの実装例です:
from typing import TypedDict, Annotated, Optional
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from langchain_community.chat_models import ChatTongyi
class WorkflowState(TypedDict):
writer: Optional[str]
humor: Optional[str]
def recommend_writer(state: WorkflowState) -> dict:
response = ChatTongyi(model="qwen-plus", api_key="...").invoke(
"人気のある現代作家を一人、名前のみで答えてください。"
)
return {"writer": response.content.strip()}
def generate_humor(state: WorkflowState) -> dict:
prompt = f"作家『{state['writer']}』の文体を真似て、100文字以内のユーモアを書いてください。"
response = ChatTongyi(model="qwen-plus", api_key="...").invoke(prompt)
return {"humor": response.content.strip()}
# グラフ構築
builder = StateGraph(WorkflowState)
builder.add_node("writer_step", recommend_writer)
builder.add_node("humor_step", generate_humor)
builder.add_edge(START, "writer_step")
builder.add_edge("writer_step", "humor_step")
builder.add_edge("humor_step", END)
# チェックポイント保存機能を有効化
memory = InMemorySaver()
graph = builder.compile(checkpointer=memory)
実行後、結果を確認すると:
config = {"configurable": {"thread_id": "demo-001"}}
final_state = graph.invoke({}, config)
print("作家:", final_state["writer"]) # → 村上春樹
print("ジョーク:", final_state["humor"]) # → 「猫が村上春樹の登場人物だと告白…」
さらに重要なのは、状態の編集と再実行が可能である点です。たとえば、作家を「村上春樹」から「郭德纲」に差し替えて再実行すれば、そのスタイルに合わせた新しいジョークが即座に生成されます。
# 特定ステップの状態を更新
history = list(graph.get_state_history(config))
target_state = history[1] # writer_step の直後の状態
updated_config = graph.update_state(
target_state.config,
{"writer": "郭德纲"}
)
# 再実行(humor_step から再開)
revised = graph.invoke(None, updated_config)
print(revised["humor"]) # → 郭德纲風のコメディ出力
このように、LangGraph は「LLM + 状態保持 + 条件付きループ + ノード間遷移」という四要素を統合し、真の意味での自律的エージェントを構築する基盤を提供します。
エージェント設計の三つの基本パターン
実用的なエージェントは、以下の三つの再利用可能な構造パターンを組み合わせて構築されます。
- リフレクション(Reflection):
出力を自己評価し、不十分であれば再生成を繰り返すループ。コード生成や論理検証に有効。 - ツールルーティング(Tool Routing):
LLM がクエリ内容を分析し、適切な外部ツール(検索API、計算エンジン、DB接続など)を動的に選択・呼び出す。 - スーパーバイザーアーキテクチャ(Supervisor Pattern):
上位のLLMがタスクをサブエージェント(専門ノード)に分割・割り当て、進捗を監視・調整する。マルチステップ・マルチエキスパートなワークフローに最適。
これらのパターンは、ネストや並列実行により複雑化できます。たとえば、「コード生成エージェント」内部にリフレクションループを持たせ、「ドキュメント調査エージェント」にツールルーティングを組み込むことで、高信頼性の開発支援システムが構築可能です。
まとめ:エージェントとは「制御の抽象化」である
LangChain は LLM を「関数として扱う」手法を示しました。一方、LangGraph は LLM を「状態を持つプログラムの中心制御ユニット」として位置づけ、実行時における意思決定と修正を可能にします。
したがって、エージェントの本質は、LLM の推論能力を、状態管理と制御フローというソフトウェア工学の原則で包摂することにあります。これは、単なるプロンプトエンジニアリングではなく、AI ベースのアプリケーション設計という新たな分野の誕生を意味します。