当前位置:   article > 正文

一個令人著迷的話題!LangGraph 有何用途?_langgraph 应用场景

langgraph 应用场景

LangGraph 是一个用于使用 LLM 建立有状态、多参与者应用程式的函式库,它建构在 LangChain 之上(并旨在与 LangChain 一起使用)。 它扩展了 LangChain 表达式语言,能够以循环方式跨多个计算步骤协调多个链(或参与者)。 它的灵感来自 Pregel 和 Apache Beam。 目前公开的介面是受 NetworkX 启发的。

主要用途是为您的法学硕士申请添加周期。 至关重要的是,LangGraph 并未仅针对 DAG 工作流程进行最佳化。 如果你想建立一个DAG,你应该使用LangChain表达式语言。

循环对于类似代理的行为非常重要,您可以在循环中呼叫 LLM,询问它下一步要采取什么操作。

以下是 LangGraph 的一些主要功能:

  1. 可读性:LangGraph 被设计为具有高度可读性,使用自然语言语法和结构。
  2. 效率:透过利用程式设计和自然语言的优势,LangGraph 旨在减少编码时间并提高生产力。
  3. 灵活性:LangGraph 允许开发人员编写比传统程式语言更灵活、适应性更强的程式码。


LangGraph 语法的一些范例:

x = 5(赋值)
if x > 10: print("x 大于 10") (条件语句)
for i in range(3): print(i) (循环)


LangGraph 的目标是创建一种易于学习、使用和维护的语言,使其成为想要快速且有效率地编写程式码的开发人员的有吸引力的选择。 然而,与任何新的程式语言一样,它还处于早期阶段,社区、采用率和生态系统是决定其成功的关键因素。

主要特征:

  1. 自然语言语法:LangGraph使用类似自然语言的语法,使非技术人员更容易理解和使用。
  2. 可读性:该语言被设计为具有高度可读性,并减少开发人员的认知负担。
  3. 效率:LangGraph 旨在透过提供捷径和简化常见程式设计任务来减少编码时间。
  4. 灵活性:它允许开发人员编写适应不断变化的需求并可以处理复杂任务的程式码。

为什么要学 LangGraph?

  1. 简化您的开发工作流程:LangGraph 的语法和结构旨在易于理解,非常适合初学者或希望简化编码过程的人。
  2. 改善协作:借助 LangGraph 受自然语言启发的语法,开发人员可以轻松地与非技术团队成员解释和共享程式码。
  3. 保持领先地位:作为一种相对较新的语言,LangGraph 仍在不断发展,提供了为其发展做出贡献并塑造其未来的机会。

LangGraph 有哪些潜在应用?

  1. Web 开发:LangGraph 的易用性和灵活性使其成为 Web 开发任务的有前途的候选者。
  2. 资料分析:其受自然语言启发的语法可以简化资料分析任务,从而更轻松地处理大型资料集。
  3. 人工智慧 (AI):随着人工智慧的不断发展,LangGraph 可以用作与人工智慧系统互动或创建更类似于人类的人工智慧模型的一种方式。

LangGraph 有哪些限制?

  1. 成熟度:LangGraph仍然是一种新兴语言,其生态系统尚未完全建立。
  2. 相容性:由于它是一种相对较新的语言,因此与现有软体和框架的兼容性可能会受到限制。
  3. 效能:与任何新语言一样,效能最佳化和记忆体管理是需要解决的关键方面。

安装

pip install langgraph

快速开始

LangGraph 的核心概念之一是状态。 每个图执行都会创建一个状态,该状态在执行时在图中的节点之间传递,并且每个节点在执行后用其返回值更新此内部状态。 图形更新其内部状态的方式由所选图形的类型或自定义函数定义。

LangGraph 中的状态可能非常通用,但为了让事情更容易开始,我们将展示一个示例,其中使用内置 MessageGraph 类将图的状态限制为聊天消息列表。 当将 LangGraph 与 LangChain 聊天模型一起使用时,这很方便,因为我们可以直接返回聊天模型输出。

首先,安装LangChain OpenAI集成包:

pip install langchain_openai

我们还需要导出一些环境变量:

export OPENAI_API_KEY=sk-...

现在我们准备好了! 下图包含一个名为“oracle”的节点,它执行聊天模型,然后返回结果:

  1. from langchain_openai import ChatOpenAI
  2. from langchain_core.messages import HumanMessage
  3. from langgraph.graph import END, MessageGraph
  4. model = ChatOpenAI(temperature=0)
  5. graph = MessageGraph()
  6. graph.add_node("oracle", model)
  7. graph.add_edge("oracle", END)
  8. graph.set_entry_point("oracle")
  9. runnable = graph.compile()

Let's run it!

runnable.invoke(HumanMessage("What is 1 + 1?"))
[HumanMessage(content='What is 1 + 1?'), AIMessage(content='1 + 1 equals 2.')]

那么我们在这里做了什么? 让我们一步步分解:

  1. 首先,我们初始化模型和 MessageGraph。
  2. 接下来,我们向图中添加一个名为“oracle”的节点,它只是使用给定的输入调用模型。
  3. 我们从这个“oracle”节点添加一条边到特殊字符串 END。 这意味着执行将在当前节点之后结束。
  4. 我们将“oracle”设置为该图的入口点。
  5. 我们编译该图,确保不能对其进行更多修改。

然后,当我们执行该图时:

  1. LangGraph 将输入消息添加到内部状态,然后将状态传递到入口点节点“oracle”。
  2. “oracle”节点执行,调用聊天模型。
  3. 聊天模型返回一条 AIMessage。 LangGraph 将其添加到状态中。
  4. 执行进行到特殊的 END 值并输出最终状态。

结果,我们得到了两个聊天消息的列表作为输出。

与 LCEL 互动

对于那些已经熟悉 LangChain 的人来说,add_node 实际上接受任何函数或可运行程序作为输入。 在上面的示例中,模型“按原样”使用,但我们也可以传入一个函数:

  1. def call_oracle(messages: list):
  2. return model.invoke(messages)
  3. graph.add_node("oracle", call_oracle)

只需确保您注意到可运行的输入是整个当前状态这一事实即可。 所以这会失败:

  1. # 这不适用于 MessageGraph!
  2. from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
  3. prompt = ChatPromptTemplate.from_messages([
  4. ("system", "You are a helpful assistant named {name} who always speaks in pirate dialect"),
  5. MessagesPlaceholder(variable_name="messages"),
  6. ])
  7. chain = prompt | model
  8. # 状态是消息列表,但我们的链需要一个字典输入:
  9. #
  10. # { "name": some_string, "messages": [] }
  11. #
  12. # 因此,图在这里执行时会抛出异常。
  13. graph.add_node("oracle", chain)

条件边

现在,让我们转向一些不那么琐碎的事情。 因为数学对于法学硕士来说可能很困难,所以让我们允许法学硕士使用工具调用有条件地调用“乘法”节点。

我们将使用附加的“乘法”重新创建图表,如果它是工具调用,则该“乘法”将获取最新消息的结果,并计算结果。 我们还将计算器作为工具绑定到 OpenAI 模型,以允许模型选择性地使用响应当前状态所需的工具:

  1. import json
  2. from langchain_core.messages import ToolMessage
  3. from langchain_core.tools import tool
  4. from langchain_core.utils.function_calling import convert_to_openai_tool
  5. @tool
  6. def multiply(first_number: int, second_number: int):
  7. """Multiplies two numbers together."""
  8. return first_number * second_number
  9. model = ChatOpenAI(temperature=0)
  10. model_with_tools = model.bind(tools=[convert_to_openai_tool(multiply)])
  11. graph = MessageGraph()
  12. def invoke_model(state: List[BaseMessage]):
  13. return model_with_tools.invoke(state)
  14. graph.add_node("oracle", invoke_model)
  15. def invoke_tool(state: List[BaseMessage]):
  16. tool_calls = state[-1].additional_kwargs.get("tool_calls", [])
  17. multiply_call = None
  18. for tool_call in tool_calls:
  19. if tool_call.get("function").get("name") == "multiply":
  20. multiply_call = tool_call
  21. if multiply_call is None:
  22. raise Exception("No adder input found.")
  23. res = multiply.invoke(
  24. json.loads(multiply_call.get("function").get("arguments"))
  25. )
  26. return ToolMessage(
  27. tool_call_id=multiply_call.get("id"),
  28. content=res
  29. )
  30. graph.add_node("multiply", invoke_tool)
  31. graph.add_edge("multiply", END)
  32. graph.set_entry_point("oracle")

现在让我们想一想——我们希望发生什么?

  • 如果“oracle”节点返回一条需要工具调用的消息,我们要执行“multiply”节点
  • 如果没有,我们可以结束执行

我们可以使用条件边来实现这一点,条件边使用函数根据当前状态将执行路由到节点。

看起来是这样的:

  1. def router(state: List[BaseMessage]):
  2. tool_calls = state[-1].additional_kwargs.get("tool_calls", [])
  3. if len(tool_calls):
  4. return "multiply"
  5. else:
  6. return "end"
  7. graph.add_conditional_edges("oracle", router, {
  8. "multiply": "multiply",
  9. "end": END,
  10. })

如果模型输出包含工具调用,我们将移至“乘法”节点。 否则,我们就结束了。

伟大的! 现在剩下的就是编译图表并尝试一下。 与数学相关的问题将转至计算器工具:

  1. runnable = graph.compile()
  2. runnable.invoke(HumanMessage("What is 123 * 456?"))
  1. [HumanMessage(content='What is 123 * 456?'),
  2. AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_OPbdlm8Ih1mNOObGf3tMcNgb', 'function': {'arguments': '{"first_number":123,"second_number":456}', 'name': 'multiply'}, 'type': 'function'}]}),
  3. ToolMessage(content='56088', tool_call_id='call_OPbdlm8Ih1mNOObGf3tMcNgb')]

而对话响应是直接输出的:

runnable.invoke(HumanMessage("What is your name?"))
  1. [HumanMessage(content='What is your name?'),
  2. AIMessage(content='My name is Assistant. How can I assist you today?')]

循环次数

现在,让我们看一个更一般的循环示例。 我们将从 LangChain 重新创建 AgentExecutor 类。 代理本身将使用聊天模型和函数调用。 该代理将其所有状态表示为消息列表。

我们需要安装一些 LangChain 软件包以及 Tavily 来用作示例工具。

pip install -U langchain langchain_openai tavily-python

我们还需要导出一些额外的环境变量以供 OpenAI 和 Tavily API 访问。

  1. export OPENAI_API_KEY=sk-...
  2. export TAVILY_API_KEY=tvly-...

或者,我们可以设置 LangSmith 以获得一流的可观察性。

  1. export LANGCHAIN_TRACING_V2="true"
  2. export LANGCHAIN_API_KEY=ls__...

设置工具

如上所述,我们首先定义要使用的工具。 对于这个简单的示例,我们将通过 Tavily 使用内置搜索工具。 然而,创建自己的工具确实很容易 - 请参阅此处的文档了解如何做到这一点。

  1. from langchain_community.tools.tavily_search import TavilySearchResults
  2. tools = [TavilySearchResults(max_results=1)]

我们现在可以将这些工具包装在一个简单的 LangGraph ToolExecutor 中。 此类接收 ToolIncation 对象、调用该工具并返回输出。 ToolIncation 是具有 tool 和 tool_input 属性的任何类。

  1. from langgraph.prebuilt import ToolExecutor
  2. tool_executor = ToolExecutor(tools)

设置模型

现在我们需要加载我们想要使用的聊天模型。 这次,我们将使用旧的函数调用接口。 本演练将使用 OpenAI,但我们可以选择任何支持 OpenAI 函数调用的模型。

  1. from langchain_openai import ChatOpenAI
  2. # We will set streaming=True so that we can stream tokens
  3. # See the streaming section for more information on this.
  4. model = ChatOpenAI(temperature=0, streaming=True)

完成此操作后,我们应该确保模型知道它可以调用这些工具。 我们可以通过将LangChain工具转换为OpenAI函数调用的格式,然后将它们绑定到模型类来实现。

  1. from langchain.tools.render import format_tool_to_openai_function
  2. functions = [format_tool_to_openai_function(t) for t in tools]
  3. model = model.bind_functions(functions)

定义代理状态

这次,我们将使用更通用的 StateGraph。 该图由传递到每个节点的状态对象参数化。 请记住,每个节点都会返回更新该状态的操作。 这些操作可以设置状态的特定属性(例如覆盖现有值)或添加到现有属性。 是设置还是添加是通过注释用于构造图形的状态对象来指示的。

对于这个例子,我们将跟踪的状态只是一个消息列表。 我们希望每个节点只将消息添加到该列表中。 因此,我们将使用带有一个键(消息)的 TypedDict 并对其进行注释,以便消息属性始终添加到第二个参数(operator.add)。

  1. from typing import TypedDict, Annotated, Sequence
  2. import operator
  3. from langchain_core.messages import BaseMessage
  4. class AgentState(TypedDict):
  5. messages: Annotated[Sequence[BaseMessage], operator.add]

您可以将初始示例中使用的 MessageGraph 视为该图的预配置版本,其中状态直接是消息数组,并且更新步骤始终是将节点的返回值附加到内部状态。

定义节点

我们现在需要在图中定义一些不同的节点。 在 langgraph 中,节点可以是函数,也可以是可运行的。 为此,我们需要两个主要节点:

  1. 代理:负责决定采取什么(如果有)行动。
  2. 调用工具的函数:如果代理决定采取操作,则该节点将执行该操作。

我们还需要定义一些边。 其中一些边缘可能是有条件的。 它们是有条件的原因是,根据节点的输出,可以采用多个路径之一。 在该节点运行之前,所采用的路径是未知的(LLM 决定)。

  1. 条件边缘:调用代理后,我们应该:

    a. 如果代理说要采取行动,那么应该调用调用工具的函数
    b. 如果代理说已经完成,那么就应该完成
  2. Normal Edge:调用工具后,它应该始终返回给代理来决定下一步做什么

让我们定义节点以及决定如何采用条件边的函数。

  1. from langgraph.prebuilt import ToolInvocation
  2. import json
  3. from langchain_core.messages import FunctionMessage
  4. # 定义判断是否继续的函数
  5. def should_continue(state):
  6. messages = state['messages']
  7. last_message = messages[-1]
  8. # 如果没有函数调用,那么我们就完成了
  9. if "function_call" not in last_message.additional_kwargs:
  10. return "end"
  11. # 否则,如果有,我们继续
  12. else:
  13. return "continue"
  14. # 定义调用模型的函数
  15. def call_model(state):
  16. messages = state['messages']
  17. response = model.invoke(messages)
  18. # 我们返回一个列表,因为这将被添加到现有列表中
  19. return {"messages": [response]}
  20. #定义执行工具的函数
  21. def call_tool(state):
  22. messages = state['messages']
  23. # 基于继续条件
  24. # 我们知道最后一条消息涉及函数调用
  25. last_message = messages[-1]
  26. # 我们从 function_call 构造一个 ToolInspiration
  27. action = ToolInvocation(
  28. tool=last_message.additional_kwargs["function_call"]["name"],
  29. tool_input=json.loads(last_message.additional_kwargs["function_call"]["arguments"]),
  30. )
  31. # 我们调用 tool_executor 并得到响应
  32. response = tool_executor.invoke(action)
  33. #我们使用响应来创建 FunctionMessage
  34. function_message = FunctionMessage(content=str(response), name=action.tool)
  35. # 我们返回一个列表,因为这将被添加到现有列表中
  36. return {"messages": [function_message]}

定义图表

我们现在可以将它们放在一起并定义图表!

  1. from langgraph.graph import StateGraph, END
  2. # 定义一个新图
  3. workflow = StateGraph(AgentState)
  4. # 定义我们将在其之间循环的两个节点
  5. workflow.add_node("agent", call_model)
  6. workflow.add_node("action", call_tool)
  7. # 将入口点设置为`agent`
  8. # 这意味着该节点是第一个被调用的节点
  9. workflow.set_entry_point("agent")
  10. # 我们现在添加一个条件边
  11. workflow.add_conditional_edges(
  12. # 首先,我们定义起始节点。 我们使用“代理”。
  13. # 这意味着这些是调用 `agent` 节点后获取的边。
  14. "agent",
  15. # 接下来,我们传入函数来确定接下来调用哪个节点。
  16. should_continue,
  17. # 最后我们传入一个映射。
  18. # 键是字符串,值是其他节点。
  19. # END 是一个特殊的节点,标记图应该结束。
  20. # 将会发生的事情是我们将调用 `should_continue`,然后输出
  21. # 将与此映射中的键进行匹配。
  22. # 根据匹配的节点,将调用该节点。
  23. {
  24. # 如果是`tools`,那么我们称之为工具节点。
  25. "continue": "action",
  26. # 否则我们就结束了。
  27. "end": END
  28. }
  29. )
  30. # 我们现在添加从 `tools` 到 `agent` 的正常边缘。
  31. # 这意味着在调用`tools`之后,接下来调用`agent`节点。
  32. workflow.add_edge('action', 'agent')
  33. # 最后,我们编译它!
  34. # 这会将其编译成 LangChain Runnable,
  35. # 意味着你可以像使用任何其他可运行程序一样使用它
  36. app = workflow.compile()

用它!

我们现在可以使用它了! 现在,它公开了与所有其他 LangChain 可运行程序相同的接口。 该可运行程序接受消息列表。

  1. from langchain_core.messages import HumanMessage
  2. inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
  3. app.invoke(inputs)

这可能需要一点时间——它在幕后进行了一些调用。 为了开始看到一些中间结果,我们可以使用流式传输 - 有关更多信息,请参阅下文。

流媒体

LangGraph 支持多种不同类型的流式传输。

流节点输出

使用 LangGraph 的好处之一是可以轻松地传输由每个节点生成的输出。

  1. inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
  2. for output in app.stream(inputs):
  3. # Stream() 产生字典,其输出以节点名称为键
  4. for key, value in output.items():
  5. print(f"Output from node '{key}':")
  6. print("---")
  7. print(value)
  8. print("\n---\n")
  1. Output from node 'agent':
  2. ---
  3. {'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n "query": "weather in San Francisco"\n}', 'name': 'tavily_search_results_json'}})]}
  4. ---
  5. Output from node 'action':
  6. ---
  7. {'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')]}
  8. ---
  9. Output from node 'agent':
  10. ---
  11. {'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.")]}
  12. ---
  13. Output from node '__end__':
  14. ---
  15. {'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.")]}
  16. ---

流媒体 LLM 代币

您还可以访问每个节点生成的 LLM 令牌。 在这种情况下,只有“代理”节点生成 LLM 令牌。 为了使其正常工作,您必须使用支持流式传输的LLM,并在构建LLM时对其进行设置(例如ChatOpenAI(model =“gpt-3.5-turbo-1106”,streaming = True))

  1. inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
  2. async for output in app.astream_log(inputs, include_types=["llm"]):
  3. # astream_log() yields the requested logs (here LLMs) in JSONPatch format
  4. for op in output.ops:
  5. if op["path"] == "/streamed_output/-":
  6. # this is the output from .stream()
  7. ...
  8. elif op["path"].startswith("/logs/") and op["path"].endswith(
  9. "/streamed_output/-"
  10. ):
  11. # because we chose to only include LLMs, these are LLM tokens
  12. print(op["value"])
  1. content='' additional_kwargs={'function_call': {'arguments': '', 'name': 'tavily_search_results_json'}}
  2. content='' additional_kwargs={'function_call': {'arguments': '{\n', 'name': ''}}
  3. content='' additional_kwargs={'function_call': {'arguments': ' ', 'name': ''}}
  4. content='' additional_kwargs={'function_call': {'arguments': ' "', 'name': ''}}
  5. content='' additional_kwargs={'function_call': {'arguments': 'query', 'name': ''}}
  6. ...

何时使用

什么时候应该使用它而不是 LangChain 表达式语言?

如果你需要循环。

Langchain 表达式语言允许您轻松定义链 (DAG),但没有良好的添加循环的机制。 langgraph 添加了该语法。

操作指南

这些指南展示了如何以特定方式使用 LangGraph。

异步

如果您在异步工作流程中运行 LangGraph,您可能希望默认创建异步节点。 有关如何执行此操作的演练,请参阅此文档

流媒体代币

有时,语言模型需要一段时间才能响应,您可能希望将令牌流式传输给最终用户。 有关如何执行此操作的指南,请参阅此文档

坚持

LangGraph 具有内置的持久性,允许您保存图形在该点的状态并从那里恢复。 有关如何执行此操作的演练,请参阅此文档

人在回路

LangGraph 内置了对人机交互工作流程的支持。 当您希望在进入特定节点之前让人工检查当前状态时,这非常有用。 有关如何执行此操作的演练,请参阅此文档

可视化图表

使用 LangGraph 创建的代理可能很复杂。 为了更容易理解幕后发生的事情,我们添加了打印和可视化图表的方法。 这可以创建 ascii 艺术和 png。 有关如何执行此操作的演练,请参阅此文档

“时间旅行”

通过“时间旅行”功能,您可以跳转到图形执行中的任何点,修改状态,然后从那里重新运行。 这对于调试工作流程以及面向最终用户的工作流程非常有用,以允许他们更正状态。 有关如何执行此操作的演练,请参阅此文档

例子

ChatAgentExecutor:带函数调用

该代理执行器将消息列表作为输入并输出消息列表。 所有代理状态都表示为消息列表。 这里具体使用了OpenAI函数调用。 对于支持函数调用的基于聊天的较新模型,推荐使用代理执行器。

  • 入门笔记本:逐步从头开始创建此类执行器
  • 高级入口点:演练如何使用聊天代理执行器的高级入口点。

修改

我们还有很多示例强调如何稍微修改基本聊天代理执行器。 这些都是基于入门笔记本构建的,因此建议您首先从入门笔记本开始。

  • 人机交互:如何添加人机交互组件
  • 强制先调用工具:如何始终先调用特定工具
  • 以特定格式响应:如何强制代理以特定格式响应
  • 动态直接返回工具输出:如何动态地让代理选择是否将工具的结果直接返回给用户
  • 管理代理步骤:如何更明确地管理代理采取的中间步骤
代理执行者

该代理执行器使用现有的LangChain代理。

  • 入门笔记本:逐步从头开始创建此类执行器
  • 高级入口点:演练如何使用聊天代理执行器的高级入口点。

修改

我们还有很多示例强调如何稍微修改基本聊天代理执行器。 这些都是基于入门笔记本构建的,因此建议您首先从入门笔记本开始。

  • 人机交互:如何添加人机交互组件
  • 强制先调用工具:如何始终先调用特定工具
  • 管理代理步骤:如何更明确地管理代理采取的中间步骤
规划代理示例

以下笔记本实现了“计划和执行”风格的代理架构原型,其中 LLM 计划器将用户请求分解为程序,执行器执行程序,LLM 综合基于响应(和/或动态重新计划) 关于程序输出。

  • 计划和执行:一个简单的代理,其中包含生成多步骤任务列表的计划程序、调用计划中的工具的执行程序以及响应或生成更新计划的重新计划程序。 基于 Wang 等人的 Plan-and-solve 论文。 等人。
  • 无观察推理:规划器生成一个任务列表,其观察结果保存为变量。 变量可以在后续任务中使用,以减少进一步重新规划的需要。 基于 Xu 等人的 ReWOO 论文。 等人。
  • LLMCompiler:规划器生成具有可变响应的任务 DAG。 任务被急切地流式传输和执行,以最大限度地减少工具执行时间。 基于 Kim 等人的论文。 等人。

反思/自我批评

当输出质量成为主要问题时,通常会结合自我批评或反思和外部验证来完善系统的输出。 以下示例展示了实现此类设计的研究。

  • 基本反射:在图表中添加一个简单的“反射”步骤,以提示您的系统修改其输出。
  • 反思:批评代理响应中缺失和多余的方面,以指导后续步骤。 基于 Shinn 等人的《Reflexion》。 等人。
  • 语言代理树搜索:并行执行多个代理,使用反射和环境奖励来驱动蒙特卡罗树搜索。 基于 Zhou 等人的 LATS。 等人。
多代理示例
  • 多代理协作:如何创建两个共同完成任务的代理
  • 带主管的多代理:如何通过使用法学硕士作为“主管”来协调各个代理来分配工作
  • 分层代理团队:如何将代理“团队”编排为可以协作解决问题的嵌套图
网络研究
  • STORM:写作系统,可生成关于任何主题的维基百科风格的文章,应用大纲生成(规划)+多视角问答以增加广度和可靠性。 基于 Shao 等人的 STORM。 等人。
通过模拟评估聊天机器人

在多轮情况下评估聊天机器人通常很困难。 做到这一点的一种方法是模拟。

  • 聊天机器人评估作为多代理模拟:如何模拟“虚拟用户”和聊天机器人之间的对话
  • 对数据集进行评估:在 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:

构建图时,您需要传入状态的模式。 然后每个节点返回操作来更新该状态。 这些操作可以设置状态的特定属性(例如覆盖现有值)或添加到现有属性。 是设置还是添加是通过注释用于构造图形的状态对象来指示的。

指定模式的推荐方法是使用类型化字典:fromtyping import TypedDict

然后,您可以使用 from 键入 import Annotated 来注释不同的属性。 目前,唯一支持的注解是 import 操作符; 运算符.add. 此注释将使任何返回此属性的节点将新结果添加到现有值中。

让我们看一个例子:

  1. from typing import TypedDict, Annotated, Union
  2. from langchain_core.agents import AgentAction, AgentFinish
  3. import operator
  4. class AgentState(TypedDict):
  5. # 输入字符串
  6. input: str
  7. # 对代理的给定调用的结果
  8. # 需要 `None` 作为有效类型,因为这就是它的开头
  9. agent_outcome: Union[AgentAction, AgentFinish, None]
  10. # 行动清单和相应的观察结果
  11. # 这里我们用 `operator.add` 来注释它来指示操作
  12. # 该状态应该添加到现有值中(而不是覆盖它)
  13. intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]

然后我们可以这样使用它:

  1. # 使用该状态初始化 StateGraph
  2. graph = StateGraph(AgentState)
  3. # 创建节点和边
  4. ...
  5. # 编译图表
  6. app = graph.compile()
  7. # 输入应该是字典,因为状态是 TypedDict
  8. inputs = {
  9. # 我们假设这是输入
  10. "input": "hi"
  11. # 假设 agent_outcome 由图表设置为某个点
  12. # 不需要提供,默认为None
  13. # 假设“intermediate_steps”是由图表随着时间的推移而构建的
  14. # 不需要提供,默认为空列表
  15. # `intermediate_steps` 是一个空列表而不是 `None` 的原因是
  16. # 它用 `operator.add` 注释
  17. }
.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
  1. def add_conditional_edges(
  2. self,
  3. start_key: str,
  4. condition: Callable[..., str],
  5. conditional_edge_mapping: Dict[str, str],
  6. ) -> None:

此方法添加条件边。 这意味着只会采用下游边之一,具体选择哪一条取决于起始节点的结果。 这需要三个参数:

  • start_key:表示起始节点名称的字符串。 该密钥必须已在图中注册。
  • 条件:调用以确定下一步做什么的函数。 输入将是起始节点的输出。 它应该返回一个存在于conditional_edge_mapping中并表示要采用的边缘的字符串。
  • conditional_edge_mapping:字符串到字符串的映射。 键应该是可以按条件返回的字符串。 如果返回该条件,这些值应该是要调用的下游节点。
.set_entry_point
    def set_entry_point(self, key: str) -> None:

图表的入口点。 这是首先被调用的节点。 它只需要一个参数:

  • key:应该首先调用的节点的名称。
.set_conditional_entry_point
  1. def set_conditional_entry_point(
  2. self,
  3. condition: Callable[..., str],
  4. conditional_edge_mapping: Optional[Dict[str, str]] = None,
  5. ) -> None:

此方法添加一个条件入口点。 这意味着当调用图时,它会调用条件Callable来决定首先进入哪个节点。

  • 条件:调用以确定下一步做什么的函数。 输入将是图形的输入。 它应该返回一个存在于conditional_edge_mapping中并表示要采用的边缘的字符串。
  • conditional_edge_mapping:字符串到字符串的映射。 键应该是可以按条件返回的字符串。 如果返回该条件,这些值应该是要调用的下游节点。
.set_finish_point
    def set_finish_point(self, key: str) -> None:

这是图表的出口点。 当调用该节点时,结果将是图表的最终结果。 它只有一个参数:

  • key:节点名称,调用时会返回调用结果作为最终输出

注意:如果您之前在任何时候创建了一条到 END 的边(条件或正常),则不需要调用此函数

图形
  1. from langgraph.graph import Graph
  2. graph = Graph()

它与 StateGraph 具有相同的接口,但它不会随着时间的推移更新状态对象,而是依赖于传递每个步骤的完整状态。 这意味着从一个节点返回的任何内容都是下一个节点的输入。

结尾
from langgraph.graph import END

这是一个特殊的节点,代表图的末端。 这意味着传递到该节点的任何内容都将是该图的最终输出。 它可以用在两个地方:

  • 作为add_edge中的end_key
  • 作为 conditional_edge_mapping 中的值传递给 add_conditional_edges

预建示例

我们还添加了一些方法,以便轻松使用常见的预构建图表和组件。

工具执行器
from langgraph.prebuilt import ToolExecutor

这是一个简单的帮助器类,用于帮助调用工具。 它由工具列表参数化:

  1. tools = [...]
  2. tool_executor = ToolExecutor(tools)

然后它公开一个可运行的接口。 它可用于调用工具:您可以传入 AgentAction,它将查找相关工具并使用适当的输入调用它。

chat_agent_executor.create_function_calling_executor
from langgraph.prebuilt import chat_agent_executor

这是一个辅助函数,用于创建与利用函数调用的聊天模型配合使用的图表。 可以通过传入模型和工具列表来创建。 该模型必须是支持OpenAI函数调用的模型。

  1. from langchain_openai import ChatOpenAI
  2. from langchain_community.tools.tavily_search import TavilySearchResults
  3. from langgraph.prebuilt import chat_agent_executor
  4. from langchain_core.messages import HumanMessage
  5. tools = [TavilySearchResults(max_results=1)]
  6. model = ChatOpenAI()
  7. app = chat_agent_executor.create_function_calling_executor(model, tools)
  8. inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
  9. for s in app.stream(inputs):
  10. print(list(s.values())[0])
  11. print("----")
chat_agent_executor.create_tool_calling_executor
from langgraph.prebuilt import chat_agent_executor

这是一个辅助函数,用于创建与利用工具调用的聊天模型一起使用的图表。 可以通过传入模型和工具列表来创建。 该模型必须是支持OpenAI工具调用的模型。

  1. from langchain_openai import ChatOpenAI
  2. from langchain_community.tools.tavily_search import TavilySearchResults
  3. from langgraph.prebuilt import chat_agent_executor
  4. from langchain_core.messages import HumanMessage
  5. tools = [TavilySearchResults(max_results=1)]
  6. model = ChatOpenAI()
  7. app = chat_agent_executor.create_tool_calling_executor(model, tools)
  8. inputs = {"messages": [HumanMessage(content="what is the weather in sf")]}
  9. for s in app.stream(inputs):
  10. print(list(s.values())[0])
  11. print("----")
create_agent_executor
from langgraph.prebuilt import create_agent_executor

这是一个辅助函数,用于创建与 LangChain Agent 一起使用的图表。 可以通过传入代理和工具列表来创建。

  1. from langgraph.prebuilt import create_agent_executor
  2. from langchain_openai import ChatOpenAI
  3. from langchain import hub
  4. from langchain.agents import create_openai_functions_agent
  5. from langchain_community.tools.tavily_search import TavilySearchResults
  6. tools = [TavilySearchResults(max_results=1)]
  7. # Get the prompt to use - you can modify this!
  8. prompt = hub.pull("hwchase17/openai-functions-agent")
  9. # Choose the LLM that will drive the agent
  10. llm = ChatOpenAI(model="gpt-3.5-turbo-1106")
  11. # Construct the OpenAI Functions agent
  12. agent_runnable = create_openai_functions_agent(llm, tools, prompt)
  13. app = create_agent_executor(agent_runnable, tools)
  14. inputs = {"input": "what is the weather in sf", "chat_history": []}
  15. for s in app.stream(inputs):
  16. print(list(s.values())[0])
  17. print("----")

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号