和Chain的区别:Chain是有向无环,而LangGraph提供循环机制。
和AgentExecutor的区别:AgentExecutor是相对简单的循环,而LangGraph提供更多的控制。比如(1)你可能总是希望强制代理首先调用特定工具。(2)你可能希望对如何调用工具有更多控制。(3)根据代理所处的状态,你可能希望为代理设置不同的提示。
当讨论更多控制的流程,其实是在讨论状态机state machines的概念。这些状态机具有循环的能力——允许处理比简单链更模糊的输入。然而,在如何构建那个循环的方面,仍然存在人为指导的要素。
LangGraph定义:一种通过将状态机指定为图形来创建它们的方法。
下文是对 LangGraph框架内的 组件和功能进行必要的介绍,掌握LangGraph是掌握LangChain的必要过程。
StateGraph 状态图
表示 图形 graph的类。传入 state 初始化此类。
这个状态定义代表了一个随时间更新的中心状态对象。这个状态由图中的节点更新,这些节点返回对这个状态属性的操作。
状态属性的值更新有两种方式:
(1)完全覆盖原属性「希望节点返回属性的新值」
(2)添加属性值来更新原属性「希望节点返回所采取的新操作、并将这些操作自动添加到属性中」
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类初始化
# 以上完成了 StateGraph实例化。
Nodes 节点
创建StateGraph后,可以使用 graph.add_node(name, value) 语法添加节点。
name参数是一个字符串,添加Edges时使用该字符串来引用节点。
value参数是要调用的函数或ICEL可运行对象(Runnable实例化)。 此函数/ICEL 应接受与 State对象 相同形式的字典作为输入,并输出带有要更新的 State对象的key的dict。
graph.add_node("model", model)
graph.add_node("tools", tool_executor)
还有一个特殊的END节点,用于表示图的结尾。
from langgraph.graph import END
Edges 边
接下来,给图形graph增加边edges。 edges有几种类型:
(1)起始边 the starting edge
graph.set_entry_point("model")
这条边的作用:将特点节点作为graph的起点。使得此节点作为输入传递给graph时第一个调用的节点。
(2)普通边 normal edges
这些边的作用:一个节点应该始终在另一个节点之后被调用。
举个例子:
graph.add_edge("tools", "model") # 我们总是希望在调用工具(tools节点)之后调用模型(model)
(3)条件边 Conditional Edges
重点
条件边的作用:确定首先前往哪个节点(通常是LLM来决定,也就是说可以由别的决定)。
创建条件边,需要传入以下3个:
a.上游节点 upstream node:基于此节点的输出来确定下一步做什么
b.一个函数 function:调用此函数来确定接下来要调用哪个节点,此函数应该返回一个字符串
c.一个映射 mapping:此映射将b.中函数输出的字符串映射到另一个节点。此映射的key应该是b.中函数返回的所有可能值,value是对应的节点名称。
举个例子:
# 以下代码表示:在调用模型之后,要么结束graph并返回给用户,要么根据用户的决定调用一个工具tools
graph.add_conditional_edge(
"model", # 上游节点
should_continue, # function
{
"end": END,
"continue": "tools"
} # mapping
)
Compile 编译
以上操作(初始化StateGraph,定义节点,定义边)即完成了graph的定义。接下来可以将graph编译成可运行的。
编译的结果是 获取目前为止创建的graph定义,并返回一个可运行的对象。
这个可运行的对象和LangChain的runnables有相同的方法(invoke, stream, astream等),也允许以Chain的方式调用。
app = graph.compile()
Agent Executor 代理执行人
以上,我们已经用 LangGraph 重新创建了标准的 LangChain AgentExecutor。也就是说,只是用LangGraph框架重新建了一遍 AgentExecutor。 原来创建 AgentExecutor 只需要以下两句:
agent = create_structured_chat_agent(llm, tools, prompt, stop_sequence=True)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True, return_intermediate_steps=True, max_iterations=3)
迁移到LangGraph的作用:更容易修改 AgentExecutor 的内部结构。
而 graph 默认的状态包括以下 概念:input 、 chat_history 、 intermediate_steps 、agent_outcome (最新的agent结果)
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] # 状态属性的更新方式二、记录llm的对话历史
agent_outcome: Union[AgentAction, AgentFinish, None] # 状态属性的更新方式二、记录agent最新的动作
intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add] # 状态属性的更新方式二、记录agent调用tool的历史和tool调用结果
Chat Agent Executor 聊天代理执行人
越来越多的模型是基于消息列表运行的“聊天”模型,并且这些模型通常配置了function calling的功能,这使得agent式体验更加可行。所以在使用这些类型模型时,将agent的state设置为message列表更加直观。
如下所示:
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 的逻辑。
1、强制调用工具 force calling a tool
2、在调用工具之前添加人机交互步骤 human in the loop
3、管理agent步骤 managing agent steps:用于添加自定义逻辑,以处理代理可能采取的中间步骤。
4、以特定格式返回输出 returning output in a specific format
5、直接动态返回工具的输出 dynamically returning the output of a tool directly
示例参见 langgraph代码库
后续计划
1、Stateful tools 有状态的工具:允许工具修改某些状态
2、Multi-agent workflows 多代理工作流程
3、More controlled human-in-the-loop workflows 更加可控的人机交互
个人总结
找一个LangGraph的标准实例、结合代码可以有更好的掌握比较复杂的Agent流程、最好是多循环模式。
敬请期待。