RemoteGraph とサブグラフネスト:コンパイル済みグラフをノードとして使う
役割
LangGraph はコンパイル済みのグラフ (ローカルの Pregel インスタンスやリモートの RemoteGraph) を別のグラフのノードとして使うことをサポートします。RemoteGraph(RemoteGraph:118) は PregelProtocol のクライアント実装で、LangGraph Server API を通じて遠隔デプロイされたグラフを呼び出します。ローカルの StateGraph.compile() の産物自体が Pregel (PregelProtocol でもある) なので、そのまま parent_graph.add_node(child_graph) で組み込めます。
この層は「ネストされたグラフ」を抽象化します。親グラフがこのノードにスケジュールした時、背後がローカル Pregel でも遠隔 HTTP API でも、統一された invoke / stream インターフェースを走ります。PregelNode は find_subgraph_pregel(find_subgraph_pregel:47) でノードの runnable の中を再帰的に Pregel インスタンスを探し、それを node.subgraphs にぶら下げます。これにより親グラフの get_subgraphs がサブグラフ (subgraph) を列挙でき、stream(subgraphs=True) でサブグラフ内部のイベントを取得できます。
設計動機
- 分散デプロイ:大きなグラフはしばしば複数のサービス (対話管理、検索、ツール呼び出し) に分割され、各サービスが 1 つの LangGraph デプロイになり、親グラフが
RemoteGraphでそれらをワークフローに繋ぎます。各サービスは独立にスケールし、独立にアップグレードでき、親グラフの再デプロイは不要です。 - ネストチェックポイントの独立性:
add_node(compiled_graph)の後、サブグラフは独自の checkpointer を持てます (compile(checkpointer=...)) し、親グラフから継承もできます。サブグラフの thread_id は親グラフがCONFIG_KEY_CHECKPOINT_NSで自動的に namespace プレフィックスを付けます (parent_node:<task_id>|child_node:<task_id>)。これにより親子グラフの checkpoint が混ざりません。 PregelProtocolで統一インターフェース:ローカルPregelもRemoteGraphもPregelProtocolを実装します——invoke/stream/astream/get_state/aget_state/get_subgraphsなどのメソッドシグネチャが一致します。親グラフは個別に区別する必要がありません。- stream のパススルー:
subgraphs=Trueを有効にすると、親グラフの_outputはデキュー時にイベントに namespace プレフィックスを付け (get_subgraphs:1076)、呼び出し側は(ns, mode, payload)の 3 要組を受け取り、このイベントがどのネスト階層のどのノードから来たか分かります。 Command.PARENTで階層横断:サブグラフのノードがreturn Command(graph=Command.PARENT, update=...)で更新を親グラフの状態に書けます——pending writes を通じて親子グラフ間で伝え、親グラフの_firstが後処理でサブグラフからの報告 writes を処理します。
主要ファイル
RemoteGraph:118—RemoteGraph(PregelProtocol)クライアント。assistant_id/client/sync_client/nameを持ちます。RemoteGraph.__init__:132—url/api_key/headers/client/sync_clientを受け取り、デフォルトはget_client/get_sync_clientで生成します。RemoteGraph.stream:757— 同期ストリーミングの入り口。リクエストをsync_client.run_streamに転送します。RemoteGraph.astream:912— 非同期ストリーミングの入り口。client.run_streamの非同期イテレータに転送します。RemoteGraph.invoke:1133— 同期収束の入り口。内部はstreamで最終値を取得します。find_subgraph_pregel:47— ノードのboundrunnable の中を再帰的にPregelProtocolインスタンスを探し、サブグラフを識別します。PregelNode:97—PregelNodeクラス。subgraphs: Sequence[PregelProtocol]フィールドを持ちます。PregelNode subgraphs init:180—PregelNode.__init__がfind_subgraph_pregel(self.bound)を呼んでサブグラフを自動識別します。get_subgraphs:1076— 親グラフが直接サブグラフを列挙し、recurse=Trueの場合は下に再帰します。attach_node:1431— コンパイル時にadd_nodeで登録された action をPregelNode(bound=node.runnable, ...)に変換します。サブグラフ識別はこのステップで発生します。coerce_to_runnable:529— 任意のRunnableインスタンス (Pregel/RemoteGraphを含む) は直接 return されます。これが「compiled graph as node」の入り口です。
データフロー
add_node(compiled_child_graph) は coerce_to_runnable(coerce_to_runnable:529) を経由します:
def coerce_to_runnable(
thing: RunnableLike, *, name: str | None, trace: bool
) -> Runnable:
"""Coerce a runnable-like object into a Runnable."""
if isinstance(thing, Runnable):
return thing
elif is_async_generator(thing) or inspect.isgeneratorfunction(thing):
return RunnableLambda(thing, name=name)
elif callable(thing):
# ...
elif isinstance(thing, dict):
return RunnableParallel(thing)
else:
raise TypeError(...)Pregel と RemoteGraph はどちらも Runnable を継承するため、最初の分岐に入り直接返されます。PregelNode.__init__ は attach_node 段階で find_subgraph_pregel(self.bound)(find_subgraph_pregel:47) を呼びます:
def find_subgraph_pregel(candidate: Runnable) -> PregelProtocol | None:
from langgraph.pregel import Pregel
candidates: list[Runnable] = [candidate]
for c in candidates:
if (
isinstance(c, PregelProtocol)
# subgraphs that disabled checkpointing are not considered
and (not isinstance(c, Pregel) or c.checkpointer is not False)
):
return c
elif isinstance(c, RunnableSequence) or isinstance(c, RunnableSeq):
candidates.extend(c.steps)
elif isinstance(c, RunnableLambda):
candidates.extend(c.deps)
elif isinstance(c, RunnableCallable):
if c.func is not None:
candidates.extend(
nl.__self__ if hasattr(nl, "__self__") else nl
for nl in get_function_nonlocals(c.func)
)
elif c.afunc is not None:
candidates.extend(
nl.__self__ if hasattr(nl, "__self__") else nl
for nl in get_function_nonlocals(c.afunc)
)
return Noneこれは RunnableSequence / RunnableLambda / RunnableCallable の中を再帰的に探します——ノードが RunnableLambda で Pregel を呼び出すように包まれていても識別できます。識別されたサブグラフは self.subgraphs = [subgraph] にぶら下げられ、親グラフの get_subgraphs()(get_subgraphs:1076) で列挙できるようになります。
実行時、親グラフの PregelRunner.tick がサブグラフ Pregel の stream / astream (または RemoteGraph の対応メソッド) を呼び、サブグラフ内部で独自の Pregel ループを走らせます。産出されたイベントは親グラフの stream(subgraphs=True) でパススルーされます——親グラフはサブグラフ呼び出し時に CONFIG_KEY_CHECKPOINT_NS にノード名プレフィックスを付け、サブグラフのイベントはこの namespace を持って親グラフの stream キューに書き戻され、親グラフの _output がデキュー時にローカルイベントとマージします。
境界と失敗
- checkpointer を無効化したサブグラフは識別されない:
find_subgraph_pregelはc.checkpointer is Falseの Pregel を明示的にスキップします (skip no-checkpointer:54)——このような「ステートレスサブグラフ」はサブグラフとして扱われず、親グラフのget_subgraphsには見えず、イベントも namespace を持ちません。 RemoteGraphには最低urlかclientが必要:__init__でclientとsync_clientがどちらもNoneの場合、urlを渡していないと 2 つの client が生成されず、その後の_validate_client()がraise ValueErrorします(validate client:181)。RemoteGraphはすべてのstream_modeをサポートするわけではない:_reject_v3_unsupportedが一部のパラメータ組み合わせを拒否します。遠隔 API の stream プロトコルの制限はローカル Pregel より多いです(reject v3:195)。- ネスト namespace は
NS_SEPを使用:namespace フォーマットはparent_node:<task_id>+NS_SEP+child_node:<task_id>で、下に再帰します。add_node時にはノード名にNS_SEPが含まれていると拒否します (reject NS_SEP:794)、namespace の曖昧さを避けるためです。 - サブグラフの
interrupt()は namespace 沿いに伝播:サブグラフのノードがinterrupt()でGraphInterruptを送出すると、親グラフのtickがサブグラフの pending interrupt を検出し、親グラフも interrupt 状態に入ります。クライアントが resume する際、Command(resume=...)はCONFIG_KEY_RESUME_MAPで namespace ごとに対応サブグラフにルーティングされます(Command resume mapping:904)。 Command.PARENTの境界:サブグラフのノードがreturn Command(graph=Command.PARENT, ...)する時、サブグラフのmap_commandは直接raise InvalidUpdateError("There is no parent graph")します(PARENT guard:58)——これは実際にはフォールバックで、通常のパスはサブグラフのattach_node時に_get_updatesmapper がCommand.PARENTをスキップし、そのままサブグラフの pending writes に書き込み、親グラフが_firstで識別してルーティングします。
まとめ
RemoteGraph で LangGraph はクロスサービス、クロスプロセスのグラフネストをサポートします。ローカル Pregel のネストは find_subgraph_pregel で自動識別されます——どちらも PregelProtocol を経由します。親グラフの stream(subgraphs=True) は namespace でサブグラフイベントをパススルーします。サブグラフのイベントストリームの詳細は /stream/run-stream、サブグラフのノードスケジュールは /pregel/pregel を参照してください。公式資料:LangGraph ドキュメント · README