Module 6: Functional API

Introduction: Another Way to Build Agents

Overview

In Module 5 you built workflows with StateGraph. You defined nodes as functions, wired them together with edges and conditional edges, designed typed state with TypedDict and Annotated, and visualized your graphs with draw_mermaid_png(). Powerful, explicit, visual. It all works.

But then you looked at the code and thought: "for a pipeline that's basically get query → decompose → search → synthesize... do I really need to define a StateGraph with nodes, edges, compile it, and all the rest? Those are functions calling functions."

Right. Some workflows don't need explicit graphs. A research pipeline is essentially a function that calls other functions in sequence, with a bit of branching and error handling. Forcing it into an explicit graph adds ceremony without adding clarity.

LangGraph's Functional API lets you write that pipeline as plain Python — and get LangGraph's superpowers (durability, checkpointing, streaming) for free. It isn't the "easy" version of the Graph API. It's a different way of expressing workflows, optimized for flows that read like sequential Python code.


Where are we in the guide?

This is Module 6 of the guide LangChain & LangGraph: From Chains to Agents. It's the second module of Block 2 (LangGraph Fundamentals).

Block 1: LangChain Core (Modules 1-4)             ✅ Completed
Block 2: LangGraph Fundamentals (Modules 5-7)     ← YOU ARE HERE (Module 6)
Block 3: Advanced LangGraph (Modules 8-10)
Block 4: Production (Modules 11-12)
Your progress:

Block 1 — LangChain Core                    ✅ Completed
    │
    │  Module 1: Models and Providers        ✅
    │  Module 2: Tools and Tool Calling      ✅
    │  Module 3: Agents (create_agent)       ✅
    │  Module 4: Middleware and Customization ✅
    │
    ▼
Block 2 — LangGraph Fundamentals
    │
    │  Module 5: Introduction to LangGraph   ✅ Completed
    │  Module 6: Functional API              ← YOU ARE HERE
    │  Module 7: Advanced Flows              🔒 Next
    │
    ▼
Blocks 3-4 — Advanced + Production          🔒

In Module 5 you mastered the Graph API: StateGraph, nodes, edges, conditional edges, typed state, compilation, visualization. Now you learn the alternative: the Functional API, which lets you express workflows as Python functions with decorators.


The bridge: from explicit graphs to functions with superpowers

What you already know how to do

In Module 5 you built workflows like this one:

from typing import TypedDict, Annotated
import operator
from langgraph.graph import StateGraph, START, END

class ResearchState(TypedDict):
    messages: Annotated[list[str], operator.add]
    query: str
    results: Annotated[list[str], operator.add]

def decompose(state: ResearchState) -> dict:
    return {"messages": ["Decomposing query..."], "results": [f"Sub-query for: {state['query']}"]}

def search(state: ResearchState) -> dict:
    return {"messages": ["Searching for information..."], "results": ["Search result"]}

def synthesize(state: ResearchState) -> dict:
    return {"messages": [f"Synthesis: {len(state['results'])} results processed"]}

graph_builder = StateGraph(ResearchState)
graph_builder.add_node("decompose", decompose)
graph_builder.add_node("search", search)
graph_builder.add_node("synthesize", synthesize)
graph_builder.add_edge(START, "decompose")
graph_builder.add_edge("decompose", "search")
graph_builder.add_edge("search", "synthesize")
graph_builder.add_edge("synthesize", END)

graph = graph_builder.compile()
result = graph.invoke({"messages": [], "query": "What is prompt engineering?", "results": []})
print(result["messages"])
# ['Decomposing query...', 'Searching for information...', 'Synthesis: 2 results processed']

It works. But look at the structure: it's a linear sequence. START → decompose → search → synthesize → END. No complex branching, no conditional edges. It is, in essence, three functions calling each other in sequence.

What you'll learn here

The same pipeline, with the Functional API:

from langgraph.func import entrypoint, task

@task
def decompose(query: str) -> list[str]:
    return [f"Sub-query for: {query}"]

@task
def search(sub_queries: list[str]) -> list[str]:
    return ["Search result"]

@task
def synthesize(results: list[str]) -> str:
    return f"Synthesis: {len(results)} results processed"

@entrypoint()
def research_agent(query: str) -> str:
    sub_queries = decompose(query).result()
    results = search(sub_queries).result()
    summary = synthesize(results).result()
    return summary

result = research_agent.invoke("What is prompt engineering?")
print(result)
# Synthesis: 1 results processed

Read the code in research_agent. It's plain Python: it calls functions, saves results in variables, returns a value. But underneath, LangGraph is building an implicit graph, managing a checkpoint for every @task, and enabling progress streaming.

It looks like Python. It has superpowers.


The motivation: not everything needs an explicit graph

LangGraph's Graph API shines when your workflow has a complex topology:

         ┌──── code_handler ────┐
         │                      │
START → classify ─── qa_handler ──── END
         │                      │
         └── creative_handler ──┘

That diagram has conditional branching. The Graph API expresses it perfectly: you define nodes, add conditional edges, compile, and visualize with draw_mermaid_png().

But plenty of real-world workflows are sequential:

query → decompose → search → analyze → synthesize → output

Forcing that sequence into an explicit graph with StateGraph, add_node, add_edge, a TypedDict with reducers, and a compile step... adds ceremony without adding clarity. It's like drawing a flowchart to describe a cooking recipe: technically correct, but a list of steps would be clearer.

The Functional API exists for those cases. You write the sequence as Python functions, and LangGraph adds the infrastructure underneath.


What the Functional API is

LangGraph's Functional API uses two decorators:

DecoratorWhat it doesGraph API equivalent
@entrypointDefines the workflow's entry point. It's your main function.StateGraph + compile()
@taskDefines an independent unit of work. Returns a Future.A node (add_node)
from langgraph.func import entrypoint, task

What you get for free by using these decorators:

  • Durability: if the workflow crashes, checkpointing lets you resume from the last completed @task
  • Streaming: you can stream the workflow's progress step by step
  • Human-in-the-loop: you can pause the workflow with interrupt() to ask for human approval
  • Same interface: workflow.invoke() and workflow.stream() — identical to a compiled graph

What looks like plain Python has LangGraph's infrastructure underneath. That's the core idea.


Two APIs, one toolkit

Graph API and Functional API don't compete. They're two ways of expressing workflows that share the same runtime:

AspectGraph API (StateGraph)Functional API (@entrypoint)
How you define the flowExplicit nodes + edgesPython control flow (if, for, try)
StateTypedDict with reducersThe function's local variables
Visualizationdraw_mermaid_png()Not available (graph is generated at runtime)
CheckpointingCheckpoint after every superstepCheckpoint per completed @task
Best forComplex topologies, visual branchingSequential flows, branching in Python
Learning curveSteeper (new graph paradigm)Gentler (it's Python with decorators)
VerbosityMore setup codeLess setup code

You can mix both APIs in the same project. An @entrypoint can call a compiled StateGraph, and a StateGraph node can use @task. It isn't a permanent decision — it's a per-workflow decision.

The decision rule

Does your workflow have more than 5 nodes with complex conditional routing?
    → Graph API (StateGraph). The visualization will save you.

Is it a sequential flow with simple branching (if/else, try/except)?
    → Functional API (@entrypoint + @task). Reads like Python, has superpowers.

Not sure?
    → Start with the Functional API. It's faster to prototype.
       If the flow gets complicated, migrate to the Graph API.

Module map

CapsuleTopicWhat you'll learn
02@entrypoint: defining an agent as a functionThe @entrypoint() decorator, injectable parameters, running with invoke and stream, side-by-side comparison with StateGraph
03@task: the units that make up the agentThe @task decorator, Futures and .result(), tasks as checkpointable units, when to use @task vs plain code
04Native control flowwhile loops for agent loops, if/else for routing, try/except for error handling, for loops for iteration — all without explicit edges
05Graph API vs Functional API: a deep comparisonA detailed comparison table, the same problem solved with both APIs, migration criteria, the strengths of each approach
06Patterns with the Functional APITool execution pattern, multi-step reasoning, parallel task execution with futures, task composition
07Combining Graph and Functional APIUsing @task inside a StateGraph, calling compiled graphs from an @entrypoint, when the combination makes sense
08Evolving project: Research Agent baselineAI Research Assistant v1 with the Functional API — takes a topic, decomposes it, searches, synthesizes a structured report

Learning flow: You start by understanding @entrypoint as the functional equivalent of StateGraph (02). Then you master @task and the concept of Futures (03). With both decorators in hand, you learn to steer the flow with native Python (04). Next you build judgment by comparing both APIs in depth (05). With that judgment, you learn professional patterns (06) and how to combine both APIs (07). Finally, you build the first version of the evolving project (08).


The evolving project starts here

This module marks an important shift in the guide. In Modules 1-5, each module had its own standalone mini-project. From here on, you build a single project that evolves module by module: the AI Research Assistant.

Module 6:  Research Agent baseline (Functional API)
    │
    ▼
Module 7:  + Retry logic, parallel branching, error handling
    │
    ▼
Module 8:  + Persistence (checkpointing, memory)
    │
    ▼
Module 9:  + Human approvals (human-in-the-loop)
    │
    ▼
Module 10: + Multi-agent (researcher, analyst, writer, supervisor)
    │
    ▼
Module 11: + Deep Agent (planning, filesystem, subagents)
    │
    ▼
Module 12: + Observability and production (LangSmith, evaluation)

What you build in this module becomes the foundation for 6 more modules of iteration. The v1 is simple but functional: it takes a research topic, decomposes the task into sub-queries with an LLM, runs searches, and produces a structured summary. Every module after this adds a layer of sophistication.

Do the v1 well. You're going to iterate on it for a long time.

What the Research Assistant v1 does

In Capsule 08 of this module you'll build a functional workflow that:

  1. Takes a research topic — a question or subject the user wants to understand
  2. Decomposes the task with an LLM — generating specific sub-queries to investigate different angles of the topic
  3. Runs searches with @task — each sub-query looks for information (with tools or simulated search)
  4. Synthesizes a report — combining the results into a structured answer with sections, sources, and conclusions

It's simple compared to what it will be by Module 12. But it's functional end to end, and every piece you build here (@entrypoint, @task, the decompose → search → synthesize flow) survives all the way to the final version.


What this module does NOT cover

  • Advanced flows (retry cycles, parallel branching, subgraphs) — Covered in Module 7. Here you master the fundamentals of the Functional API.
  • Persistence (checkpointing, memory across sessions) — Covered in Module 8. You'll get a preview of the concept here, but we don't go deep.
  • Human-in-the-loop (interrupts, approvals) — Covered in Module 9. We'll mention that the Functional API enables it with interrupt(), but we don't implement it.
  • Multi-agent systems — Covered in Module 10. Here you work with a single functional workflow.
  • Deep Agents — Covered in Module 11. Here you build with LangGraph directly.

What happens underneath: it isn't just Python

The Functional API looks like "just writing Python." That's the intent: it should feel familiar. But underneath, LangGraph is doing serious work:

  1. Implicit graph: When LangGraph runs your @entrypoint, it builds an execution graph at runtime based on the @task calls you make. You don't see it, but it's there.
  2. Automatic checkpointing: Every completed @task is saved in a checkpoint. If your workflow fails after 3 of 5 tasks, the first 3 aren't re-run when you resume.
  3. Granular streaming: Every @task emits an event in the stream. You can give the user progressive feedback without implementing anything extra.
  4. Interrupt support: You can pause your workflow with interrupt() at any point to ask for human input. LangGraph handles the pause and the resume.

That's the difference between "writing Python" and "writing Python with @entrypoint". It looks the same. It isn't.


Technical setup

Prerequisites

Before continuing, make sure you have:

  • Module 5 completed — you know how to create graphs with StateGraph, nodes, edges, conditional edges, typed state, compilation and execution
  • Python 3.11+ installed
  • ✅ At least one API key from a provider (OpenAI recommended for this module)

Installation

If you completed Module 5, you already have everything installed. Verify it:

pip install langgraph langchain-openai python-dotenv

The Functional API ships inside langgraph — there are no extra packages:

from langgraph.func import entrypoint, task
print("Functional API available")
# Expected output: Functional API available

Verify everything works

from langgraph.func import entrypoint, task

@task
def greet(name: str) -> str:
    return f"Hello, {name}!"

@entrypoint()
def my_workflow(name: str) -> str:
    greeting = greet(name).result()
    return greeting

result = my_workflow.invoke("LangGraph")
print(result)
# Expected output: Hello, LangGraph!

If you see the greeting, your setup is ready for the Functional API.

If something fails:

ErrorCauseFix
ImportError: cannot import name 'entrypoint' from 'langgraph.func'Old langgraph versionpip install --upgrade langgraph (you need v0.2.60+)
TypeError: 'task' object is not callableYou called a @task outside an @entrypoint@tasks can only run inside an @entrypoint or a StateGraph node
SerializationError when using invokeInput or output isn't JSON-serializableUse primitive types (dict, list, str, int, bool). Don't pass custom objects as input

Signs of success

By the end of this module, you'll know it worked if:

  • ✅ You can create workflows with @entrypoint as the entry point and @task for independent tasks
  • ✅ You understand that @task returns a Future and you know how to use .result() to get the value
  • ✅ You use native Python control flow (if/else, for, try/except) to steer execution without explicit edges
  • ✅ You can compare Graph API vs Functional API and decide when to use each
  • ✅ You know how to combine both APIs when the case calls for it
  • ✅ Your AI Research Assistant v1 works end to end: topic → decomposition → search → synthesis

Preview: from a functional workflow to advanced flows

In this module you'll build workflows with the Functional API: @entrypoint to define the entry point, @task for the units of work, and native Python for control flow. The result will be a Research Agent that works, but with a linear flow and no robust error handling.

In Module 7 (Advanced Flows), you'll add production patterns: retry with backoff when an API fails, parallel branching to search multiple sources at once, subgraphs to encapsulate logic, and error handling that makes your workflow resilient.

The Research Agent will go from "works on the happy path" to "works in the real world."


Summary

  • In Module 5 you mastered the Graph API: StateGraph, nodes, edges, conditional edges, typed state, compilation, and visualization
  • The Functional API is an alternative way to build workflows in LangGraph — it uses decorators (@entrypoint, @task) and Python control flow instead of explicit graphs
  • It isn't the easy version. It's a different one, optimized for flows that read like sequential Python code
  • It looks like plain Python, but it has superpowers: durability, checkpointing, streaming, human-in-the-loop — all free with the decorators
  • Graph API and Functional API share the same runtime. They're complementary, not competitors. You can mix them in the same project
  • Decision rule: >5 nodes with complex routing → Graph API. Sequential flow with simple branching → Functional API. Not sure → start with Functional
  • The evolving project starts here. The AI Research Assistant v1 will be the foundation for 6 more modules of iteration
  • The Functional API doesn't support visualization with draw_mermaid_png() — the graph is generated dynamically at runtime

Additional resources

  1. Functional API Overview — Official documentation for LangGraph's Functional API
  2. How to use the Functional API — Practical guide with step-by-step examples
  3. Choosing between Graph API and Functional API — Official criteria for choosing between the two APIs
  4. Introducing the LangGraph Functional API — Launch blog post with motivation and examples
  5. @entrypoint Reference — Complete reference for the @entrypoint decorator
  6. @task Reference — Complete reference for the @task decorator

Module 6 — LangChain & LangGraph: From Chains to Agents

Next capsule: @entrypoint: Defining an Agent as a Function — you'll learn how @entrypoint replaces StateGraph + compile() and how to run functional workflows with invoke and stream.