第四章-智能体经典范式构建-cnblog
第四章 智能体经典范式构建
React范式
核心是Thought-Action-Observation,执行流程为\((th_i,a_i)=\pi((a_{i-1},o_{i-1}),(a_{i-2},o_{i-2})\dots)\),第\(i\)步的观察\(o_i=T(a_i)\),然后又将\((a_i,o_i)\)加入记录。
我们要设计一个React的智能体,需要以下几个组件:
- 写一个prompt,用于指示智能体在自己无法解决问题或者需要借助外部工具时,可以调用tools,同时要求输出格式,必须有Thought和Action;
- LLM输出解析器,我们需要解析出Thought和Action的参数,去获取工具;
- 一个工具执行器ToolExecuter,负责管理和执行工具,对外提供接口用来注册工具、修改工具等;
- 一个大模型LLM;
- 上下文和最大步数(用来控制LLM不会无限消耗资源);
React提示词模板
# ReAct 提示词模板
REACT_PROMPT_TEMPLATE = """
请注意,你是一个有能力调用外部工具的智能助手。
可用工具如下:
{tools}
请严格按照以下格式进行回应:
Thought: 你的思考过程,用于分析问题、拆解任务和规划下一步行动。
Action: 你决定采取的行动,必须是以下格式之一:
- `{{tool_name}}[{{tool_input}}]`:调用一个可用工具。
- `Finish[最终答案]`:当你认为已经获得最终答案时。
- 当你收集到足够的信息,能够回答用户的最终问题时,你必须在Action:字段后使用 Finish[最终答案] 来输出最终答案。
现在,请开始解决以下问题:
Question: {question}
History: {history}
"""
执行流程:
- 注册工具;
- 执行run方法;
- 初始化prompt:询问和工具以及历史上下文;
- 循环执行,在一轮中,首先调用LLM得到一个回答,解析出Thought和Action,如果Action以Finish开头,那么结束循环,否则从工具执行器中拿到工具,将参数传入得到观察,将Action和Observation加入上下文中,如果超出轮数,那么退出;
- 得到结果;
优点:
- 适合于一些实时信息、需要调用外部工具的场景;
- 高可解释性,因为有心路历程;
- 动态规划与探索能力,因为这个是行动和结果互相影响的过程,所以在探索未知时非常适合;
缺点:
- 对LLM自身能力的强依赖,React流程成功与否,高度依赖LLM的综合能力;
- 可能陷入局部最优,这是很容易想到的,这个是探索每一个方向,就像走迷宫,如果开始没有很好的规划或者回退,那么很有可能陷入局部最优;
Plan and Solve范式
这是先做规划,做任务清单,之后执行清单中的每一个任务。
形式化表达
假设我们的最终目标是q,首先通过Planer执行器将任务拆分,得到\(P=\pi_{plan}(q)=(p_1,p_2,p_3,\dots,p_n)\),随后模型会逐一执行,对于第一\(i\)个步骤,解决方案\(s_i\)的生成会同时依赖原始问题\(q\),完整计划\(P\)以及之前的所有步骤的执行结果\((s_1,s_2,\dots s_{i-1})\),即
最终答案就是\(s_n\)。
要构建这种范式的智能体,我们需要很多组件和提示词。
Planner组件和提示词
PLANNER_PROMPT_TEMPLATE = """
你是一个顶级的AI规划专家。你的任务是将用户提出的复杂问题分解成一个由多个简单步骤组成的行动计划。
请确保计划中的每个步骤都是一个独立的、可执行的子任务,并且严格按照逻辑顺序排列。
你的输出必须是一个Python列表,其中每个元素都是一个描述子任务的字符串。
问题: {question}
请严格按照以下格式输出你的计划,```python与```作为前后缀是必要的:
```python
["步骤1", "步骤2", "步骤3", ...]
```
"""
# 假定 llm_client.py 中的 HelloAgentsLLM 类已经定义好
# from llm_client import HelloAgentsLLM
class Planner:
def __init__(self, llm_client):
self.llm_client = llm_client
def plan(self, question: str) -> list[str]:
"""
根据用户问题生成一个行动计划。
"""
prompt = PLANNER_PROMPT_TEMPLATE.format(question=question)
# 为了生成计划,我们构建一个简单的消息列表
messages = [{"role": "user", "content": prompt}]
print("--- 正在生成计划 ---")
# 使用流式输出来获取完整的计划
response_text = self.llm_client.think(messages=messages) or ""
print(f"✅ 计划已生成:\n{response_text}")
# 解析LLM输出的列表字符串
try:
# 找到```python和```之间的内容
plan_str = response_text.split("```python")[1].split("```")[0].strip()
# 使用ast.literal_eval来安全地执行字符串,将其转换为Python列表
plan = ast.literal_eval(plan_str)
return plan if isinstance(plan, list) else []
except (ValueError, SyntaxError, IndexError) as e:
print(f"❌ 解析计划时出错: {e}")
print(f"原始响应: {response_text}")
return []
except Exception as e:
print(f"❌ 解析计划时发生未知错误: {e}")
return []
执行器和提示词/状态管理
执行器就是执行每一个子任务的,会得到一个"第几步:结果"的字符串,我们需要将每一步的结果存进history中,进行一个状态管理。
EXECUTOR_PROMPT_TEMPLATE = """
你是一位顶级的AI执行专家。你的任务是严格按照给定的计划,一步步地解决问题。
你将收到原始问题、完整的计划、以及到目前为止已经完成的步骤和结果。
请你专注于解决“当前步骤”,并仅输出该步骤的最终答案,不要输出任何额外的解释或对话。
# 原始问题:
{question}
# 完整计划:
{plan}
# 历史步骤与结果:
{history}
# 当前步骤:
{current_step}
请仅输出针对“当前步骤”的回答:
"""
class Executor:
def __init__(self, llm_client):
self.llm_client = llm_client
def execute(self, question: str, plan: list[str]) -> str:
"""
根据计划,逐步执行并解决问题。
"""
history = "" # 用于存储历史步骤和结果的字符串
print("\n--- 正在执行计划 ---")
for i, step in enumerate(plan):
print(f"\n-> 正在执行步骤 {i+1}/{len(plan)}: {step}")
prompt = EXECUTOR_PROMPT_TEMPLATE.format(
question=question,
plan=plan,
history=history if history else "无", # 如果是第一步,则历史为空
current_step=step
)
messages = [{"role": "user", "content": prompt}]
response_text = self.llm_client.think(messages=messages) or ""
# 更新历史记录,为下一步做准备
history += f"步骤 {i+1}: {step}\n结果: {response_text}\n\n"
print(f"✅ 步骤 {i+1} 已完成,结果: {response_text}")
# 循环结束后,最后一步的响应就是最终答案
final_answer = response_text
return final_answer
优点:非常适合复杂任务,尤其是构建项目、数学推理等。
Reflection范式
这个范式主要是三步:执行、反思和优化。
提供了一个内部纠错思路,不同于React的Observation。
记忆模块设计
为了在实战中体现 Reflection 机制,我们将引入记忆管理机制,因为reflection通常对应着信息的存储和提取,如果上下文足够长的情况,想让“评审员”直接获取所有的信息然后进行反思往往会传入很多冗余信息。
from typing import List, Dict, Any, Optional
class Memory:
"""
一个简单的短期记忆模块,用于存储智能体的行动与反思轨迹。
"""
def __init__(self):
"""
初始化一个空列表来存储所有记录。
"""
self.records: List[Dict[str, Any]] = []
def add_record(self, record_type: str, content: str):
"""
向记忆中添加一条新记录。
参数:
- record_type (str): 记录的类型 ('execution' 或 'reflection')。
- content (str): 记录的具体内容 (例如,生成的代码或反思的反馈)。
"""
record = {"type": record_type, "content": content}
self.records.append(record)
print(f"📝 记忆已更新,新增一条 '{record_type}' 记录。")
def get_trajectory(self) -> str:
"""
将所有记忆记录格式化为一个连贯的字符串文本,用于构建提示词。
"""
trajectory_parts = []
for record in self.records:
if record['type'] == 'execution':
trajectory_parts.append(f"--- 上一轮尝试 (代码) ---\n{record['content']}")
elif record['type'] == 'reflection':
trajectory_parts.append(f"--- 评审员反馈 ---\n{record['content']}")
return "\n\n".join(trajectory_parts)
def get_last_execution(self) -> Optional[str]:
"""
获取最近一次的执行结果 (例如,最新生成的代码)。
如果不存在,则返回 None。
"""
for record in reversed(self.records):
if record['type'] == 'execution':
return record['content']
return None
我们先定一个初始任务,其实这种Reflection范式的智能体在面对到算法竞赛中的一些题时,相比于前面两种有非常大的优势,因为时刻有一个评审员对回答进行评审,找不足和可优化的点,并提出优化思路。Codeforce上面中的一些题就是,让大模型多迭代反思几次,就基本可以正确了。
这里就给一个找1到n以内的素数吧。从学习的角度来讲,我们知道下面几种方法:
- 遍历1到n,一次判断数i是不是质数,这个可以通过枚举2到i-1,或者\(\sqrt{i}\);
- 使用埃氏筛;
- 使用线性筛;
- 对于实在较大的数n,我们考虑使用带概率的判定方法;
...
使用这个Reflection的方式很大程度上和人类优化的方式是一样的。
构建一个Reflection范式的智能体
初始化提示词
INITIAL_PROMPT_TEMPLATE = """
你是一位资深的Python程序员。请根据以下要求,编写一个Python函数。
你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
要求: {task}
请直接输出代码,不要包含任何额外的解释。
"""
反思提示词
REFLECT_PROMPT_TEMPLATE = """
你是一位极其严格的代码评审专家和资深算法工程师,对代码的性能有极致的要求。
你的任务是审查以下Python代码,并专注于找出其在<strong>算法效率</strong>上的主要瓶颈。
# 原始任务:
{task}
# 待审查的代码:
```python
{code}
```
请分析该代码的时间复杂度,并思考是否存在一种<strong>算法上更优</strong>的解决方案来显著提升性能。
如果存在,请清晰地指出当前算法的不足,并提出具体的、可行的改进算法建议(例如,使用筛法替代试除法)。
如果代码在算法层面已经达到最优,才能回答“无需改进”。
请直接输出你的反馈,不要包含任何额外的解释。
"""
优化提示词
REFINE_PROMPT_TEMPLATE = """
你是一位资深的Python程序员。你正在根据一位代码评审专家的反馈来优化你的代码。
# 原始任务:
{task}
# 你上一轮尝试的代码:
{last_code_attempt}
评审员的反馈:
{feedback}
请根据评审员的反馈,生成一个优化后的新版本代码。
你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
请直接输出优化后的代码,不要包含任何额外的解释。
"""
智能体
# 假设 llm_client.py 和 memory.py 已定义
# from llm_client import HelloAgentsLLM
# from memory import Memory
class ReflectionAgent:
def __init__(self, llm_client, max_iterations=3):
self.llm_client = llm_client
self.memory = Memory()
self.max_iterations = max_iterations
def run(self, task: str):
print(f"\n--- 开始处理任务 ---\n任务: {task}")
# --- 1. 初始执行 ---
print("\n--- 正在进行初始尝试 ---")
initial_prompt = INITIAL_PROMPT_TEMPLATE.format(task=task)
initial_code = self._get_llm_response(initial_prompt)
self.memory.add_record("execution", initial_code)
# --- 2. 迭代循环:反思与优化 ---
for i in range(self.max_iterations):
print(f"\n--- 第 {i+1}/{self.max_iterations} 轮迭代 ---")
# a. 反思
print("\n-> 正在进行反思...")
last_code = self.memory.get_last_execution()
reflect_prompt = REFLECT_PROMPT_TEMPLATE.format(task=task, code=last_code)
feedback = self._get_llm_response(reflect_prompt)
self.memory.add_record("reflection", feedback)
# b. 检查是否需要停止
if "无需改进" in feedback:
print("\n✅ 反思认为代码已无需改进,任务完成。")
break
# c. 优化
print("\n-> 正在进行优化...")
refine_prompt = REFINE_PROMPT_TEMPLATE.format(
task=task,
last_code_attempt=last_code,
feedback=feedback
)
refined_code = self._get_llm_response(refine_prompt)
self.memory.add_record("execution", refined_code)
final_code = self.memory.get_last_execution()
print(f"\n--- 任务完成 ---\n最终生成的代码:\n```python\n{final_code}\n```")
return final_code
def _get_llm_response(self, prompt: str) -> str:
"""一个辅助方法,用于调用LLM并获取完整的流式响应。"""
messages = [{"role": "user", "content": prompt}]
response_text = self.llm_client.think(messages=messages) or ""
return response_text
执行步骤
- 初始化第一次的方案;
- 迭代循环反思和优化,直到达到迭代轮数上限或者评审家认为已经非常优了;

浙公网安备 33010602011771号