LangGraph(一)——LangGraph简介

一、LangGraph

1.1 简介

LangGraph 是构建在 LangChain 之上的,并且与 LangChain 生态系统完全互操作。它主要通过引入一种简单的方式创建循环图。这在创建Agent Runtimes通常非常有用。

1.2 动机

通过LCEL(LangChain Expression Language)我们可以轻松创建chains,实际chains是有向无环图(DAG)。但是在创建更复杂的大型语言模型(LLM)应用时,常见的模式是在Runtime引入循环。

最简单的Angent的一个循环,基本上有以下两个步骤

  1. 调用LLM以确定(a)要执行的操作,或(b)要向用户提供什么响应
  2. 执行给定操作,然后返回步骤1

重复以上步骤,直到生成最终结果。AgentExecutorAutoGPT都使用了上述循环。

但是在很多情况下,上述结构是一个黑盒,无法满足需要更多控制的情况。例如,始终强制Agent首先调用特定工具;更好地控制工具的调用方式;根据具体状态为Agent提供不同的Propmt。

LangChain官方将这些更受控制的流程称为state machines认知架构的博客中找到下图

这些状态机具有循环的能力,允许处理比简单链更模糊的输入。然而,就如何构建该循环而言,仍然需要人参与。

LangGraph 是一种通过将状态机指定为图来创建这些状态机的方法。

1.3 功能性 Functionality

LangGraph 提供了一个相对狭窄的接口[1],用于创建状态机,通过图形化的方式指定。

1.3.1 StateGraph

StateGraph 是一个代表图的类。你通过传入一个state定义来初始化这个类。这个sate定义代表了一个随着时间更新的中心状态对象。这个state由图中的节点更新,这些节点返回对这个sate操作的属性(以键值存储的形式)。sate的属性有两种更新方法。第一种是一个属性可以被完全覆盖。第二种是通过向其值添加内容来更新属性。

在创建初始状态定义时,您可以指定是应该覆盖属性还是添加属性。以下是一个伪代码示例:

1
2
3
4
5
6
7
8
9
10
11
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)

1.3.2 节点 Nodes

StateGraph创建后,可以使用graph.add_node(name, value)来添加节点,name是一个stringname用来添加edges时引用nodevalue应该是一个functionLCEL Runnablefunction/LCEL Runnable输入参数应该和State对象的输入一样是一个字典,并输出带有要更新的State对象键的字典。

以下是一个伪代码示例:

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

还有一个特殊的END节点,用于表示图的终点。你的循环最终必须能够结束!

1
from langgraph.graph import END

1.3.3 边 Edges

在添加节点后,就可以添加边来创建图了。有以下几种边。

起始边(Starting Edge)

将图形的起点连接到特定节点。这使得该节点成为在输入传递到图形时首先被调用的节点。伪代码如下:

1
graph.set_entry_point("model")

常规边(Normal Edges)

在这些边上,一个节点应始终在另一个节点之后被调用。例如,在基本Agent运行时,我们通常希望在调用工具后再调用模型:

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

条件边(Conditional Edges)

这些边使用函数(通常由LLM提供支持)来确定首先转到哪个节点。要创建这样的边,需要传入三个参数:

  1. 上游节点:它查看此节点的输出以确定下一步应该做什么。
  2. 一个函数:该函数将被调用以确定接下来要调用哪个节点。它应该返回一个字符串。
  3. 映射:此映射将用于将第二个参数中函数的输出映射到另一个节点。键应该是函数可能返回的可能值。如果返回了某个值,那么这些值应该是要转到的节点的名称。

例如在调用了一个模型之后,我们要么退出图并返回给用户,要么调用一个工具——这取决于用户的决定!以下是伪代码示例:

1
2
3
4
5
6
7
8
graph.add_conditional_edge(
"model",
should_continue,
{
"end": END,
"continue": "tools"
}
)

1.3.4 编译 Compile

在定义好graph后,可以将其编译为 runnable,这里的runnable和LangChain的一样,有.invoke,.stream,.astream_log等方法。

1
app = graph.compile()

1.4 Agent Executor

LangGraph 重新创建了标准的 LangChain AgentExecutor,这将允许您使用现有的 LangChain Agent,并且允许您更轻松地修改 AgentExecutor 的内部结构。默认情况下,此图的状态包含了一些在使用 LangChain Agent 时应该熟悉的概念:inputchat_historyintermediate_stepsagent_outcome

1
2
3
4
5
6
7
8
9
10
11
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

1.5 Chat Agent Executor

Chat Model对消息列表进行操作,而且这些模型通常具有function calling的功能,这使得类似Agent的体验更加可行。使用这些类型的模型时,通常可以直观地将Agent的状态表示为消息列表。如下代码,输入是一个消息列表,节点只是随时间推移简单地添加到此消息列表中。

1
2
3
4
5
6
7
from typing import TypedDict, Annotated, Sequence
import operator
from langchain_core.messages import BaseMessage


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

更详细的可参看官方notebook

1.6 Modifications

LangGraph 的一个重要优势在于以更自然且可修改的方式暴露了 AgentExecutor 的逻辑。我们提供了一些我们听到过请求的修改示例:

  • 强制调用工具:当您总是希望代理首先调用工具时使用。适用于 Agent ExecutorChat Agent Executor

  • 人机交互:如何在调用工具之前添加人机交互。适用于 Agent ExecutorChat Agent Executor

  • 管理Anget步骤:用于添加处理Anget可能采取的中间步骤的自定义逻辑(在步骤较多时非常有用)。适用于 Agent ExecutorChat Agent Executor

  • 以特定格式返回输出:如何使用函数调用使代理以特定格式返回输出。仅适用于 Chat Agent Executor

  • 动态直接返回工具的输出:有时您可能希望直接返回工具的输出。我们在 LangChain 中提供了一种简单的方法来实现这一点。但这会导致工具的输出始终直接返回。有时,您可能希望让 LLM 自行决定是否直接返回响应。仅适用于 Chat Agent Executor

1.7 LangGraph未来工作

期待 LangGraph 能够实现更多自定义和强大的Agent Runtimes

计划实现的内容包括:
- 来自学术界的更先进的Agent Runtimes(LLM Compiler, plan-and-solve,etc)
- 有状态的工具(允许工具修改某些状态)
- 更受控的human-in-the-loop工作流程。
- Multi-agent工作流程。
  1. <“一个狭窄的接口”指的是一个暴露操作数量少或模块间潜在依赖性少的接口。与之相对的是“一个宽泛的接口”,它暴露了许多操作或模块间的潜在依赖性。> ↩︎

LangGraph(一)——LangGraph简介
https://mztchaoqun.com.cn/posts/D32_LangGraph/
作者
mztchaoqun
发布于
2024年8月13日
许可协议