目录:
用 Copilot 学 Agent ------ AI 编程的新范式
深入认识 Agent ------ 实现你自己的 Agent
前文介绍了 Agent 相关的基本概念和 Copilot Agent 的使用方式,本文将继续深入认识 agent。
本文大部分内容(包括代码)来自于参考资料1,这是一个很优秀的 Agent 学习项目,非常推荐。
Agent Demo
为了更好地理解 Agent,直接从最简单的 demo 开始。下面创建一个 agent demo,名字叫做"旅行助手":
python
import requests
import re
from openai import AzureOpenAI
AGENT_SYSTEM_PROMPT = """
你是一个智能旅行助手。你的任务是分析用户的请求,并使用可用工具一步步地解决问题。
# 可用工具:
- `get_weather(city: str)`: 查询指定城市的实时天气。
# 输出格式要求:
你的每次回复必须严格遵循以下格式,包含一对Thought和Action:
Thought: [你的思考过程和下一步计划]
Action: [你要执行的具体行动]
Action的格式必须是以下之一:
1. 调用工具:function_name(arg_name="arg_value")
2. 结束任务:Finish[最终答案]
# 重要提示:
- 每次只输出一对Thought-Action
- Action必须在同一行,不要换行
- 当收集到足够信息可以回答用户问题时,必须使用 Action: Finish[最终答案] 格式结束
请开始吧!
"""
def get_weather(city: str) -> str:
"""
通过调用 wttr.in API 查询真实的天气信息
"""
url = f"https://wttr.in/{city}?format=j1"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
current_condition = data['current_condition'][0]
weather_desc = current_condition['weatherDesc'][0]['value']
temp_c = current_condition['temp_C']
return f"{city}当前天气: {weather_desc},气温{temp_c}摄氏度"
except requests.exceptions.RequestException as e:
return f"错误:查询天气异常 - {e}"
except (KeyError, IndexError) as e:
return f"错误:解析天气数据失败 - {e}"
class OpenAICompatibleClient:
def __init__(self, model: str, api_key: str, base_url: str, api_version: str):
self.model = model
self.client = AzureOpenAI(api_key=api_key, azure_endpoint=base_url, api_version=api_version)
def generate(self, prompt: str, system_prompt: str) -> str:
print("正在调用大语言模型...")
try:
messages = [
{'role': 'system', 'content': system_prompt},
{'role': 'user', 'content': prompt}
]
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
stream=False
)
answer = response.choices[0].message.content
return answer
except Exception as e:
print(f"调用LLM API时发生错误: {e}")
return "错误:调用语言模型服务时出错"
available_tools = {
"get_weather": get_weather
}
API_KEY = "<YOUR_API_KEY>"
BASE_URL = "<YOUR_AZURE_ENDPOINT>"
API_VERSION = "<YOUR_API_VERSION>"
MODEL_ID = "<AZURE_OPENAI_DEPLOYMENT_ID>"
llm = OpenAICompatibleClient(
model=MODEL_ID,
api_key=API_KEY,
base_url=BASE_URL,
api_version=API_VERSION
)
user_prompt = "你好,请帮我查询一下今天北京的天气,然后根据天气推荐一个合适的旅游景点。"
prompt_history = [f"用户请求: {user_prompt}"]
print(f"用户输入: {user_prompt}\n" + "=" * 40)
for i in range(5):
print(f"--- 循环 {i + 1} ---\n")
full_prompt = "\n".join(prompt_history)
llm_output = llm.generate(full_prompt, system_prompt=AGENT_SYSTEM_PROMPT)
match = re.search(r'(Thought:.*?Action:.*?)(?=\n\s*(?:Thought:|Action:|Observation:)|\Z)', llm_output, re.DOTALL)
if match:
truncated = match.group(1).strip()
if truncated != llm_output.strip():
llm_output = truncated
print("已截断多余的 Thought-Action 对")
print(f"模型输出:\n{llm_output}\n")
prompt_history.append(llm_output)
action_match = re.search(r"Action: (.*)", llm_output, re.DOTALL)
if not action_match:
observation = "错误: 未能解析到 Action 字段。请确保你的回复严格遵循 'Thought: ... Action: ...' 的格式。"
observation_str = f"Observation: {observation}"
print(f"{observation_str}\n" + "=" * 40)
prompt_history.append(observation_str)
continue
action_str = action_match.group(1).strip()
if action_str.startswith("Finish"):
final_answer = re.match(r"Finish\[(.*)]", action_str).group(1)
print(f"任务完成,最终答案: {final_answer}")
break
tool_name = re.search(r"(\w+)\(", action_str).group(1)
args_str = re.search(r"\((.*)\)", action_str).group(1)
kwargs = dict(re.findall(r'(\w+)="([^"]*)"', args_str))
if tool_name in available_tools:
observation = available_tools[tool_name](**kwargs)
else:
observation = f"错误:未定义的工具 '{tool_name}'"
observation_str = f"Observation: {observation}"
print(f"{observation_str}\n" + "=" * 40)
prompt_history.append(observation_str)
运行结果如下:
用户输入: 你好,请帮我查询一下今天北京的天气,然后根据天气推荐一个合适的旅游景点。
========================================
--- 循环 1 ---
正在调用大语言模型...
模型输出:
Thought: [需要先查询北京今天的实时天气,然后根据天气情况推荐一个合适的旅游景点。]
Action: get_weather(city="北京")
Observation: 北京当前天气: Overcast,气温17摄氏度
========================================
--- 循环 2 ---
正在调用大语言模型...
模型输出:
Thought: [北京当前天气为阴天,气温17摄氏度,这种天气适合推荐室内活动的旅游景点。]
Action: Finish[北京今天是阴天,气温17摄氏度,适宜参观室内景点。我推荐您前往故宫博物院,这是北京的著名景点,适合在室内参观,同时也能了解中国历史文化。]
任务完成,最终答案: 北京今天是阴天,气温17摄氏度,适宜参观室内景点。我推荐您前往故宫博物院,这是北京的著名景点,适合在室内参观,同时也能了解中国历史文化。
从上面这个例子可以看出,Agent 是一个以大语言模型为核心决策器,结合工具调用、状态管理和控制循环,实现多步任务执行的系统。
不同架构的智能体
智能体的核心能力在于能将大语言模型的推理能力与外部世界联通,能够自主理解用户意图,拆解复杂任务,并通过调用代码解释器、搜索引擎、API 等一系列工具来获取信息,执行操作从而达成最终目标。
但是智能体也不是万能的,同样面临着来自大模型本身的幻觉问题。在复杂任务中可能陷入推理循环,以及对工具的错误使用等挑战,这些也构成了智能体的能力边界。
为了更好地组织智能体思考和行动的过程,业界涌现了很多经典的架构模型。其中最具有代表性的有三种:
- ReAct(Reasoning And Action):一种将思考和行动紧密结合的范式,让智能体边想边做,动态调整。
- Plan-and-Solve:先计划后行动。
- Reflection:一种赋予智能体反思能力的模式,通过自我批评和修正来优化结果。
ReAct Agent
ReAct 智能体遵循"思考-行动-观察"的循环,思考指导行动,而行动的结果又反过来修正思考。这种智能体适合以下场景:
- 需要外部知识,如查询实时信息、搜索专业领域的知识等
- 需要精确计算的任务,如通过计算器工具来完成精确计算,避免 LLM 的计算错误
- 需要与 API 交互的任务,如操作数据库,调用其他服务的 API 等
接下来将构建一个 ReAct Agent,来回答一个问题------"苹果最新的手机是哪一款?它的主要卖点是什么"。 这是一个实时问题,大语言模型本身是无法回答的,需要联网搜索才行。
先定义搜索工具 tools.py:
python
import os
from serpapi import SerpApiClient
from dotenv import load_dotenv
from typing import Dict, Any
load_dotenv()
def search(query: str) -> str:
"""
一个基于 SerpApi 的实时网页搜索引擎工具,智能解析搜索结果,优先返回直接答案或知识图谱信息
"""
print(f"正在通过 [SerpApi] 进行网页搜索 🔍")
try:
api_key = os.getenv("SERPAPI_KEY")
if not api_key:
return "ERROR: SERPAPI_KEY NOT FOUND"
params = {
"engine": "google",
"q": query,
"api_key": api_key,
"gl": "cn",
"hl": "zh-cn"
}
client = SerpApiClient(params)
results = client.get_dict()
# 搜索结果解析:优先寻找最直接的答案------答案框、知识图谱、前3条搜索结果摘要
if "answer_box_list" in results:
return "\n".join(results["answer_box_list"])
if "answer_box" in results and "answer" in results["answer_box"]:
return results["answer_box"]["answer"]
if "knowledge_graph" in results and "description" in results["knowledge_graph"]:
return results["knowledge_graph"]["description"]
if "organic_results" in results and results["organic_results"]:
snippets = [
f"[{i + 1}] {res.get('title', '')}\n{res.get('snippet', '')}"
for i, res in enumerate(results["organic_results"][:3])
]
return "\n\n".join(snippets)
return f"对不起,没有找到关于 '{query}' 的信息。"
except Exception as e:
return f"搜索时发生错误: {e}"
class ToolExecutor:
"""
工具执行器,负责管理和执行工具
"""
def __init__(self):
self.tools: Dict[str, Dict[str, Any]] = {}
def register_tool(self, name: str, description: str, func: callable):
self.tools[name] = {"description": description, "func": func}
print(f"工具 {name} 已注册")
def get_tool(self, name: str)-> callable:
return self.tools.get(name, {}).get("func")
def get_available_tools(self) -> str:
return "\n".join([
f"- {name}: {info['description']}"
for name, info in self.tools.items()
])
if __name__ == '__main__':
toolExecutor = ToolExecutor()
tool_name = "Search"
tool_description = "google search engine"
toolExecutor.register_tool(tool_name, tool_description, search)
print(f"\n--- available tools ---")
print(toolExecutor.get_available_tools())
user_input = "What's the newest GPU of Nvidia"
print(f"\n--- Action: Search[{user_input}] ---")
tool_function = toolExecutor.get_tool(tool_name)
if tool_function:
observation = tool_function(user_input)
print("--- Observation ---")
print(observation)
else:
print(f"ERROR: {tool_name} NOT FOUND")
接下来封装一个 llm 客户端 llm_client.py:
python
import os
from openai import AzureOpenAI
from dotenv import load_dotenv
from typing import List, Dict
load_dotenv() # 加载环境变量: .env
class HelloAgentsLLM:
def __init__(self, model: str = None, api_key: str = None, base_url: str = None, api_version: str = None):
self.model = model or os.getenv("LLM_MODEL_ID")
api_key = api_key or os.getenv("LLM_API_KEY")
base_url = base_url or os.getenv("LLM_BASE_URL")
api_version = api_version or os.getenv("LLM_API_VERSION")
if not all([self.model, api_key, base_url]):
raise ValueError("llm api parameters not found")
self.client = AzureOpenAI(api_key=api_key, azure_endpoint=base_url, api_version=api_version)
def think(self, messages: List[Dict[str, str]], temperature: float = 0) -> str:
print(f"🧠正在调用 {self.model} 模型...")
collected_content = []
try:
with self.client.chat.completions.stream(
model=self.model,
messages=messages,
temperature=temperature
) as stream:
for event in stream:
if event.type == "content.delta":
content = event.delta
print(content, end="", flush=True) # 实时输出结果
collected_content.append(content)
print() # 结束后换行
return "".join(collected_content)
except Exception as e:
print(f"❌调用LLM API时发生错误: {e}")
return "错误: 调用语言模型服务时出错"
这里将敏感参数信息放到 .env 文件中通过 dotenv 库读取。调用 llm 时通过流式接口同步访问。
最后再创建 ReAct Agent:
python
import re
from tools import ToolExecutor, search
from llm_client import HelloAgentsLLM
REACT_PROMPT_TEMPLATE = """
你是一个有能力调用外部工具的智能助手。
可用工具如下:{tools}
请严格按照以下格式进行回应:
Thought: 你的思考过程,用于分析问题、拆解任务和规划下一步行动。
Action:你决定采取的行动,必须是以下格式之一:
- `{{tool_name}}[{{tool_input}}]`: 调用一个可用工具。
- `Finish[最终答案]`: 当你认为已经获得最终答案时。
- 当你收集足够的信息时,能够回答用户的最终问题时,你必须在 Action:字段后使用 Finish[最终答案] 来输出最终答案。
现在,请开始解决以下问题:
Question: {question}
History: {history}
"""
class ReActAgent:
def __init__(self, llm_client: HelloAgentsLLM, tool_executor: ToolExecutor, max_steps: int = 5):
self.llm_client = llm_client
self.tool_executor = tool_executor
self.max_steps = max_steps
self.history = []
def run(self, question: str):
self.history = []
current_step = 0
while current_step < self.max_steps:
current_step += 1
print(f"\n--- 第 {current_step} 步 ---")
tools_desc = self.tool_executor.get_available_tools()
history_str = "\n".join(self.history)
prompt = REACT_PROMPT_TEMPLATE.format(tools=tools_desc, question=question, history=history_str)
messages = [{"role": "user", "content": prompt}]
response_text = self.llm_client.think(messages=messages)
if not response_text:
print("❌LLM 未能返回有效响应")
break
thought, action = self._parse_output(response_text)
if thought:
print(f"[Thought]: {thought}")
if not action:
print("❌未能解析出有效 Action,流程终止")
break
if action.startswith("Finish"):
final_answer = self._parse_action_input(action)
print(f"🎉最终答案:{final_answer}")
return final_answer
tool_name, tool_input = self._parse_action(action)
if not tool_name or not tool_input:
self.history.append("Observation: 无效的 Action 格式,请检查")
continue
print(f"[Action]: {tool_name}[{tool_input}]")
tool_function = self.tool_executor.get_tool(tool_name)
observation = tool_function(tool_input) if tool_function else f"❌{tool_name} not found"
print(f"[Observation]: {observation}")
self.history.append(f"Action: {action}")
self.history.append(f"Observation: {observation}")
print("max steps")
return None
def _parse_output(self, text: str):
thought_match = re.search(r"Thought:\s*(.*?)(?=\nAction:|$)", text, re.DOTALL)
action_match = re.search(r"Action:\s*(.*?)$", text, re.DOTALL)
thought = thought_match.group(1).strip("`") if thought_match else None
action = action_match.group(1).strip("`") if action_match else None
return thought, action
def _parse_action(self, action_text: str):
match = re.match(r"(\w+)\[(.*)\]", action_text, re.DOTALL)
return (match.group(1), match.group(2)) if match else (None, None)
def _parse_action_input(self, action_text: str):
match = re.match(r"\w+\[(.*)\]", action_text, re.DOTALL)
return match.group(1) if match else ""
if __name__ == '__main__':
llm = HelloAgentsLLM()
tool_executor = ToolExecutor()
tool_name = "Search"
search_desc = "Google search engine"
tool_executor.register_tool(tool_name, search_desc, search)
agent = ReActAgent(llm_client=llm, tool_executor=tool_executor)
question = "苹果最新的手机是哪一款,主要卖点是什么?"
agent.run(question)
运行后输出结果如下:
工具 Search 已注册
--- 第 1 步 ---
🧠正在调用 GPT-4o 模型...
Thought: 我需要查找苹果最新发布的手机型号以及其主要卖点。为此,我需要进行一次搜索以获取最新信息。
Action: `Search[苹果最新手机型号及主要卖点]`
[Thought]: 我需要查找苹果最新发布的手机型号以及其主要卖点。为此,我需要进行一次搜索以获取最新信息。
[Action]: Search[苹果最新手机型号及主要卖点]
正在通过 [SerpApi] 进行网页搜索 🔍
[Observation]: [1] 一图看懂苹果2025秋季新品发布会,八大新品卖点价格全汇总
本次发布会上,苹果带来了iPhone17、iPhone 17 Pro/Pro Max以及全新系列iPhone Air,还有AirPods Pro 3、Apple Watch Series 11、Apple Watch SE 3以及Apple ...
[2] 2026年苹果iPhone手机推荐哪一款性价比最高?(8000字选 ...
最新的iPhone共有iPhone 17、iPhone Air、iPhone 17 Pro以及iPhone 17 Pro Max四款机型。其中,属于基本款的iPhone 17配备6.3英寸120Hz高刷显示屏和A19芯片,相对平价且已 ...
[3] iPhone - 机型比较
比较各款iPhone 机型的功能和技术规格,包括iPhone 17 Pro、iPhone 17 Pro Max、iPhone Air、iPhone 17、iPhone 17e 以及更多机型。
--- 第 2 步 ---
🧠正在调用 GPT-4o 模型...
Thought: 根据搜索结果,苹果最新的手机是iPhone 17系列,包括iPhone 17、iPhone 17 Pro、iPhone 17 Pro Max以及全新系列iPhone Air。我需要总结这些机型的主要卖点。
Action: Finish[苹果最新的手机是iPhone 17系列,包括iPhone 17、iPhone 17 Pro、iPhone 17 Pro Max以及全新系列iPhone Air。主要卖点包括:iPhone 17配备6.3英寸120Hz高刷显示屏和A19芯片;Pro系列可能具备更高端的功能和配置,如更强的摄像头系统和更高性能的芯片。]
[Thought]: 根据搜索结果,苹果最新的手机是iPhone 17系列,包括iPhone 17、iPhone 17 Pro、iPhone 17 Pro Max以及全新系列iPhone Air。我需要总结这些机型的主要卖点。
🎉最终答案:苹果最新的手机是iPhone 17系列,包括iPhone 17、iPhone 17 Pro、iPhone 17 Pro Max以及全新系列iPhone Air。主要卖点包括:iPhone 17配备6.3英寸120Hz高刷显示屏和A19芯片;Pro系列可能具备更高端的功能和配置,如更强的摄像头系统和更高性能的芯片。
ReAct 模式的特点和局限性
ReAct 模式有如下特点:
- 可解释性高,用户可以通过"思考-行动-观测"链清晰地看出智能体每一步的思考和行动。这对于理解和调试智能体的行为至关重要。
- 具有动态规划和纠错能力,它根据每一步的结果来动态地调整后续的思考和行动,如果上一步的结果不理想,它可以在下一步尝试修正。
- 工具协同能力强,ReAct 模式天然地将大语言模型的推理能力和外部工具的执行能力结合在一起协同工作,突破了大语言模型本身的能力限制。
虽然 ReAct Agent 有诸多优点,但也有其局限性:
- 强依赖于 LLM 自身能力,ReAct 流程的成功与否高度依赖于底层 LLM 的综合能力。如果 LLM 的逻辑推理能力、指令遵循能力或格式化输出能力不足,就很容易在思考环境产生错误的规划或者在行动环节生成不符合格式的指令。
- 执行效率问题,由于其循序渐进的特性,完成一个任务通常需要多次调用 LLM,每次调用都伴随着网络延迟和计算成本,这对于复杂任务来说,会产生巨量的时间和金钱成本。
- 提示词的脆弱性,整个机制的稳定运行建立在一个精心设计的提示词模板之上,任何微小的改动都可能影响 LLM 的行为。
- 可能陷于局部最优,步进式决策意味着智能体缺乏一个全局的、长远的规划。它可能会因为眼前的
Observation而选择一个看似正确但从长远来看并非最优的路径,甚至在某些情况下陷入"原地打转"的循环中。
Plan-and-Solve
上文介绍了步进决策的 ReAct Agent,接下来继续介绍 Plan-and-Solve Agent,这种 Agent 将任务处理明确地分为两个阶段:先规划后执行。
Plan-and-Solve 适用于结构性强、可以被清晰分解的复杂任务。例如:
- 多步数学应用题,需要先列出计算步骤再逐一求解。
- 需要整合多个信息源的报告撰写。
- 代码生成任务,需要先构思好模块、类和函数的结构再实现。
下面是代码实现,其中 llm_client 复用上文封装好的。
python
import ast
from llm_client import HelloAgentsLLM
from dotenv import load_dotenv
load_dotenv()
PLANNER_PROMPT_TEMPLATE = """
你是一个顶级的AI规划专家。你的任务是将用户提出的复杂问题分解成一个由多个简单步骤组成的行动计划。
请确保计划中的每个步骤都是一个独立的、可执行的子任务,并且严格按照逻辑顺序排列。
你的输出必须是一个Python列表,其中每个元素都是一个描述子任务的字符串。
问题: {question}
请严格按照以下格式输出你的计划,```python与```作为前后缀是必要的:
```python
["步骤1", "步骤2", "步骤3", ...]
```
"""
class Planner:
def __init__(self, llm_client: HelloAgentsLLM):
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"✅ 计划已生成")
try:
plan_str = response_text.split("```python")[1].split("```")[0].strip()
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 []
EXECUTOR_PROMPT_TEMPLATE = """
你是一位顶级的AI执行专家。你的任务是严格按照给定的计划,一步步地解决问题。
你将收到原始问题、完整的计划、以及到目前为止已经完成的步骤和结果。
请你专注于解决"当前步骤",并仅输出该步骤的最终答案,不要输出任何额外的解释或对话。
# 原始问题:
{question}
# 完整计划:
{plan}
# 历史步骤与结果:
{history}
# 当前步骤:
{current_step}
请仅输出针对"当前步骤"的回答:
"""
class Executor:
def __init__(self, llm_client: HelloAgentsLLM):
self.llm_client = llm_client
def execute(self, question: str, plan: list[str]) -> str:
history = ""
final_answer = ""
print("\n--- 正在执行计划 ---")
for i, step in enumerate(plan, 1):
print(f"\n-> 正在执行步骤 {i}/{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}: {step}\n结果: {response_text}\n\n"
final_answer = response_text
print(f"✅ 步骤 {i} 已完成,结果: {final_answer}")
return final_answer
class PlanAndSolveAgent:
def __init__(self, llm_client: HelloAgentsLLM):
self.llm_client = llm_client
self.planner = Planner(self.llm_client)
self.executor = Executor(self.llm_client)
def run(self, question: str):
print(f"\n--- 开始处理问题 ---\n问题:{question}")
plan = self.planner.plan(question)
if not plan:
print("\n--- 任务终止,无法生成有效的执行计划 ---\n")
return
final_answer = self.executor.execute(question, plan)
print(f"\n--- 任务完成 ---\n最终答案: {final_answer}")
if __name__ == '__main__':
try:
llm_client = HelloAgentsLLM()
agent = PlanAndSolveAgent(llm_client)
question = "一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果?"
agent.run(question)
except ValueError as e:
print(e)
运行结果如下:
--- 开始处理问题 ---
问题:一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果?
--- 正在生成计划 ---
🧠正在调用 GPT-4o 模型...
```python
[
"步骤1: 确定周一卖出的苹果数量为15个。",
"步骤2: 计算周二卖出的苹果数量,等于周一的两倍,即15 * 2。",
"步骤3: 计算周三卖出的苹果数量,等于周二的数量减去5个。",
"步骤4: 将周一、周二和周三卖出的苹果数量相加,得到三天总共卖出的苹果数量。",
"步骤5: 输出三天总共卖出的苹果数量。"
]
```
✅ 计划已生成
--- 正在执行计划 ---
-> 正在执行步骤 1/5: 步骤1: 确定周一卖出的苹果数量为15个。
🧠正在调用 GPT-4o 模型...
15
✅ 步骤 1 已完成,结果: 15
-> 正在执行步骤 2/5: 步骤2: 计算周二卖出的苹果数量,等于周一的两倍,即15 * 2。
🧠正在调用 GPT-4o 模型...
30
✅ 步骤 2 已完成,结果: 30
-> 正在执行步骤 3/5: 步骤3: 计算周三卖出的苹果数量,等于周二的数量减去5个。
🧠正在调用 GPT-4o 模型...
25
✅ 步骤 3 已完成,结果: 25
-> 正在执行步骤 4/5: 步骤4: 将周一、周二和周三卖出的苹果数量相加,得到三天总共卖出的苹果数量。
🧠正在调用 GPT-4o 模型...
70
✅ 步骤 4 已完成,结果: 70
-> 正在执行步骤 5/5: 步骤5: 输出三天总共卖出的苹果数量。
🧠正在调用 GPT-4o 模型...
70
✅ 步骤 5 已完成,结果: 70
--- 任务完成 ---
最终答案: 70
从这个例子可以看出,Plan-and-Solve 模式的 Agent 的工作流程:
- 规划阶段,先通过
Planner将复杂的应用分解成多个子步骤。 - 执行阶段,
Executor根据生成的计划,按顺序执行子步骤。在每一步中都将历史结果作为上下文,确保了信息的正确传递。
Reflection
上文实现的 Agent 中,不管是 ReAct 的还是 Plan-and-Solve 的,智能体一旦完成了任务,其工作流程便已结束。但是由于 LLM 本身的特性,其输出的内容,无论是行动轨迹还是最终结果,都可能存在谬误之处。因此业界还提出了 Reflection 模式。
Reflection Agent 引入了自我矫正循环,在每轮结果输出之后可以自我校验,发现不足并进行迭代优化。其重做流程可以概括为: 执行 -> 反思 -> 优化:
- Execution:执行阶段可使用 ReAct 或 Plan-and-Solve 的方式尝试完成任务,可以看作是初稿。
- Reflection:反思阶段会调用一个独立的、带有特殊提示词的大语言模型来扮演一个"评审员"的角色来评估上一步得到的初稿,评估维度包括多方面,如事实性错误检查、逻辑漏洞、效率问题、遗漏信息等。根据评估结果会生成一个结构化的反馈,指出具体问题和改进建议。
- Refinement:优化阶段会将第一步的初稿和第二步的反馈作为新的上下文再次调用大语言模型,生成一个更完善的结果。
事实上整个流程是一个循环,而且这个循环可以重复进行多次,直到反思阶段不再发现新的问题,或者达到预设的迭代次数上限。
假设现在有一个任务:编写一个 Python 函数,找出 1 到 n 之间所有的素数。这个任务非常适合使用 Reflection 模式:
- 存在明确的优化路径:大语言模型初次生成的代码可能是一个简单但效率低下的递归实现。
- 反思点清晰:可以通过反思发现其"时间复杂度过高"或"存在重复计算"的问题。
- 优化方向明确:可以根据反馈将其优化为更高效的迭代版本。
Reflection 模式核心在于迭代,而迭代的前提是能够记住之前的尝试和获得的反馈,所以一个记忆模块是必须的:
python
class Memory:
"""
记忆模块,用于存储智能体的行动和反思轨迹
"""
def __init__(self):
self.records: List[Dict[str, Any]] = []
def add_record(self, record_type: str, content: str):
"""
:param record_type: 记录类型,execution 或 reflection
:param content: 记录的内容,执行的结果或反思的反馈
"""
self.records.append({"type": record_type, "content": content})
print(f"🗒️记忆已更新,新增一条 {record_type} 记录")
def get_last_execution(self):
for record in reversed(self.records):
if record['type'] == 'execution':
return record['content']
return None
继续构建 ReflectionAgent 并调用:
python
# 初始执行提示词
INITIAL_PROMPT_TEMPLATE = """
你是一位资深的Python程序员。请根据以下要求,编写一个Python函数。
你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
要求: {task}
请直接输出代码,不要包含任何额外的解释。
"""
# 反思提示词
REFLECT_PROMPT_TEMPLATE = """
你是一位极其严格的代码评审专家和资深算法工程师,对代码的性能有极致的要求。
你的任务是审查以下Python代码,并专注于找出其在**算法效率**上的主要瓶颈。
# 原始任务:
{task}
# 待审查的代码:
```python
{code}
```
请分析该代码的时间复杂度,并思考是否存在一种**算法上更优**的解决方案来显著提升性能。
如果存在,请清晰地指出当前算法的不足,并提出具体的、可行的改进算法建议。
如果代码在算法层面已经达到最优,才能回答"无需改进"。
请直接输出你的反馈,不要包含任何额外的解释。
"""
# 优化提示词
REFINE_PROMPT_TEMPLATE = """
你是一位资深的Python程序员。你正在根据一位代码评审专家的反馈来优化你的代码。
# 原始任务:
{task}
# 你上一轮尝试的代码:
{last_code_attempt}
# 评审员的反馈:
{feedback}
请根据评审员的反馈,生成一个优化后的新版本代码。
你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
请直接输出优化后的代码,不要包含任何额外的解释。
"""
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}")
initial_prompt = INITIAL_PROMPT_TEMPLATE.format(task=task)
initial_code = self._get_llm_response(initial_prompt)
self.memory.add_record("execution", initial_code)
# 迭代循环:反思与优化
for i in range(self.max_iterations):
print(f"--- 第 {i + 1}/{self.max_iterations} 轮迭代 ---")
print("-> 正在进行反思...")
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)
if "无需改进" in feedback or "no need for improvement" in feedback.lower():
print("\n✅ 反思认为代码已无需改进,任务完成。")
break
print("-> 正在进行优化...")
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{final_code}")
return final_code
def _get_llm_response(self, prompt: str) -> str:
messages = [{"role": "user", "content": prompt}]
response_text = self.llm_client.think(messages=messages) or ""
return response_text
if __name__ == '__main__':
llm_client = HelloAgentsLLM()
agent = ReflectionAgent(llm_client, max_iterations=3)
task = "编写一个Python函数,找出1到n之间所有的素数"
agent.run(task)
运行结果如下:
python
--- 开始处理任务 ---
任务: 编写一个Python函数,找出1到n之间所有的素数
🧠正在调用 GPT-4o 模型...
```python
from typing import List
def find_primes(n: int) -> List[int]:
... # 这里是大模型的输出结果,省略
```
🗒️记忆已更新,新增一条 execution 记录
--- 第 1/3 轮迭代 ---
-> 正在进行反思...
🧠正在调用 GPT-4o 模型...
### 审查反馈:
#### 时间复杂度分析:
1. 外层循环 (`for num in range(2, n + 1)`) 遍历从 2 到 n 的所有数字,执行了 `O(n)` 次。
2. 内层循环 (`for i in range(2, int(num**0.5) + 1)`) 对每个数字 `num` 检查其因子,最坏情况下执行 `O(√n)` 次。
3. 综合时间复杂度为 **O(n√n)**。
#### 当前算法的主要瓶颈:
1. **重复性检查**:每次检查一个数字是否为素数时,都重新计算其因子范围,导致大量重复计算。
2. **非必要的逐个检查**:算法没有利用已知素数的性质(如筛选法)来减少不必要的计算。
#### 改进建议:
使用 **埃拉托色尼筛法**,它是一种更高效的算法,时间复杂度为 **O(n log log n)**,显著优于当前的 **O(n√n)**。
#### 改进后的代码:
```python
def find_primes(n: int) -> List[int]:
... # 这里省略了大模型实际输出的代码
```
#### 改进后的优势:
1. **减少重复计算**:通过标记非素数,避免重复检查每个数字的因子。
2. **更高效的时间复杂度**:从 **O(n√n)** 降至 **O(n log log n)**,适合处理更大的输入范围。
#### 结论:
当前代码存在显著的性能瓶颈,建议改用埃拉托色尼筛法以提升算法效率。
🗒️记忆已更新,新增一条 reflection 记录
-> 正在进行优化...
🧠正在调用 GPT-4o 模型...
```python
from typing import List
def find_primes(n: int) -> List[int]:
...
```
🗒️记忆已更新,新增一条 execution 记录
--- 第 2/3 轮迭代 ---
-> 正在进行反思...
🧠正在调用 GPT-4o 模型...
### 审查反馈:
#### 时间复杂度分析:
- 当前代码实现的是**埃拉托色尼筛法 (Sieve of Eratosthenes)**,其时间复杂度为 **O(n log log n)**。
- 这是一个经典且高效的算法,用于生成范围内的所有素数。
- 内层循环的步长为 `i`,因此每个合数只会被标记一次,保证了整体复杂度的高效性。
#### 算法效率瓶颈:
1. **内存使用**:
...
2. **偶数优化**:
...
3. **缓存局部性**:
- 当前算法在标记合数时,访问内存的模式可能导致较差的缓存局部性,尤其是对于大范围的 `n`。
#### 改进建议:
1. **内存优化**:
...
2. **偶数优化**:
...
3. **缓存优化**:
...
#### 改进后的代码示例(分块埃拉托色尼筛法):
```python
from typing import List
import math
def find_primes_segmented(n: int) -> List[int]:
...
```
#### 改进后的优点:
1. **内存使用显著减少**:
- 分块处理避免了分配整个范围的布尔数组。
2. **性能提升**:
- 跳过偶数,减少不必要的计算。
- 分块处理提高了缓存局部性,减少了内存访问开销。
#### 结论:
- 当前代码在算法层面已经接近最优,时间复杂度为 **O(n log log n)**,无法进一步降低。
- 但对于超大范围的 `n`,可以通过 **分块埃拉托色尼筛法** 和 **偶数优化** 来改进内存使用和实际运行性能。
🗒️记忆已更新,新增一条 reflection 记录
-> 正在进行优化...
🧠正在调用 GPT-4o 模型...
```python
from typing import List
import math
def find_primes(n: int) -> List[int]:
...
```
🗒️记忆已更新,新增一条 execution 记录
--- 第 3/3 轮迭代 ---
-> 正在进行反思...
🧠正在调用 GPT-4o 模型...
### 审查反馈
#### 时间复杂度分析
1. **Step 1: Simple Sieve (up to √n)**
2. **Step 2: Segmented Sieve (from √n to n)**
#### 算法效率瓶颈
1. **分段大小的固定性**:
...
2. **重复计算起始点**:
...
3. **未充分利用并行化**:
...
#### 改进建议
1. **动态调整分段大小**:
...
2. **优化起始点计算**:
...
3. **并行化分段筛法**:
...
4. **更高效的分段筛法实现**:
...
#### 改进后的时间复杂度
- 动态分段和并行化后,理论上时间复杂度仍为 `O(n log log n)`,但实际运行时间会显著降低。
- 使用 Wheel Factorization 后,常数因子进一步减少,尤其对大范围的素数筛选更为高效。
#### 结论
当前代码在算法层面接近最优,但存在实现上的改进空间。通过动态分段、起始点优化、并行化和 Wheel Factorization,可以显著提升性能。
🗒️记忆已更新,新增一条 reflection 记录
-> 正在进行优化...
🧠正在调用 GPT-4o 模型...
```python
from typing import List
import math
from concurrent.futures import ThreadPoolExecutor
def find_primes(n: int) -> List[int]:
...
```
🗒️记忆已更新,新增一条 execution 记录
--- 任务完成 ---
最终生成的代码:
```python
from typing import List
import math
from concurrent.futures import ThreadPoolExecutor
def find_primes(n: int) -> List[int]:
...
```
Reflection 模式的优点和缺陷
Reflection 模式在提升任务解决质量上表现出色,能够通过多轮迭代,将一个初始可能刚刚合格的方案优化成一个优秀的方案。通过内部的自我纠错循环,智能体能够发现并修复初始方案中可能存在的逻辑漏洞、事实性错误或边界情况处理不当等问题,从而大大提高了最终结果的可靠性。
但这种能力的获得也是有代价的:
- 成本高昂,每进行一轮迭代,至少需要额外调用两次大语言模型,一次用于反思,一次用于优化。在迭代多轮的情况下,大模型调用成本剧增。
- 任务延迟显著提高,多次迭代使得任务的总耗时显著增长,不适合对实时性要求较高的场景。
- 提示工程复杂度上升,执行、反思、优化等不同阶段的提示词都是不同的,设计高质量的提示词需要投入更多的时间和精力。
Reflection 模式适合对最终结果的可靠性、准确性有极高要求,同时对任务完成的实时性要求相对宽松的场景。
参考资料
1\]. https://github.com/datawhalechina/hello-agents