Building LangGraph Applications and AI Agents

The source material provides a comprehensive introduction to Langraph, a framework for building and managing AI workflows, often in conjunction with Langchain. It begins by explaining core concepts such as the state graph (the blueprint of the workflow) and runnables (standardized executable components), illustrating their relationship to nodes and the importance of message types like human, AI, system, tool, and function messages, often familiar from LLM APIs. The text then progressively guides the reader through building simple sequential graphs, handling multiple inputs, implementing conditional logic for routing with conditional edges, and finally, constructing looping graphs. Building upon these fundamentals, the source transitions to the exciting world of AI agents, demonstrating how to integrate LLMs to create a basic chatbot, enhance it with a simple memory mechanism using message history, and develop more sophisticated agents like React agents capable of using tools for reasoning and acting, culminating in the creation of a practical document drafter project featuring human-AI collaboration and an introduction to Retrieval Augmented Generation (RAG) agents that interact with a knowledge base. Throughout the examples, the importance of clear docstrings for tools and systematic graph construction is emphasized, highlighting how Langraph facilitates creating robust and modular AI applications.

Langraph State Graph Fundamentals

Based on the sources provided, here is a discussion of the State graph in Langraph:

The StateGraph is a fundamental and important element in Langraph. Its main purpose is to build and compile the graph structure. You can think of the StateGraph as a framework that helps you design and manage the flow of tasks in your application. It manages the nodes, the edges, and the overall state, ensuring that the workflow operates in a unified way and that data flows correctly between components.

An analogy used to describe the StateGraph is a blueprint of a building. Just as a blueprint outlines the design and connections within a building, the StateGraph defines the structure and flow of your workflow or application.

When coding in Langraph, the StateGraph is often one of the first elements you actually interact with. To create a graph in Langraph, you use the StateGraph attribute and initialize it by passing in your state schema. This state schema is typically defined using a class inheriting from TypeDict, such as AgentState, which holds the attributes that represent the shared data or context of the application. After defining the state schema, you then use the StateGraph instance to add nodes and edges to build the complete graph structure. Finally, the graph structure is compiled using the compile() method of the StateGraph instance.

Langraph Runnables: Building AI Workflows

Based on the sources, here is a discussion of Runnables components in Langraph:

In Langraph, a Runnable is defined as a standardized executable component. It serves as a fundamental building block that performs a specific task within an AI workflow. The concept allows for the creation of modular systems.

An analogy used to describe Runnables is that of a Lego brick. Just as Lego bricks can be snapped together to construct complex structures, runnables can be combined to create sophisticated AI workflows.

The sources contrast a Runnable with a Node, stating that a runnable can represent various operations, whereas a node in Langraph typically receives a state, performs an action on that state, and then updates the state. The distinction might not be immediately clear but is expected to become more apparent in coding sections.

The term “runnable” also appears in the context of defining conditional edges, specifically in the type hint for the path parameter as hashable runnable any. However, the source indicates that this complexity is not something to worry about initially.

Langraph and Langchain Message Types

Message types are standardized components within Langraph that represent different parts of an AI workflow, particularly interactions with large language models (LLMs). They are fundamental building blocks, treated as inbuilt data types by the developers of Langraph and Langchain.

Several specific message types are discussed in the sources:

  • Human Message: Represents the input from a user. When invoking a graph with user input, it is often provided as a Human Message. These messages are stored in the state, often as part of a list, to maintain conversation history. The content of a human message remains unaltered by the AI or other components. You import HumanMessage from langchain.schema.messages or langchain_core.messages.
  • AI Message: Represents responses generated by AI models. The content from an LLM’s response is extracted and converted into an AI Message to be appended to the state’s message history. Like Human Messages, they are stored in the state to maintain conversation history.
  • System Message: Used to provide instructions or context to the LLM. Examples include prompts like “You are a helpful assistant”. Explicitly using the SystemMessage type when invoking the model is preferred over just passing a string, as it enhances readability and signals to the LLM that it is a system instruction.
  • Tool Message: Represents data passed back to the LLM after a tool has been called. This data can include the content generated by the tool and the tool call ID. Tool messages are used in workflows involving tools, such as the React agent, to show the results of tool execution. The content of a Tool Message can also be used in the graph’s logic, for example, to determine the next step based on which tool was used.
  • Function Message: Similar to the Tool Message but specific to function calls. (The sources do not provide further detail on this type beyond its mention).

At the core of these specific message types is the BaseMessage. BaseMessage is described as the foundational class for all message types in Langraph. It acts as a parent class from which types like AIMessage, HumanMessage, ToolMessage, and SystemMessage inherit properties. These child classes may also have their own unique properties.

Message types play a crucial role in defining the structure of the AgentState, which we discussed previously as the shared data structure for a graph. The messages attribute within the state is typically defined using type annotations like List[HumanMessage] or List[Union[HumanMessage, AIMessage]] when memory is needed. A more advanced and commonly used way to define the state messages is using annotated with Sequence[BaseMessage] and a reducer function like add_messages. This structure, facilitated by these message types and supporting functions, automatically handles state updates for sequences (like chat history) by appending new messages rather than overwriting existing ones, thereby preserving the conversation flow.

By representing different interaction types as distinct message types and storing them in the state, Langraph enables the construction of stateful AI applications like chatbots with memory, where the entire history of messages can be passed to the LLM for context. They are also important for structuring output and logging the workflow progress, as demonstrated by functions that format and print different message types. Utilizing these predefined message types within Langraph and Langchain libraries provides a balance between flexible control over the workflow and reducing the need to write extensive manual code for managing conversations.

Langraph Node Actions Explained

Based on the sources and our conversation history, here is a discussion of Nodes actions in Langraph:

In Langraph, a Node is defined as an individual function or operation that performs a specific task within the graph. Think of nodes as fundamental building blocks or individual stations on an assembly line, each doing one specific job.

The primary action of a typical node is to receive the current state of the application as input, process it, and then produce an output or an updated state. This state is a shared data structure holding the current information or context of the entire application. The input to a node function is typically the AgentState (or whichever class represents your state schema) [6, 9, 10, 12, 16, 21, 24, 25, 30, 35/36, 41], and the expected output is the updated state of the same type [6, 9, 10, 12, 16, 21, 24, 25, 30, 35/36, 41].

When building a graph, the node’s action is defined by a normal standard python function. This function is passed as the second parameter to the graph.add_node() method. The first parameter is the chosen name for the node within the graph structure.

Common types of actions performed by nodes include:

  • Updating or processing data within the state: This involves accessing attributes of the state, performing operations (like string concatenation, arithmetic, list manipulation), and assigning the results back to the state’s attributes. For state attributes that are sequences like message history, the action might involve appending new items to the list rather than replacing it entirely, especially when using reducer functions like add_messages.
  • Invoking Large Language Models (LLMs): Nodes can perform the action of calling an LLM, often by passing the current state’s messages to the model using methods like lm.invoke() [24, 25, 30, 35/36, 41]. The node’s action then typically involves extracting the relevant part of the LLM’s response (like response.content) and updating the state, frequently by appending the AI’s response to the message history.
  • Running Tools: While tools are functionalities used within nodes, a special kind of node called a Tool Node has the main job of running a tool. The action of a Tool Node is to execute the configured tools and connect the tool’s output back into the state. The tools themselves perform specific tasks, and the Tool Node facilitates integrating their results into the workflow.
  • Making Decisions (Router Nodes): Some nodes, particularly those used with conditional edges, don’t necessarily modify the state but perform the action of deciding the next node to execute based on the current state’s content [16/17, 22, 31, 36]. Their “action” is to return a string representing the name of the next edge to follow [16/17, 22, 31, 36]. The logic within the function (often using if/else statements) determines which edge name is returned [16/17, 22]. These nodes are crucial for implementing conditional logic and loops in the graph structure.

Docstrings are also important for describing a node’s action, especially for nodes or tools that interact with LLMs in agentic workflows. They help inform the LLM about the function’s purpose and expected behavior. For tools, a docstring is often necessary for the graph to work correctly.

In essence, node actions are the specific operations performed by the Python functions linked to nodes in the graph structure. They are responsible for receiving, processing, and updating the shared state, or for directing the flow of execution through the graph based on logic applied to the state.

Langraph Conditional Edges Explained

Based on the sources and our conversation history, let’s discuss Conditional edges in Langraph.

Conditional edges are a type of specialized connection between nodes in a Langraph structure. Unlike a standard edge which dictates a fixed sequence of execution (e.g., always go from Node A to Node B), a conditional edge allows the graph’s flow to change dynamically based on specific conditions or logic applied to the current state of the application.

You can think of a conditional edge like a traffic light or an if/else statement. The “condition” (like the light color or the result of the if check) decides which path to take next. This capability is crucial for implementing conditional logic and branching in your workflow.

To implement conditional edges in Langraph, you use the graph.add_conditional_edge() method. This method typically requires three main components:

  1. Source Node: The node from which the conditional edge originates. This is the node where the decision is made about the next step.
  2. Decision Function (Path): A Python function that takes the current state as input and returns a value (often a string or boolean) that dictates the next edge to follow. This function embodies the conditional logic (e.g., checking a state attribute’s value). It’s important to note that when a node’s primary role is just to make a decision for a conditional edge without modifying the state, the function returns the edge name/key rather than the updated state. If the function itself doesn’t return the state, you might need to use a pass-through like lambda state: state when adding the node if the node requires state output, though the value determining the edge is what the conditional edge uses.
  3. Path Map: A dictionary that maps the possible return values from the decision function (the keys) to the names of the target nodes (the values). This tells Langraph which node to go to for each possible outcome of the decision function. For example, if the decision function returns the string ‘continue’, the path map specifies which node is associated with the ‘continue’ key.

Conditional edges are particularly important for creating loops in the graph structure. By using a conditional edge, you can direct the flow of execution back to a previous node based on a condition, allowing certain parts of the graph to be executed repeatedly until the condition is met. This is seen in the looping graph example where a should_continue function determines if the counter is less than five, returning a ‘loop’ edge that goes back to the ‘random’ node.

They are also fundamental in building more complex agent architectures like React agents and the Drafter project. In these cases, a conditional edge allows the agent to decide whether to use a tool (loop back to a tool execution node or the agent itself to process tool output) or to finish the process (go to the end point). The logic in the decision function checks the state (e.g., if the last message contains tool calls, or if a specific tool like ‘save’ was used) to determine the next step.

In summary, conditional edges are a powerful feature in Langraph that enable dynamic workflows by allowing the graph’s execution path to be determined by logic applied to the current application state, implemented using add_conditional_edge, a decision function returning a path key, and a path map linking keys to target nodes.

LangGraph Complete Course for Beginners – Complex AI Agents with Python

By Amjad Izhar
Contact: amjad.izhar@gmail.com
https://amjadizhar.blog


Discover more from Amjad Izhar Blog

Subscribe to get the latest posts sent to your email.

Comments

Leave a comment