The Building Blocks of LangGraph (Part 0) LangGraph, an orchestration framework from the LangChain team, enables developers to build stateful, controllable AI workflows by modeling them as graphs with nodes, edges, and shared state. The framework addresses the challenge of coordinating complex AI systems that reason, use tools, and collaborate across tasks. For other parts of the series : Part 0 , Part 1 , Part 2 , Part 3 As Large Language Models LLMs have become more capable, developers have moved beyond simple chatbots and begun building systems that can reason, make decisions, use tools, retrieve information, interact with APIs, and collaborate with other AI agents. Building these systems introduces a new challenge: How do we coordinate and manage the flow of intelligence? This is the problem that LangGraph was created to solve. At its core, LangGraph is a framework for building stateful, controllable, and production-ready AI workflows . It allows developers to define how AI agents think, make decisions, communicate with tools, and move through complex tasks. If LangChain helps you connect AI components together, LangGraph helps you orchestrate how those components behave over time. LangGraph is an orchestration framework built by the team behind LangChain. It allows developers to model AI applications as a graph Let’s build a simple graph with 3 nodes and one conditional edge. The easiest way to understand nodes, edges, and state is to imagine a food delivery process. A node is simply a task or action that does something. For example: Receive Order is a node. Prepare Food is another node. Deliver Food is another node. Every time some work is performed, you are at a node. An edge is the path that tells the system where to go next. For example: Receive Order ↓Prepare Food ↓Deliver Food Those arrows are the edges. The edge is not doing any work itself. It simply says: “After this step finishes, go to that step.” Think of an edge as a road connecting two cities. The cities are the nodes, and the road is the edge. A state is the information that travels through the entire process. Imagine a customer orders: PizzaAddress: 123 Main StreetCustomer: John When the order is received, that information enters the system. As the order moves from: Receive Order ↓Prepare Food ↓Deliver Food the information moves along with it. That information is the state. Think of state as the graph’s shared memory. It is the information that travels through the workflow as it moves from one node to another. Every node can read the state, update it, and pass the updated version to the next node. In this example, the state contains a single piece of information called graph state. First, define the State https://docs.langchain.com/oss/python/langgraph/graph-api state of the graph. The State schema serves as the input schema for all Nodes and Edges in the graph. Let’s use the TypedDict class from python's typing module as our schema, which provides type hints for the keys. python from typing extensions import TypedDictclass State TypedDict : graph state: strNodes A node is simply a function that performs some work. When a node runs, it receives the current state, does something with it, and returns an updated state. You can think of a node as a worker in a factory. The worker receives a package the state , modifies it, and then passes it along. The first positional argument is the state, as defined above. Because the state is a TypedDict with schema as defined above, each node can access the key, graph state, with state 'graph state' . Each node returns a new value of the state key graph state. By default, the new value returned by each node will override https://docs.langchain.com/oss/python/langgraph/graph-api/ reducers the prior state value. python def node 1 state : print "---Node 1---" return {"graph state": state 'graph state' + "I am"}def node 2 state : print "---Node 2---" return {"graph state": state 'graph state' + "happy "}def node 3 state : print "---Node 3---" return {"graph state": state 'graph state' + "sad "} An edge is simply a connection between nodes. It tells the graph where to go after a node finishes its work. A normal edge is a fixed path. After one node completes, the graph always moves to the same next node. For example, if a workflow has “Collect Data” followed by “Analyze Data,” the graph will always move from the first node to the second. A conditional edge is a decision point. Instead of always following the same path, the graph looks at the current state and decides where to go next. For example, after analyzing data, the graph might ask: “Do I have enough information?” If the answer is yes, it moves to “Generate Report.” If the answer is no, it moves back to “Collect More Data.” Conditional edges are implemented as functions that return the next node to visit based on some logic. php import randomfrom typing import Literaldef decide mood state - Literal "node 2", "node 3" : Often, we will use state to decide on the next node to visit user input = state 'graph state' Here, let's just do a 50 / 50 split between nodes 2, 3 if random.random < 0.5 50% of the time, we return Node 2 return "node 2" 50% of the time, we return Node 3 return "node 3" Now, we build the graph from our components defined above. The StateGraph class https://docs.langchain.com/oss/python/langgraph/graph-api/ stategraph is the graph class that we can use. First, we initialize a StateGraph with the State class we defined above. Then, we add our nodes and edges. We use the START Node, a special node https://docs.langchain.com/oss/python/langgraph/graph-api/ start-node that sends user input to the graph, to indicate where to start our graph. The END Node https://docs.langchain.com/oss/python/langgraph/graph-api/ end-node is a special node that represents a terminal node. Finally, we compile our graph https://docs.langchain.com/oss/python/langgraph/graph-api/ compiling-your-graph to perform a few basic checks on the graph structure. We can visualize the graph as a Mermaid diagram https://github.com/mermaid-js/mermaid . python from IPython.display import Image, displayfrom langgraph.graph import StateGraph, START, END Build Graphbuilder = StateGraph state builder.add node "node 1", node 1 builder.add node "node 2", node 2 builder.add node "node 3", node 3 Logicbuilder.add edge START, "node 1" builder.add conditional edges "node 1", decide mood builder.add edge "node 2", END builder.add edge "node 3", END Addgraph = builder.compile Viewdisplay Image graph.get graph .draw mermaid png OUTPUT The compiled graph implements the runnable https://reference.langchain.com/python/langchain core/runnables/?h=runnables protocol. This provides a standard way to execute LangChain components. invoke is one of the standard methods in this interface. The input is a dictionary {"graph state": "Hi, this is lance."}, which sets the initial value for our graph state dict. When invoke is called, the graph starts execution from the START node. It progresses through the defined nodes node 1, node 2, node 3 in order. The conditional edge will traverse from node 1 to node 2 or 3 using a 50/50 decision rule. Each node function receives the current state and returns a new value, which overrides the graph state. The execution continues until it reaches the END node. graph.invoke {"graph state" : "Hi, this is Lance."} OUTPUT---Node 1------Node 3---{'graph state': 'Hi, this is Lance. I am sad '} invoke runs the entire graph synchronously. This waits for each step to complete before moving to the next. It returns the final state of the graph after all nodes have executed. In this case, it returns the state after node 3 has completed: {'graph state': 'Hi, this is Lance. I am sad '} We built a simple graph with nodes, normal edges, and conditional edges. Now, let’s build up to a simple chain that combines 4 concepts. %%capture --no-stderr%pip install --quiet -U langchain openai langchain core langgraph Chat models can use messages https://docs.langchain.com/oss/python/langchain/messages , which capture different roles within a conversation. LangChain supports various message types, including HumanMessage, AIMessage, SystemMessage, and ToolMessage. These represent a message from the user, from chat model, for the chat model to instruct behavior, and from a tool call. Let’s create a list of messages. Each message can be supplied with a few things: python from pprint import pprintfrom langchain core.messages import AIMessage, HumanMessagemessages = AIMessage content=f"So you said you were researching ocean mammals?", name="Model" messages.append HumanMessage content=f"Yes, that's right.",name="Lance" messages.append AIMessage content=f"Great, what would you like to learn about.", name="Model" messages.append HumanMessage content=f"I want to learn about the best place to see Orcas in the US.", name="Lance" for m in messages: m.pretty print OUTPUT================================== Ai Message ==================================Name: ModelSo you said you were researching ocean mammals?================================ Human Message =================================Name: LanceYes, that's right.================================== Ai Message ==================================Name: ModelGreat, what would you like to learn about.================================ Human Message =================================Name: LanceI want to learn about the best place to see Orcas in the US. Chat models use a sequence of messages as input and support message types, as discussed above. There are many https://docs.langchain.com/oss/python/integrations/chat to choose from Let’s work with OpenAI. We can load a chat model and invoke it with out list of messages. We can see that the result is an AIMessage with specific response metadata. python from langchain openai import ChatOpenAIllm = ChatOpenAI model="gpt-4o" result = llm.invoke messages type result result OUTPUTlangchain core.messages.ai.AIMessageresultAIMessage content='One of the best places to see orcas in the United States is the Pacific Northwest, particularly around the San Juan Islands in Washington State. Here are some details:\n\n1. San Juan Islands, Washington : These islands are a renowned spot for whale watching, with orcas frequently spotted between late spring and early fall. The waters around the San Juan Islands are home to both resident and transient orca pods, making it an excellent location for sightings.\n\n2. Puget Sound, Washington : This area, including places like Seattle and the surrounding waters, offers additional opportunities to see orcas, particularly the Southern Resident killer whale population.\n\n3. Olympic National Park, Washington : The coastal areas of the park provide a stunning backdrop for spotting orcas, especially during their migration periods.\n\nWhen planning a trip for whale watching, consider peak seasons for orca activity and book tours with reputable operators who adhere to responsible wildlife viewing practices. Additionally, land-based spots like Lime Kiln Point State Park, also known as “Whale Watch Park,” on San Juan Island, offer great opportunities for orca watching from shore.', additional kwargs={'refusal': None}, response metadata={'token usage': {'completion tokens': 228, 'prompt tokens': 67, 'total tokens': 295, 'completion tokens details': {'accepted prediction tokens': 0, 'audio tokens': 0, 'reasoning tokens': 0, 'rejected prediction tokens': 0}, 'prompt tokens details': {'audio tokens': 0, 'cached tokens': 0}}, 'model name': 'gpt-4o-2024-08-06', 'system fingerprint': 'fp 50cad350e4', 'finish reason': 'stop', 'logprobs': None}, id='run-57ed2891-c426-4452-b44b-15d0a5c3f225-0', usage metadata={'input tokens': 67, 'output tokens': 228, 'total tokens': 295, 'input token details': {'audio': 0, 'cache read': 0}, 'output token details': {'audio': 0, 'reasoning': 0}} Tools are useful whenever you want a model to interact with external systems. External systems e.g., APIs often require a particular input schema or payload, rather than natural language. When we bind an API, for example, as a tool we given the model awareness of the required input schema. The model will choose to call a tool based upon the natural language input from the user. And, it will return an output that adheres to the tool’s schema. Many LLM providers support tool calling https://docs.langchain.com/oss/python/integrations/chat and tool calling interface https://blog.langchain.com/improving-core-tool-interfaces-and-docs-in-langchain/ in LangChain is simple. You can simply pass any Python function into ChatModel.bind tools function . Let’s showcase a simple example of tool calling The multiply function is our tool. php def multiply a:int, b:int - int: return a bllm with tools = llm.bind tools multiply If we pass an input — e.g., "What is 2 multiplied by 3" - we see a tool call returned. The tool call has specific arguments that match the input schema of our function along with the name of the function to call. {‘arguments’: ‘{“a”:2,”b”:3}’, ‘name’: ‘multiply’} tool call = llm with tools.invoke HumanMessage content=f"What is 2 multiplied by 3", name="Lance" tool call.tool calls OUTPUT {'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call lBBBNo5oYpHGRqwxNaNRbsiT', 'type': 'tool call'} With these foundations in place, we can now use messages https://docs.langchain.com/oss/python/langchain/overview messages in our graph state. Let’s define our state, MessagesState, as a TypedDict with a single key: messages. messages is simply a list of messages, as we defined above e.g., HumanMessage, etc . python from typing extentions import TypedDictfrom langchain core.messges import AnyMessageclass MessagesState TypedDict : messages : list AnyMessage Now, we have a minor problem As we discussed, each node will return a new value for our state key messages. But, this new value will overwrite the prior messages value As our graph runs, we want to append messages to our messages state key. We can use reducer functions https://docs.langchain.com/oss/python/langgraph/graph-api reducers to address this. Reducers specify how state updates are performed. If no reducer function is specified, then it is assumed that updates to the key should override it as we saw before. But, to append messages, we can use the pre-built add messages reducer. This ensures that any messages are appended to the existing list of messages. We simply need to annotate our messages key with the add messages reducer function as metadata. python from typing import Annotatedfrom langgraph.graph.message import add messagesclass MessagesState TypedDict : messages: Annotated list AnyMessage , add messages Since having a list of messages in graph state is so common, LangGraph has a pre-built MessagesState https://docs.langchain.com/oss/python/langgraph/graph-api messagesstate MessagesState is defined: We’ll usually use MessagesState because it is less verbose than defining a custom TypedDict, as shown above. python from langgraph.graph import MessagesStateclass MessagesState MessagesState : Add any keys needed beyond messages, which is pre-built pass To go a bit deeper, we can see how the add messages reducer works in isolation. Initial stateinitial messages = AIMessage content="Hello How can I assist you?", name="Model" , HumanMessage content="I'm looking for information on marine biology.", name="Lance" New message to addnew message = AIMessage content="Sure, I can help with that. What specifically are you interested in?", name="Model" Testadd messages initial messages , new message Now, lets use MessagesState with a graph. python from IPython.display import Image, displayfrom langgraph.graph import StateGraph, START, END Nodedef tool calling llm state: MessagesState : return {"messages": llm with tools.invoke state "messages" } Build graphbuilder = StateGraph MessagesState builder.add node "tool calling llm", tool calling llm builder.add edge START, "tool calling llm" builder.add edge "tool calling llm", END graph = builder.compile Viewdisplay Image graph.get graph .draw mermaid png OUTPUT If we pass in Hello , the LLM responds without any tool calls. messages = graph.invoke {"messages": HumanMessage content="Hello " } for m in messages 'messages' : m.pretty print OUTPUT================================ Human Message =================================Hello ================================== Ai Message ==================================Hi there How can I assist you today? The LLM chooses to use a tool when it determines that the input or task requires the functionality provided by that tool. messages = graph.invoke {"messages": HumanMessage content="Multiply 2 and 3" } for m in messages 'messages' : m.pretty print OUTPUT================================ Human Message =================================Multiply 2 and 3 ================================== Ai Message ==================================Tool Calls: multiply call Er4gChFoSGzU7lsuaGzfSGTQ Call ID: call Er4gChFoSGzU7lsuaGzfSGTQ Args: a: 2 b: 3 GREAT JOB GUYS 🚀 The Building Blocks of LangGraph Part 0 https://pub.towardsai.net/the-building-blocks-of-langgraph-1975904edac8 was originally published in Towards AI https://pub.towardsai.net on Medium, where people are continuing the conversation by highlighting and responding to this story.