关于 LangGraph
LangGraph是一个用于使用 LLM 构建有状态、多参与者应用程序的库,它构建于 LangChain 之上(并旨在与 LangChain 一起使用)。
它扩展了LangChain 表达式语言,能够以循环方式跨多个计算步骤协调多个链(或参与者)。
它的灵感来自Pregel和Apache Beam。
当前公开的界面是受NetworkX启发。
主要用途是为您的 LLM 申请添加周期。至关重要的是,LangGraph 并未仅针对DAG工作流程进行优化。如果你想构建一个 DAG,你应该使用LangChain 表达式语言。
循环对于类似代理的行为非常重要,您可以在循环中调用 LLM,询问它下一步要采取什么操作。
安装
pip install langgraph
快速使用
LangGraph 的核心概念之一是状态。
每个图执行都会创建一个状态,该状态在执行时在图中的节点之间传递,并且每个节点在执行后用其返回值更新此内部状态。
图形更新其内部状态的方式 由所选图形的类型或自定义函数定义。
LangGraph 中的状态可以非常通用,但为了让事情更容易开始,我们将展示一个示例,其中图的状态仅限于使用内置类的聊天消息列表MessageGraph
。
当将 LangGraph 与 LangChain 聊天模型一起使用时,这很方便,因为我们可以直接返回聊天模型输出。
首先,安装LangChain OpenAI集成包:
pip install langchain_openai
我们还需要导出一些环境变量:
export OPENAI_API_KEY=sk-...
现在我们准备好了!下图包含一个名为的节点,"oracle"
该节点执行聊天模型,然后返回结果:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langgraph.graph import END, MessageGraph
model = ChatOpenAI(temperature=0)
graph = MessageGraph()
graph.add_node("oracle", model)
graph.add_edge("oracle", END)
graph.set_entry_point("oracle")
runnable = graph.compile()
运行:
runnable.invoke(HumanMessage("What is 1 + 1?"))
[HumanMessage(content='What is 1 + 1?'), AIMessage(content='1 + 1 equals 2.')]
那么我们在这里做了什么?让我们一步步分解:
- 首先,我们初始化模型和
MessageGraph
. - 接下来,我们向图中添加一个名为 的节点
"oracle"
,它只是使用给定的输入调用模型。 "oracle"
我们从该节点向特殊字符串添加一条边END
。这意味着执行将在当前节点之后结束。- 我们将其设置
"oracle"
为图表的入口点。 - 我们编译该图,确保不能对其进行更多修改。
然后,当我们执行该图时:
- LangGraph 将输入消息添加到内部状态,然后将状态传递到入口点节点
"oracle"
。 - 节点
"oracle"
执行,调用聊天模型。 - 聊天模型返回一个
AIMessage
. LangGraph 将其添加到状态中。 - 执行进行到特殊
END
值并输出最终状态。
结果,我们得到了两个聊天消息的列表作为输出。
与LCEL 交互
对于那些已经熟悉 LangChain 的人来说,add_node
实际上需要任何函数或可运行程序作为输入。
在上面的示例中,模型“按原样”使用,但我们也可以传入一个函数:
def call_oracle(messages: list):
return model.invoke(message)
graph.add_node("oracle", call_oracle)
只要确保您注意到可运行的输入是整个当前状态这一事实即可。所以这会失败:
# This will not work with MessageGraph!
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant named {name} who always speaks in pirate dialect"),
MessagesPlaceholder(variable_name="messages"),
])
chain = prompt | model
# State is a list of messages, but our chain expects a dict input:
#
# { "name": some_string, "messages": [] }
#
# Therefore, the graph will throw an exception when it executes here.
graph.add_node("oracle", chain)
条件边
现在,让我们转向一些不那么琐碎的事情。
因为数学对于 LLM 来说可能很困难,所以让我们允许 LLM "multiply"
使用工具调用有条件地调用节点。
我们将使用附加功能重新创建图表,"multiply"
该附加功能将获取最新消息的结果(如果是工具调用)并计算结果。
我们还将计算器作为工具绑定到 OpenAI 模型,以允许模型选择性地使用响应当前状态所需的工具:
import json
from langchain_core.messages import ToolMessage
from langchain_core.tools import tool
from langchain_core.utils.function_calling import convert_to_openai_tool
@tool
def multiply(first_number: int, second_number: int):
"""Multiplies two numbers together."""
return first_number * second_number
model = ChatOpenAI(temperature=0)
model_with_tools = model.bind(tools=[convert_to_openai_tool(multiply)])
graph = MessageGraph()
def invoke_model(state: List[BaseMessage]):
return model_with_tools.invoke(state)
graph.add_node("oracle", invoke_model)
def invoke_tool(state: List[BaseMessage]):
tool_calls = state[-1].additional_kwargs.get("tool_calls", [])
multiply_call = None
for tool_call in tool_calls:
if tool_call.get("function").get("name") == "multiply":
multiply_call = tool_call
if multiply_call is None:
raise Exception("No adder input found.")
res = multiply.invoke(
json.loads(multiply_call.get("function").get("arguments"))
)
return ToolMessage(
tool_call_id=multiply_call.get("id"),
content=res
)
graph.add_node("multiply", invoke_tool)
graph.add_edge("multiply", END)
graph.set_entry_point("oracle")
现在让我们想一想——我们希望发生什么?
- 如果
"oracle"
节点返回一条等待工具调用的消息,我们要执行该"multiply"
节点 - 如果没有,我们可以结束执行
我们可以使用条件边来实现这一点,条件边使用函数根据当前状态将执行路由到节点。
看起来是这样的:
def router(state: List[BaseMessage]):
tool_calls = state[-1].additional_kwargs.get("tool_calls", [])
if len(tool_calls):
return "multiply"
else:
return "end"
graph.add_conditional_edges("oracle", router, {
"multiply": "multiply",
"end": END,
})
如果模型输出包含工具调用,我们将移至该"multiply"
节点。否则,我们就结束了。
现在剩下的就是编译图表并尝试一下。与数学相关的问题将转至计算器工具:
runnable = graph.compile()
runnable.invoke(HumanMessage("What is 123 * 456?"))
[HumanMessage(content='What is 123 * 456?'),
AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_OPbdlm8Ih1mNOObGf3tMcNgb', 'function': {'arguments': '{"first_number":123,"second_number":456}', 'name': 'multiply'}, 'type': 'function'}]}),
ToolMessage(content='56088', tool_call_id='call_OPbdlm8Ih1mNOObGf3tMcNgb')]
而对话响应是直接输出的:
runnable.invoke(HumanMessage("What is your name?"))
[HumanMessage(content='What is your name?'),
AIMessage(content='My name is Assistant. How can I assist you today?')]
Cycles
现在,让我们看一个更一般的循环示例。
我们AgentExecutor
将从 LangChain 重新创建该类。
代理本身将使用聊天模型和函数调用。该代理将其所有状态表示为消息列表。
我们需要安装一些 LangChain 软件包以及Tavily来用作示例工具。
pip install -U langchain langchain_openai tavily-python
我们还需要导出一些额外的环境变量以供 OpenAI 和 Tavily API 访问。
export OPENAI_API_KEY=sk-...
export TAVILY_API_KEY=tvly-...
或者,我们可以设置LangSmith以获得一流的可观察性。
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY=ls__...
设置工具
如上所述,我们首先定义要使用的工具。
对于这个简单的示例,我们将通过 Tavily 使用内置搜索工具。
然而,创建自己的工具确实很容易 - 请参阅此处的文档了解如何做到这一点。
from langchain_community.tools.tavily_search import TavilySearchResults
tools = [TavilySearchResults(max_results=1)]
我们现在可以将这些工具包装在一个简单的 LangGraph 中ToolExecutor
。
此类接收ToolInvocation
对象、调用该工具并返回输出。
ToolInvocation
是具有 tool
和 tool_input
属性的任何类。
from langgraph.prebuilt import ToolExecutor
tool_executor = ToolExecutor(tools)
设置模型
现在我们需要加载我们想要使用的聊天模型。
这次,我们将使用旧的函数调用接口。
本演练将使用 OpenAI,但我们可以选择任何支持 OpenAI 函数调用的模型。
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)
完成此操作后,我们应该确保模型知道它可以调用这些工具。
我们可以通过将LangChain工具转换为OpenAI函数调用的格式,然后将它们绑定到模型类来实现。
from langchain.tools.render import format_tool_to_openai_function
functions = [format_tool_to_openai_function(t) for t in tools]
model = model.bind_functions(functions)
定义代理状态
这次,我们将使用更通用的StateGraph
.该图由传递到每个节点的状态对象参数化。
请记住,每个节点都会返回更新该状态的操作。
这些操作可以设置状态的特定属性(例如覆盖现有值)或添加到现有属性。
是设置还是添加是通过注释用于构造图形的状态对象来指示的。
对于这个例子,我们将跟踪的状态只是一个消息列表。
我们希望每个节点只将消息添加到该列表中。
因此,我们将使用一个TypedDict
键 ( messages
) 并对其进行注释,以便该messages
属性始终添加到第二个参数 ( operator.add
) 中。
from typing import TypedDict, Annotated, Sequence
import operator
from langchain_core.messages import BaseMessage
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], operator.add]
您可以将MessageGraph
初始示例中使用的视为该图的预配置版本,其中状态直接是消息数组,并且更新步骤始终将节点的返回值附加到内部状态。
定义节点
我们现在需要在图中定义一些不同的节点。
在 langgraph
中,节点可以是函数,也可以是可运行的。
为此,我们需要两个主要节点:
- 代理:负责决定采取什么(如果有)行动。
- 调用工具的函数:如果代理决定采取操作,则该节点将执行该操作。
我们还需要定义一些边。
其中一些边缘可能是有条件的。
它们是有条件的原因是,根据节点的输出,可以采用多个路径之一。
在该节点运行之前,所采用的路径是未知的(LLM 决定)。
- 条件边缘:调用代理后,我们应该:
a. 如果代理说要采取行动,那么应该调用调用工具的函数
b. 如果代理说已经完成,那么就应该完成 - Normal Edge:调用工具后,它应该始终返回给代理来决定下一步做什么
让我们定义节点以及决定如何采用条件边的函数。
from langgraph.prebuilt import ToolInvocation
import json
from langchain_core.messages import FunctionMessage
# Define the function that determines whether to continue or not
def should_continue(state):
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"
# Define the function that calls the model
def call_model(state):
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]}
定义图表
我们现在可以将它们放在一起并定义图表!
from langgraph.graph import StateGraph, END
# Define a new graph
workflow = StateGraph(AgentState)
# Define the two nodes we will cycle between
workflow.add_node("agent", call_model)
workflow.add_node("action", call_tool)
# Set the entrypoint as `agent`
# This means that this node is the first one called
workflow.set_entry_point("agent")
# We now add a conditional edge
workflow.add_conditional_edges(
# First, we define the start node. We use `agent`.
# This means these are the edges taken after the `agent` node is called.
"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`.
# This means that after `tools` is called, `agent` node is called next.
workflow.add_edge('action', 'agent')
# Finally, we compile it!
# This compiles it into a LangChain Runnable,
# meaning you can use it as you would any other runnable
app = workflow.compile()
使用
我们现在可以使用它了!现在,它公开了与所有其他 LangChain 可运行程序相同的接口。该可运行程序接受消息列表。
from langchain_core.messages import HumanMessage
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
app.invoke(inputs)
这可能需要一点时间——它在幕后进行了一些调用。
为了开始看到一些中间结果,我们可以使用流式传输 - 有关更多信息,请参阅下文。
流媒体
LangGraph 支持多种不同类型的流式传输。
流式节点输出
使用 LangGraph 的好处之一是可以轻松地传输由每个节点生成的输出。
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
for output in app.stream(inputs):
# stream() yields dictionaries with output keyed by node name
for key, value in output.items():
print(f"Output from node '{key}':")
print("---")
print(value)
print("\n---\n")
Output from node 'agent':
---
{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n "query": "weather in San Francisco"\n}', 'name': 'tavily_search_results_json'}})]}
---
Output from node 'action':
---
{'messages': [FunctionMessage(content="[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San FranciscoThis report shows the past weather for San Francisco, providing a weather history for January 2024. It features all historical weather data series we have available, including the San Francisco temperature history for January 2024. You can drill down from year to month and even day level reports by clicking on the graphs.'}]", name='tavily_search_results_json')]}
---
Output from node 'agent':
---
{'messages': [AIMessage(content="I couldn't find the current weather in San Francisco. However, you can visit [WeatherSpark](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States) to check the historical weather data for January 2024 in San Francisco.")]}
---
Output from node '__end__':
---
{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n "query": "weather in San Francisco"\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content="[{'url': 'https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States', 'content': 'January 2024 Weather History in San Francisco California, United States Daily Precipitation in January 2024 in San Francisco Observed Weather in January 2024 in San Francisco San Francisco Temperature History January 2024 Hourly Temperature in January 2024 in San Francisco Hours of Daylight and Twilight in January 2024 in San FranciscoThis report shows the past weather for San Francisco, providing a weather history for January 2024. It features all historical weather data series we have available, including the San Francisco temperature history for January 2024. You can drill down from year to month and even day level reports by clicking on the graphs.'}]", name='tavily_search_results_json'), AIMessage(content="I couldn't find the current weather in San Francisco. However, you can visit [WeatherSpark](https://weatherspark.com/h/m/557/2024/1/Historical-Weather-in-January-2024-in-San-Francisco-California-United-States) to check the historical weather data for January 2024 in San Francisco.")]}
---
流式 LLM Tokens
您还可以访问每个节点生成的 LLM 令牌。
在这种情况下,只有“代理”节点生成 LLM 令牌。
为了使其正常工作,您必须使用支持流式传输的LLM,并在构建LLM时对其进行设置(例如ChatOpenAI(model="gpt-3.5-turbo-1106", streaming=True)
)
inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
async for output in app.astream_log(inputs, include_types=["llm"]):
# astream_log() yields the requested logs (here LLMs) in JSONPatch format
for op in output.ops:
if op["path"] == "/streamed_output/-":
# this is the output from .stream()
...
elif op["path"].startswith("/logs/") and op["path"].endswith(
"/streamed_output/-"
):
# because we chose to only include LLMs, these are LLM tokens
print(op["value"])
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': ' ', 'name': ''}}
content='' additional_kwargs={'function_call': {'arguments': ' "', 'name': ''}}
content='' additional_kwargs={'function_call': {'arguments': 'query', 'name': ''}}
content='' additional_kwargs={'function_call': {'arguments': '":', 'name': ''}}
content='' additional_kwargs={'function_call': {'arguments': ' "', 'name': ''}}
content='' additional_kwargs={'function_call': {'arguments': 'weather', 'name': ''}}
content='' additional_kwargs={'function_call': {'arguments': ' in', 'name': ''}}
content='' additional_kwargs={'function_call': {'arguments': ' San', 'name': ''}}
content='' additional_kwargs={'function_call': {'arguments': ' Francisco', 'name': ''}}
content='' additional_kwargs={'function_call': {'arguments': '"\n', 'name': ''}}
content='' additional_kwargs={'function_call': {'arguments': '}', 'name': ''}}
content=''
content=''
content='I'
content="'m"
content=' sorry'
content=','
content=' but'
content=' I'
content=' couldn'
content="'t"
content=' find'
content=' the'
content=' current'
content=' weather'
content=' in'
content=' San'
content=' Francisco'
content='.'
content=' However'
content=','
content=' you'
content=' can'
content=' check'
content=' the'
content=' historical'
content=' weather'
content=' data'
content=' for'
content=' January'
content=' '
content='202'
content='4'
content=' in'
content=' San'
content=' Francisco'
content=' ['
content='here'
content=']('
content='https'
content='://'
content='we'
content='athers'
content='park'
content='.com'
content='/h'
content='/m'
content='/'
content='557'
content='/'
content='202'
content='4'
content='/'
content='1'
content='/H'
content='istorical'
content='-'
content='Weather'
content='-in'
content='-Jan'
content='uary'
content='-'
content='202'
content='4'
content='-in'
content='-S'
content='an'
content='-F'
content='r'
content='anc'
content='isco'
content='-Cal'
content='ifornia'
content='-'
content='United'
content='-'
content='States'
content=').'
content=''
何时使用
什么时候应该使用它而不是LangChain 表达式语言?
如果你需要循环。
Langchain 表达式语言允许您轻松定义链 (DAG),但没有良好的添加循环的机制。 langgraph
添加该语法。
操作指南
这些指南展示了如何以特定方式使用 LangGraph。
异步
如果您在异步工作流程中运行 LangGraph,您可能希望默认创建异步节点。
有关如何执行此操作的演练,请参阅此文档
流式 Tokens
有时,语言模型需要一段时间才能响应,您可能希望将令牌流式传输给最终用户。
有关如何执行此操作的指南,请参阅此文档
持久化
LangGraph 具有内置的持久性,允许您保存图形在该点的状态并从那里恢复。
有关如何执行此操作的演练,请参阅此文档
人机交互
LangGraph 内置了对人机交互工作流程的支持。
当您希望在进入特定节点之前让人工检查当前状态时,这非常有用。
有关如何执行此操作的演练,请参阅此文档
可视化图表
使用 LangGraph 创建的代理可能很复杂。
为了更容易理解幕后发生的事情,我们添加了打印和可视化图表的方法。这可以创建 ascii 艺术和 png。
有关如何执行此操作的演练,请参阅此文档
“时间旅行”
通过“时间旅行”功能,您可以跳转到图形执行中的任何点,修改状态,然后从那里重新运行。
这对于调试工作流程以及面向最终用户的工作流程非常有用,以允许他们更正状态。
有关如何执行此操作的演练,请参阅此文档
示例
ChatAgentExecutor:带函数调用
该代理执行器将消息列表作为输入并输出消息列表。
所有代理状态都表示为消息列表。
这里具体使用了OpenAI函数调用。
对于支持函数调用的基于聊天的较新模型,推荐使用代理执行器。
修改
我们还有很多示例强调如何稍微修改基本聊天代理执行器。这些都是基于getting started notebook构建的,因此建议您首先从入门笔记本开始。
- 人机交互:如何添加人机交互组件
- 强制先调用工具:如何始终先调用特定工具
- 以特定格式响应:如何强制代理以特定格式响应
- 动态直接返回工具输出:如何动态地让代理选择是否直接将工具的结果返回给用户
- 管理代理步骤:如何更明确地管理代理采取的中间步骤
代理执行者
该代理执行器使用现有的LangChain代理。
修改
我们还有很多示例强调如何稍微修改基本聊天代理执行器。这些都是基于入门笔记本构建的,因此建议您首先从入门笔记本开始。
规划代理示例
以下笔记本实现了“计划和执行”风格的代理架构原型,其中 LLM 计划器将用户请求分解为程序,执行器执行程序,LLM 综合基于响应(和/或动态重新计划)关于程序输出。
- 计划和执行:一个简单的代理,具有生成多步骤任务列表的计划程序、调用计划中的工具的执行程序以及响应或生成更新计划的重新计划程序。基于Wang 等人的Plan-and-solve论文。等人。
- 没有观察的推理:规划器生成一个任务列表,其观察结果被保存为变量。变量可以在后续任务中使用,以减少进一步重新规划的需要。基于Xu 等人的ReWOO论文。等人。
- LLMCompiler:规划器生成具有可变响应的任务DAG 。任务被急切地流式传输和执行,以最大限度地减少工具执行时间。基于Kim 等人的论文。
反思/自我批评
当输出质量成为主要问题时,通常会结合自我批评或反思和外部验证来完善系统的输出。以下示例展示了实现此类设计的研究。
- 基本反射:在图表中添加一个简单的“反射”步骤,以提示您的系统修改其输出。
- 反思:批评代理响应中缺失和多余的方面,以指导后续步骤。基于Shinn 等人的Reflexion。等人。
- 语言代理树搜索:并行执行多个代理,使用反射和环境奖励来驱动蒙特卡罗树搜索。基于Zhou 等人的LATS 。等人。
多代理示例
网络研究
通过模拟评估 Chatbot
在多轮情况下评估聊天机器人通常很困难。做到这一点的一种方法是模拟。
- 聊天机器人评估作为多代理模拟:如何模拟“虚拟用户”和聊天机器人之间的对话
- 对数据集进行评估:在 LangSmith 数据集上对您的助手进行基准测试,该数据集要求模拟客户对您的聊天机器人进行红组。
多模式示例
- WebVoyager:支持视觉的 Web 浏览代理,使用标记集提示来导航 Web 浏览器并执行任务
表链
Chain of Table是一个框架,在回答有关表格数据的问题时可实现 SOTA 性能。
Github 用户CYQIQ的这个实现使用 LangGraph 来控制流程。
文档
只有几个新的 API 可供使用。
状态图
主要入口点是StateGraph
.
from langgraph.graph import StateGraph
该类负责构建图。它公开了一个受NetworkX启发的界面。
该图由传递到每个节点的状态对象参数化。
__init__
def __init__(self, schema: Type[Any]) -> None:
构建图时,您需要传入状态的模式。然后每个节点返回操作来更新该状态。
这些操作可以设置状态的特定属性(例如覆盖现有值)或添加到现有属性。
是设置还是添加是通过注释用于构造图形的状态对象来指示的。
指定模式的推荐方法是使用类型化字典:
from typing import TypedDict
然后您可以使用注释不同的属性from typing import Annotated
。
目前,唯一支持的注释是import operator; operator.add
.
此注释将使任何返回此属性的节点将新结果添加到现有值中。
让我们看一个例子:
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]
然后我们可以这样使用它:
# 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
def add_node(self, key: str, action: RunnableLike) -> None:
此方法向图中添加一个节点。它需要两个参数:
key
:代表节点名称的字符串。这一定是独一无二的。action
:调用此节点时要执行的操作。这应该是一个函数或一个可运行的。
.add_edge
def add_edge(self, start_key: str, end_key: str) -> None:
创建从一个节点到下一个节点的边。这意味着第一个节点的输出将传递到下一个节点。它需要两个参数。
start_key
:表示起始节点名称的字符串。该密钥必须已在图中注册。end_key
:表示结束节点名称的字符串。该密钥必须已在图中注册。
.add_conditional_edges
def add_conditional_edges(
self,
start_key: str,
condition: Callable[..., str],
conditional_edge_mapping: Dict[str, str],
) -> None:
此方法添加条件边。这意味着只会采用下游边之一,具体选择哪一条取决于起始节点的结果。这需要三个参数:
start_key
:表示起始节点名称的字符串。该密钥必须已在图中注册。condition
:调用该函数来决定下一步做什么。输入将是起始节点的输出。它应该返回一个存在于conditional_edge_mapping
并表示要采用的边缘的字符串。conditional_edge_mapping
:字符串到字符串的映射。键应该是可由condition
.返回的字符串。如果返回该条件,这些值应该是要调用的下游节点。
.set_entry_point
def set_entry_point(self, key: str) -> None:
图表的入口点。这是首先被调用的节点。它只需要一个参数:
key
:应该首先调用的节点的名称。
.set_conditional_entry_point
def set_conditional_entry_point(
self,
condition: Callable[..., str],
conditional_edge_mapping: Optional[Dict[str, str]] = None,
) -> None:
此方法添加一个条件入口点。这意味着当调用图时,它会调用condition
Callable 来决定首先进入哪个节点。
condition
:调用该函数来决定下一步做什么。输入将是图形的输入。它应该返回一个存在于conditional_edge_mapping
并表示要采用的边缘的字符串。conditional_edge_mapping
:字符串到字符串的映射。键应该是可由condition
.返回的字符串。如果返回该条件,这些值应该是要调用的下游节点。
.set_finish_point
def set_finish_point(self, key: str) -> None:
这是图表的出口点。当调用该节点时,结果将是图表的最终结果。它只有一个参数:
key
:节点名称,调用时将返回调用结果作为最终输出
注意:如果您之前在任何时候创建了一条边(条件或正常)来END
图
from langgraph.graph import Graph
graph = Graph()
它具有相同的接口,但StateGraph
它不会随着时间的推移更新状态对象,而是依赖于传递每个步骤的完整状态。这意味着从一个节点返回的任何内容都是下一个节点的输入。
END
from langgraph.graph import END
这是一个特殊的节点,代表图的末端。这意味着传递到该节点的任何内容都将是该图的最终输出。它可以用在两个地方:
- 正如
end_key
在add_edge
conditional_edge_mapping
作为传递给的值add_conditional_edges
预建示例
我们还添加了一些方法,以便轻松使用常见的预构建图表和组件。
工具执行器
from langgraph.prebuilt import ToolExecutor
这是一个简单的帮助器类,用于帮助调用工具。它由工具列表参数化:
tools = [...]
tool_executor = ToolExecutor(tools)
然后它公开一个可运行的接口。
它可用于调用工具:您可以传入 AgentAction 它将查找相关工具并使用适当的输入调用它。
chat_agent_executor.create_function_calling_executor
from langgraph.prebuilt import chat_agent_executor
这是一个辅助函数,用于创建与利用函数调用的聊天模型配合使用的图表。
可以通过传入模型和工具列表来创建。该模型必须是支持OpenAI函数调用的模型。
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
from langgraph.prebuilt import chat_agent_executor
这是一个辅助函数,用于创建与利用工具调用的聊天模型一起使用的图表。
可以通过传入模型和工具列表来创建。该模型必须是支持OpenAI工具调用的模型。
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
from langgraph.prebuilt import create_agent_executor
这是一个辅助函数,用于创建与 LangChain Agents一起使用的图表。
可以通过传入 代理 和 工具列表 来创建。
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("----")
2024-04-03(三)
放假去哪儿玩?