摘要 Introduction
LangGraph是在LangChain之上构建的模块,它主要是用于更好地创建循环图,这种图经常需要用在代理运行时中。LangGraph主要通过引入一种创建循环图的简单方式来增加新的价值。我们将首先介绍LangGraph的动机,然后探讨它提供的基本功能。之后,我们将重点介绍我们已经实现的两个代理运行时,然后我们将突出几种我们听到的对这些运行时的常见修改要求,以及实现这些要求的例子。最后,将展示将要发布的预览。
动机 Motivation
- LangChain的一大优势是可以轻松创建自定义链。然而,却一直缺乏一个能方便添加循环到这些链的方法。
- 一种常见的模式是人们在创建更复杂的LLM应用程序时引入运行时的循环。这些循环当LLM在循环中决定下一步要做什么时候,常常会利用LLM进行推理。这可以被理解为在for循环中运行LLM。这种类型的系统通常被称为代理。
在考虑典型的检索增强生成(RAG)应用程序时,我们可以找到一些强大的示例,展示了为什么这种代理行为如此有效。在典型的RAG应用程序中,首先调用检索器以获取一些文档。然后,这些文档传递给LLM(语言模型)以生成最终答案。尽管这通常是有效的,但如果第一个检索步骤未能返回有用的结果,整个过程可能会失败。在这种情况下,如果LLM能够推断检索器返回的结果不佳,并且可能需要发出第二个(更精细的)查询,然后使用这些结果,那将是理想的。从本质上讲,通过在循环中运行LLM,我们可以创建更灵活的应用程序,以完成可能未预定义的更模糊的用例。
这些类型的应用程序通常被称为"代理"。其中最简单但也最雄心勃勃的形式是一个循环,它基本上包含两个步骤:
- 调用LLM以确定要执行的操作或要向用户提供的响应。
- 执行给定的操作,然后返回到步骤1。
重复这些步骤,直到生成最终响应。这个循环本质上是我们核心AgentExecutor的驱动力,与导致AutoGPT等项目声名鹊起的逻辑相似。虽然这个循环很简单,但它也是最雄心勃勃的,因为它将几乎所有的决策和推理能力都交给了LLM。
在实际应用中,我们与社区和公司合作,将代理投入生产。我们发现,通常需要更多的控制。您可能希望始终强制代理首先调用特定工具,或者更好地控制工具的调用方式。您还可能希望根据代理所处的状态为其提供不同的提示。
在讨论这些更受控制的流程时,我们在内部将其称为"状态机"。您可以在我们关于认知架构的博客中找到下图。

这些状态机具有循环的能力,允许处理比简单链更模糊的输入。然而,就如何构建该循环而言,仍然存在人类指导的元素。
LangGraph 是一种通过将状态机指定为图形来创建这些状态机的方法。
功能性 Functionality
从本质上讲,LangGraph 在 LangChain 之上暴露了一个非常狭窄的接口。
StateGraph
StateGraph 是一个表示图形的类。通过传入定义来初始化这个类。这个状态定义代表着随时间更新的核心状态对象。这个状态会被图中的节点更新,这些节点会返回操作的属性(以键值对的形式)。 state 可以通过两种方式来更新这个状态的属性。首先,可以完全覆盖属性。如果您希望节点返回属性的新值,这将非常有用。其次,可以通过增加属性的值来更新属性。如果属性是所执行操作(或类似操作)的列表,并且您希望节点返回已执行的新操作(并将这些操作自动添加到属性中),这将非常有用。
在创建初始状态定义时,您可以指定是应该覆盖属性还是添加属性。以下是一个伪代码示例:
python
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)
节点 Nodes
在创建状态机之后,我们可以使用语法来添加节点。这些节点的参数应该是字符串,我们将使用它们来在添加边时引用这些节点。参数应该是将要调用的函数或可运行的LCEL(语言模型)。这些函数或LCEL应该接受与对象形式相同的字典作为输入,并输出带有要更新的对象键的字典。
以下是一个伪代码示例:
python
# 添加节点
graph.add_node("model", model)
graph.add_node("tools", tool_executor)
还有一个特殊的节点,用于表示图的末尾
python
from langgraph.graph import END
边缘 Edges
在创建节点之后,您可以添加边以构建图形。有几种类型的边:
起始边:将图形的起点连接到特定节点。这使得该节点成为在输入传递到图形时首先被调用的节点。伪代码如下:
python
graph.set_entry_point("model")
正常边:在这些边上,一个节点应始终在另一个节点之后被调用。例如,在基本代理运行时,我们通常希望在调用工具后再调用模型:
python
graph.add_edge("tools", "model")
条件边:这些边使用函数(通常由LLM提供支持)来确定首先转到哪个节点。要创建这样的边,需要传入三个参数:
- 上游节点:它查看此节点的输出以确定下一步应该做什么。
- 一个函数:该函数将被调用以确定接下来要调用哪个节点。它应该返回一个字符串。
- 映射:此映射将用于将第二个参数中函数的输出映射到另一个节点。键应该是函数可能返回的可能值。如果返回了某个值,那么这些值应该是要转到的节点的名称。
例如,在调用模型后,我们可以选择要么退出图形并返回给用户,要么调用工具 - 这取决于用户的决定!以下是伪代码示例:
python
graph.add_conditional_edge(
"model",
should_continue,
{
"end": END,
"continue": "tools"
}
)
编译 Compile
在我们定义了状态图之后,我们可以将其编译成可运行的代码!这个过程简单地将我们迄今为止创建的状态图定义转换为可运行的代码。这个可运行的代码暴露了与LangChain可运行代码相同的方法(例如,invoke
、stream
、astream_log
等),因此可以以与链式调用相同的方式进行调用。
python
app = graph.compile()
代理执行器 Agent Executor
我们使用 LangGraph 重新创建了标准的 LangChain AgentExecutor。它允许您使用现有的 LangChain 代理,同时更容易地修改 AgentExecutor 的内部结构。默认情况下,此图的状态包含了一些您在使用 LangChain 代理时应该熟悉的概念:input
、chat_history
、intermediate_steps
和 agent_outcome
。
python
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]
聊天代理执行器 Chat Agent Executor
我们注意到一个普遍趋势,即越来越多的模型是基于消息列表进行操作的"聊天"模型。这些模型通常配备了诸如函数调用之类的功能,使得代理类似的体验变得更加可行。在使用这些类型的模型时,将代理的状态表示为消息列表通常是直观的。
因此,我们创建了一个与此状态一起工作的代理运行时。输入是一个消息列表,节点只需随着时间的推移不断添加到这个消息列表中。
python
from typing import TypedDict, Annotated, Sequence
import operator
from langchain_core.messages import BaseMessage
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], operator.add]
修改 Modifications
LangGraph 的一个重要优势在于以更自然且可修改的方式暴露了 AgentExecutor 的逻辑。我们提供了一些我们听到过请求的修改示例:
- 强制调用工具:当您总是希望代理首先调用工具时使用。适用于 Agent Executor 和 Chat Agent Executor。
- 循环步骤中:如何在调用工具之前添加循环步骤。适用于 Agent Executor 和 Chat Agent Executor。
- 管理代理步骤:用于添加处理代理可能采取的中间步骤的自定义逻辑(在步骤较多时非常有用)。适用于 Agent Executor 和 Chat Agent Executor。
- 以特定格式返回输出:如何使用函数调用使代理以特定格式返回输出。仅适用于 Chat Agent Executor。
- 动态直接返回工具的输出:有时您可能希望直接返回工具的输出。我们在 LangChain 中提供了一种简单的方法来实现这一点。但这会导致工具的输出始终直接返回。有时,您可能希望让 LLM 自行决定是否直接返回响应。仅适用于 Chat Agent Executor。