Tutorials Logic, IN info@tutorialslogic.com

LangGraph Functional API and Subgraphs: Entrypoints, Tasks, Replay, and Composition

LangGraph Functional API and Subgraphs

LangGraph provides two complementary ways to express orchestration. The Graph API makes state, nodes, and edges explicit. The Functional API lets ordinary Python control flow become durable by decorating an entrypoint and the operations that must be recorded as tasks.

Neither API is universally better. The Graph API is strongest when workflow topology should be visible and inspectable. The Functional API is attractive when the process reads more naturally as conditionals, loops, and function calls.

Subgraphs add composition. A parent graph can delegate a bounded workflow to another graph while preserving clear state and persistence rules. This is useful for reusable specialists, domain modules, and multi-agent systems.

Mental Model

Think of the Graph API as a visible workflow map and the Functional API as durable Python control flow. Tasks record unstable work; subgraphs package reusable workflows.

Replay Safety: Durable execution resumes by replaying workflow code from a saved point. Isolate side effects and non-deterministic work so replay does not repeat completed external actions.

Graph API Versus Functional API

Choose the Graph API when named nodes, routes, visualization, and state transitions are central to understanding the business process. Choose the Functional API when the workflow is easier to read as normal Python and you still need persistence, streaming, interrupts, and durable execution.

The two APIs can coexist. A Functional API entrypoint can call graph-backed components, and a graph node can delegate work to a function or subgraph.

  • Graph API: explicit topology and state transitions.
  • Functional API: ordinary control flow with durable primitives.
  • Use the representation that makes failures and ownership easiest to explain.
  • Do not convert deterministic helper functions into graph nodes without a reason.

Entrypoints Define Durable Workflow Boundaries

An `@entrypoint` function represents the workflow invocation boundary. Its input and return value form the public contract, while a checkpointer can persist progress for a thread.

Keep entrypoint inputs serializable and explicit. Hidden global state makes replay, testing, and production recovery harder to reason about.

  • Accept a typed request object or dictionary.
  • Return a stable result shape.
  • Use thread configuration when runs must resume.
  • Separate user-facing result data from operational metadata.

Tasks Protect Side Effects and Non-Deterministic Work

Durable execution may replay workflow code from an earlier point when a run resumes. Operations such as API calls, random values, timestamps, file writes, and payments should therefore be wrapped in tasks or isolated nodes.

Recorded task results can be reused during replay instead of repeating completed work. Write operations should still be idempotent because a task that started but failed before recording completion may run again.

  • Wrap network calls and side effects in `@task` functions.
  • Use idempotency keys for writes.
  • Avoid reading the current time or randomness directly in replayed control flow.
  • Keep one meaningful side effect per task where practical.

Subgraphs Create Reusable Workflow Modules

A subgraph is a compiled graph used inside another graph. It is a good fit when one part of the system has its own state transitions, tests, ownership, or reuse across several parent workflows.

State can be shared directly when parent and child use common keys, or mapped through a wrapper node when their schemas differ. Explicit mapping is often safer for independently owned modules.

  • Use shared schemas for tightly coupled parent and child graphs.
  • Use adapter nodes when child state should remain private.
  • Document which fields cross the boundary.
  • Test the subgraph independently before parent integration.

Persistence and Interrupts Across Subgraphs

Stateful subgraphs can inherit the parent checkpointer, which allows checkpointing, interrupts, and state inspection to work across the composed execution.

When a subgraph pauses or fails, resumption may replay the parent node that invoked it as well as work inside the child. That makes side-effect isolation and idempotency important at both levels.

  • Use one thread identity for the composed run.
  • Assume the parent call site may replay during resume.
  • Keep interrupt payloads JSON-serializable.
  • Avoid placing unprotected side effects before an interrupt.

Choose Boundaries That Improve Operations

A subgraph boundary should make deployment, testing, tracing, permissions, or team ownership clearer. Splitting every node into a subgraph adds ceremony without improving control.

Trace parent and child names distinctly so operators can see whether a failure came from routing, state mapping, or the child workflow itself.

Functional API and Subgraphs in the Right Order

Choose the Functional API when the workflow is easiest to understand as ordinary Python control flow but still needs durable execution. The entrypoint describes the main workflow, and tasks wrap work that may need checkpointing, replay awareness, retries, or safe side-effect handling. This is especially useful for sequential workflows that would look awkward if forced into many tiny graph nodes.

Choose subgraphs when a part of the workflow deserves its own internal structure. A research workflow, approval workflow, or specialist-agent workflow may have enough state, routes, and tests to justify a subgraph. The parent graph should not need to know every internal step; it should only know the subgraph contract: input state, output update, failure behavior, and trace boundary.

The expert decision is not Graph API versus Functional API versus subgraph forever. Many systems combine them. Use explicit graphs where architecture review matters, functional entrypoints where Python-shaped orchestration is clearer, and subgraphs where composition improves ownership, testing, or reuse.

  • Use Functional API for durable Python-shaped workflows.
  • Wrap side effects and non-deterministic work in tasks.
  • Use subgraphs when a workflow module has its own state and routes.
  • Document parent-child state mapping clearly.
  • Test replay and resume behavior around task and subgraph boundaries.

Beginner Example: Functional API Entrypoint and Task

This workflow records the external lookup as a task while keeping the orchestration in normal Python control flow.

Beginner Example: Functional API Entrypoint and Task
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.func import entrypoint, task

@task
def lookup_order(order_id: str) -> dict:
    # External API call belongs inside a task.
    return {"order_id": order_id, "status": "shipped"}

@entrypoint(checkpointer=InMemorySaver())
def order_status_workflow(request: dict) -> dict:
    order = lookup_order(request["order_id"]).result()
    return {
        "answer": f"Order {order['order_id']} is {order['status']}.",
        "order": order,
    }

config = {"configurable": {"thread_id": "order-17"}}
print(order_status_workflow.invoke({"order_id": "ORD-17"}, config))
  • The entrypoint is the durable workflow boundary.
  • The external lookup is isolated in a task.
  • The thread ID identifies persisted execution state.

Intermediate Example: Idempotent Write Task

A stable idempotency key prevents duplicate side effects if work must be retried.

Intermediate Example: Idempotent Write Task
from langgraph.func import task

@task
def create_ticket(run_id: str, summary: str) -> dict:
    idempotency_key = f"{run_id}:create-ticket"

    # Pass the key to the backend so a retry returns the existing ticket.
    return {
        "ticket_id": "INC-1042",
        "idempotency_key": idempotency_key,
        "summary": summary,
    }
  • Task recording reduces repeated work during replay.
  • Backend idempotency still protects ambiguous failure cases.
  • Use stable run or operation IDs instead of random keys created during retry.

Advanced Example: Parent Graph Calling a Subgraph

The child graph owns research-specific state while a wrapper maps data across the boundary.

Advanced Example: Parent Graph Calling a Subgraph
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END

class ResearchState(TypedDict):
    query: str
    findings: list[str]

def search(state: ResearchState) -> dict:
    return {"findings": [f"Result for {state['query']}"]}

research_builder = StateGraph(ResearchState)
research_builder.add_node("search", search)
research_builder.add_edge(START, "search")
research_builder.add_edge("search", END)
research_graph = research_builder.compile()

class ParentState(TypedDict):
    question: str
    evidence: list[str]

def run_research(state: ParentState) -> dict:
    child_result = research_graph.invoke({
        "query": state["question"],
        "findings": [],
    })
    return {"evidence": child_result["findings"]}

parent_builder = StateGraph(ParentState)
parent_builder.add_node("research", run_research)
parent_builder.add_edge(START, "research")
parent_builder.add_edge("research", END)
parent_graph = parent_builder.compile()
  • The wrapper makes parent-to-child state mapping explicit.
  • The subgraph can be tested independently.
  • Compile the parent with persistence when the composed run must be durable.
Key Takeaways
  • Choose Graph or Functional API based on workflow readability and operational needs.
  • Wrap non-deterministic work and side effects in tasks or isolated nodes.
  • Make write operations idempotent.
  • Use subgraphs for meaningful reusable workflow boundaries.
  • Document state and persistence behavior across parent-child calls.
Common Mistakes to Avoid
Calling external APIs directly inside replayed entrypoint control flow.
Assuming task recording removes the need for idempotent writes.
Sharing the entire parent state with every subgraph.
Creating subgraphs that add naming layers but no ownership or testing value.

Practice Tasks

  • Rewrite one simple StateGraph as a Functional API entrypoint.
  • Identify every non-deterministic or side-effecting operation and wrap it in a task.
  • Build a child graph with a private schema and a parent adapter node.
  • Write a replay test proving that a completed write is not duplicated.

Frequently Asked Questions

Yes. They are complementary orchestration styles and can call shared functions or graph-backed components.

No. Tasks improve durable replay, but writes should still use idempotency because failures can occur before completion is recorded.

Often it can inherit the parent checkpointer. Use separate persistence only when the ownership and lifecycle genuinely require it.

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.