TL;DR: LangGraph 是构建于 LangChain 之上的模块,旨在更好地创建循环图,这通常是代理运行时所需要的。
简介
在我们的 LangChain v0.1 公告中,我们重点介绍了一个新的库:LangGraph。LangGraph 构建于 LangChain 之上,并与 LangChain 生态系统完全互操作。它主要通过引入一种简单的方法来创建循环图来增加新的价值。这在创建代理运行时时通常很有用。
在这篇博文中,我们将首先探讨 LangGraph 的动机。然后,我们将介绍它提供的基本功能。接着,我们将重点介绍我们已经实现的两个代理运行时。然后,我们将强调一些我们听到的关于这些运行时的常见修改请求,并提供实现这些修改的示例。最后,我们将预览我们接下来将发布的内容。
动机
LangChain 的一大价值主张是能够轻松创建自定义链。我们为此投入了大量的功能,即 LangChain 表达式语言。然而,到目前为止,我们仍然缺乏一种简单的方法将循环引入这些链中。实际上,这些链是有向无环图 (DAGs) —— 大多数 数据编排框架 也是如此。
当我们看到人们创建更复杂的 LLM 应用程序时,常见的模式之一是将循环引入运行时。这些循环通常使用 LLM 来推理下一步在循环中做什么。LLM 的一个重大突破是能够将它们用于这些推理任务。这基本上可以被认为是像在 for 循环中运行 LLM。这些类型的系统通常被称为代理。
当考虑典型的 检索增强生成 (RAG) 应用程序 时,可以找到这种代理行为如此强大的一个例子。在典型的 RAG 应用程序中,会调用检索器以返回一些文档。然后将这些文档传递给 LLM 以生成最终答案。虽然这通常是有效的,但在第一次检索步骤未能返回任何有用的结果时,它就会失效。在这种情况下,如果 LLM 可以推理出从检索器返回的结果很差,并可能向检索器发出第二次(更精细的)查询,并使用这些结果,这通常是理想的。本质上,在循环中运行 LLM 有助于创建更灵活的应用程序,从而可以完成更多可能未预定义的模糊用例。
这些类型的应用程序通常被称为代理。其中最简单——但同时也是最雄心勃勃——的形式是一个本质上包含两个步骤的循环
- 调用 LLM 以确定 (a) 要采取哪些操作,或 (b) 要给用户什么响应
- 采取给定的操作,然后传回步骤 1
重复这些步骤,直到生成最终响应。这基本上是驱动我们核心 AgentExecutor 的循环,也是导致像 AutoGPT 这样的项目声名鹊起的相同逻辑。这很简单,因为它是一个相对简单的循环。它是最雄心勃勃的,因为它几乎将所有的决策和推理能力都卸载到 LLM。
在我们与社区和公司合作将代理投入生产的过程中,我们发现在实践中,通常需要更多的控制。您可能希望始终强制代理首先调用特定的工具。您可能希望对工具的调用方式有更多的控制。您可能希望根据代理所处的状态,为代理使用不同的提示。
当谈到这些更受控制的流程时,我们在内部将它们称为“状态机”。请参阅我们关于认知架构的博客中的下图。

这些状态机具有循环的能力——允许处理比简单链更模糊的输入。然而,在如何构建该循环方面,仍然存在人为指导的因素。
LangGraph 是一种通过将状态机指定为图来创建这些状态机的方法。
功能
LangGraph 的核心是在 LangChain 之上公开了一个相当狭窄的接口。
StateGraph
StateGraph
是表示图的类。您通过传入 state
定义来初始化此类。此状态定义表示随时间更新的中心状态对象。此状态由图中的节点更新,这些节点返回对该状态属性的操作(以键值对存储的形式)。
可以通过两种方式更新此状态的属性。首先,可以完全覆盖属性。如果您希望节点返回属性的新值,这将非常有用。其次,可以通过添加到其值来更新属性。如果属性是已采取操作的列表(或类似内容),并且您希望节点返回新采取的操作(并让这些操作自动添加到属性中),这将非常有用。
您可以在创建初始状态定义时指定是应覆盖属性还是添加到属性。请参见下面的伪代码示例。
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)
节点
创建 StateGraph
后,您可以使用 graph.add_node(name, value)
语法添加节点。name
参数应该是一个字符串,我们将在添加边时使用它来引用节点。value
参数应该是函数或 LCEL runnable,将被调用。此函数/LCEL 应该接受与 State
对象相同形式的字典作为输入,并输出一个字典,其中包含要更新的 State
对象的键。
请参见下面的伪代码示例。
graph.add_node("model", model)
graph.add_node("tools", tool_executor)
还有一个特殊的 END
节点,用于表示图的结尾。重要的是,您的循环能够最终结束!
from langgraph.graph import END
边
添加节点后,您可以添加边来创建图。有几种类型的边。
起始边
这是将图的起始点连接到特定节点的边。这将使该节点成为将输入传递到图时第一个被调用的节点。伪代码如下:
graph.set_entry_point("model")
普通边
这些边是指一个节点应始终在另一个节点之后被调用的边。例如,在基本代理运行时中,我们总是希望在调用工具后调用模型。
graph.add_edge("tools", "model")
条件边
这些边是指使用函数(通常由 LLM 驱动)来确定首先要转到哪个节点的边。要创建此边,您需要传入三件事
- 上游节点:将查看此节点的输出以确定下一步做什么
- 函数:将调用此函数以确定下一步调用哪个节点。它应该返回一个字符串
- 映射:此映射将用于将 (2) 中函数的输出映射到另一个节点。键应该是 (2) 中的函数可能返回的可能值。值应该是如果返回该值则要转到的节点的名称。
例如,在调用模型后,我们可以退出图并返回给用户,或者我们可以调用工具——这取决于用户决定!请参见下面的伪代码示例
graph.add_conditional_edge(
"model",
should_continue,
{
"end": END,
"continue": "tools"
}
)
编译
在我们定义了图之后,我们可以将其编译成 runnable!这只需获取我们目前创建的图定义并返回一个 runnable。此 runnable 公开了与 LangChain runnables 相同的所有方法(.invoke
、.stream
、.astream_log
等),使其可以像链一样被调用。
app = graph.compile()
Agent Executor
我们已经使用 LangGraph 重建了规范的 LangChain AgentExecutor。这将允许您使用现有的 LangChain 代理,但允许您更轻松地修改 AgentExecutor 的内部结构。默认情况下,此图的状态包含如果您使用过 LangChain 代理,您应该熟悉的概念:input
、chat_history
、intermediate_steps
(以及 agent_outcome
来表示最近的代理结果)
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]
请参阅 此 notebook 了解如何开始
Chat Agent Executor
我们看到的一个常见趋势是,越来越多的模型是“聊天”模型,这些模型对消息列表进行操作。这些模型通常配备了诸如函数调用之类的功能,这使得类似代理的体验更加可行。当使用这些类型的模型时,将代理的状态表示为消息列表通常是很直观的。
因此,我们创建了一个与此状态一起工作的代理运行时。输入是消息列表,节点只是随着时间的推移简单地添加到此消息列表中。
from typing import TypedDict, Annotated, Sequence
import operator
from langchain_core.messages import BaseMessage
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], operator.add]
请参阅 此 notebook 了解如何开始
修改
LangGraph 的一大优势在于,它以一种更自然和可修改的方式公开了 AgentExecutor 的逻辑。我们提供了一些我们听到的请求的修改示例
强制调用工具
用于当您始终希望代理首先调用工具时。适用于 Agent Executor 和 Chat Agent Executor。
人机环路
如何在调用工具之前添加人机环路步骤。适用于 Agent Executor 和 Chat Agent Executor。
管理代理步骤
用于添加关于如何处理代理可能采取的中间步骤的自定义逻辑(当步骤很多时很有用)。适用于 Agent Executor 和 Chat Agent Executor。
以特定格式返回输出
如何使代理使用函数调用以特定格式返回输出。仅适用于 Chat Agent Executor。
动态直接返回工具的输出
有时您可能希望直接返回工具的输出。我们在 LangChain 中提供了一种使用 return_direct
参数轻松实现此目的的方法。但是,这使得工具的输出始终直接返回。有时,您可能希望让 LLM 选择是直接返回响应还是不返回。仅适用于 Chat Agent Executor。
未来工作
我们对 LangGraph 能够实现更自定义和更强大的代理运行时感到非常兴奋。我们希望在不久的将来实现的一些功能包括
- 来自学术界的更高级代理运行时 (LLM Compiler, plan-and-solve, 等)
- 有状态工具(允许工具修改某些状态)
- 更受控制的人机环路工作流程
- 多代理工作流程
如果其中任何一项与您产生共鸣,请随时在 LangGraph 仓库中添加示例 notebook,或通过 hello@langchain.dev 联系我们进行更深入的合作!