代理架构¶
许多 LLM 应用程序在 LLM 调用之前或之后实现特定的控制流程步骤。例如,RAG 会检索与问题相关的文档,并将这些文档传递给 LLM 以使模型的响应有据可依。
我们有时希望 LLM 系统能够选择自己的控制流程来解决更复杂的问题,而不是硬编码固定的控制流程!这是对代理的定义之一:代理是一个使用 LLM 来决定应用程序控制流程的系统。LLM 可以通过多种方式来控制应用程序
- LLM 可以在这两种潜在路径之间进行路由
- LLM 可以决定调用哪些工具
- LLM 可以决定生成的答案是否足够,还是需要更多工作
因此,存在许多不同类型的代理架构,这些架构赋予 LLM 不同程度的控制力。
路由器¶
路由器允许 LLM 从一组指定的选项中选择单个步骤。这是一种控制能力相对有限的代理架构,因为 LLM 通常只控制单个决策,并且只能返回有限的输出。路由器通常采用几种不同的概念来实现这一点。
结构化输出¶
LLM 的结构化输出通过提供 LLM 应该在其响应中遵循的特定格式或模式来实现。这类似于工具调用,但更通用。虽然工具调用通常涉及选择和使用预定义的函数,但结构化输出可用于任何类型的格式化响应。常见的实现结构化输出的方法包括
- 提示工程:指示 LLM 以特定格式进行响应。
- 输出解析器:使用后处理从 LLM 响应中提取结构化数据。
- 工具调用:利用某些 LLM 的内置工具调用功能来生成结构化输出。
结构化输出对于路由至关重要,因为它可以确保 LLM 的决策可以被系统可靠地解释和执行。在本操作指南中了解更多关于结构化输出的信息。
工具调用代理¶
虽然路由器允许 LLM 做出单个决策,但更复杂的代理架构通过两种主要方式扩展了 LLM 的控制力
- 多步决策:LLM 可以控制一系列决策,而不仅仅是一个决策。
- 工具访问:LLM 可以从各种工具中选择并使用这些工具来完成任务。
ReAct 是一种流行的通用代理架构,它结合了这些扩展,整合了三个核心概念。
工具调用
:允许 LLM 根据需要选择和使用各种工具。内存
:使代理能够保留和使用来自先前步骤的信息。规划
:使 LLM 能够创建和遵循多步计划来实现目标。
此架构允许更复杂和灵活的代理行为,超越简单的路由,使能够在多个步骤中进行动态解决问题。您可以使用create_react_agent
来使用它。
工具调用¶
只要您希望代理与外部系统交互,工具就会很有用。外部系统(例如 API)通常需要特定的输入模式或有效负载,而不是自然语言。例如,当我们将 API 绑定为工具时,我们使模型能够了解所需的输入模式。模型将根据用户的自然语言输入来选择调用工具,并且它将返回符合工具模式的输出。
许多 LLM 提供商支持工具调用,并且LangChain 中的工具调用接口很简单:您只需将任何 Python function
传递到 ChatModel.bind_tools(function)
中即可。
内存¶
内存对于代理至关重要,它使代理能够在解决问题的多个步骤中保留和利用信息。它在不同的规模上运作
- 短期记忆:允许代理访问在序列中较早步骤期间获取的信息。
- 长期记忆:使代理能够回忆起先前交互中的信息,例如对话中的先前消息。
LangGraph 提供了对内存实现的完全控制
State
:用户定义的模式,指定要保留的内存的精确结构。Checkpointers
:在跨不同交互的每个步骤中存储状态的机制。
这种灵活的方法使您能够根据您的特定代理架构需求来定制内存系统。有关将内存添加到图形的实用指南,请参见本教程。
有效的内存管理增强了代理维护上下文、从过去经验中学习以及随着时间的推移做出更明智决策的能力。
规划¶
在 ReAct 架构中,LLM 会在 while 循环中重复调用。在每个步骤中,代理都会决定调用哪些工具以及这些工具的输入应该是什么。然后执行这些工具,并将输出作为观察结果反馈到 LLM 中。当代理决定不再值得调用任何工具时,while 循环终止。
ReAct 实现¶
本文与预构建的create_react_agent
实现之间存在一些差异
- 首先,我们使用工具调用让 LLM 调用工具,而本文则使用提示 + 解析原始输出。这是因为工具调用在本文撰写时还不存在,但通常更好且更可靠。
- 其次,我们使用消息来提示 LLM,而本文则使用字符串格式化。这是因为在撰写本文时,LLM 甚至没有公开基于消息的接口,而现在这是他们公开的唯一接口。
- 第三,本文要求所有工具的输入都必须是单个字符串。这在很大程度上是由于当时的 LLM 能力有限,只能真正生成单个输入。我们的实现允许使用需要多个输入的工具。
- 第四,本文只关注一次调用一个工具,这在很大程度上是由于当时 LLM 的性能限制。我们的实现允许一次调用多个工具。
- 最后,本文要求 LLM 在决定调用哪些工具之前显式地生成一个“思考”步骤。这是“ReAct”中的“推理”部分。我们的实现默认情况下不会这样做,这在很大程度上是因为 LLM 已经有了很大的改进,因此不再那么必要。当然,如果您希望提示它这样做,您当然可以。
自定义代理架构¶
虽然路由器和工具调用代理(如 ReAct)很常见,但定制代理架构通常会导致特定任务的性能提升。LangGraph 提供了一些功能强大的功能来构建定制的代理系统
人机协作¶
人为参与可以显着提高代理的可靠性,尤其是在处理敏感任务时。这可能涉及
- 批准特定操作
- 提供反馈来更新代理的状态
- 在复杂的决策过程中提供指导
当完全自动化不可行或不可取时,人机协作模式至关重要。在我们的人机协作指南中了解更多信息。
并行化¶
并行处理对于高效的多代理系统和复杂任务至关重要。LangGraph 通过其Send API 支持并行化,从而实现
- 多个状态的并发处理
- 类似于 map-reduce 的操作的实现
- 高效地处理独立的子任务
有关实际实现,请参见我们的map-reduce 教程。
子图¶
子图对于管理复杂的代理架构至关重要,尤其是在多代理系统中。它们允许
- 为单个代理隔离状态管理
- 代理团队的层次结构组织
- 代理与主系统之间的受控通信
子图通过状态模式中的重叠键与父图进行通信。这使能够灵活、模块化的代理设计。有关实现细节,请参阅我们的子图教程。
反思¶
反思机制可以通过以下方式显着提高代理的可靠性
- 评估任务完成情况和正确性
- 提供反馈以进行迭代改进
- 启用自我纠正和学习
虽然通常基于 LLM,但反思也可以使用确定性方法。例如,在编码任务中,编译错误可以作为反馈。这种方法在使用 LangGraph 进行自我纠正代码生成的视频中有所体现。
通过利用这些功能,LangGraph 使能够创建复杂的、特定于任务的代理架构,这些架构可以处理复杂的工作流、有效协作并不断提高其性能。