
How-Tos
How to mix workflows and agents in LangGraph
Build LangGraph workflows that chain, parallelize, route, or spawn orchestrator workers with the Send API.
Searcher → Analyst → Writer → Editor · subagentic-20261008-2000
The LangGraph guide on workflows and agents separates two control styles. Workflows have predetermined code paths and are designed to operate in a certain order. Agents are dynamic and define their own processes and tool usage. The same page shows prompt chaining, parallelization, and routing, then the Send API for workers that cannot be listed until a plan exists.
On this page, LangGraph offers persistence, streaming, support for debugging, and deployment for those builds. It also recommends tracing the patterns with LangSmith, and setting up LangSmith Engine, which monitors traces, detects issues, and proposes fixes. Treat the page as a pattern reference, not a dated release note.
Set up a model that can structure output and call tools
Any chat model that supports structured outputs and tool calling can drive the examples. The documented client is Anthropic. Install the packages named in the setup section:
pip install langchain_core langchain-anthropic langgraph
The snippet imports os, getpass, and ChatAnthropic. A helper writes an environment variable only if it is missing. The guide calls that helper for the Anthropic API key, so a missing key is collected with getpass rather than hardcoded. Create the client with this line:
llm = ChatAnthropic(model="claude-sonnet-4-6")
Two augmentations come before the graphs. Structured output binds a Pydantic schema and returns an instance of that schema. The sample schema is SearchQuery, with optional search-query and justification fields. The documented binding is this assignment:
structured_llm = llm.with_structured_output(SearchQuery)
Tool binding attaches a function such as multiply. The documented line is:
llm_with_tools = llm.bind_tools([multiply])
Invoking that bound model returns a tool-call request rather than the computed product. Later sections reuse structured output for routing and planning, and tool binding for the agent loop.
Chain steps, and stop when a check passes
Prompt chaining means each model call processes the output of the previous call. The guide uses it for well-defined tasks that break into smaller, verifiable steps, such as translating documents or checking generated content for consistency.
The Graph API example is a StateGraph joke pipeline. State is a TypedDict with topic, joke, improved joke, and final joke. Three nodes call the model: a short joke, then wordplay, then a surprising twist. A plain Python gate returns Pass when the joke contains a question mark or an exclamation mark, and Fail otherwise.
workflow.add_edge(START, "generate_joke")
workflow.add_conditional_edges(
"generate_joke", check_punchline, {"Fail": "improve_joke", "Pass": END}
)
workflow.add_edge("improve_joke", "polish_joke")
workflow.add_edge("polish_joke", END)
Pass goes straight to the end, so the later fields are never written. Fail runs both edits. The compile step in the guide is this line:
chain = workflow.compile()
The documented invoke uses a topic of cats. The printout reads the improved and final jokes only when the improved joke key is present.
The Functional API tab inverts that gate. Its check returns Fail when the joke contains a question mark or an exclamation mark, and Pass otherwise. That tab uses the task decorator and this entrypoint line:
@entrypoint()
Do not mix the two predicates.
Fan out from the start when every branch is known
Parallelization runs model calls at the same time. Use it to split independent subtasks and go faster, or to repeat a task and compare outputs. The page's examples are a keyword pass beside a formatting check, and scoring a document separately on citation count, number of sources, and source quality.
The coded graph is the fixed split. One topic yields a joke, a story, and a poem. An aggregator writes the combined output. Three edges leave the start, and three edges join the aggregator:
parallel_builder.add_edge(START, "call_llm_1")
parallel_builder.add_edge(START, "call_llm_2")
parallel_builder.add_edge(START, "call_llm_3")
parallel_builder.add_edge("call_llm_1", "aggregator")
parallel_builder.add_edge("call_llm_2", "aggregator")
parallel_builder.add_edge("call_llm_3", "aggregator")
parallel_builder.add_edge("aggregator", END)
Invoke with a topic of cats and read the combined output. Every branch is named in the builder. The guide uses orchestrator-worker instead when subtasks cannot be predefined the way they can with parallelization.
Route with a schema, then a conditional edge
Routing processes an input and sends it to a context-specific task. The guide's product sketch classifies a question, then hands it to pricing, refunds, or returns.
The sample chooses among poem, story, and joke. A Route schema constrains the next step to those three strings. Bind it with this line:
router = llm.with_structured_output(Route)
The router node sends a system message that tells the model to route the input to story, joke, or poem, plus a human message of the user input, and stores the chosen step. A routing function returns the story node, the joke node, or the poem node. The path map uses those same names:
router_builder.add_edge(START, "llm_call_router")
router_builder.add_conditional_edges(
"llm_call_router",
route_decision,
{ # Name returned by route_decision : Name of next node to visit
"llm_call_1": "llm_call_1",
"llm_call_2": "llm_call_2",
"llm_call_3": "llm_call_3",
},
)
Each branch writes an output and edges to the end. The documented input asks for a joke about cats. The destination set is fixed. Only the choice is dynamic.
Spawn workers with Send
An orchestrator breaks a task into subtasks, delegates them, and synthesizes worker outputs. The guide points to code-writing and multi-file edits, including updates to installation instructions for multiple Python libraries across an unknown number of documents.
Send, imported from langgraph.types, dynamically creates worker nodes and gives each one specific inputs. Each worker has its own state. All worker outputs are written to a shared state key the orchestrator graph can read.
Planning uses structured output. A section has a name and a description. A sections object holds the list. The planner binding is:
planner = llm.with_structured_output(Sections)
The orchestrator asks for a plan for the topic on state and returns that list. Graph state keeps the topic, the sections, completed sections, and the final report. Completed sections is an annotated list reduced with operator.add. The guide's comment says all workers write to this key in parallel. Worker state carries one section plus that same list. The worker writes one section in markdown with no preamble and appends the text.
The spawn is this conditional edge function:
def assign_workers(state: State):
"""Assign a worker to each section in the plan"""
# Kick off section writing in parallel via Send() API
return [Send("llm_call", {"section": s}) for s in state["sections"]]
Connect it so the orchestrator's conditional edge is that function, and each worker continues to the synthesizer:
orchestrator_worker_builder.add_edge(START, "orchestrator")
orchestrator_worker_builder.add_conditional_edges(
"orchestrator", assign_workers, ["llm_call"]
)
orchestrator_worker_builder.add_edge("llm_call", "synthesizer")
orchestrator_worker_builder.add_edge("synthesizer", END)
The synthesizer joins completed sections with a markdown rule and stores the final report. It does not call the model. The documented topic is a report on LLM scaling laws.
The Functional API tab does not use Send. Its entrypoint maps the worker over the planned sections and joins the futures in a synthesizer task. Use Send when the job is a StateGraph and the worker count is known only after the planner runs.
Keep a tool loop for the step you cannot name
Agents, on this page, are a model acting through tools in a continuous feedback loop, for problems and solutions that are unpredictable. They have more autonomy than workflows and can decide which tools to use. You can still define the toolset and the guidelines.
The example decorates add, multiply, and divide, then binds them:
tools = [add, multiply, divide]
tools_by_name = {tool.name: tool for tool in tools}
llm_with_tools = llm.bind_tools(tools)
The graph uses a messages state. The model node is told to perform arithmetic on a set of inputs. A tool node looks up each call by name and returns a tool message. The continue check returns the tool node when the last message has tool calls. The next comment says that otherwise the loop stops. The fetched page cuts off in that branch, so the rest of the stop path is not reproduced here. For the broader setup, the page points to the LangChain quickstart and to how agents work in LangChain.
Chain, fan out, or route the steps you can name. Use Send when a planner must invent the worker list. Reserve the tool-calling loop for a step whose action is not known until the model requests a tool.
What to run first
Install the three packages, initialize the Anthropic chat client from the setup snippet, and compile one graph. Invoke the parallel workflow with the topic cats and print the combined output. Invoke the orchestrator-worker graph with a report on LLM scaling laws and read the final report. To see which nodes ran, follow the tracing quickstart linked from the workflows-and-agents page and compare one chained trace with one Send trace in LangSmith.