State
: A shared data structure that represents the current snapshot of your application. It can be any data type, but is typically defined using a shared state schema.
Nodes
: Functions that encode the logic of your agents. They receive the current state as input, perform some computation or side-effect, and return an updated state.
Edges
: Functions that determine which Node
to execute next based on the current state. They can be conditional branches or fixed transitions.
Nodes
and Edges
, you can create complex, looping workflows that evolve the state over time. The real power, though, comes from how LangGraph manages that state. To emphasize: Nodes
and Edges
are nothing more than functions - they can contain an LLM or just good ol’ code.
In short: nodes do the work, edges tell what to do next.
LangGraph’s underlying graph algorithm uses message passing to define a general program. When a Node completes its operation, it sends messages along one or more edges to other node(s). These recipient nodes then execute their functions, pass the resulting messages to the next set of nodes, and the process continues. Inspired by Google’s Pregel system, the program proceeds in discrete “super-steps.”
A super-step can be considered a single iteration over the graph nodes. Nodes that run in parallel are part of the same super-step, while nodes that run sequentially belong to separate super-steps. At the start of graph execution, all nodes begin in an inactive
state. A node becomes active
when it receives a new message (state) on any of its incoming edges (or “channels”). The active node then runs its function and responds with updates. At the end of each super-step, nodes with no incoming messages vote to halt
by marking themselves as inactive
. The graph execution terminates when all nodes are inactive
and no messages are in transit.
StateGraph
class is the main graph class to use. This is parameterized by a user defined State
object.
.compile
method:
State
of the graph. The State
consists of the schema of the graph as well as reducer
functions which specify how to apply updates to the state. The schema of the State
will be the input schema to all Nodes
and Edges
in the graph, and can be either a TypedDict
or a Pydantic
model. All Nodes
will emit updates to the State
which are then applied using the specified reducer
function.
TypedDict
. If you want to provide default values in your state, use a dataclass
. We also support using a Pydantic BaseModel as your graph state if you want recursive data validation (though note that pydantic is less performant than a TypedDict
or dataclass
).
By default, the graph will have the same input and output schemas. If you want to change this, you can also specify explicit input and output schemas directly. This is useful when you have a lot of keys, and some are explicitly for input and others for output. See the guide here for how to use.
PrivateState
.
It is also possible to define explicit input and output schemas for a graph. In these cases, we define an “internal” schema that contains all keys relevant to graph operations. But, we also define input
and output
schemas that are sub-sets of the “internal” schema to constrain the input and output of the graph. See this guide for more detail.
Let’s look at an example:
state: InputState
as the input schema to node_1
. But, we write out to foo
, a channel in OverallState
. How can we write out to a state channel that is not included in the input schema? This is because a node can write to any state channel in the graph state. The graph state is the union of the state channels defined at initialization, which includes OverallState
and the filters InputState
and OutputState
.
StateGraph(OverallState,input_schema=InputState,output_schema=OutputState)
. So, how can we write to PrivateState
in node_2
? How does the graph gain access to this schema if it was not passed in the StateGraph
initialization? We can do this because nodes can also declare additional state channels as long as the state schema definition exists. In this case, the PrivateState
schema is defined, so we can add bar
as a new state channel in the graph and write to it.
State
. Each key in the State
has its own independent reducer function. If no reducer function is explicitly specified then it is assumed that all updates to that key should override it. There are a few different types of reducers, starting with the default type of reducer:
{"foo": 1, "bar": ["hi"]}
. Let’s then assume the first Node
returns {"foo": 2}
. This is treated as an update to the state. Notice that the Node
does not need to return the whole State
schema - just an update. After applying this update, the State
would then be {"foo": 2, "bar": ["hi"]}
. If the second node returns {"bar": ["bye"]}
then the State
would then be {"foo": 2, "bar": ["bye"]}
Example B:
Annotated
type to specify a reducer function (operator.add
) for the second key (bar
). Note that the first key remains unchanged. Let’s assume the input to the graph is {"foo": 1, "bar": ["hi"]}
. Let’s then assume the first Node
returns {"foo": 2}
. This is treated as an update to the state. Notice that the Node
does not need to return the whole State
schema - just an update. After applying this update, the State
would then be {"foo": 2, "bar": ["hi"]}
. If the second node returns {"bar": ["bye"]}
then the State
would then be {"foo": 2, "bar": ["hi", "bye"]}
. Notice here that the bar
key is updated by adding the two lists together.
ChatModel
in particular accepts a list of Message
objects as inputs. These messages come in a variety of forms such as HumanMessage
(user input) or AIMessage
(LLM response). To read more about what message objects are, please refer to this conceptual guide.
Message
objects and annotate it with a reducer function (see messages
key in the example below). The reducer function is vital to telling the graph how to update the list of Message
objects in the state with each state update (for example, when a node sends an update). If you don’t specify a reducer, every state update will overwrite the list of messages with the most recently provided value. If you wanted to simply append messages to the existing list, you could use operator.add
as a reducer.
However, you might also want to manually update messages in your graph state (e.g. human-in-the-loop). If you were to use operator.add
, the manual state updates you send to the graph would be appended to the existing list of messages, instead of updating existing messages. To avoid that, you need a reducer that can keep track of message IDs and overwrite existing messages, if updated. To achieve this, you can use the prebuilt add_messages
function. For brand new messages, it will simply append to existing list, but it will also handle the updates for existing messages correctly.
add_messages
function will also try to deserialize messages into LangChain Message
objects whenever a state update is received on the messages
channel. See more information on LangChain serialization/deserialization here. This allows sending graph inputs / state updates in the following format:
Messages
when using add_messages
, you should use dot notation to access message attributes, like state["messages"][-1].content
. Below is an example of a graph that uses add_messages
as its reducer function.
MessagesState
which makes it easy to use messages. MessagesState
is defined with a single messages
key which is a list of AnyMessage
objects and uses the add_messages
reducer. Typically, there is more state to track than just messages, so we see people subclass this state and add more fields, like:
state
: The state of the graphconfig
: A RunnableConfig
object that contains configuration information like thread_id
and tracing information like tags
runtime
: A Runtime
object that contains runtime context
and other information like store
and stream_writer
NetworkX
, you add these nodes to a graph using the add_node method:
START
NodeSTART
Node is a special node that represents the node that sends user input to the graph. The main purpose for referencing this node is to determine which nodes should be called first.
END
NodeEND
Node is a special node that represents a terminal node. This node is referenced when you want to denote which edges have no actions after they are done.
key_func
used to generate a cache key based on the input to a node, which defaults to a hash
of the input with pickle.ttl
, the time to live for the cache in seconds. If not specified, the cache will never expire.routing_function
accepts the current state
of the graph and returns a value.
By default, the return value routing_function
is used as the name of the node (or list of nodes) to send the state to next. All those nodes will be run in parallel as a part of the next superstep.
You can optionally provide a dictionary that maps the routing_function
’s output to the name of the next node.
Command
instead of conditional edges if you want to combine state updates and routing in a single function.add_edge
method from the virtual START
node to the first node to execute to specify where to enter the graph.
add_conditional_edges
from the virtual START
node to accomplish this.
routing_function
’s output to the name of the next node.
Send
Nodes
and Edges
are defined ahead of time and operate on the same shared state. However, there can be cases where the exact edges are not known ahead of time and/or you may want different versions of State
to exist at the same time. A common example of this is with map-reduce design patterns. In this design pattern, a first node may generate a list of objects, and you may want to apply some other node to all those objects. The number of objects may be unknown ahead of time (meaning the number of edges may not be known) and the input State
to the downstream Node
should be different (one for each generated object).
To support this design pattern, LangGraph supports returning Send
objects from conditional edges. Send
takes two arguments: first is the name of the node, and second is the state to pass to that node.
Command
Command
object from node functions:
Command
you can also achieve dynamic control flow behavior (identical to conditional edges):
Command
in your node functions, you must add return type annotations with the list of node names the node is routing to, e.g. Command[Literal["my_other_node"]]
. This is necessary for the graph rendering and tells LangGraph that my_node
can navigate to my_other_node
.Command
.
Command
when you need to both update the graph state and route to a different node. For example, when implementing multi-agent handoffs where it’s important to route to a different agent and pass some information to that agent.graph=Command.PARENT
in Command
:
graph
to Command.PARENT
will navigate to the closest parent graph.When you send updates from a subgraph node to a parent graph node for a key that’s shared by both parent and subgraph state schemas, you must define a reducer for the key you’re updating in the parent graph state. See this example.Command
is an important part of human-in-the-loop workflows: when using interrupt()
to collect user input, Command
is then used to supply the input and resume execution via Command(resume="User input")
. Check out this conceptual guide for more information.
context_schema
for runtime context passed to nodes. This is useful for passing
information to nodes that is not part of the graph state. For example, you might want to pass dependencies such as model name or a database connection.
context
parameter of the invoke
method.
GraphRecursionError
. By default this value is set to 25 steps. The recursion limit can be set on any graph at runtime, and is passed to .invoke
/.stream
via the config dictionary. Importantly, recursion_limit
is a standalone config
key and should not be passed inside the configurable
key as all other user-defined configuration. See the example below: