12个经典的AI代理设计模式
代理可以定义为能够推理、计划并与环境互动的大规模语言模型。就像在软件工程中一样,AI代理也出现了设计模式,使开发经过验证且可靠的架构成为可能。

近年来,许多大型企业专注于开发代理和代理工作流。这些先进的解决方案处于全自动化任务的前沿。幸运的是,实现这些系统并不需要重新发明轮子,因为已经开发出了许多用于构建AI代理和代理工作流的框架。这些框架允许开发人员创建复杂的代理系统,而无需理解代理构建的每一个复杂细节。就像在软件工程中一样,AI代理也出现了设计模式,使开发经过验证且可靠的架构成为可能。
代理可以定义为能够推理、计划并与环境互动的大规模语言模型。它们还能够与其他代理进行更复杂的任务协作和通信。如果你想了解更多关于代理的信息,您可以查看我的文章。对于本文,我将假设你知道代理及其组件是什么。

随着代理框架的发展,许多公司开始构建自己的多代理系统,并寻找一种万能的解决方案来解决所有代理任务。两年前,研究人员[1] 设计了一个名为ChatDev的多代理协作系统。ChatDev是一个虚拟的软件公司,通过各种智能代理运作,这些代理担任不同的角色,如首席执行官、首席产品官、艺术设计师、编码员、审核员、测试员等,就像一个普通的软件工程公司。

所有这些代理一起工作并相互交流,以创建一个视频游戏,并且他们的努力证明是成功的。在此成就之后,许多人认为任何软件工程任务都可以使用这种多代理架构来完成,其中每个AI都有一个独特的角色。然而,现实世界的实验表明,并非每个问题都可以用相同的架构解决。在某些情况下,更简单的架构可以提供更有效、成本更低的解决方案。
1、ReAct (思考+行动)
最简单的AI代理设计模式称为ReAct。在这种模式下,一个LLM首先思考要做什么,然后决定采取什么行动,该行动将在环境中执行并返回观察结果。有了这个观察结果,LLM会重复操作,再次思考要做什么,决定另一个行动并继续,直到它决定完成任务。

ReAct设计模式可以用纯代码轻松构建,而无需使用框架来处理简单任务。
首先我们需要一个大规模语言模型作为代理的大脑:
from dotenv import load_dotenv
from openai import OpenAI
_ = load_dotenv()
client = OpenAI()
然后我们可以将我们的简单代理构建为一个类来返回消息:
class Agent:
def __init__(self, system=""):
self.system = system
self.messages = []
if self.system:
self.messages.append({"role": "system", "content": system})
def __call__(self, message):
self.messages.append({"role": "user", "content": message})
result = self.execute()
self.messages.append({"role": "assistant", "content": result})
return result
def execute(self):
completion = client.chat.completions.create(
model="gpt-4o",
temperature=0,
messages=self.messages)
return completion.choices[0].message.content
然后我们需要一个系统提示来给代理提供指令,使其使用两个其他工具完成任务:一个是用于数学计算的工具,另一个是用于找出给定犬种的平均体重。
import openai
import re
import httpx
import os
prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you - then return PAUSE.
Observation will be the result of running those actions.
Your available actions are:
calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary
average_dog_weight:
e.g. average_dog_weight: Collie
returns average weight of a dog when given the breed
Example session:
Question: How much does a Bulldog weigh?
Thought: I should look the dogs weight using average_dog_weight
Action: average_dog_weight: Bulldog
PAUSE
You will be called again with this:
Observation: A Bulldog weights 51 lbs
You then output:
Answer: A bulldog weights 51 lbs
""".strip()
def calculate(what):
return eval(what)
def average_dog_weight(name):
if name in "Scottish Terrier":
return("Scottish Terriers average 20 lbs")
elif name in "Border Collie":
return("a Border Collies average weight is 37 lbs")
elif name in "Toy Poodle":
return("a toy poodles average weight is 7 lbs")
else:
return("An average dog weights 50 lbs")
known_actions = {
"calculate": calculate,
"average_dog_weight": average_dog_weight
}
现在我们可以通过循环运行代理来构建它,以便在多个步骤中工作:
abot = Agent(prompt)
def query(question, max_turns=5):
i = 0
bot = Agent(prompt)
next_prompt = question
while i < max_turns:
i += 1
result = bot(next_prompt)
print(result)
actions = [
action_re.match(a)
for a in result.split('\n')
if action_re.match(a)
]
if actions:
# There is an action to run
action, action_input = actions[0].groups()
if action not in known_actions:
raise Exception("Unknown action: {}: {}".format(action, action_input))
print(" -- running {} {}".format(action, action_input))
observation = known_actions[action](action_input)
print("Observation:", observation)
next_prompt = "Observation: {}".format(observation)
else:
return
question = """I have 2 dogs, a border collie and a scottish terrier. \
What is their combined weight"""
query(question)
Thought: I need to find the average weight of a Border Collie and a Scottish Terrier, then add them together to get the combined weight.
Action: average_dog_weight: Border Collie
PAUSE
-- running average_dog_weight Border Collie
Observation: a Border Collies average weight is 37 lbs
Action: average_dog_weight: Scottish Terrier
PAUSE
-- running average_dog_weight Scottish Terrier
Observation: Scottish Terriers average 20 lbs
Thought: Now that I have the average weights of both dogs, I can calculate their combined weight by adding them together.
Action: calculate: 37 + 20
PAUSE
-- running calculate 37 + 20
Observation: 57
Answer: The combined weight of a Border Collie and a Scottish Terrier is 57 lbst
正如上面所见,代理通过使用两种不同的工具解决了找到边境牧羊犬和苏格兰㹴犬品种的平均体重并求和的问题。
可以用纯代码构建不同的架构。但重要的是要记住,代理如此有前景的原因在于它们可以通过通信和分配任务给彼此来完成更复杂的任务。构建这类架构可能会很快成为一个相当大的挑战,因此许多AI公司构建了自己的框架来简化代理工作流的构建。我个人在许多框架中进行了实验,并在我之前的文章中进行了比较。
许多研究表明,不同的设计模式适用于不同的任务。与其从头开始设计和构建这些架构,我们可以利用经过验证的解决方案,这些解决方案针对我们的具体问题进行了优化。例如,LangGraph在其文档中提供了多种多代理架构。在这篇文章中,我们将探索这些架构,以便更好地了解如何将它们应用于我们自己的挑战。
LangGraph将这些代理架构分为三大类:
- 多代理系统
- 规划代理
- 反思与批评
Part 1:多代理系统
2、Network (网络)
一种解决复杂任务的方法是采用“分而治之”的策略。使用路由器,任务可以被路由到专门处理特定任务的代理。

这种架构被称为多代理网络架构。
3、Supervisor (监督者)
这种架构与网络架构非常相似,不同之处在于有一个监督者代理来协调不同的代理,而不是路由器。

4、Hierarchical Teams (层次团队)
层次团队架构源于这样一个想法:“如果单个代理不足以解决特定任务怎么办?”在这种情况下,不是由监督者代理协调几个代理,而是由监督者代理协调由多个代理组成的几个团队。

Part 2:规划代理
5、Plan-and-execute (规划与执行)
在这种架构中,首先代理根据给定任务生成顺序的子任务。单任务(专业化)代理解决子任务,如果任务完成,则结果会被发送回规划代理。规划代理根据结果形成不同的计划。如果任务完成,规划代理将响应用户。

6、Reasoning-without-observation (不观察推理)
在ReWOO中,Xu等人[6]引入了一种集成多步规划器和变量替换以优化工具使用的代理。这种方法与规划与执行架构非常相似。然而,与传统模型不同,ReWOO架构在每次动作后不包括观察步骤。相反,整个计划提前创建并且固定不变,不受任何后续观察的影响。

规划代理构建解决任务的子任务计划,工作代理简单地完成子任务,然后响应用户。
7、LLM Compiler (LLM编译器)
LLM编译器是一种代理架构,旨在通过在有向无环图(DAG)中积极执行任务来加速代理任务的执行。它还通过减少对LLM的调用来节省冗余令牌使用的成本。以下是其计算图的概述:

它有三个主要组件:
- 规划器:流式传输任务的DAG。
- 任务获取单元:安排并尽快执行任务。
- 连接器:响应用户或触发第二个计划。
Part 3:反思与批评
8、Basic Reflection (基本反思)
反思代理提示LLM反思过去的行为,使其随着时间学习和改进。有两个代理:生成器和批评家。最简单的例子可以是一个作家和批评家。作家根据用户请求写一篇文章,批评家审查文章,然后将他们的反思发回给作家。这个循环会持续到给定的迭代次数。

9、Reflexion (反射)
反射由Shinn等人[7]提出,是一种通过口头反馈和自我反思来学习的架构。代理明确批评其响应以生成更高质量的最终响应,但会增加执行时间。反射代理还包括工具执行,与反思架构不同。

论文概述了三个主要组件:
- 具有自我反思的演员(代理)
- 外部评估者(任务特定,例如代码编译步骤)
- 存储来自(1)的反思的偶发记忆
10、Tree of Thoughts (思维树)
思维树(ToT),由Yao等人[8]提出,是一种结合了反思/评估和简单搜索(在这种情况下是广度优先搜索,尽管您可以应用深度优先搜索或其他算法)的一般LLM代理搜索算法。

它有三个主要步骤:
- 扩展:生成一个问题的1个或多个候选解决方案。
- 评分:衡量响应的质量。
- 剪枝:保留最佳的K个候选方案。
如果没有找到解决方案(或解决方案质量不足),则返回到“扩展”。
11、Lang Agent Tree Search (语言代理树搜索)
语言代理树搜索(LATS),由Zhou等人[9]提出,是一种结合了反思/评估和搜索(特别是蒙特卡洛树搜索)的一般LLM代理搜索算法,以实现比类似技术(如ReACT、反射或思维树)更好的整体任务性能。

它有四个主要步骤:
- 选择:基于步骤(2)中的累积奖励挑选最佳的下一个动作。要么响应(如果找到解决方案或达到最大搜索深度),要么继续搜索。
- 扩展和模拟:选择“最佳”的5个潜在动作并并行执行。
- 反思+评估:观察这些动作的结果并根据反思(以及可能的外部反馈)评分决策。
- 回溯传播:根据结果更新根轨迹的分数。
12、Self-Discover Agent (自发现代理)
自发现帮助大规模语言模型(LLMs)找出解决问题的最佳方法。
- 首先,它为每个问题找到一个独特的计划,通过选择和改变基本推理步骤。
- 然后,它使用这个计划逐步解决问题。
这样,LLM使用不同的推理工具并调整到问题上,以获得比仅使用一种方法更高效的解决方案。

自发现与其他规划方法的不同之处在于它自动为每个任务创建一个独特的推理策略。以下是它的不同之处:
- 推理模块:它使用基本推理步骤并将它们按特定顺序组合在一起。
- 无需人工帮助:它自行找出这些策略,而不需要人为标记任务。
- 适应任务:它找到解决每个问题的最佳方式,就像人类制定计划一样。
- 可转移:它创建的推理策略可以被不同类型的语言模型使用。
简而言之,自发现的独特之处在于它结合了不同的推理方法来创建一个计划,而不需要特定的任务指令。
13、结束语
在这篇文章中,我们探讨了AI代理及其设计模式的演变格局,强调了代理框架如何使复杂AI系统的开发民主化。通过讨论基础概念和实际实现(如ReAct模式),我们展示了更简单、更具成本效益的架构如何经常有效地解决特定任务。我们还研究了高级多代理系统、规划代理和反思框架,这些框架增强了AI的能力。像ChatDev这样的项目的成功突显了多代理协作的潜力,而像LangGraph这样的工具简化了构建复杂代理工作流的过程。总体而言,理解和利用这些设计模式使开发人员能够为各种应用创建可靠且可扩展的AI系统。
原文链接:AI Agents Design Patterns Explained
汇智网翻译整理,转载请标明出处
