mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-22 15:42:25 +02:00
Update README (#405)
This commit is contained in:
@@ -10,27 +10,24 @@
|
||||
## Overview
|
||||
|
||||
[LangGraph](https://langchain-ai.github.io/langgraph/) is a library for building stateful, multi-actor applications with LLMs.
|
||||
It extends the [LangChain Expression Language](https://python.langchain.com/docs/expression_language/) with the ability to coordinate multiple chains (or actors) across multiple steps of computation in a cyclic manner.
|
||||
It is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/).
|
||||
The current interface exposed is one inspired by [NetworkX](https://networkx.org/documentation/latest/).
|
||||
Inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/), LangGraph lets you coordinate and checkpoint multiple chains (or actors) across cyclic computational steps using regular python functions (or [JS]()).
|
||||
The current public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/).
|
||||
|
||||
The main use is for adding **cycles** to your LLM application.
|
||||
Crucially, LangGraph is **NOT optimized for acyclic**, or Directed Acyclic Graph (DAG), workflows.
|
||||
If you want to build a DAG, you should just use [LangChain Expression Language](https://python.langchain.com/docs/expression_language/).
|
||||
The main use is for adding **cycles** and **persistance** to your LLM application. If you only need quick Directed Acyclic Graphs (DAGs), you can already accomplish this using [LangChain Expression Language](https://python.langchain.com/docs/expression_language/).
|
||||
|
||||
Cycles are important for agent-like behaviors, where you call an LLM in a loop, asking it what action to take next.
|
||||
Cycles are important for agentic behaviors, where you call an LLM in a loop, asking it what action to take next.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
pip install langgraph
|
||||
pip install -U langgraph
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
One of the central concepts of LangGraph is state. Each graph execution creates a state that is passed between nodes in the graph as they execute, and each node updates this internal state with its return value after it executes. The way that the graph updates its internal state is defined by either the type of graph chosen or a custom function.
|
||||
|
||||
State in LangGraph can be pretty general, but to keep things simpler to start, we'll show off an example where the graph's state is limited to a list of chat messages using the built-in `MessageGraph` class. This is convenient when using LangGraph with LangChain chat models because we can return chat model output directly.
|
||||
State in LangGraph can be pretty general, but to keep things simpler to start, we'll show off an example where the graph's state is limited to a list of chat messages using the built-in `MessageGraph` class. This is convenient when using LangGraph with LangChain chat models because we can directly return chat model output.
|
||||
|
||||
First, install the LangChain OpenAI integration package:
|
||||
|
||||
@@ -77,9 +74,9 @@ So what did we do here? Let's break it down step by step:
|
||||
|
||||
1. First, we initialize our model and a `MessageGraph`.
|
||||
2. Next, we add a single node to the graph, called `"oracle"`, which simply calls the model with the given input.
|
||||
3. We add an edge from this `"oracle"` node to the special string `END`. This means that execution will end after current node.
|
||||
3. We add an edge from this `"oracle"` node to the special string `END` (`"__end__"`). This means that execution will end after the current node.
|
||||
4. We set `"oracle"` as the entrypoint to the graph.
|
||||
5. We compile the graph, ensuring that no more modifications to it can be made.
|
||||
5. We compile the graph, translating it to low-level [pregel operations](https://research.google/pubs/pregel-a-system-for-large-scale-graph-processing/) ensuring that it can be run.
|
||||
|
||||
Then, when we execute the graph:
|
||||
|
||||
@@ -92,7 +89,7 @@ And as a result, we get a list of two chat messages as output.
|
||||
|
||||
### Interaction with LCEL
|
||||
|
||||
As an aside for those already familiar with LangChain - `add_node` actually takes any function or runnable as input. In the above example, the model is used "as-is", but we could also have passed in a function:
|
||||
As an aside for those already familiar with LangChain - `add_node` actually takes any function or [runnable](https://python.langchain.com/docs/expression_language/interface/) as input. In the above example, the model is used "as-is", but we could also have passed in a function:
|
||||
|
||||
```python
|
||||
def call_oracle(messages: list):
|
||||
@@ -101,7 +98,7 @@ def call_oracle(messages: list):
|
||||
graph.add_node("oracle", call_oracle)
|
||||
```
|
||||
|
||||
Just make sure you are mindful of the fact that the input to the runnable is the **entire current state**. So this will fail:
|
||||
Just make sure you are mindful of the fact that the input to the [runnable](https://python.langchain.com/docs/expression_language/interface/) is the **entire current state**. So this will fail:
|
||||
|
||||
```python
|
||||
# This will not work with MessageGraph!
|
||||
@@ -124,10 +121,10 @@ graph.add_node("oracle", chain)
|
||||
|
||||
## Conditional edges
|
||||
|
||||
Now, let's move onto something a little bit less trivial. Because math can be difficult for LLMs, let's allow the LLM to conditionally call a `"multiply"` node using tool calling.
|
||||
Now, let's move onto something a little bit less trivial. LLMs struggle with math, so let's allow the LLM to conditionally call a `"multiply"` node using [tool calling](https://python.langchain.com/docs/modules/model_io/chat/function_calling/).
|
||||
|
||||
We'll recreate our graph with an additional `"multiply"` that will take the result of the most recent message, if it is a tool call, and calculate the result.
|
||||
We'll also [bind](https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html#langchain_openai.chat_models.base.ChatOpenAI.bind_tools) the calculator to the OpenAI model as a tool to allow the model to optionally use the tool necessary to respond to the current state:
|
||||
We'll also [bind](https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html#langchain_openai.chat_models.base.ChatOpenAI.bind_tools) the calculator's schema to the OpenAI model as a tool to allow the model to optionally use the tool necessary to respond to the current state:
|
||||
|
||||
```python
|
||||
from langchain_core.tools import tool
|
||||
@@ -141,16 +138,16 @@ def multiply(first_number: int, second_number: int):
|
||||
model = ChatOpenAI(temperature=0)
|
||||
model_with_tools = model.bind_tools([multiply])
|
||||
|
||||
graph = MessageGraph()
|
||||
builder = MessageGraph()
|
||||
|
||||
graph.add_node("oracle", model_with_tools)
|
||||
builder.add_node("oracle", model_with_tools)
|
||||
|
||||
tool_node = ToolNode([multiply])
|
||||
graph.add_node("multiply", tool_node)
|
||||
builder.add_node("multiply", tool_node)
|
||||
|
||||
graph.add_edge("multiply", END)
|
||||
builder.add_edge("multiply", END)
|
||||
|
||||
graph.set_entry_point("oracle")
|
||||
builder.set_entry_point("oracle")
|
||||
```
|
||||
|
||||
Now let's think - what do we want to have happened?
|
||||
@@ -158,30 +155,29 @@ Now let's think - what do we want to have happened?
|
||||
- If the `"oracle"` node returns a message expecting a tool call, we want to execute the `"multiply"` node
|
||||
- If not, we can just end execution
|
||||
|
||||
We can achieve this using **conditional edges**, which routes execution to a node based on the current state using a function.
|
||||
We can achieve this using **conditional edges**, which call a function on the current state and routes execution to a node the function's output.
|
||||
|
||||
Here's what that looks like:
|
||||
|
||||
```python
|
||||
def router(state: List[BaseMessage]):
|
||||
from typing import Literal
|
||||
|
||||
def router(state: List[BaseMessage]) -> Literal["multiply", "__end__"]:
|
||||
tool_calls = state[-1].additional_kwargs.get("tool_calls", [])
|
||||
if len(tool_calls):
|
||||
return "multiply"
|
||||
else:
|
||||
return "end"
|
||||
return "__end__"
|
||||
|
||||
graph.add_conditional_edges("oracle", router, {
|
||||
"multiply": "multiply",
|
||||
"end": END,
|
||||
})
|
||||
builder.add_conditional_edges("oracle", router)
|
||||
```
|
||||
|
||||
If the model output contains a tool call, we move to the `"multiply"` node. Otherwise, we end.
|
||||
If the model output contains a tool call, we move to the `"multiply"` node. Otherwise, we end execution.
|
||||
|
||||
Great! Now all that's left is to compile the graph and try it out. Math-related questions are routed to the calculator tool:
|
||||
|
||||
```python
|
||||
runnable = graph.compile()
|
||||
runnable = builder.compile()
|
||||
|
||||
runnable.invoke(HumanMessage("What is 123 * 456?"))
|
||||
```
|
||||
@@ -206,13 +202,13 @@ runnable.invoke(HumanMessage("What is your name?"))
|
||||
|
||||
## Cycles
|
||||
|
||||
Now, let's go over a more general example with a cycle. We will recreate the `AgentExecutor` class from LangChain. The agent itself will use chat models and function calling.
|
||||
Now, let's go over a more general cyclic example. We will recreate the `AgentExecutor` class from LangChain. The agent itself will use chat models and tool calling.
|
||||
This agent will represent all its state as a list of messages.
|
||||
|
||||
We will need to install some LangChain packages, as well as [Tavily](https://app.tavily.com/sign-in) to use as an example tool.
|
||||
We will need to install some LangChain community packages, as well as [Tavily](https://app.tavily.com/sign-in) to use as an example tool.
|
||||
|
||||
```shell
|
||||
pip install -U langchain langchain_openai tavily-python
|
||||
pip install -U langgraph langchain_openai tavily-python
|
||||
```
|
||||
|
||||
We also need to export some additional environment variables for OpenAI and Tavily API access.
|
||||
@@ -232,7 +228,7 @@ export LANGCHAIN_API_KEY=ls__...
|
||||
### Set up the tools
|
||||
|
||||
As above, we will first define the tools we want to use.
|
||||
For this simple example, we will use a built-in search tool via Tavily.
|
||||
For this simple example, we will use a web search tool.
|
||||
However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/modules/agents/tools/custom_tools) on how to do that.
|
||||
|
||||
```python
|
||||
@@ -241,31 +237,30 @@ from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
```
|
||||
|
||||
We can now wrap these tools in a simple LangGraph `ToolExecutor`.
|
||||
This class receives `ToolInvocation` objects, calls that tool, and returns the output.
|
||||
`ToolInvocation` is any class with `tool` and `tool_input` attributes.
|
||||
We can now wrap these tools in a simple LangGraph [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode).
|
||||
This class receives the list of messages (containing [tool_calls](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.ai.AIMessage.html#langchain_core.messages.ai.AIMessage.tool_calls), calls the tool(s) the LLM has requested to run, and returns the output as new [ToolMessage](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.tool.ToolMessage.html#langchain_core.messages.tool.ToolMessage)(s).
|
||||
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import ToolExecutor
|
||||
from langgraph.prebuilt import ToolNode
|
||||
|
||||
tool_executor = ToolExecutor(tools)
|
||||
tool_node = ToolNode(tools)
|
||||
```
|
||||
|
||||
### Set up the model
|
||||
|
||||
Now we need to load the chat model we want to use.
|
||||
This time, we'll use the older function calling interface. This walkthrough will use OpenAI, but we can choose any model that supports OpenAI function calling.
|
||||
Now we need to load the chat model to use.
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
# We will set streaming=True so that we can stream tokens
|
||||
# See the streaming section for more information on this.
|
||||
model = ChatOpenAI(temperature=0, streaming=True)
|
||||
model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0, streaming=True)
|
||||
```
|
||||
|
||||
After we've done this, we should make sure the model knows that it has these tools available to call.
|
||||
We can do this by converting the LangChain tools into the format for OpenAI tool calling using the [`bind_tools()`](https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html#langchain_openai.chat_models.base.ChatOpenAI.bind_tools) method.
|
||||
We can do this by converting the LangChain tools into the format for OpenAI tool calling using the [bind_tools()](https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html#langchain_openai.chat_models.base.ChatOpenAI.bind_tools) method.
|
||||
|
||||
```python
|
||||
model = model.bind_tools(tools)
|
||||
@@ -281,13 +276,15 @@ Whether to set or add is denoted by annotating the state object you construct th
|
||||
|
||||
For this example, the state we will track will just be a list of messages.
|
||||
We want each node to just add messages to that list.
|
||||
Therefore, we will use a `TypedDict` with one key (`messages`) and annotate it so that the `messages` attribute is always added to with the second parameter (`operator.add`).
|
||||
Therefore, we will use a `TypedDict` with one key (`messages`) and annotate it so that we always **add** to the `messages` key when updating it using the is always added to with the second parameter (`operator.add`).
|
||||
(Note: the state can be any [type](https://docs.python.org/3/library/stdtypes.html#type-objects), including [pydantic BaseModel's](https://docs.pydantic.dev/latest/api/base_model/)).
|
||||
|
||||
```python
|
||||
from typing import TypedDict, Annotated
|
||||
from langgraph.graph.message import add_messages
|
||||
|
||||
def add_messages(left: list, right: list):
|
||||
"""Add-don't-overwrite."""
|
||||
return left + right
|
||||
|
||||
class AgentState(TypedDict):
|
||||
# The `add_messages` function within the annotation defines
|
||||
@@ -296,72 +293,54 @@ class AgentState(TypedDict):
|
||||
```
|
||||
|
||||
You can think of the `MessageGraph` used in the initial example as a preconfigured version of this graph, where the state is directly an array of messages,
|
||||
and the update step is always to append the returned values of a node to the internal state.
|
||||
and the update step always appends the returned values of a node to the internal state.
|
||||
|
||||
### Define the nodes
|
||||
|
||||
We now need to define a few different nodes in our graph.
|
||||
In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/expression_language/).
|
||||
In `langgraph`, a node can be either a regular python function or a [runnable](https://python.langchain.com/docs/expression_language/).
|
||||
|
||||
There are two main nodes we need for this:
|
||||
|
||||
1. The agent: responsible for deciding what (if any) actions to take.
|
||||
2. A function to invoke tools: if the agent decides to take an action, this node will then execute that action.
|
||||
2. A function to invoke tools: if the agent decides to take an action, this node will then execute that action. We already defined this above.
|
||||
|
||||
We will also need to define some edges.
|
||||
Some of these edges may be conditional.
|
||||
The reason they are conditional is that based on the output of a node, one of several paths may be taken.
|
||||
The path that is taken is not known until that node is run (the LLM decides).
|
||||
The reason they are conditional is that the destination depends on the contents of the graph's `State`.
|
||||
|
||||
The path that is taken is not known until that node is run (the LLM decides). For our use case, we will need one of each type of edge:
|
||||
|
||||
1. Conditional Edge: after the agent is called, we should either:
|
||||
|
||||
a. If the agent said to take an action, then the function to invoke tools should be called
|
||||
a. Run tools if the agent said to take an action, OR
|
||||
|
||||
b. If the agent said that it was finished, then it should finish
|
||||
b. Finish (respond to the user) if the agent did not ask to run tools
|
||||
|
||||
2. Normal Edge: after the tools are invoked, it should always go back to the agent to decide what to do next
|
||||
2. Normal Edge: after the tools are invoked, the graph should always return to the agent to decide what to do next
|
||||
|
||||
Let's define the nodes, as well as a function to decide how what conditional edge to take.
|
||||
Let's define the nodes, as well as a function to define the conditional edge to take.
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import ToolInvocation
|
||||
import json
|
||||
from langchain_core.messages import FunctionMessage
|
||||
from typing import Literal
|
||||
|
||||
# Define the function that determines whether to continue or not
|
||||
def should_continue(state):
|
||||
def should_continue(state: AgentState) -> Literal["action", "__end__"]:
|
||||
messages = state['messages']
|
||||
last_message = messages[-1]
|
||||
# If there is no function call, then we finish
|
||||
if "function_call" not in last_message.additional_kwargs:
|
||||
return "end"
|
||||
# Otherwise if there is, we continue
|
||||
else:
|
||||
return "continue"
|
||||
# If the LLM makes a tool call, then we route to the "action" node
|
||||
if last_message.tool_calls:
|
||||
return "action"
|
||||
# Otherwise, we stop (reply to the user)
|
||||
return "__end__"
|
||||
|
||||
|
||||
# Define the function that calls the model
|
||||
def call_model(state):
|
||||
def call_model(state: AgentState):
|
||||
messages = state['messages']
|
||||
response = model.invoke(messages)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [response]}
|
||||
|
||||
# Define the function to execute tools
|
||||
def call_tool(state):
|
||||
messages = state['messages']
|
||||
# Based on the continue condition
|
||||
# we know the last message involves a function call
|
||||
last_message = messages[-1]
|
||||
# We construct an ToolInvocation from the function_call
|
||||
action = ToolInvocation(
|
||||
tool=last_message.additional_kwargs["function_call"]["name"],
|
||||
tool_input=json.loads(last_message.additional_kwargs["function_call"]["arguments"]),
|
||||
)
|
||||
# We call the tool_executor and get back a response
|
||||
response = tool_executor.invoke(action)
|
||||
# We use the response to create a FunctionMessage
|
||||
function_message = FunctionMessage(content=str(response), name=action.tool)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [function_message]}
|
||||
```
|
||||
|
||||
### Define the graph
|
||||
@@ -375,7 +354,7 @@ workflow = StateGraph(AgentState)
|
||||
|
||||
# Define the two nodes we will cycle between
|
||||
workflow.add_node("agent", call_model)
|
||||
workflow.add_node("action", call_tool)
|
||||
workflow.add_node("action", tool_node)
|
||||
|
||||
# Set the entrypoint as `agent`
|
||||
# This means that this node is the first one called
|
||||
@@ -388,18 +367,6 @@ workflow.add_conditional_edges(
|
||||
"agent",
|
||||
# Next, we pass in the function that will determine which node is called next.
|
||||
should_continue,
|
||||
# Finally we pass in a mapping.
|
||||
# The keys are strings, and the values are other nodes.
|
||||
# END is a special node marking that the graph should finish.
|
||||
# What will happen is we will call `should_continue`, and then the output of that
|
||||
# will be matched against the keys in this mapping.
|
||||
# Based on which one it matches, that node will then be called.
|
||||
{
|
||||
# If `tools`, then we call the tool node.
|
||||
"continue": "action",
|
||||
# Otherwise we finish.
|
||||
"end": END
|
||||
}
|
||||
)
|
||||
|
||||
# We now add a normal edge from `tools` to `agent`.
|
||||
@@ -416,7 +383,7 @@ app = workflow.compile()
|
||||
|
||||
We can now use it!
|
||||
This now exposes the [same interface](https://python.langchain.com/docs/expression_language/) as all other LangChain runnables.
|
||||
This runnable accepts a list of messages.
|
||||
This [runnable](https://python.langchain.com/docs/expression_language/interface/) accepts a list of messages.
|
||||
|
||||
```python
|
||||
from langchain_core.messages import HumanMessage
|
||||
@@ -438,7 +405,7 @@ One of the benefits of using LangGraph is that it is easy to stream output as it
|
||||
|
||||
```python
|
||||
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
|
||||
for output in app.stream(inputs):
|
||||
for output in app.stream(inputs, stream_mode="updates"):
|
||||
# stream() yields dictionaries with output keyed by node name
|
||||
for key, value in output.items():
|
||||
print(f"Output from node '{key}':")
|
||||
@@ -496,7 +463,7 @@ async for output in app.astream_log(inputs, include_types=["llm"]):
|
||||
|
||||
```
|
||||
content='' additional_kwargs={'function_call': {'arguments': '', 'name': 'tavily_search_results_json'}}
|
||||
content='' additional_kwargs={'function_call': {'arguments': '{\n', 'name': ''}}
|
||||
content='' additional_kwargs={'function_call': {'arguments': '{\n', 'name': ''}}}
|
||||
content='' additional_kwargs={'function_call': {'arguments': ' ', 'name': ''}}
|
||||
content='' additional_kwargs={'function_call': {'arguments': ' "', 'name': ''}}
|
||||
content='' additional_kwargs={'function_call': {'arguments': 'query', 'name': ''}}
|
||||
@@ -512,414 +479,20 @@ If you need cycles.
|
||||
Langchain Expression Language allows you to easily define chains (DAGs) but does not have a good mechanism for adding in cycles.
|
||||
`langgraph` adds that syntax.
|
||||
|
||||
## How-to Guides
|
||||
|
||||
These guides show how to use LangGraph in particular ways.
|
||||
|
||||
### Async
|
||||
|
||||
If you are running LangGraph in async workflows, you may want to create the nodes to be async by default.
|
||||
For a walkthrough on how to do that, see [this documentation](https://github.com/langchain-ai/langgraph/blob/main/examples/async.ipynb)
|
||||
|
||||
### Streaming Tokens
|
||||
|
||||
Sometimes language models take a while to respond and you may want to stream tokens to end users.
|
||||
For a guide on how to do this, see [this documentation](https://github.com/langchain-ai/langgraph/blob/main/examples/streaming-tokens.ipynb)
|
||||
|
||||
### Persistence
|
||||
|
||||
LangGraph comes with built-in persistence, allowing you to save the state of the graph at point and resume from there.
|
||||
For a walkthrough on how to do that, see [this documentation](https://github.com/langchain-ai/langgraph/blob/main/examples/persistence.ipynb)
|
||||
|
||||
### Human-in-the-loop
|
||||
|
||||
LangGraph comes with built-in support for human-in-the-loop workflows. This is useful when you want to have a human review the current state before proceeding to a particular node.
|
||||
For a walkthrough on how to do that, see [this documentation](https://github.com/langchain-ai/langgraph/blob/main/examples/human-in-the-loop.ipynb)
|
||||
|
||||
### Visualizing the graph
|
||||
|
||||
Agents you create with LangGraph can be complex. In order to make it easier to understand what is happening under the hood, we've added methods to print out and visualize the graph.
|
||||
This can create both ascii art and pngs.
|
||||
For a walkthrough on how to do that, see [this documentation](https://github.com/langchain-ai/langgraph/blob/main/examples/visualization.ipynb)
|
||||
|
||||
### "Time Travel"
|
||||
|
||||
With "time travel" functionality you can jump to any point in the graph execution, modify the state, and rerun from there.
|
||||
This is useful for both debugging workflows, as well as end user-facing workflows to allow them to correct the state.
|
||||
For a walkthrough on how to do that, see [this documentation](https://github.com/langchain-ai/langgraph/blob/main/examples/time-travel.ipynb)
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### ChatAgentExecutor: with function calling
|
||||
|
||||
This agent executor takes a list of messages as input and outputs a list of messages.
|
||||
All agent state is represented as a list of messages.
|
||||
This specifically uses OpenAI function calling.
|
||||
This is recommended agent executor for newer chat based models that support function calling.
|
||||
|
||||
- [Getting Started Notebook](https://github.com/langchain-ai/langgraph/blob/main/examples/chat_agent_executor_with_function_calling/base.ipynb): Walks through creating this type of executor from scratch
|
||||
- [High Level Entrypoint](https://github.com/langchain-ai/langgraph/blob/main/examples/chat_agent_executor_with_function_calling/high-level.ipynb): Walks through how to use the high level entrypoint for the chat agent executor.
|
||||
|
||||
**Modifications**
|
||||
|
||||
We also have a lot of examples highlighting how to slightly modify the base chat agent executor. These all build off the [getting started notebook](https://github.com/langchain-ai/langgraph/blob/main/examples/chat_agent_executor_with_function_calling/base.ipynb) so it is recommended you start with that first.
|
||||
|
||||
- [Human-in-the-loop](https://github.com/langchain-ai/langgraph/blob/main/examples/chat_agent_executor_with_function_calling/human-in-the-loop.ipynb): How to add a human-in-the-loop component
|
||||
- [Force calling a tool first](https://github.com/langchain-ai/langgraph/blob/main/examples/chat_agent_executor_with_function_calling/force-calling-a-tool-first.ipynb): How to always call a specific tool first
|
||||
- [Respond in a specific format](https://github.com/langchain-ai/langgraph/blob/main/examples/chat_agent_executor_with_function_calling/respond-in-format.ipynb): How to force the agent to respond in a specific format
|
||||
- [Dynamically returning tool output directly](https://github.com/langchain-ai/langgraph/blob/main/examples/chat_agent_executor_with_function_calling/dynamically-returning-directly.ipynb): How to dynamically let the agent choose whether to return the result of a tool directly to the user
|
||||
- [Managing agent steps](https://github.com/langchain-ai/langgraph/blob/main/examples/chat_agent_executor_with_function_calling/managing-agent-steps.ipynb): How to more explicitly manage intermediate steps that an agent takes
|
||||
|
||||
### AgentExecutor
|
||||
|
||||
This agent executor uses existing LangChain agents.
|
||||
|
||||
- [Getting Started Notebook](https://github.com/langchain-ai/langgraph/blob/main/examples/agent_executor/base.ipynb): Walks through creating this type of executor from scratch
|
||||
- [High Level Entrypoint](https://github.com/langchain-ai/langgraph/blob/main/examples/agent_executor/high-level.ipynb): Walks through how to use the high level entrypoint for the chat agent executor.
|
||||
|
||||
**Modifications**
|
||||
|
||||
We also have a lot of examples highlighting how to slightly modify the base chat agent executor. These all build off the [getting started notebook](https://github.com/langchain-ai/langgraph/blob/main/examples/agent_executor/base.ipynb) so it is recommended you start with that first.
|
||||
|
||||
- [Human-in-the-loop](https://github.com/langchain-ai/langgraph/blob/main/examples/agent_executor/human-in-the-loop.ipynb): How to add a human-in-the-loop component
|
||||
- [Force calling a tool first](https://github.com/langchain-ai/langgraph/blob/main/examples/agent_executor/force-calling-a-tool-first.ipynb): How to always call a specific tool first
|
||||
- [Managing agent steps](https://github.com/langchain-ai/langgraph/blob/main/examples/agent_executor/managing-agent-steps.ipynb): How to more explicitly manage intermediate steps that an agent takes
|
||||
|
||||
### Planning Agent Examples
|
||||
|
||||
The following notebooks implement agent architectures prototypical of the "plan-and-execute" style, where an LLM planner decomposes a user request into a program, an executor executes the program, and an LLM synthesizes a response (and/or dynamically replans) based on the program outputs.
|
||||
|
||||
- [Plan-and-execute](https://github.com/langchain-ai/langgraph/blob/main/examples/plan-and-execute/plan-and-execute.ipynb): a simple agent with a **planner** that generates a multi-step task list, an **executor** that invokes the tools in the plan, and a **replanner** that responds or generates an updated plan. Based on the [Plan-and-solve](https://arxiv.org/abs/2305.04091) paper by Wang, et. al.
|
||||
- [Reasoning without Observation](https://github.com/langchain-ai/langgraph/blob/main/examples/rewoo/rewoo.ipynb): planner generates a task list whose observations are saved as **variables**. Variables can be used in subsequent tasks to reduce the need for further re-planning. Based on the [ReWOO](https://arxiv.org/abs/2305.18323) paper by Xu, et. al.
|
||||
- [LLMCompiler](https://github.com/langchain-ai/langgraph/blob/main/examples/llm-compiler/LLMCompiler.ipynb): planner generates a **DAG** of tasks with variable responses. Tasks are **streamed** and executed eagerly to minimize tool execution runtime. Based on the [paper](https://arxiv.org/abs/2312.04511) by Kim, et. al.
|
||||
|
||||
### Reflection / Self-Critique
|
||||
|
||||
When output quality is a major concern, it's common to incorporate some combination of self-critique or reflection and external validation to refine your system's outputs. The following examples demonstrate research that implement this type of design.
|
||||
|
||||
- [Basic Reflection](./examples/reflection/reflection.ipynb): add a simple "reflect" step in your graph to prompt your system to revise its outputs.
|
||||
- [Reflexion](./examples/reflexion/reflexion.ipynb): critique missing and superfluous aspects of the agent's response to guide subsequent steps. Based on [Reflexion](https://arxiv.org/abs/2303.11366), by Shinn, et. al.
|
||||
- [Language Agent Tree Search](./examples/lats/lats.ipynb): execute multiple agents in parallel, using reflection and environmental rewards to drive a Monte Carlo Tree Search. Based on [LATS](https://arxiv.org/abs/2310.04406), by Zhou, et. al.
|
||||
|
||||
### Multi-agent Examples
|
||||
|
||||
- [Multi-agent collaboration](https://github.com/langchain-ai/langgraph/blob/main/examples/multi_agent/multi-agent-collaboration.ipynb): how to create two agents that work together to accomplish a task
|
||||
- [Multi-agent with supervisor](https://github.com/langchain-ai/langgraph/blob/main/examples/multi_agent/agent_supervisor.ipynb): how to orchestrate individual agents by using an LLM as a "supervisor" to distribute work
|
||||
- [Hierarchical agent teams](https://github.com/langchain-ai/langgraph/blob/main/examples/multi_agent/hierarchical_agent_teams.ipynb): how to orchestrate "teams" of agents as nested graphs that can collaborate to solve a problem
|
||||
|
||||
### Web Research
|
||||
|
||||
- [STORM](./examples/storm/storm.ipynb): writing system that generates Wikipedia-style articles on any topic, applying outline generation (planning) + multi-perspective question-answering for added breadth and reliability. Based on [STORM](https://arxiv.org/abs/2402.14207) by Shao, et. al.
|
||||
|
||||
### Chatbot Evaluation via Simulation
|
||||
|
||||
It can often be tough to evaluation chat bots in multi-turn situations. One way to do this is with simulations.
|
||||
|
||||
- [Chat bot evaluation as multi-agent simulation](https://github.com/langchain-ai/langgraph/blob/main/examples/chatbot-simulation-evaluation/agent-simulation-evaluation.ipynb): how to simulate a dialogue between a "virtual user" and your chat bot
|
||||
- [Evaluating over a dataset](./examples/chatbot-simulation-evaluation/langsmith-agent-simulation-evaluation.ipynb): benchmark your assistant over a LangSmith dataset, which tasks a simulated customer to red-team your chat bot.
|
||||
|
||||
### Multimodal Examples
|
||||
|
||||
- [WebVoyager](https://github.com/langchain-ai/langgraph/blob/main/examples/web-navigation/web_voyager.ipynb): vision-enabled web browsing agent that uses [Set-of-marks](https://som-gpt4v.github.io/) prompting to navigate a web browser and execute tasks
|
||||
|
||||
### [Chain-of-Table](https://github.com/CYQIQ/MultiCoT)
|
||||
|
||||
[Chain of Table](https://arxiv.org/abs/2401.04398) is a framework that elicits SOTA performance when answering questions over tabular data. [This implementation](https://github.com/CYQIQ/MultiCoT) by Github user [CYQIQ](https://github.com/CYQIQ) uses LangGraph to control the flow.
|
||||
|
||||
## Documentation
|
||||
|
||||
There are only a few new APIs to use.
|
||||
We hope this gave you a taste of what you can build! Check out the rest of the docs to learn more.
|
||||
|
||||
### StateGraph
|
||||
### Tutorials
|
||||
|
||||
The main entrypoint is `StateGraph`.
|
||||
Learn to build with LangGraph through guided examples in the [LangGraph Tutorials](https://langchain-ai.github.io/langgraph/tutorials/).
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph
|
||||
```
|
||||
We recommend starting with the [Introduction to LangGraph](https://langchain-ai.github.io/langgraph/tutorials/introduction/) before trying out the more advanced guides.
|
||||
|
||||
This class is responsible for constructing the graph.
|
||||
It exposes an interface inspired by [NetworkX](https://networkx.org/documentation/latest/).
|
||||
This graph is parameterized by a state object that it passes around to each node.
|
||||
### How-to Guides
|
||||
|
||||
#### `__init__`
|
||||
The [LangGraph how-to guides](https://langchain-ai.github.io/langgraph/how-tos/) show how to accomplish specific things within LangGraph, from streaming, to adding memory & persistance, to common design patterns (branching, subgraphs, etc.), these are the place to go if you want to copy and run a specific code snippet.
|
||||
|
||||
```python
|
||||
def __init__(self, schema: Type[Any]) -> None:
|
||||
```
|
||||
### Reference
|
||||
|
||||
When constructing the graph, you need to pass in a schema for a state.
|
||||
Each node then returns operations to update that state.
|
||||
These operations can either SET specific attributes on the state (e.g. overwrite the existing values) or ADD to the existing attribute.
|
||||
Whether to set or add is denoted by annotating the state object you construct the graph with.
|
||||
|
||||
The recommended way to specify the schema is with a typed dictionary: `from typing import TypedDict`
|
||||
|
||||
You can then annotate the different attributes using `from typing import Annotated`.
|
||||
Currently, the only supported annotation is `import operator; operator.add`.
|
||||
This annotation will make it so that any node that returns this attribute ADDS that new result to the existing value.
|
||||
|
||||
Let's take a look at an example:
|
||||
|
||||
```python
|
||||
from typing import TypedDict, Annotated, Union
|
||||
from langchain_core.agents import AgentAction, AgentFinish
|
||||
import operator
|
||||
|
||||
|
||||
class AgentState(TypedDict):
|
||||
# The input string
|
||||
input: str
|
||||
# The outcome of a given call to the agent
|
||||
# Needs `None` as a valid type, since this is what this will start as
|
||||
agent_outcome: Union[AgentAction, AgentFinish, None]
|
||||
# List of actions and corresponding observations
|
||||
# Here we annotate this with `operator.add` to indicate that operations to
|
||||
# this state should be ADDED to the existing values (not overwrite it)
|
||||
intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]
|
||||
|
||||
```
|
||||
|
||||
We can then use this like:
|
||||
|
||||
```python
|
||||
# Initialize the StateGraph with this state
|
||||
graph = StateGraph(AgentState)
|
||||
# Create nodes and edges
|
||||
...
|
||||
# Compile the graph
|
||||
app = graph.compile()
|
||||
|
||||
# The inputs should be a dictionary, because the state is a TypedDict
|
||||
inputs = {
|
||||
# Let's assume this the input
|
||||
"input": "hi"
|
||||
# Let's assume agent_outcome is set by the graph as some point
|
||||
# It doesn't need to be provided, and it will be None by default
|
||||
# Let's assume `intermediate_steps` is built up over time by the graph
|
||||
# It doesn't need to provided, and it will be empty list by default
|
||||
# The reason `intermediate_steps` is an empty list and not `None` is because
|
||||
# it's annotated with `operator.add`
|
||||
}
|
||||
```
|
||||
|
||||
#### `.add_node`
|
||||
|
||||
```python
|
||||
def add_node(self, key: str, action: RunnableLike) -> None:
|
||||
```
|
||||
|
||||
This method adds a node to the graph.
|
||||
It takes two arguments:
|
||||
|
||||
- `key`: A string representing the name of the node. This must be unique.
|
||||
- `action`: The action to take when this node is called. This should either be a function or a runnable.
|
||||
|
||||
#### `.add_edge`
|
||||
|
||||
```python
|
||||
def add_edge(self, start_key: str, end_key: str) -> None:
|
||||
```
|
||||
|
||||
Creates an edge from one node to the next.
|
||||
This means that output of the first node will be passed to the next node.
|
||||
It takes two arguments.
|
||||
|
||||
- `start_key`: A string representing the name of the start node. This key must have already been registered in the graph.
|
||||
- `end_key`: A string representing the name of the end node. This key must have already been registered in the graph.
|
||||
|
||||
#### `.add_conditional_edges`
|
||||
|
||||
```python
|
||||
def add_conditional_edges(
|
||||
self,
|
||||
start_key: str,
|
||||
condition: Callable[..., str],
|
||||
conditional_edge_mapping: Dict[str, str],
|
||||
) -> None:
|
||||
```
|
||||
|
||||
This method adds conditional edges.
|
||||
What this means is that only one of the downstream edges will be taken, and which one that is depends on the results of the start node.
|
||||
This takes three arguments:
|
||||
|
||||
- `start_key`: A string representing the name of the start node. This key must have already been registered in the graph.
|
||||
- `condition`: A function to call to decide what to do next. The input will be the output of the start node. It should return a string that is present in `conditional_edge_mapping` and represents the edge to take.
|
||||
- `conditional_edge_mapping`: A mapping of string to string. The keys should be strings that may be returned by `condition`. The values should be the downstream node to call if that condition is returned.
|
||||
|
||||
#### `.set_entry_point`
|
||||
|
||||
```python
|
||||
def set_entry_point(self, key: str) -> None:
|
||||
```
|
||||
|
||||
The entrypoint to the graph.
|
||||
This is the node that is first called.
|
||||
It only takes one argument:
|
||||
|
||||
- `key`: The name of the node that should be called first.
|
||||
|
||||
#### `.set_conditional_entry_point`
|
||||
|
||||
```python
|
||||
def set_conditional_entry_point(
|
||||
self,
|
||||
condition: Callable[..., str],
|
||||
conditional_edge_mapping: Optional[Dict[str, str]] = None,
|
||||
) -> None:
|
||||
```
|
||||
|
||||
This method adds a conditional entry point.
|
||||
What this means is that when the graph is called, it will call the `condition` Callable to decide what node to enter into first.
|
||||
|
||||
- `condition`: A function to call to decide what to do next. The input will be the input to the graph. It should return a string that is present in `conditional_edge_mapping` and represents the edge to take.
|
||||
- `conditional_edge_mapping`: A mapping of string to string. The keys should be strings that may be returned by `condition`. The values should be the downstream node to call if that condition is returned.
|
||||
|
||||
#### `.set_finish_point`
|
||||
|
||||
```python
|
||||
def set_finish_point(self, key: str) -> None:
|
||||
```
|
||||
|
||||
This is the exit point of the graph.
|
||||
When this node is called, the results will be the final result from the graph.
|
||||
It only has one argument:
|
||||
|
||||
- `key`: The name of the node that, when called, will return the results of calling it as the final output
|
||||
|
||||
Note: This does not need to be called if at any point you previously created an edge (conditional or normal) to `END`
|
||||
|
||||
### Graph
|
||||
|
||||
```python
|
||||
from langgraph.graph import Graph
|
||||
|
||||
graph = Graph()
|
||||
```
|
||||
|
||||
This has the same interface as `StateGraph` with the exception that it doesn't update a state object over time, and rather relies on passing around the full state from each step.
|
||||
This means that whatever is returned from one node is the input to the next as is.
|
||||
|
||||
### `END`
|
||||
|
||||
```python
|
||||
from langgraph.graph import END
|
||||
```
|
||||
|
||||
This is a special node representing the end of the graph.
|
||||
This means that anything passed to this node will be the final output of the graph.
|
||||
It can be used in two places:
|
||||
|
||||
- As the `end_key` in `add_edge`
|
||||
- As a value in `conditional_edge_mapping` as passed to `add_conditional_edges`
|
||||
|
||||
## Prebuilt Examples
|
||||
|
||||
There are also a few methods we've added to make it easy to use common, prebuilt graphs and components.
|
||||
|
||||
### ToolExecutor
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import ToolExecutor
|
||||
```
|
||||
|
||||
This is a simple helper class to help with calling tools.
|
||||
It is parameterized by a list of tools:
|
||||
|
||||
```python
|
||||
tools = [...]
|
||||
tool_executor = ToolExecutor(tools)
|
||||
```
|
||||
|
||||
It then exposes a [runnable interface](https://python.langchain.com/docs/expression_language/interface).
|
||||
It can be used to call tools: you can pass in an [AgentAction](https://python.langchain.com/docs/modules/agents/concepts#agentaction) and it will look up the relevant tool and call it with the appropriate input.
|
||||
|
||||
### chat_agent_executor.create_function_calling_executor
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import chat_agent_executor
|
||||
```
|
||||
|
||||
This is a helper function for creating a graph that works with a chat model that utilizes function calling.
|
||||
Can be created by passing in a model and a list of tools.
|
||||
The model must be one that supports OpenAI function calling.
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
from langgraph.prebuilt import chat_agent_executor
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
model = ChatOpenAI()
|
||||
|
||||
app = chat_agent_executor.create_function_calling_executor(model, tools)
|
||||
|
||||
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
|
||||
for s in app.stream(inputs):
|
||||
print(list(s.values())[0])
|
||||
print("----")
|
||||
```
|
||||
|
||||
### chat_agent_executor.create_tool_calling_executor
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import chat_agent_executor
|
||||
```
|
||||
|
||||
This is a helper function for creating a graph that works with a chat model that utilizes tool calling.
|
||||
Can be created by passing in a model and a list of tools.
|
||||
The model must be one that supports OpenAI tool calling.
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
from langgraph.prebuilt import chat_agent_executor
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
model = ChatOpenAI()
|
||||
|
||||
app = chat_agent_executor.create_tool_calling_executor(model, tools)
|
||||
|
||||
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
|
||||
for s in app.stream(inputs):
|
||||
print(list(s.values())[0])
|
||||
print("----")
|
||||
```
|
||||
|
||||
### create_agent_executor
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_agent_executor
|
||||
```
|
||||
|
||||
This is a helper function for creating a graph that works with [LangChain Agents](https://python.langchain.com/docs/modules/agents/).
|
||||
Can be created by passing in an agent and a list of tools.
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_agent_executor
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain import hub
|
||||
from langchain.agents import create_openai_functions_agent
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
|
||||
# Get the prompt to use - you can modify this!
|
||||
prompt = hub.pull("hwchase17/openai-functions-agent")
|
||||
|
||||
# Choose the LLM that will drive the agent
|
||||
llm = ChatOpenAI(model="gpt-3.5-turbo-1106")
|
||||
|
||||
# Construct the OpenAI Functions agent
|
||||
agent_runnable = create_openai_functions_agent(llm, tools, prompt)
|
||||
|
||||
app = create_agent_executor(agent_runnable, tools)
|
||||
|
||||
inputs = {"input": "what is the weather in sf", "chat_history": []}
|
||||
for s in app.stream(inputs):
|
||||
print(list(s.values())[0])
|
||||
print("----")
|
||||
```
|
||||
LangGraph's API has a few important classes and methods that are all covered in the [Reference Documents](https://langchain-ai.github.io/langgraph/reference/graphs/). Check these out to see the specific funcion arguments and simple examples of how to use the graph + checkpointing APIs or to see some of the higher-level prebuilt components.
|
||||
|
||||
Reference in New Issue
Block a user