随着大语言模型的快速发展,如何让AI更好地理解和执行复杂任务,成为了开发者们关注的焦点。Agent设计模式应运而生,它们就像是给大模型装上"大脑",让AI能够像人类一样思考、规划和执行任务。
今天,我们就来深入探讨9种主流的Agent设计模式,看看它们是如何让大模型变得更智能、更高效的。
一、Few-Shot模式:让AI学会举一反三
Few-Shot模式是最基础也是最常用的Agent范式之一。它的核心思想是通过提供少量示例,让大模型学会如何完成任务。
核心组成
Few-Shot模式包含三个关键部分:
角色描述 :明确告诉大模型需要扮演什么角色,具备哪些能力
指令任务描述 :清晰说明需要完成的任务,可以是一句话,也可以是多步骤引导
样例 :提供完整的"任务-解决方案"示例,或者输入输出的格式规范
实际应用
在B端开发场景中,Few-Shot模式使用频率最高。比如,你可以让大模型扮演"网络安全专家",分析Java漏洞调用堆栈,找出用户业务代码所在的类。通过提供格式示例和堆栈日志,大模型就能按照你期望的格式输出结果。
这种模式的最大优势是:工程师可以通过大模型的指令遵循能力,将原本需要复杂规则定义和处理的环节,都交给大模型来处理,大大提升了工作效率。
代码示例
以下是一个Few-Shot模式的提示词示例:
# 定义提示词模板
prompt = """你是一个网络安全专家,我这里一份RASP上报的Java漏洞调用堆栈,请帮我找出用户业务代码所在的类。
请按照下面的格式输出:
[
{{
"javaFile": "LinkedHashSet.java",
"method": "LinkedHashSet<E>(HashSet<E>).readObject(ObjectInputStream)",
"codeline": "40"
}},
{{
"javaFile": "AnnotationInvocationHandler.java",
"method": "AnnotationInvocationHandler.invoke(Object, Method, Object[])",
"codeline": "133"
}}
]
堆栈日志如下:
{stack\_trace}"""
# 使用示例
stack\_trace = """Runtime.exec(String) line: 345
EvilCodes.<init>() line: 17
NativeConstructorAccessorImpl.newInstance0(Constructor, Object[])
..."""
response = llm.generate(prompt.format(stack\_trace=stack\_trace))
通过提供格式示例,大模型就能按照期望的JSON格式输出结果,大大简化了后续的数据处理工作。
二、ReAct模式:思考-行动-观察的循环
ReAct模式将推理(Reasoning)和行动(Acting)结合起来,形成了一个"思考-行动-观察"的循环过程。
工作原理
ReAct针对给出的问题,先进行思考(Thought),再根据思考的结果行动(Act),然后观察行动的结果(Observation)。如果不满足要求,再进行思考、行动,直至得到满意的结果为止。
三个核心概念
Thought(思考) :由LLM生成,是LLM产生行为的依据。可以根据LLM的思考,来衡量他要采取的行为是否合理。这就像人类在做决策前的思考过程,让AI的决策变得更有可解释性和可信度。
Act(行动) :LLM判断本次需要执行的具体行为。一般由两部分组成:行为和对象,用编程的说法就是API名称和对应的入参。LLM可以根据Thought的判断,选择需要使用的API并生成需要填入API的参数。
Obs(观察) :LLM框架对于外界输入的获取。就像LLM的"五官",将外界的反馈信息同步给LLM模型,协助LLM模型进一步做分析或者决策。
执行流程
一个完整的ReAct行为包含以下流程:
输入目标 :任务的起点,可以是用户的手动输入,也可以是系统故障报警等触发器
循环执行 :LLM模型开始分析问题需要的步骤(Thought),按步骤执行Act;根据观察到的信息(Obs),循环执行这个过程,直到判断任务目标达成
完成 :任务最终执行成功,返回最终结果
代码示例
以下是一个简化的ReAct模式实现示例:
classReActAgent:
def\_\_init\_\_(self, llm, tools):
self.llm = llm
self.tools = tools # 工具字典,如 {"search": search\_tool, "calculator": calc\_tool}
defrun(self, question, max\_iterations=5):
context = f"问题: {question}\n"
for i inrange(max\_iterations):
# Thought: 生成思考过程
thought\_prompt = f"{context}\n思考:"
thought = self.llm.generate(thought\_prompt)
context += f"\n思考: {thought}"
# Act: 决定要执行的动作
act\_prompt = f"{context}\n行动:"
action = self.llm.generate(act\_prompt)
context += f"\n行动: {action}"
# 解析动作并执行
tool\_name, tool\_input = self.\_parse\_action(action)
if tool\_name == "完成":
returnself.\_extract\_answer(context)
# Observation: 执行工具并观察结果
if tool\_name inself.tools:
observation = self.tools[tool\_name](tool\_input)
context += f"\n观察: {observation}"
else:
context += f"\n观察: 工具 {tool\_name} 不存在"
return"达到最大迭代次数,未能完成任务"
def\_parse\_action(self, action):
# 解析动作字符串,提取工具名和输入
# 例如: "搜索[Python教程]" -> ("搜索", "Python教程")
if"完成"in action:
return"完成", None
# 简化的解析逻辑
return"搜索", action.strip()
def\_extract\_answer(self, context):
# 从上下文中提取最终答案
returnself.llm.generate(f"{context}\n最终答案:")
这个示例展示了ReAct模式的核心循环:思考-行动-观察,直到任务完成。
三、Plan and Solve模式:先规划再执行
Plan and Solve模式解决了多步推理的步骤缺失问题。它的核心思想是:先设计计划,将整个任务划分为多个更小的子任务,然后根据计划执行子任务。
适用场景
如果说ReAct更适合完成"厨房拿酱油"这样的简单任务,那么Plan & Solve更适合完成"西红柿炒鸡蛋"这样的复杂任务。你需要计划,并且过程中计划可能会变化(比如你打开冰箱发现没有西红柿时,你将购买西红柿作为新的步骤加入计划)。
核心组件
规则器(Planner) :负责让LLM生成一个多步计划来完成一个大任务。代码中有Planner和Replanner,Planner负责第一次生成计划;Replanner是指在完成单个任务后,根据目前任务的完成情况进行重新规划。
执行器(Executor) :接受用户查询和规划中的步骤,并调用一个或多个工具来完成该任务。
提示词示例
Plan and Solve模式通常需要调用两次LLM:
- • 第一次生成推理过程:通过提示词如"Let’s think step by step",模型会生成解决问题的推理过程
- • 第二次提取最终答案:使用生成的推理过程和原始问题,模型再次被调用以提取或生成最终的答案
代码示例
以下是一个Plan and Solve模式的实现示例:
classPlanAndSolveAgent:
def\_\_init\_\_(self, llm):
self.llm = llm
defsolve(self, problem):
# 第一步:生成计划
plan\_prompt = f"""
让我们首先理解问题并制定解决问题的计划。然后,让我们一步一步地执行解决问题的计划。
问题: {problem}
请先制定一个详细的计划:
"""
plan = self.llm.generate(plan\_prompt)
# 第二步:执行计划并生成推理过程
solve\_prompt = f"""
问题: {problem}
计划:
{plan}
现在,让我们按照计划一步一步地解决问题:
"""
reasoning = self.llm.generate(solve\_prompt)
# 第三步:提取最终答案
answer\_prompt = f"""
问题: {problem}
推理过程:
{reasoning}
基于以上推理过程,请给出最终答案:
"""
answer = self.llm.generate(answer\_prompt)
return {
"plan": plan,
"reasoning": reasoning,
"answer": answer
}
# 使用示例
agent = PlanAndSolveAgent(llm)
result = agent.solve("如果一个数的3倍加上5等于20,这个数是多少?")
print(f"计划: {result['plan']}")
print(f"推理: {result['reasoning']}")
print(f"答案: {result['answer']}")
这种两阶段方法(规划+执行)能够更好地处理复杂的多步骤问题。
四、Reason without Observation模式:推理与观察分离
Reason without Observation(REWOO)模式的核心思想是将推理(Reasoning)过程与外部观察(Observation)分离,以此来提高模型的效率和性能。
为什么需要REWOO?
在传统的LLM增强系统中,如ReAct模式,模型的推理过程是与外部工具的调用和观察结果紧密交织在一起的。这种模式虽然简单易用,但往往会导致计算复杂性高,因为需要多次调用语言模型(LLM)并重复执行操作,这不仅增加了计算成本,也增加了执行时间。
三个核心组件
Planner(规划器) :接收用户输入的任务,并将其分解为一系列的计划。每个计划都详细说明了需要使用哪些外部工具以及如何使用这些工具。负责生成一个相互依赖的"链式计划",定义每一步所依赖的上一步的输出。
Worker(执行器) :根据规划器提供的计划,调用相应的外部工具来执行任务,并获取必要的信息或证据。循环遍历每个任务,并将任务输出分配给相应的变量。
Solver(合并器) :将所有计划的执行结果整合起来,形成对原始任务的最终解决方案。
优势
这种模块化的设计显著减少了令牌消耗和执行时间,因为它允许一次性生成完整的工具链,而不是在每次迭代中都重复调用LLM。此外,由于规划数据不依赖于工具的输出,因此可以在不实际调用工具的情况下对模型进行微调,进一步简化了微调过程。
提示词模板示例
REWOO模式的关键在于定义步骤间的依赖关系,以下是一个提示词模板示例:
REWOO\_PROMPT\_TEMPLATE = """
对于以下任务,制定可以逐步解决问题的计划。对于每个计划,
指示检索证据的具体工具和工具输入,您可以存储将证据转换为变量,
以后的工具可以调用该变量。(Plan.#E1, Plan.#E2, Plan.#E3...)
问题:{question}
计划示例:
问题:科罗拉多造山带南东段延伸到的地区的海拔范围是多少?
计划:搜索更多关于科罗拉多造山运动的信息。
#E1=维基百科[科罗拉多造山运动]
计划:找出科罗拉多造山运动东段延伸到的区域。
#E2=LLM[科罗拉多州东部延伸到的地区名称是什么?给定上下文:#E1]
计划:搜索有关该地区的更多信息。
#E3=维基百科[#E2]
计划:找出该区域的标高范围。
#E4=LLM[区域#E2的标高范围是多少?给定上下文:#E3]
"""
# 使用示例
question = "北京和上海哪个城市的人口更多?"
prompt = REWOO\_PROMPT\_TEMPLATE.format(question=question)
plan = llm.generate(prompt)
# 解析计划并执行
# Planner生成计划后,Worker按顺序执行,Solver合并结果
这种设计允许一次性生成完整的工具调用链,大大提高了执行效率。
五、LLMCompiler模式:并行执行提升效率
LLMCompiler模式的核心思想是通过并行Function calling来提高效率。
工作原理
比如用户提问"Scott Derrickson和Ed Wood是否是同一个国家的国民?"传统方式可能需要先搜索Scott Derrickson的国籍,再搜索Ed Wood的国籍。而LLMCompiler可以让planner同时搜索两人的国籍,最后合并结果即可。
架构组成
LLMCompiler由三个组件组成:
Planner(规划器) :将原始问题分解为一个DAG(有向无环图)的任务列表
Task Fetching Unit(并行执行器) :根据任务的依赖,调度任务并行执行
Joiner(合并器) :综合DAG执行结果反馈给用户,如果没达预期可以重新规划任务
这种设计大大提升了处理效率,特别是在需要多个独立查询的场景下。
代码示例
以下是一个简化的LLMCompiler模式实现示例:
import asyncio
from typing importList, Dict
classLLMCompilerAgent:
def\_\_init\_\_(self, llm, tools):
self.llm = llm
self.tools = tools
asyncdefsolve(self, question):
# 第一步:Planner生成DAG任务列表
plan\_prompt = f"""
问题: {question}
请将这个问题分解为可以并行执行的任务,并标识任务间的依赖关系。
输出格式:任务列表,每个任务包含id、描述、依赖的任务id列表。
示例:
任务1: 搜索Scott Derrickson的国籍,依赖: []
任务2: 搜索Ed Wood的国籍,依赖: []
任务3: 比较两个国籍是否相同,依赖: [任务1, 任务2]
"""
plan = self.llm.generate(plan\_prompt)
tasks = self.\_parse\_plan(plan)
# 第二步:并行执行独立任务
results = {}
while tasks:
# 找出可以并行执行的任务(无依赖或依赖已完成)
ready\_tasks = [t for t in tasks ifall(dep in results for dep in t['dependencies'])]
# 并行执行这些任务
if ready\_tasks:
task\_results = await asyncio.gather(*[
self.\_execute\_task(task) for task in ready\_tasks
])
for task, result inzip(ready\_tasks, task\_results):
results[task['id']] = result
tasks.remove(task)
else:
break
# 第三步:Joiner合并结果
join\_prompt = f"""
原始问题: {question}
任务执行结果: {results}
请综合所有结果,给出最终答案:
"""
final\_answer = self.llm.generate(join\_prompt)
return final\_answer
def\_parse\_plan(self, plan):
# 解析计划,提取任务列表
# 这里简化处理,实际需要更复杂的解析逻辑
tasks = []
# ... 解析逻辑
return tasks
asyncdef\_execute\_task(self, task):
# 执行单个任务
tool\_name = self.\_identify\_tool(task['description'])
if tool\_name inself.tools:
returnawaitself.tools[tool\_name](task['description'])
returnNone
# 使用示例
asyncdefmain():
agent = LLMCompilerAgent(llm, tools)
answer = await agent.solve("Scott Derrickson和Ed Wood是否是同一个国家的国民?")
print(answer)
# asyncio.run(main())
这种并行执行的设计能够显著提升处理多个独立查询的效率。
六、Basic Reflection模式:左右互搏的自我优化
Basic Reflection可以类比于学生(Generator)写作业,老师(Reflector)来批改建议,学生根据批改建议来修改,如此反复。
核心原理
Basic Reflection可以类比于左右互搏:
- • 左手是Generator,负责根据用户指令生成结果
- • 右手是Reflector,来审查Generator的生成结果并给出建议
在左右互搏的情况下,Generator生成的结果越来越好,Reflector的检查越来越严格,输出的结果也越来越有效。
执行流程
-
- Generator接收来自用户的输入,输出initial response
-
- Reflector接收来自Generator的response,根据开发者设置的要求,给出Reflections,即评语、特征、建议
-
- Generator再根据Reflector给出的反馈进行修改和优化,输出下一轮response
-
- 循环往复,直到达到循环次数或满足要求
代码示例
以下是一个Basic Reflection模式的实现示例:
classBasicReflectionAgent:
def\_\_init\_\_(self, llm, max\_iterations=3):
self.llm = llm
self.max\_iterations = max\_iterations
defgenerate(self, user\_input):
# Generator生成初始响应
generator\_prompt = f"用户输入: {user\_input}\n\n请生成响应:"
response = self.llm.generate(generator\_prompt)
for i inrange(self.max\_iterations):
# Reflector审查响应
reflector\_prompt = f"""
用户输入: {user\_input}
当前响应: {response}
请审查这个响应,给出以下方面的反馈:
1. 准确性
2. 完整性
3. 清晰度
4. 改进建议
"""
reflection = self.llm.generate(reflector\_prompt)
# Generator根据反馈改进响应
generator\_prompt = f"""
用户输入: {user\_input}
之前的响应: {response}
审查反馈: {reflection}
请根据反馈改进你的响应:
"""
improved\_response = self.llm.generate(generator\_prompt)
# 如果响应没有显著改进,可以提前结束
if improved\_response == response:
break
response = improved\_response
return response
# 使用示例
agent = BasicReflectionAgent(llm)
result = agent.generate("请解释什么是机器学习?")
print(result)
这种左右互搏的方式能够不断优化输出质量,直到达到满意的结果。
七、Reflexion模式:从失败中学习的反思机制
Reflexion模式使用语言反馈信号来帮助agent从前失败的经验中学习。具体地,Reflexion将传统梯度更新中的参数信号转变为添加在大模型上下文中的语言总结,使得agent在下一个episode中能参考上次执行失败的失败经验,从而提高agent的执行效果。
四个核心组件
Actor :由LLM担任,主要工作是基于当前环境生成下一步的动作
Evaluator :主要工作是衡量Actor生成结果的质量。就像强化学习中的Reward函数对Actor的执行结果进行打分
Self-reflexion :一般由LLM担任,是Reflexion框架中最重要的部分。它能结合离散的reward信号(如success/fail)、trajectory(轨迹,也就是推理上下文)等生成具体且详细语言反馈信号,这种反馈信号会储存在Memory中,启发下一次实验的Actor执行动作
Memory :分为短期记忆(short-term)和长期记忆(long-term)。在一次实验中的上下文称为短期记忆,多次试验中Self-reflexion的结果称为长期记忆
与Basic Reflection的区别
-
- Reflexion会把之前的生成-tool调用结果-评价的所有过程数据,当做下次生成的prompt
-
- 可以通过外部工具查询数据来作为评价修正的依据
Reflexion在决策、推理和代码生成任务上均取得了不错的效果,特别是在代码生成任务上成为了最新的SOTA。
代码示例
以下是一个简化的Reflexion模式实现示例:
classReflexionAgent:
def\_\_init\_\_(self, llm, evaluator, max\_episodes=3):
self.llm = llm
self.evaluator = evaluator # 评估函数
self.max\_episodes = max\_episodes
self.memory = [] # 长期记忆:存储历史反思
defsolve(self, task):
for episode inrange(self.max\_episodes):
# Actor: 基于当前环境和记忆生成动作
actor\_prompt = f"""
任务: {task}
历史经验(长期记忆):
{self.\_format\_memory()}
请基于任务和历史经验,生成解决方案:
"""
solution = self.llm.generate(actor\_prompt)
# Evaluator: 评估解决方案质量
evaluation = self.evaluator(solution, task)
# Self-reflexion: 生成语言反馈信号
ifnot evaluation['success']:
reflection\_prompt = f"""
任务: {task}
生成的解决方案: {solution}
评估结果: {evaluation}
请分析失败的原因,并给出具体的改进建议:
"""
reflection = self.llm.generate(reflection\_prompt)
# 将反思存入长期记忆
self.memory.append({
'episode': episode,
'solution': solution,
'evaluation': evaluation,
'reflection': reflection
})
else:
# 成功则返回解决方案
return {
'solution': solution,
'episode': episode,
'evaluation': evaluation
}
# 达到最大episode数,返回最后一次的解决方案
return {
'solution': solution,
'episode': self.max\_episodes - 1,
'evaluation': evaluation,
'memory': self.memory
}
def\_format\_memory(self):
ifnotself.memory:
return"暂无历史经验"
formatted = []
for mem inself.memory:
formatted.append(f"""
Episode {mem['episode']}:
- 解决方案: {mem['solution']}
- 评估: {mem['evaluation']}
- 反思: {mem['reflection']}
""")
return"\n".join(formatted)
# 使用示例
defcode\_evaluator(code, task):
"""代码评估函数示例"""
# 实际应用中,这里会运行代码并检查结果
return {
'success': len(code) > 100, # 简化示例
'score': 0.8,
'feedback': '代码基本正确,但需要优化'
}
agent = ReflexionAgent(llm, code\_evaluator)
result = agent.solve("编写一个快速排序算法")
print(f"最终解决方案: {result['solution']}")
print(f"经过 {result['episode']} 次迭代")
Reflexion模式通过记忆机制,让Agent能够从历史失败中学习,不断改进解决方案。
八、Language Agent Tree Search模式:树搜索+反思的融合
LATS(Language Agent Tree Search)简单来说是Tree search + ReAct + Plan&solve的融合体。
核心特点
与传统的基于MCTS的推理决策框架相比,LATS的主要改进在于:
- • 使用了蒙特卡罗树搜索算法,可以有效地探索可能的解决方案
- • 利用了预训练的语言模型来评估节点的价值,从而更好地指导搜索过程
- • 引入了自我反思机制,可以从失败的轨迹中学习并提高决策能力
主要步骤
选择 :根据总奖励选择最佳的下一步行动。要么做出响应(如果找到解决方案或达到最大搜索深度),要么继续搜索
扩展和执行 :生成N个潜在操作以并行执行它们
反思+评估 :观察这些行动的结果并根据反思(以及可能的外部反馈)对决策进行评分
反向传播 :根据结果更新根轨迹的分数
总结一下,选择当前节点,行动、反思、评分,并将结果反向传播给父节点,同时根据节点数量是否达到上限以及结果情况决定是否继续向下延伸或输出结果。
九、Self-Discover模式:自动发现推理结构
SELF-DISCOVER旨在使模型能够自动发现用于解决复杂推理问题的任务内在推理结构。这类问题对于传统的提示方法来说构成了挑战。
核心思想
SELF-DISCOVER的核心是一个自我发现过程。LLMs在这一过程中选择多个原子推理模块(如批判性思维和逐步思考)并将它们组合成一个明确的推理结构,供LLMs在解码时遵循。
优势
-
- SELF-DISCOVER增强LLM处理复杂推理问题的能力,尤其是那些传统提示方法难以应对的问题
-
- 在基于理论的代理推理和MATH等具有挑战性的推理基准测试上的表现,相比链式推理(CoT)提高了32%
-
- 在效率上也超过了推理密集型方法,如CoT-Self-Consistency,同时所需的推理计算量减少了10到40倍
-
- 展示了自我发现的推理结构在不同的模型家族之间具有普适性
两阶段方法
第一阶段-发现推理结构 ,包括三个操作:
选择(Select) :从39个预定义的推理模块中选择几个关键模块,这些模块可以帮助解决特定的任务
适应(Adapt) :选择完关键推理模块后,调整和完善每个模块的描述使其更好地适应待解决的具体任务
实施(Implement) :将调整后的推理模块组合成一个JSON格式的分步推理计划
第二阶段-应用推理结构 :完成阶段一之后,模型将拥有一个专门为当前任务定制的推理结构。在解决问题的实例时,模型只需遵循这个结构,逐步填充JSON中的值,直到得出最终答案。
总结:选择合适的Agent模式
通过以上9种Agent设计模式的介绍,我们可以看到,不同的模式适用于不同的场景:
- • Few-Shot模式 :适合需要格式化和结构化输出的简单任务
- • ReAct模式 :适合需要交互式探索和工具调用的任务
- • Plan and Solve模式 :适合需要多步骤规划的复杂任务
- • REWOO模式 :适合需要减少计算成本和提高效率的场景
- • LLMCompiler模式 :适合可以并行执行的独立查询任务
- • Basic Reflection模式 :适合需要自我优化和迭代改进的任务
- • Reflexion模式 :适合需要从失败经验中学习的长期任务
- • LATS模式 :适合需要探索多个解决方案路径的复杂问题
- • Self-Discover模式 :适合需要自动发现推理结构的复杂推理问题
在实际应用中,开发者可以根据具体需求选择合适的Agent模式,或者将多种模式组合使用,以达到最佳的效果。随着大模型技术的不断发展,相信未来还会涌现出更多创新的Agent设计模式,让我们拭目以待。
写在最后 :Agent设计模式为大模型应用提供了强大的框架支持,但选择合适的模式并正确实现,需要深入理解每种模式的特点和适用场景。希望本文能帮助你在实际项目中做出更好的选择。
