Introduction

Example

Necessity

Key Terms

StateGraph

from langgraph.graph import StateGraph
from typing import TypedDict, List, Annotated
import Operator


class State(TypedDict):
    input: str
    all_actions: Annotated[List[str], operator.add]


graph = StateGraph(State)

Node

See an example in pseudocode below.

graph.add_node("model", model)
graph.add_node("tools", tool_executor)

There is also a special END node that is used to represent the end of the graph. It is essential that your cycles be able to end eventually!

from langgraph.graph import END

Edges

After adding nodes, you can then add edges to create the graph. There are a few types of edges.

The Starting Edge

graph.set_entry_point("model")

Normal Edges

graph.add_edge("tools", "model")

Conditional Edges

These are where a function (often powered by an LLM) is used to determine which node to go to first. To create this edge, you need to pass in three things:

  1. The upstream node: the output of this node will be looked at to determine what to do next.
  2. A function: this will be called to determine which node to call next. It should return a string.
  3. A mapping: this mapping will be used to map the function output in (2) to another node.
    • The keys should be possible values that the function in (2) could return.
    • The values should be names of nodes to go to if that value is returned.

An example of this could be that after a model is called, we either exit the graph and return to the user, or we call a tool - depending on what a user decides! See an example in pseudocode below:

graph.add_conditional_edge(
    "model",
    should_continue,
    {
        "end": END,
        "continue": "tools"
    }
)

Compile

After we define our graph, we can compile it into a runnable! This simply takes the graph definition we've created so far and returns a runnable. This runnable exposes all the same methods as LangChain runnables (.invoke.stream.astream_log, etc) allowing it to be called in the same manner as a chain.

app = graph.compile()

Agent Executor

from typing import TypedDict, Annotated, List, Union
from langchain_core.agents import AgentAction, AgentFinish
from langchain_core.messages import BaseMessage
import operator


class AgentState(TypedDict):
   input: str
   chat_history: list[BaseMessage]
   agent_outcome: Union[AgentAction, AgentFinish, None]
   intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]

See this notebook for how to get started

Chat Agent Executor

As such, we've created an agent runtime that works with this state. The input is a list of messages, and nodes just simply add to this list of messages over time.

from typing import TypedDict, Annotated, Sequence
import operator
from langchain_core.messages import BaseMessage


class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]

See this notebook for how to get started

Modifications

One of the big benefits of LangGraph is that it exposes the logic of AgentExecutor in a far more natural and modifiable way. We've provided a few examples of modifications that we've heard requests for:

Force Calling a Tool

For when you always want to make an agent call a tool first. For Agent Executor and Chat Agent Executor.

Human-in-the-loop

How to add a human-in-the-loop step before calling tools. For Agent Executor and Chat Agent Executor.

Managing Agent Steps

For adding custom logic on how to handle the intermediate steps an agent might take (useful for when there are a lot of steps). For Agent Executor and Chat Agent Executor.

Returning Output in a Specific Format

How to make the agent return output in a specific format using function calling. Only for Chat Agent Executor.

Dynamically Returning the Output of a Tool Directly

Sometimes you may want to return the output of a tool directly. We provide an easy way to do this with the return_direct parameter in LangChain. However, this makes it so that the output of a tool is ALWAYS returned directly. Sometimes, you may want to let the LLM choose whether to return the response directly or not. Only for Chat Agent Executor.

Future Work

We're incredibly excited about the possibility of LangGraph enabling more custom and powerful agent runtimes. Some of the things we are looking to implement in the near future:

If any of these resonate with you, please feel free to add an example notebook in the LangGraph repo, or reach out to us at [email protected] for more involved collaboration!

Also Read

Thoughts 🤔 by Soumendra Kumar Sahoo is licensed under CC BY 4.0