Skip to content

RemoteGraph とサブグラフネスト:コンパイル済みグラフをノードとして使う

源码版本1.2.9

役割

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 インターフェースを走ります。PregelNodefind_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 で統一インターフェース:ローカル PregelRemoteGraphPregelProtocol を実装します——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:118RemoteGraph(PregelProtocol) クライアント。assistant_id / client / sync_client / name を持ちます。
  • RemoteGraph.__init__:132url / 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 — ノードの bound runnable の中を再帰的に PregelProtocol インスタンスを探し、サブグラフを識別します。
  • PregelNode:97PregelNode クラス。subgraphs: Sequence[PregelProtocol] フィールドを持ちます。
  • PregelNode subgraphs init:180PregelNode.__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) を経由します:

python
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(...)

PregelRemoteGraph はどちらも Runnable を継承するため、最初の分岐に入り直接返されます。PregelNode.__init__attach_node 段階で find_subgraph_pregel(self.bound)(find_subgraph_pregel:47) を呼びます:

python
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 がサブグラフ Pregelstream / astream (または RemoteGraph の対応メソッド) を呼び、サブグラフ内部で独自の Pregel ループを走らせます。産出されたイベントは親グラフの stream(subgraphs=True) でパススルーされます——親グラフはサブグラフ呼び出し時に CONFIG_KEY_CHECKPOINT_NS にノード名プレフィックスを付け、サブグラフのイベントはこの namespace を持って親グラフの stream キューに書き戻され、親グラフの _output がデキュー時にローカルイベントとマージします。

境界と失敗

  • checkpointer を無効化したサブグラフは識別されない:find_subgraph_pregelc.checkpointer is False の Pregel を明示的にスキップします (skip no-checkpointer:54)——このような「ステートレスサブグラフ」はサブグラフとして扱われず、親グラフの get_subgraphs には見えず、イベントも namespace を持ちません。
  • RemoteGraph には最低 urlclient が必要:__init__clientsync_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_updates mapper が Command.PARENT をスキップし、そのままサブグラフの pending writes に書き込み、親グラフが _first で識別してルーティングします。

まとめ

RemoteGraph で LangGraph はクロスサービス、クロスプロセスのグラフネストをサポートします。ローカル Pregel のネストは find_subgraph_pregel で自動識別されます——どちらも PregelProtocol を経由します。親グラフの stream(subgraphs=True) は namespace でサブグラフイベントをパススルーします。サブグラフのイベントストリームの詳細は /stream/run-stream、サブグラフのノードスケジュールは /pregel/pregel を参照してください。公式資料:LangGraph ドキュメント · README