The Functional API

LangGraph · Advanced · 13 min

Some workflows read better as ordinary functions with loops and ifs than as explicit graphs.

The Functional API gives you LangGraph's runtime features without drawing a graph. Two decorators:

  • @task marks a unit of work (an API call, a model call). Calling a task returns a future, a placeholder for a result that may still be running; call .result() to wait for it. Several tasks started before you wait run in parallel.
  • @entrypoint(checkpointer=...) marks the workflow function. Inside it you use normal Python: if, for, while, function calls.

You run an entrypoint like a graph: .invoke(input, config), .stream(...), with a thread_id when it has a checkpointer.

How persistence works here: task results are saved as they complete. If the workflow is resumed (after an interrupt() or a crash), finished tasks are not re-executed; their saved results are reused. So put side effects and expensive calls inside tasks, and keep the entrypoint body deterministic (same inputs, same path).

Other features:

  • interrupt() works inside an entrypoint, resumed with Command(resume=...).
  • A parameter named previous receives the last return value on this thread, giving simple short-term memory.
  • entrypoint.final(value=..., save=...) returns one value to the caller while saving a different one for next time.

Graph API or Functional API? Graphs make complex branching, parallel paths and team collaboration visible. Functions suit linear or loop-heavy logic, or adding durability to existing code. They share one runtime and can call each other.

Example

from langgraph.checkpoint.memory import InMemorySaver
from langgraph.func import entrypoint, task
from langgraph.types import Command, interrupt


@task
def write_section(topic: str) -> str:
    return f"A paragraph about {topic}."       # real code: a model call


@entrypoint(checkpointer=InMemorySaver())
def write_report(topics: list[str]) -> str:
    futures = [write_section(t) for t in topics]  # run in parallel
    draft = "\n".join(f.result() for f in futures)
    verdict = interrupt({"review": draft})        # pause for a human
    return draft if verdict == "ok" else "Rejected"


cfg = {"configurable": {"thread_id": "r1"}}
for part in write_report.stream(["bees", "wasps"], cfg):
    print(part)          # ... {'__interrupt__': (Interrupt(value={'review': ...}),)}
print(write_report.invoke(Command(resume="ok"), cfg))
# tasks are not re-run on resume; their saved results are reused

Remember

  • @entrypoint = workflow in plain Python; @task = checkpointed unit of work
  • Tasks return futures; .result() waits; start several to run in parallel
  • Finished tasks are reused, not re-run, on resume
  • Same runtime as the Graph API: interrupts, streaming, persistence

Watch out for

  • Doing side effects directly in the entrypoint body instead of in a @task, so they repeat when the workflow resumes.

Check yourself

  1. A workflow resumes after an interrupt. What happens to tasks that already finished?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.langchain.com/oss/python/langgraph/functional-api