第16回: LangGraph Platform — 書いたエージェントを REST API として配るしくみ¶
Software Design 2025年2月号 連載第16回。これまでの回 (09〜15) では「LangGraph で組んだエージェントを CLI / Streamlit で自分のマシンで動かす」段階だったが、本回は エージェントを Docker でパッケージし、REST API + Web UI として動かす ためのツールキット LangGraph Platform が主役。
エージェント本体のコードは最小 (call_model + tool_node だけの 2 ノード ReAct)。新規ポイントは「コードではなく、配布の仕組み」 にある。
全体像¶
flowchart TD
Dev["開発者: my_agent/agent.py に graph を書く"] --> Config["langgraph.json<br>(エントリポイントを記述)"]
Config --> CLI["langgraph CLI: langgraph up"]
CLI --> Docker["Docker Compose を生成 + 起動"]
subgraph DockerEnv["Docker (ローカル or Cloud)"]
Server["LangGraph Server<br>(FastAPI ベース)"]
Postgres["Postgres<br>(checkpointer/threads 永続化)"]
Redis["Redis<br>(pub-sub / task queue)"]
Server --- Postgres
Server --- Redis
end
Docker --> DockerEnv
DockerEnv -->|REST API :8123| Client1["Python SDK"]
DockerEnv -->|REST API :8123| Client2["TypeScript SDK"]
DockerEnv -->|baseUrl=:8123| Studio["LangSmith Studio (Web)<br>https://smith.langchain.com/studio<br>(ブラウザ UI でグラフ可視化)"]
書いたエージェント (graph オブジェクト) を langgraph.json で指し、langgraph up で Docker 一式が立ち上がる。あとは REST API でどの言語からでも呼べる + Studio UI でステップ実行できる。Streamlit が UI 一体型のローカル実行だったのに対し、こちらはサービス化。
エージェント内部 (本回サンプルの graph) はシンプル:
flowchart LR
Start([START]) --> CallModel["call_model<br>LLM 呼び出し"]
CallModel -->|tool_calls あり| ToolNode["tool_node<br>Tavily 検索実行"]
CallModel -->|tool_calls なし| End([END])
ToolNode --> CallModel
第11回 ReAct と同じ構造。違いは:
- 11回は create_react_agent ショートカット使用 → 16回は StateGraph でフル明示
- 11回はモデル固定 → 16回は config_schema=GraphConfig で OpenAI / Anthropic を呼び出し時に切替
使用ライブラリ・原理¶
1. LangGraph Platform とは¶
LangChain 社が提供する 「LangGraph で書いたエージェントを本番運用するための実行環境 + デプロイツール」 一式の総称:
| 構成要素 | 役割 |
|---|---|
langgraph-cli |
CLI ツール。langgraph build / langgraph up / langgraph deploy |
| LangGraph Server | FastAPI ベースの実行サーバ。グラフを REST API 化 (Threads, Runs, Streaming) |
| Postgres | thread (会話セッション) と checkpoint (グラフ状態) の永続化 |
| Redis | pub-sub / バックグラウンドジョブキュー |
| LangGraph Studio | ブラウザ UI。グラフ構造の可視化 + ステップ実行デバッガ |
これまで自前で書いていた「Streamlit + MemorySaver」(14回) の本番版が LangGraph Server + Postgres に置き換わるイメージ。
2. langgraph.json がデプロイの起点¶
3 行で何を伝えているか:
- dependencies: Docker イメージにインストールする Python パッケージ。"." は pyproject.toml の依存全部
- graphs: 公開する graph オブジェクト。agent はエンドポイント名で、./my_agent/agent.py:graph のパス + 変数名を指す
- env: コンテナに注入する環境変数ファイル
langgraph build はこれを読んで Dockerfile を自動生成する。手で Dockerfile を書く必要なし。
3. langgraph up の中身¶
langgraph up 一発で:
1. Dockerfile 自動生成 → Docker イメージビルド
2. docker-compose.yml も自動生成 (langgraph-api / postgres / redis の 3 サービス)
3. compose up
4. http://localhost:8123 に REST API + ドキュメント (/docs)
5. ブラウザで https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:8123 を開くと LangSmith Studio (Web) がローカル API に接続してグラフを可視化
重要: Studio UI は独立した別ポートのサーバではない。LangSmith Cloud 側にホストされた Studio Web アプリに、
baseUrlクエリパラメータでローカル LangGraph API を指す形。langgraph up起動時のログにもこの URL が表示される。
ローカル開発 (up) と本番デプロイ (deploy for LangSmith) で同じイメージが使えるのがポイント。Docker を介すので Python のバージョン差・ライブラリ衝突がない。
4. REST API: Threads と Runs の概念¶
LangGraph Server が公開するエンドポイントは主に 2 種類:
POST /threads # 新しい会話セッションを作成
POST /threads/{id}/runs # そのセッションでグラフを実行
GET /threads/{id}/state # 現在の State を取得
POST /threads/{id}/runs/stream # ストリーミング実行
thread_id は 14回で見た LangGraph の checkpointer の thread_id と同じ。サーバ側で thread ごとに Postgres に状態保存。クライアントは thread_id を引き回すだけで会話継続できる。
5. config_schema=GraphConfig でランタイム切替¶
class GraphConfig(BaseModel):
ANTHROPIC: ClassVar[str] = "anthropic"
OPENAI: ClassVar[str] = "openai"
model_name: Literal["anthropic", "openai"]
workflow = StateGraph(AgentState, config_schema=GraphConfig)
ANTHROPIC / OPENAI は ClassVar[str] のクラス定数で Pydantic フィールド扱いされない。_get_model 内で if model_name == GraphConfig.OPENAI: のようにマジック文字列ではなく定数参照で書ける → タイポ防止 + IDE 補完が効く。model_name フィールドの Literal["anthropic", "openai"] と値が一致している必要がある(ここはハードコード重複なので、定数から Literal を生成できれば本来は理想)。
config_schema は runs API 経由でクライアントが渡せるパラメータの型。クライアント側で:
client.runs.create(
thread_id=...,
assistant_id="agent",
input={"messages": [...]},
config={"configurable": {"model_name": "openai"}}, # ← ここ
)
このパラメータが call_model 内の config.get("configurable", {}).get("model_name", ...) で取れる。「同じグラフで複数のモデルを切り替える」運用がコード変更なしでできる。
6. @lru_cache(maxsize=2) でモデルクライアントを再利用¶
@lru_cache(maxsize=2)
def _get_model(model_name: str):
if model_name == GraphConfig.OPENAI:
model = ChatOpenAI(temperature=0, model_name="gpt-4o")
elif model_name == GraphConfig.ANTHROPIC:
model = ChatAnthropic(temperature=0, model_name="claude-3-5-sonnet-20241022")
model = model.bind_tools(tools)
return model
サーバが多数のリクエストを処理する想定では、毎回 ChatOpenAI() を new するとコネクションプール初期化コストが嵩む。LRU キャッシュで 2 種類のクライアントをプロセス内で使い回し。
7. add_messages で messages を append 専用に¶
add_messages は LangGraph 公式の Reducer 関数。operator.add (list 連結) と似ているが、追加機能:
- BaseMessage の ID で重複排除
- メッセージ更新 (同じ ID で別内容を渡すと上書き)
会話履歴を保持する State には事実上必須の Reducer。第14回の operator.add は単純連結だったが、こちらの方が堅牢。
8. Sequence[BaseMessage] を使う理由¶
list[BaseMessage] ではなく Sequence を使うのは、LangChain が返す list / tuple 両方を許容したいから。tuple は immutable で安全、list は append しやすい。Sequence にしておくと「読み取り専用 list-like」として両方受け入れられる。
ファイル別の役割¶
| ファイル | 役割 |
|---|---|
langgraph.json |
LangGraph Platform 設定。graph エントリポイント + env ファイル + 依存 |
pyproject.toml |
uv プロジェクト定義。langgraph-cli を dev-dependencies に置く設計 |
uv.lock |
依存固定 |
my_agent/__init__.py |
パッケージマーカー (空) |
my_agent/agent.py |
グラフ本体。ReAct 2 ノードと graph = workflow.compile() でエントリ変数定義 |
README.md |
.env 設定 + langgraph up 起動方法 |
学んだこと(要点)¶
1. これまでとパラダイムが違う: 「アプリケーション」から「サービス」へ¶
01〜15回は「1 つのスクリプトを python xxx.py / streamlit run xxx.py で起動」だった。16 は:
- グラフを API として永続稼働させる (POST すると実行される)
- 複数クライアント (Python / TypeScript / curl) から同時にアクセス可能
- 状態は Postgres に永続化、サーバ再起動で消えない
- Studio UI で外部からデバッグ・観測
つまり 「LLM エージェントをマイクロサービス化するための定石」 が LangGraph Platform。
2. langgraph.json の 3 行が「BFF パターンの省略」¶
普通 API サーバを書くなら: - FastAPI / Express でエンドポイント定義 - pydantic / zod でリクエスト型定義 - middleware / auth / logging を書く - DB マイグレーション書く
LangGraph Platform はこれを langgraph.json の 3 行 + グラフ実装 に圧縮した。LLM エージェント特化なので、汎用 BFF を書くより 10 倍速い。トレードオフは「カスタマイズ性が低い」。
3. config_schema は「マルチテナント」「A/B テスト」の鍵¶
model_name だけでなく、temperature、max_tokens、ユーザー設定、フィーチャーフラグなど、ランタイムで挙動を変えたい値はすべて config_schema に置くのが定石。
LangGraph Studio は config_schema を読んで自動で UI フォームを生成する → 開発中のパラメータチューニングが即座にできる。
4. ローカル up と本番 deploy の互換性が運用負荷を下げる¶
普通の Web アプリは「ローカル動作と本番環境のギャップ」が一大バグ要因 (パッケージバージョン、env、Postgres バージョン...)。LangGraph Platform は 同じイメージ + 同じスキーマでローカル⇄本番が動くので、ローカルで動けば本番でも動く確率が高い。
5. このサンプルは「ベース動作確認用」¶
エージェントロジック自体は超シンプル (検索ツール 1 個の ReAct)。ここで学ぶのは「いかに薄くても、サービスとしてデプロイできる」という枠組み。本回のエージェントを差し替えて、14回の HITL ロジックや 12回の ARAG ロジックを乗せれば、即サービス化できる。
6. なぜ Sequence + add_messages か¶
LangGraph Platform でグラフをサービス化すると、API クライアントが送ってくる messages の型が controllable でなくなる (list か tuple か)。Sequence は両方受ける。add_messages は会話継続のため必須。サービス前提の堅牢な書き方。
7. langgraph-cli は dev-dependencies に置く¶
langgraph-cli は開発・デプロイ時にしか使わない。ランタイムイメージに含めないようにするためわざわざ dev に分ける。サンプルではあるが、本番運用を意識した分離。
ただし README は
pip install -U langgraph-cliを別途案内している。これは「uv syncだと dev-dependencies はインストールされるが、本回のようにuv sync後すぐlanggraph upを叩く場合、uv run langgraph upでないと PATH に入らない」混乱を避けるための保険。本来はuv run langgraph upかuv tool install langgraph-cliが綺麗。
拡張アイデア¶
- Streamlit (14回) の UI を本回のサーバに繋ぎ替える
- 14回の
HumanInTheLoopAgentクラスをmy_agent/agent.py:graphに移植 - Streamlit 側を REST API クライアントに書き換え (LangGraph Python SDK)
-
結果: グラフ実装と UI が完全分離、複数 UI (Streamlit / Slack bot / Web) を同じバックエンドで運用可能
-
config_schemaを拡張して A/B テスト model_nameに加えtemperature,system_prompt_versionを追加-
50% は新プロンプト、50% は旧プロンプトで A/B → LangSmith Evaluator で評価
-
langgraph deployでクラウドデプロイ - LangSmith アカウント + GitHub repo 接続で、push トリガーで自動デプロイ
-
サンプルを fork → 自分の LangSmith にデプロイして実機で動かしてみる
-
Postgres を Aurora / Cloud SQL に差し替え
docker-compose.ymlの Postgres を外部接続に変更-
本番運用想定の永続化検証
-
Studio UI で HITL を動かしてみる
- 14回の
interrupt_before=["human_approval"]を本回エージェントに追加 -
Studio UI 上で承認ボタンが出るので、Streamlit 不要で HITL デモが可能
-
複数 graph を
langgraph.jsonに登録 "graphs": {"agent_v1": "...", "agent_v2": "..."}で 2 種類同居- Canary release / Blue-Green デプロイのベース
現代版に移植するなら¶
連載原典は LangGraph 0.2.50 / langgraph-cli 0.1.56 (2024年末) で、既に本リポジトリの想定範囲。本リポジトリ方針 (1Password 経由のキー取得) に揃えるなら以下:
1. API キー取得を op 経由に¶
LangGraph Platform は .env をコンテナに inject するアーキテクチャ。ローカル up 時は .env を op inject で生成するのが一案:
.env.template:
OPENAI_API_KEY={{op://Personal/openAI_API/credential}}
TAVILY_API_KEY={{op://Personal/Tavily_API_key/credential}}
ANTHROPIC_API_KEY={{op://Personal/Anthropic/credential}}
LANGSMITH_TRACING=true
LANGSMITH_API_KEY={{op://Personal/LangSmith/credential}}
LANGSMITH_PROJECT=sd-16
ただし op inject は 書き込み系なのでリポジトリ標準では未許可。実行時は人間がターミナルで直接叩く想定。
ただし生成された .env を gitignore に確実に入れること。
2. モデル名を最新に¶
| 旧 | 新 |
|---|---|
gpt-4o |
gpt-4o-2024-08-06 以降を明示、または gpt-4o-mini |
claude-3-5-sonnet-20241022 |
claude-sonnet-4-6 (Sonnet 4.6) または claude-haiku-4-5 |
3. langgraph-cli のアップデート¶
langgraph-cli==0.1.56 (2024年末) → 最新の langgraph-cli>=0.2 を確認。Docker compose 構成や CLI コマンドが変わる可能性あり。
4. LangSmith 統合の env 名変更¶
連載原典は LANGCHAIN_TRACING_V2 / LANGCHAIN_API_KEY だが、現行は LANGSMITH_TRACING / LANGSMITH_API_KEY (README に LANGSMITH_TRACING_V2 と書かれているが正確には LANGSMITH_TRACING=true のみ)。
既知の不具合・注意点¶
- 【実機で踏んだ】
call_model(state, config: GraphConfig)は 0.8+ ランタイムでTypeError: missing 1 required positional argument: 'config'になる — サーバ側のlanggraph-api 0.8.7は、ノード関数の第 2 引数をRunnableConfig型で注釈した場合のみ config を自動注入する。サンプル原典はconfig: GraphConfig(Pydantic 型) のままなので注入されず落ちる。修正は1行:本リポジトリでは適用済み。from langchain_core.runnables import RunnableConfig def call_model(state: AgentState, config: RunnableConfig) -> dict:GraphConfigはconfig_schema側でのみ使われ、ノード関数の引数型としては使わないのが正しい - 【実機で踏んだ】Studio UI から「質問」しても
pydantic ValidationError: model_name Field requiredで落ちる —GraphConfig.model_nameをLiteral[...]の必須フィールドにしていると、Studio が Configurable フォーム未編集のまま Submit を許してしまい、空 dict{}でGraphConfig(**{})を作ろうとして validation 失敗。本リポジトリではField(default="openai")を付けて回避済み: 必須にしておくと Studio の初回起動でほぼ確実に踏む罠なので、config_schemaのフィールドは原則 default を持たせるのが安全 - README が 古い形式の env 名 (
LANGSMITH_TRACING_V2) で書かれている。最新の LangSmith はLANGSMITH_TRACING=true langgraph upは Docker Desktop 必須。Apple Silicon Mac で Docker Desktop / OrbStack を入れていないと動かない- 初回
langgraph upは Docker イメージビルド + 各種ダウンロードで 数分かかる langgraph upを foreground で動かしている状態でdocker restart 16-langgraph-api-1をかけると、CLI 側が compose の終了を検知して全コンテナごと落ちる。コード変更後はlanggraph upを一度止めてから再実行するのが安全langgraph.jsonのdependencies: ["."]は pyproject.toml を見るが、uv ロックファイル (uv.lock) は無視 → 再現性のためlanggraph-cliのバージョンを上げるとロック相当の機能 (langgraph build --pip-installer pip) を使うのが安全_get_modelの戻り型ChatOpenAI | ChatAnthropicはbind_tools後のRunnableだが、型注釈は元のBaseChatModelのままになる → mypy strict なら警告TavilySearchResultsはlangchain-community0.3.25 で deprecated。langchain-tavilyパッケージへの移行が推奨model_name=...引数はChatOpenAI/ChatAnthropicの旧 API。新しめではmodel=...が推奨 (両方動くが今後 deprecate 予定)
実機で動かしたメモ (2026-05-23)¶
uv run langgraph up --port 8123 で起動し、REST API 経由で ReAct を一周させた手順:
# 1. .env を 1Password から生成 (op read で値を直接ファイル redirect)
{
printf 'OPENAI_API_KEY=%s\n' "$(op read 'op://Personal/openAI_API/credential')"
printf 'ANTHROPIC_API_KEY=%s\n' "$(op read 'op://Personal/anthropic_api_key/credential')"
printf 'TAVILY_API_KEY=%s\n' "$(op read 'op://Personal/Tavily_API_key/credential')"
printf 'LANGSMITH_API_KEY=%s\n' "$(op read 'op://Personal/lang_smith_api_key/credential')"
printf 'LANGSMITH_TRACING=true\n'
printf 'LANGSMITH_PROJECT=sd-16\n'
} > .env && chmod 600 .env
# 2. サーバ起動 (初回は Docker イメージ pull で数分)
uv run langgraph up --port 8123
# 3. 別シェルで API を叩く
THREAD=$(curl -s http://localhost:8123/threads -X POST \
-H "Content-Type: application/json" -d '{}' \
| python3 -c "import json,sys; print(json.load(sys.stdin)['thread_id'])")
curl -s "http://localhost:8123/threads/$THREAD/runs/wait" \
-X POST -H "Content-Type: application/json" \
-d '{
"assistant_id": "agent",
"input": {"messages": [{"role": "user", "content": "東京の今日の天気をTavilyで調べて、1文で簡潔に答えてください。"}]},
"config": {"configurable": {"model_name": "openai"}}
}'
# 4. 結果確認
curl -s "http://localhost:8123/threads/$THREAD/state" | jq '.values.messages'
得られた message 列 (4 件): human → ai(tool_call=tavily_search_results_json) → tool(検索結果) → ai(要約回答) の ReAct パターン通り。config.model_name=openai で OpenAI、anthropic で Anthropic に runtime 切替できることも確認。
ハマったこと:
- Anthropic クレジット残高切れ で BadRequestError: Your credit balance is too low → OpenAI 側で動かして検証
- 1Password の LangSmith 項目名は LangSmith ではなく lang_smith_api_key (snake_case)
- 上記 RunnableConfig 問題
記事参照¶
- Software Design 2025年2月号 連載第16回「LangGraph Platform」
- 関連:
- 14回 Streamlit HITL — UI 一体型の前段。Platform 化すると UI とロジックが分離する
- 11回 ReAct — 本回エージェントのロジック原型
- LangGraph Platform 公式: https://langchain-ai.github.io/langgraph/cloud/
- LangGraph CLI 公式: https://langchain-ai.github.io/langgraph/cloud/reference/cli/
作成: 2026-05-22 / 最終更新: 2026-06-10