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:
| Decorator | What it does | Graph API equivalent |
|---|---|---|
@entrypoint | Defines the workflow's entry point. It's your main function. | StateGraph + compile() |
@task | Defines 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()andworkflow.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:
| Aspect | Graph API (StateGraph) | Functional API (@entrypoint) |
|---|---|---|
| How you define the flow | Explicit nodes + edges | Python control flow (if, for, try) |
| State | TypedDict with reducers | The function's local variables |
| Visualization | draw_mermaid_png() | Not available (graph is generated at runtime) |
| Checkpointing | Checkpoint after every superstep | Checkpoint per completed @task |
| Best for | Complex topologies, visual branching | Sequential flows, branching in Python |
| Learning curve | Steeper (new graph paradigm) | Gentler (it's Python with decorators) |
| Verbosity | More setup code | Less 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
| Capsule | Topic | What you'll learn |
|---|---|---|
| 02 | @entrypoint: defining an agent as a function | The @entrypoint() decorator, injectable parameters, running with invoke and stream, side-by-side comparison with StateGraph |
| 03 | @task: the units that make up the agent | The @task decorator, Futures and .result(), tasks as checkpointable units, when to use @task vs plain code |
| 04 | Native control flow | while loops for agent loops, if/else for routing, try/except for error handling, for loops for iteration — all without explicit edges |
| 05 | Graph API vs Functional API: a deep comparison | A detailed comparison table, the same problem solved with both APIs, migration criteria, the strengths of each approach |
| 06 | Patterns with the Functional API | Tool execution pattern, multi-step reasoning, parallel task execution with futures, task composition |
| 07 | Combining Graph and Functional API | Using @task inside a StateGraph, calling compiled graphs from an @entrypoint, when the combination makes sense |
| 08 | Evolving project: Research Agent baseline | AI 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:
- Takes a research topic — a question or subject the user wants to understand
- Decomposes the task with an LLM — generating specific sub-queries to investigate different angles of the topic
- Runs searches with
@task— each sub-query looks for information (with tools or simulated search) - 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:
- Implicit graph: When LangGraph runs your
@entrypoint, it builds an execution graph at runtime based on the@taskcalls you make. You don't see it, but it's there. - Automatic checkpointing: Every completed
@taskis saved in a checkpoint. If your workflow fails after 3 of 5 tasks, the first 3 aren't re-run when you resume. - Granular streaming: Every
@taskemits an event in the stream. You can give the user progressive feedback without implementing anything extra. - 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:
| Error | Cause | Fix |
|---|---|---|
ImportError: cannot import name 'entrypoint' from 'langgraph.func' | Old langgraph version | pip install --upgrade langgraph (you need v0.2.60+) |
TypeError: 'task' object is not callable | You called a @task outside an @entrypoint | @tasks can only run inside an @entrypoint or a StateGraph node |
SerializationError when using invoke | Input or output isn't JSON-serializable | Use 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
@entrypointas the entry point and@taskfor independent tasks - ✅ You understand that
@taskreturns 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
- Functional API Overview — Official documentation for LangGraph's Functional API
- How to use the Functional API — Practical guide with step-by-step examples
- Choosing between Graph API and Functional API — Official criteria for choosing between the two APIs
- Introducing the LangGraph Functional API — Launch blog post with motivation and examples
- @entrypoint Reference — Complete reference for the @entrypoint decorator
- @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.