Large LangGraph applications become easier to reason about when a group of nodes has its own state contract. A compiled subgraph can occupy one node in a parent StateGraph, so the parent delegates that section while keeping the surrounding route explicit.
Direct composition works when the parent and child schemas share the keys that cross their boundary. The child can also define private keys for its internal nodes; those keys stay out of the parent's returned state when the parent schema does not include them.
The model-free program uses request and result as the shared interface, while normalized exists only inside the child. Its printed node order and final dictionary show that execution returns to the parent after the child updates the shared result.
Related: Build a state graph in LangGraph
Related: Install LangGraph with pip
from typing_extensions import TypedDict from langgraph.graph import END, START, StateGraph class ParentState(TypedDict): request: str result: str class SubgraphState(TypedDict): request: str result: str normalized: str
def normalize_request(state: SubgraphState) -> dict: print("subgraph: normalize") return {"normalized": state["request"].upper()} def approve_request(state: SubgraphState) -> dict: print("subgraph: approve") return {"result": f'{state["normalized"]}: approved'}
subgraph_builder = StateGraph(SubgraphState) subgraph_builder.add_node("normalize", normalize_request) subgraph_builder.add_node("approve", approve_request) subgraph_builder.add_edge(START, "normalize") subgraph_builder.add_edge("normalize", "approve") subgraph_builder.add_edge("approve", END) review_subgraph = subgraph_builder.compile()
def prepare_request(state: ParentState) -> dict: print("parent: prepare") return {"request": state["request"].strip()} def finish_request(state: ParentState) -> dict: print(f'parent: finish ({state["result"]})') return {}
parent_builder = StateGraph(ParentState) parent_builder.add_node("prepare", prepare_request) parent_builder.add_node("review", review_subgraph) parent_builder.add_node("finish", finish_request) parent_builder.add_edge(START, "prepare") parent_builder.add_edge("prepare", "review") parent_builder.add_edge("review", "finish") parent_builder.add_edge("finish", END) parent_graph = parent_builder.compile()
The compiled child can be passed directly to add_node() because request and result exist in both state schemas. A wrapper node is required when the schemas need input or output transformation.
final_state = parent_graph.invoke({"request": " order-42 ", "result": ""}) print(final_state)
$ python subgraph_demo.py
parent: prepare
subgraph: normalize
subgraph: approve
parent: finish (ORDER-42: approved)
{'request': 'order-42', 'result': 'ORDER-42: approved'}