Agent经典范式构建

从 LLM 到 Agent 的跨越 #

大语言模型(LLM)本身具备强大的推理能力,但它存在两个致命缺陷:一是幻觉(一本正经地胡说八道),二是被动(无法主动获取外部实时信息)。 Agent(智能体)的出现正是为了解决这些问题。如果在 LLM 的“大脑”之外,为其配备感知工具(Tools)、长期记忆(Memory)和规划能力(Planning),它就能从一个被动的问答机器进化为主动解决问题的智能实体。为了真正掌握 Agent 的运作机制,接下来的教程将剥离所有复杂的第三方框架(如 LangChain 或 CrewAI),回归本质。我们将仅使用 OpenAI 的官方 SDK 和基础的 Python 环境。这种“去黑盒化”的实现方式,虽然看似原始,但能让你最直观地理解 Agent 是如何思考、决策并执行的。

构建“大脑”接口 (Foundation) #

万丈高楼平地起,在赋予 Agent 使用工具和规划的能力之前,我们需要先封装一个稳定、统一的接口来与 LLM 进行交互。这个基础类将作为我们所有后续 Agent 实例的思考引擎,当前使用的API是基于deepseek提供的。

import os
from openai import OpenAI
from dotenv import load_dotenv
from typing import List, Dict

# 加载 .env 文件中的环境变量
load_dotenv()


class HelloAgentsLLM:

    def __init__(self, model: str = None, api_key: str = None, base_url: str = None, timeout: int = None):
        """
        初始化客户端。优先使用传入参数,如果未提供,则从环境变量加载。
        """
        self.model = model or os.getenv("AI_MODEL_NAME")
        api_key = api_key or os.getenv("AI_API_KEY")
        base_url = base_url or os.getenv("AI_API_BASE_URL")
        timeout = timeout or int(os.getenv("AI_LLM_TIMEOUT", 60))

        if not all([self.model, api_key, base_url]):
            raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")

        self.client = OpenAI(api_key=api_key, base_url=base_url, timeout=timeout)

    def think(self, messages: List[Dict[str, str]], temperature: float = 0) -> str:
        """
        调用大语言模型进行思考,并返回其响应。
        """
        print(f"🧠 正在调用 {self.model} 模型...")
        try:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                temperature=temperature,
                stream=True,
            )

            # 处理流式响应
            print("✅ 大语言模型响应成功:")
            collected_content = []
            for chunk in response:
                content = chunk.choices[0].delta.content or ""
                print(content, end="", flush=True)
                collected_content.append(content)
            print()  # 在流式输出结束后换行
            return "".join(collected_content)

        except Exception as e:
            print(f"❌ 调用LLM API时发生错误: {e}")
            return None


# --- 客户端使用示例 ---
if __name__ == '__main__':
    try:
        llmClient = HelloAgentsLLM()

        exampleMessages = [
            {"role": "system", "content": "You are a helpful assistant that writes Python code."},
            {"role": "user", "content": "写一个快速排序算法"}
        ]

        print("--- 调用LLM ---")
        responseText = llmClient.think(exampleMessages)
        if responseText:
            print("\n\n--- 完整模型响应 ---")
            print(responseText)

    except ValueError as e:
        print(e)

核心范式拆解 #

协同行动:ReAct (Reasoning + Acting) #

ReAct由Shunyu Yao于2022年提出,其核心思想是模仿人类解决问题的方式,将推理 (Reasoning) 与行动 (Acting) 显式地结合起来,形成一个“思考-行动-观察”的循环。

工作流程 #

在ReAct诞生之前,主流的方法可以分为两类:一类是“纯思考”型,如思维链 (Chain-of-Thought),它能引导模型进行复杂的逻辑推理,但无法与外部世界交互,容易产生事实幻觉;另一类是“纯行动”型,模型直接输出要执行的动作,但缺乏规划和纠错能力。

ReAct的巧妙之处在于,它认识到思考与行动是相辅相成的。思考指导行动,而行动的结果又反过来修正思考。为此,ReAct范式通过一种特殊的提示工程来引导模型,使其每一步的输出都遵循一个固定的轨迹:

  • Thought (思考): 这是智能体的“内心独白”。它会分析当前情况、分解任务、制定下一步计划,或者反思上一步的结果。
  • Action (行动): 这是智能体决定采取的具体动作,通常是调用一个外部工具,例如 Search[‘华为最新款手机’]。
  • Observation (观察): 这是执行Action后从外部工具返回的结果,例如搜索结果的摘要或API的返回值。

智能体将不断重复这个 Thought -> Action -> Observation 的循环,将新的观察结果追加到历史记录中,形成一个不断增长的上下文,直到它在Thought中认为已经找到了最终答案,然后输出结果。这个过程形成了一个强大的协同效应:推理使得行动更具目的性,而行动则为推理提供了事实依据,具体的流程如下图所示:

这种机制特别适用于以下场景:

  • 需要外部知识的任务:如查询实时信息(天气、新闻、股价)、搜索专业领域的知识等。
  • 需要精确计算的任务:将数学问题交给计算器工具,避免LLM的计算错误。
  • 需要与API交互的任务:如操作数据库、调用某个服务的API来完成特定功能。

编码实现 #

实现代码可在gitlab中找到

工具定义 #

为了让大模型拥有和世界进行交互的能力,我们需要构建一个可以调用搜索引擎能力的工具,作为ReAct在解决问题是可以通过工具来获取相关的额外信息,这里我们使用tavily来提供搜索的能力,首先需要安装对应的sdk

pip install tavily-python
  1. 定义模型所需工具
from tavily import TavilyClient
import os
from dotenv import load_dotenv
from typing import Dict, Any

# 加载 .env 文件中的环境变量
load_dotenv()


def search(query: str) -> str:
    """
    一个基于 Tavily 的 AI 专用搜索引擎工具。
    它不仅返回网页链接,还能直接生成问题的简短答案或相关上下文。
    """
    print(f"🔍 正在执行 [Tavily] 网页搜索: {query}")
    try:
        api_key = os.getenv("TAVILY_API_KEY")
        if not api_key:
            return "错误: TAVILY_API_KEY 未在 .env 文件中配置。"

        # 初始化 Tavily 客户端
        client = TavilyClient(api_key=api_key)

        # 执行搜索
        # search_depth="basic" 速度快,"advanced" 质量高但慢
        # include_answer=True 让 Tavily 尝试直接生成一个简短回答
        response = client.search(
            query=query,
            search_depth="basic",
            include_answer=True,
            max_results=3
        )

        # 智能解析: 优先返回 Tavily 生成的直接答案
        if response.get("answer"):
            return f"直接答案: {response['answer']}"

        # 如果没有直接答案,返回上下文片段 (Context)
        if response.get("results"):
            snippets = []
            for i, res in enumerate(response["results"]):
                # 清洗内容,防止过长
                content = res.get('content', '')[:300]
                snippets.append(f"[{i + 1}] {res.get('title', '')}\n{content}...")
            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):
        """
        向工具箱中注册一个新工具。
        """
        if name in self.tools:
            print(f"警告:工具 '{name}' 已存在,将被覆盖。")
        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__":
    # 1. 初始化工具执行器
    toolExecutor = ToolExecutor()

    # 2. 注册我们的实战搜索工具
    search_description = "一个网页搜索引擎。当你需要回答关于时事、事实以及在你的知识库中找不到的信息时,应使用此工具。"
    toolExecutor.register_tool("Search", search_description, search)

    # 3. 打印可用的工具
    print("\n--- 可用的工具 ---")
    print(toolExecutor.get_available_tools())

    # 4. 智能体的Action调用,这次我们问一个实时性的问题
    print("\n--- 执行 Action: Search['英伟达最新的GPU型号是什么'] ---")
    tool_name = "Search"
    tool_input = "英伟达最新的GPU型号是什么"

    tool_function = toolExecutor.get_tool(tool_name)
    if tool_function:
        observation = tool_function(tool_input)
        print("--- 观察 (Observation) ---")
        print(observation)
    else:
        print(f"错误:未找到名为 '{tool_name}' 的工具。")

输出结果:

工具 'Search' 已注册。

--- 可用的工具 ---
- Search: 一个网页搜索引擎。当你需要回答关于时事、事实以及在你的知识库中找不到的信息时,应使用此工具。

--- 执行 Action: Search['英伟达最新的GPU型号是什么'] ---
🔍 正在执行 [Tavily] 网页搜索: 英伟达最新的GPU型号是什么
--- 观察 (Observation) ---
直接答案: 英伟达最新的GPU型号是H100,专为高性能AI训练优化。它具有极高的带宽和计算能力。H100在Transformer模型训练和推理性能方面表现卓越。
智能体实现 #
  1. 系统提示词设计
# ReAct 提示词模板
REACT_PROMPT_TEMPLATE = """
请注意,你是一个有能力调用外部工具的智能助手。

可用工具如下:
{tools}

请严格按照以下格式进行回应:

Thought: 你的思考过程,用于分析问题、拆解任务和规划下一步行动。
Action: 你决定采取的行动,必须是以下格式之一:
- `{{tool_name}}[{{tool_input}}]`:调用一个可用工具。
- `Finish[最终答案]`:当你认为已经获得最终答案时。
- 当你收集到足够的信息,能够回答用户的最终问题时,你必须在Action:字段后使用 finish(answer="...") 来输出最终答案。

现在,请开始解决以下问题:
Question: {question}
History: {history}
"""

这个模板定义了智能体与LLM之间交互的规范:

  • 角色定义: “你是一个有能力调用外部工具的智能助手”,设定了LLM的角色。
  • 工具清单 ({tools}): 告知LLM它有哪些可用的“手脚”。
  • 格式规约 (Thought/Action): 这是最重要的部分,它强制LLM的输出具有结构性,使我们能通过代码精确解析其意图。
  • 动态上下文 ({question}/{history}): 将用户的原始问题和不断累积的交互历史注入,让LLM基于完整的上下文进行决策。
  1. 智能体逻辑实现
from tool import ToolExecutor, search, get_current_time
from llm import HelloAgentsLLM
import re

REACT_PROMPT_TEMPLATE = """
请注意,你是一个有能力调用外部工具的智能助手。

可用工具如下:
{tools}

请严格按照以下格式进行回应:

Thought: 你的思考过程,用于分析问题、拆解任务和规划下一步行动。
Action: 你决定采取的行动,必须是以下格式之一:
- `{{tool_name}}[{{tool_input}}]`:调用一个可用工具。
- `Finish[最终答案]`:当你认为已经获得最终答案时。
- 当你收集到足够的信息,能够回答用户的最终问题时,你必须在Action:字段后使用 finish(answer="...") 来输出最终答案。

现在,请开始解决以下问题:
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):
        """
        运行ReAct智能体来回答一个问题。
        """
        self.history = []  # 每次运行时重置历史记录
        current_step = 0

        while current_step < self.max_steps:
            current_step += 1
            print(f"--- 第 {current_step} 步 ---")

            # 1. 格式化提示词
            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
            )

            # 2. 调用LLM进行思考
            messages = [{"role": "user", "content": prompt}]
            response_text = self.llm_client.think(messages=messages)

            if not response_text:
                print("错误:LLM未能返回有效响应。")
                break

            # 3. 解析LLM的输出
            thought, action = self._parse_output(response_text)

            if thought:
                print(f"思考: {thought}")

            if not action:
                print("警告:未能解析出有效的Action,流程终止。")
                break

            # 4. 执行Action
            if action.startswith("Finish"):
                # 如果是Finish指令,提取最终答案并结束
                final_answer = re.match(r"Finish\[(.*)\]", action).group(1)
                print(f"🎉 最终答案: {final_answer}")
                return final_answer

            tool_name, tool_input = self._parse_action(action)
            if not tool_name:
                # ... 处理无效Action格式 ...
                continue

            print(f"🎬 行动: {tool_name}[{tool_input}]")

            tool_function = self.tool_executor.get_tool(tool_name)
            if not tool_function:
                observation = f"错误:未找到名为 '{tool_name}' 的工具。"
            else:
                if tool_input:
                    observation = tool_function(tool_input)  # 调用真实工具
                else:
                    observation = tool_function()
            print(f"👀 观察: {observation}")

            # 将本轮的Action和Observation添加到历史记录中
            self.history.append(f"Action: {action}")
            self.history.append(f"Observation: {observation}")
        print("已达到最大步数,流程终止。")
        return None

    @staticmethod
    def _parse_output(text: str):
        """解析LLM的输出,提取Thought和Action。"""
        thought_match = re.search(r"Thought: (.*)", text)
        action_match = re.search(r"Action: (.*)", text)
        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

    @staticmethod
    def _parse_action(action_text: str):
        """解析Action字符串,提取工具名称和输入。"""
        match = re.match(r"(\w+)\[(.*)\]", action_text)
        if match:
            return match.group(1), match.group(2)
        return None, None


if __name__ == "__main__":
    # 1. 初始化工具执行器
    tool_executor = ToolExecutor()
    llm_agent = HelloAgentsLLM()
    # 2. 注册工具
    # 搜索工具
    search_description = "一个网页搜索引擎。当你需要回答关于时事、事实以及在你的知识库中找不到的信息时,应使用此工具。"
    tool_executor.register_tool("Search", search_description, search)
    time_description = "获取当前系统精确时间的工具,用于回答时间日期问题、推算相对时间,并为处理涉及“最新”或“时效性”的任务(如搜索最新新闻)提供必要的时间基准。"
    # 获取时间工具
    tool_executor.register_tool("CurrentTime", time_description, get_current_time)
    # 获取时间工具
    agent = ReActAgent(llm_agent, tool_executor)
    agent.run("华为最新的手机是哪一款?它的主要卖点是什么?")

输出结果:

工具 'Search' 已注册。
工具 'CurrentTime' 已注册。
--- 第 1 步 ---
🧠 正在调用 deepseek-chat 模型...
✅ 大语言模型响应成功:
Thought: 用户询问华为最新的手机是哪一款以及其主要卖点。这是一个关于当前产品信息的问题,具有时效性。我的知识库可能没有最新的产品发布信息,因此我需要使用搜索工具来获取准确和最新的信息。为了确保搜索结果的时效性,我应该先获取当前时间,以便在搜索时能指定时间范围或验证信息的时效性。

Action: CurrentTime[]
思考: 用户询问华为最新的手机是哪一款以及其主要卖点。这是一个关于当前产品信息的问题,具有时效性。我的知识库可能没有最新的产品发布信息,因此我需要使用搜索工具来获取准确和最新的信息。为了确保搜索结果的时效性,我应该先获取当前时间,以便在搜索时能指定时间范围或验证信息的时效性。
🎬 行动: CurrentTime[]
👀 观察: 2025-12-24 14:59:27
--- 第 2 步 ---
🧠 正在调用 deepseek-chat 模型...
✅ 大语言模型响应成功:
Thought: 用户询问华为最新的手机型号及其主要卖点。这是一个关于当前产品信息的问题,具有时效性。我刚刚获取了当前时间是2025年12月24日。为了提供准确信息,我需要搜索关于华为最新手机型号的近期新闻或官方发布信息。

Action: Search[华为最新手机 2025年 型号 主要卖点]
思考: 用户询问华为最新的手机型号及其主要卖点。这是一个关于当前产品信息的问题,具有时效性。我刚刚获取了当前时间是2025年12月24日。为了提供准确信息,我需要搜索关于华为最新手机型号的近期新闻或官方发布信息。
🎬 行动: Search[华为最新手机 2025年 型号 主要卖点]
🔍 正在执行 [Tavily] 网页搜索: 华为最新手机 2025年 型号 主要卖点
👀 观察: 直接答案: 2025年华为手机主要卖点是拍照能力和时尚设计,特别是HUAWEI Mate 60 Pro,融合科技与美学。
--- 第 3 步 ---
🧠 正在调用 deepseek-chat 模型...
✅ 大语言模型响应成功:
Thought: 用户询问华为最新的手机是哪一款以及其主要卖点。根据历史记录,我已经获取了当前时间(2025年12月24日)并进行了搜索。搜索结果显示,在2025年,华为最新的旗舰手机之一是HUAWEI Mate 60 Pro,其主要卖点是拍照能力和时尚设计,融合了科技与美学。这个信息直接回答了用户的问题,并且基于当前时间,可以认为是“最新”的。因此,我可以基于这个观察结果来组织最终答案。

Action: Finish[根据2025年的信息,华为最新的旗舰手机之一是HUAWEI Mate 60 Pro。它的主要卖点在于其卓越的拍照能力和时尚的设计美学,体现了科技与艺术的融合。]
思考: 用户询问华为最新的手机是哪一款以及其主要卖点。根据历史记录,我已经获取了当前时间(2025年12月24日)并进行了搜索。搜索结果显示,在2025年,华为最新的旗舰手机之一是HUAWEI Mate 60 Pro,其主要卖点是拍照能力和时尚设计,融合了科技与美学。这个信息直接回答了用户的问题,并且基于当前时间,可以认为是“最新”的。因此,我可以基于这个观察结果来组织最终答案。
🎉 最终答案: 根据2025年的信息,华为最新的旗舰手机之一是HUAWEI Mate 60 Pro。它的主要卖点在于其卓越的拍照能力和时尚的设计美学,体现了科技与艺术的融合。

宏观规划:Plan-and-Solve (PS) #

Plan-and-Solve Prompting 是由 Lei Wang 在2023年提出。其核心动机是为了解决思维链在处理多步骤、复杂问题时容易“偏离轨道”的问题

工作流程 #

与 ReAct 将思考和行动融合在每一步不同,Plan-and-Solve 将整个流程解耦为两个核心阶段:

  • 规划阶段 (Planning Phase): 首先,智能体会接收用户的完整问题。它的第一个任务不是直接去解决问题或调用工具,而是将问题分解,并制定出一个清晰、分步骤的行动计划。这个计划本身就是一次大语言模型的调用产物。
  • 执行阶段 (Solving Phase): 在获得完整的计划后,智能体进入执行阶段。它会严格按照计划中的步骤,逐一执行。每一步的执行都可能是一次独立的 LLM 调用,或者是对上一步结果的加工处理,直到计划中的所有步骤都完成,最终得出答案。

这种“先谋后动”的策略,使得智能体在处理需要长远规划的复杂任务时,能够保持更高的目标一致性,避免在中间步骤中迷失方向,具体的流程如下图所示:

编码实现 #

实现代码可在gitlab中找到

  1. 规划阶段提示词
PLANNER_PROMPT_TEMPLATE = """
你是一个顶级的AI规划专家。你的任务是将用户提出的复杂问题分解成一个由多个简单步骤组成的行动计划。
请确保计划中的每个步骤都是一个独立的、可执行的子任务,并且严格按照逻辑顺序排列。
你的输出必须是一个Python列表,其中每个元素都是一个描述子任务的字符串。

问题: {question}

请严格按照以下格式输出你的计划,```python与```作为前后缀是必要的:
-```python
["步骤1", "步骤2", "步骤3", ...]
-```
"""
  1. 执行阶段提示词
EXECUTOR_PROMPT_TEMPLATE = """
你是一位顶级的AI执行专家。你的任务是严格按照给定的计划,一步步地解决问题。
你将收到原始问题、完整的计划、以及到目前为止已经完成的步骤和结果。
请你专注于解决“当前步骤”,并仅输出该步骤的最终答案,不要输出任何额外的解释或对话。

# 原始问题:
{question}

# 完整计划:
{plan}

# 历史步骤与结果:
{history}

# 当前步骤:
{current_step}

请仅输出针对“当前步骤”的回答:
"""
  1. 完整代码
import ast
from llm import HelloAgentsLLM

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"✅ 计划已生成:\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 []


class Executor:
    def __init__(self, llm_client: HelloAgentsLLM):
        self.llm_client = llm_client

    def execute(self, question: str, plan: list[str]) -> str:
        """
        根据计划,逐步执行并解决问题。
        """
        response_text = ""
        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


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}")

        # 1. 调用规划器生成计划
        plan = self.planner.plan(question)

        # 检查计划是否成功生成
        if not plan:
            print("\n--- 任务终止 --- \n无法生成有效的行动计划。")
            return

        # 2. 调用执行器执行计划
        final_answer = self.executor.execute(question, plan)

        print(f"\n--- 任务完成 ---\n最终答案: {final_answer}")


if __name__ == "__main__":
    llm_client = HelloAgentsLLM()
    planner = PlanAndSolveAgent(llm_client)
    planner.run(
        "一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果?")

输出结果:

--- 开始处理问题 ---
问题: 一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果?
--- 正在生成计划 ---
🧠 正在调用 deepseek-chat 模型...
✅ 大语言模型响应成功:
-```python
["计算周二卖出的苹果数量:周一卖出的15个苹果乘以2", "计算周三卖出的苹果数量:周二卖出的数量减去5个", "计算三天卖出的苹果总数:将周一、周二、周三卖出的数量相加"]
-```
✅ 计划已生成:
-```python
["计算周二卖出的苹果数量:周一卖出的15个苹果乘以2", "计算周三卖出的苹果数量:周二卖出的数量减去5个", "计算三天卖出的苹果总数:将周一、周二、周三卖出的数量相加"]
-```

--- 正在执行计划 ---

-> 正在执行步骤 1/3: 计算周二卖出的苹果数量:周一卖出的15个苹果乘以2
🧠 正在调用 deepseek-chat 模型...
✅ 大语言模型响应成功:
30
✅ 步骤 1 已完成,结果: 30

-> 正在执行步骤 2/3: 计算周三卖出的苹果数量:周二卖出的数量减去5个
🧠 正在调用 deepseek-chat 模型...
✅ 大语言模型响应成功:
25
✅ 步骤 2 已完成,结果: 25

-> 正在执行步骤 3/3: 计算三天卖出的苹果总数:将周一、周二、周三卖出的数量相加
🧠 正在调用 deepseek-chat 模型...
✅ 大语言模型响应成功:
70
✅ 步骤 3 已完成,结果: 70

--- 任务完成 ---
最终答案: 70

自我进化:Reflection (Self-Correction) #

Reflexion 由 Noah Shinn 等人于 2023 年提出,其核心思想是模仿人类的“试错”与“自省”机制,通过引入语言反馈 (Verbal Reinforcement) 来诱导模型自我修正,形成一个“尝试-评估-改进”的迭代循环。

工作流程 #

其核心工作流程可以概括为一个简洁的三步循环:执行 -> 反思 -> 优化。

  • 执行 (Execution):首先,智能体使用我们熟悉的方法(如 ReAct 或 Plan-and-Solve)尝试完成任务,生成一个初步的解决方案或行动轨迹。这可以看作是“初稿”。
  • 反思 (Reflection):接着,智能体进入反思阶段。它会调用一个独立的、或者带有特殊提示词的大语言模型实例,来扮演一个“评审员”的角色。这个“评审员”会审视第一步生成的“初稿”,并从多个维度进行评估,例如:
    • 事实性错误:是否存在与常识或已知事实相悖的内容?
    • 逻辑漏洞:推理过程是否存在不连贯或矛盾之处?
    • 效率问题:是否有更直接、更简洁的路径来完成任务?
    • 遗漏信息:是否忽略了问题的某些关键约束或方面? 根据评估,它会生成一段结构化的反馈 (Feedback),指出具体的问题所在和改进建议。
  • 优化 (Refinement):最后,智能体将“初稿”和“反馈”作为新的上下文,再次调用大语言模型,要求它根据反馈内容对初稿进行修正,生成一个更完善的“修订稿”。

具体的流程如下图所示:

与前两种范式相比,Reflection 的价值在于:

  • 它为智能体提供了一个内部纠错回路,使其不再完全依赖于外部工具的反馈(ReAct 的 Observation),从而能够修正更高层次的逻辑和策略错误。
  • 它将一次性的任务执行,转变为一个持续优化的过程,显著提升了复杂任务的最终成功率和答案质量。
  • 它为智能体构建了一个临时的“短期记忆”。整个“执行-反思-优化”的轨迹形成了一个宝贵的经验记录,智能体不仅知道最终答案,还记得自己是如何从有缺陷的初稿迭代到最终版本的。更进一步,这个记忆系统还可以是多模态的,允许智能体反思和修正文本以外的输出(如代码、图像等),为构建更强大的多模态智能体奠定了基础。

编码实现 #

实现代码可在gitlab中找到

  1. 提示词设计
  • 初始执行提示词 (Execution Prompt) :这是智能体首次尝试解决问题的提示词,内容相对直接,只要求模型完成指定任务。

    INITIAL_PROMPT_TEMPLATE = """
    你是一位资深的Python程序员。请根据以下要求,编写一个Python函数。
    你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
    
    要求: {task}
    
    请直接输出代码,不要包含任何额外的解释。
    """
    
  • 反思提示词 (Reflection Prompt) :这个提示词是 Reflection 机制的灵魂。它指示模型扮演“代码评审员”的角色,对上一轮生成的代码进行批判性分析,并提供具体的、可操作的反馈。

    REFLECT_PROMPT_TEMPLATE = """
    你是一位极其严格的代码评审专家和资深算法工程师,对代码的性能有极致的要求。
    你的任务是审查以下Python代码,并专注于找出其在<strong>算法效率</strong>上的主要瓶颈。
    
    # 原始任务:
    {task}
    
    # 待审查的代码:
    -```python
    {code}
    -```
    
    请分析该代码的时间复杂度,并思考是否存在一种<strong>算法上更优</strong>的解决方案来显著提升性能。
    如果存在,请清晰地指出当前算法的不足,并提出具体的、可行的改进算法建议(例如,使用筛法替代试除法)。
    如果代码在算法层面已经达到最优,才能回答“无需改进”。
    
    请直接输出你的反馈,不要包含任何额外的解释。
    """
    
  • 优化提示词 (Refinement Prompt) :当收到反馈后,这个提示词将引导模型根据反馈内容,对原有代码进行修正和优化。

    REFINE_PROMPT_TEMPLATE = """
    你是一位资深的Python程序员。你正在根据一位代码评审专家的反馈来优化你的代码。
    
    # 原始任务:
    {task}
    
    # 你上一轮尝试的代码:
    {last_code_attempt}
    评审员的反馈:
    {feedback}
    
    请根据评审员的反馈,生成一个优化后的新版本代码。
    你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
    请直接输出优化后的代码,不要包含任何额外的解释。
    """
    
  1. 记忆系统 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
  1. 智能体实现
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


if __name__ == "__main__":
    llm_client = HelloAgentsLLM()
    reflection_agent = ReflectionAgent(llm_client)
    reflection_agent.run(task="编写一个Python函数,找出1到n之间所有的素数 (prime numbers)。")

输出结果:

--- 开始处理任务 ---
任务: 编写一个Python函数,找出1到n之间所有的素数 (prime numbers)
--- 正在进行初始尝试 ---
🧠 正在调用 deepseek-chat 模型...
✅ 大语言模型响应成功:
(python代码)
...
📝 记忆已更新,新增一条 'execution' 记录。

--- 第 1/3 轮迭代 ---

-> 正在进行反思...
🧠 正在调用 deepseek-chat 模型...
✅ 大语言模型响应成功:
当前代码使用了标准的埃拉托斯特尼筛法,其时间复杂度为 O(n log log n),这在算法层面对于生成从1到n的所有素数已经接近最优。

然而,该实现存在一个主要的性能瓶颈:内存使用和缓存效率。`is_prime` 列表长度为 `n+1`,当 `n` 很大(例如超过 10^8)时,会消耗大量内存(约 `n` 字节),并且对一个大数组进行顺序访问和修改,可能导致缓存未命中率较高,影响速度。

**算法上更优的解决方案**:
可以采用**分段筛法(Segmented Sieve)**。其核心思想是将区间 `[2, n]` 分成多个较小的段,每次只筛一个段,从而将内存使用量从 O(n) 降低到 O(√n + S),其中 S 是段的大小。这允许处理远大于内存容量的 `n`
**具体改进建议**:
1.  计算所有小于等于 √n 的素数(使用普通筛法)。
2.  将区间 `[2, n]` 划分为大小为 `segment_size`(通常约为 √n 或与CPU缓存匹配)的段。
3.  对于每个段 `[low, high]`,创建一个布尔数组 `is_prime_segment` 表示该段内的数是否为素数(初始为True)。
4.  对于第1步中得到的每个小素数 `p`,在段 `[low, high]` 内找到第一个 `p` 的倍数,然后标记段内所有 `p` 的倍数为非素数。
5.  收集该段中剩余的素数。
6.  重复步骤3-5,直到处理完整个区间。

分段筛法的时间复杂度仍为 O(n log log n),但空间复杂度显著降低,并且由于更好地利用了CPU缓存,实际运行速度(特别是对于非常大的 `n`)可以比普通筛法快数倍。

因此,对于处理极大 `n` 的场景,当前代码在算法效率上**存在改进空间**,应采用分段筛法进行优化。
📝 记忆已更新,新增一条 'reflection' 记录。

-> 正在进行优化...
🧠 正在调用 deepseek-chat 模型...
✅ 大语言模型响应成功:
(python代码)
...
--- 第 2/3 轮迭代 ---

-> 正在进行反思...
🧠 正在调用 deepseek-chat 模型...
✅ 大语言模型响应成功:
当前代码实现了分段筛法,时间复杂度为 O(n log log n),空间复杂度为 O(√n)。对于大范围(例如 n > 10^7)的素数查找,这已经是理论最优的算法之一。

然而,在算法层面仍存在一个可优化的点:**小素数筛选部分可以进一步优化以减少冗余操作**。具体来说,在生成小素数(≤ √n)时,代码使用了标准的埃拉托斯特尼筛法,但其内层循环的起始点可以更精确地优化。

**改进建议**:
1. **优化小素数筛法的内层循环**:在标记合数时,可以从 `i*i` 开始,步长为 `i`,但当前代码在 `i*i < limit` 时才进入内层循环。虽然这避免了小数字的冗余标记,但对于接近 √n 的素数,`i*i` 可能已经超出 `limit`,导致循环被跳过。实际上,当 `i` 较大时,`i*i` 可能远大于 `limit`,因此这个检查是必要的,但可以进一步优化循环条件,避免不必要的判断。
2. **使用位图(bit array)替代布尔列表**:虽然这更多是内存和常数优化,但可以显著减少缓存未命中,提升实际性能。例如,使用 `bytearray``bitarray` 库。
3. **并行化分段处理**:对于极大的 n(例如 n > 10^9),可以考虑将不同段分配给多个线程或进程并行筛选,但这需要处理并发和合并结果。

**具体优化代码示例(小素数筛选部分)**:
(python代码)
...

**总结**:当前算法在渐进复杂度上已是最优,但通过上述低级优化(如使用 `bytearray` 和切片赋值)可以提升常数因子,尤其在大规模数据时效果明显。因此,从纯算法角度看,无需改进;但从极致性能角度,建议实施上述低级优化。
📝 记忆已更新,新增一条 'reflection' 记录。

✅ 反思认为代码已无需改进,任务完成。

--- 任务完成 ---
最终生成的代码:
(python代码)
...

范式融合与展望 #

单一的 Agent 范式(如 ReAct 或 Plan-and-Solve)就像是掌握了特定技能的单兵,虽然在特定领域表现出色,但在面对极其复杂、长周期或容错率低的任务时,往往显得力不从心。为了构建真正的“通用智能体”,我们需要将这些范式进行融合,取长补短,并关注未来 Agent 技术演进的几个关键趋势。

1. 范式融合:构建复合型 Agent #

在实际的工程实践中,最强大的 Agent 往往不是单一范式的生搬硬套,而是采用 “Planner-Executor-Critic” (规划-执行-评估) 的复合架构。这种架构将前面提到的三种范式有机地结合在了一起:

  • 大脑 (Planner) - 基于 Plan-and-Solve:

    • 职责: 负责宏观把控。它不直接处理细节,而是利用 Plan-and-Solve 的思想,将用户模糊、复杂的需求(如“帮我写一个类似 Flappy Bird 的游戏”)拆解为一系列清晰的子任务(1. 编写游戏循环;2. 设计物理引擎;3. 绘制UI)。
    • 优势: 防止 Agent 在细节中迷失方向,确保长期目标的一致性。
  • 手脚 (Executor) - 基于 ReAct:

    • 职责: 负责具体落地。针对 Planner 下发的每一个子任务,启动一个 ReAct 循环。它调用工具(搜索引擎、代码解释器、文件系统)来解决具体问题。
    • 优势: 能够灵活应对外部环境的变化,利用工具弥补 LLM 知识的不足。
  • 质检员 (Critic) - 基于 Reflection:

    • 职责: 负责质量把控。在 Executor 完成任务后,Critic 介入进行评估。它检查代码是否可运行、答案是否准确。如果不通过,它会产生反馈,要求 Executor 重试或 Planner 调整计划。
    • 优势: 提供了自我纠错的闭环,大幅提高了最终输出的可靠性。

这种融合模式将线性的执行过程变成了一个动态的、可回溯的图结构

2. 未来展望:Agent 的进化方向 #

随着 LLM 能力的提升,Agent 正处在从“玩具”走向“生产力工具”的关键转折点。以下是几个核心的演进趋势:

2.1 从 Single Agent 到 Multi-Agent Systems (MAS) #

目前的 Agent 大多是“单打独斗”。未来的趋势是多智能体协作

  • 角色专业化: 就像软件开发团队一样,我们不再试图让一个 Agent 既懂产品又懂开发还懂测试。而是构建一个由“产品经理 Agent”、“架构师 Agent”、“工程师 Agent”组成的团队(如 MetaGPT, AutoGen 框架)。

  • 标准化作业程序 (SOP): 通过定义明确的 SOP,让不同的 Agent 按照既定流程交互,能够解决远超单个 LLM 上下文限制的超大型任务。

2.2 从“无状态”到“长期记忆” (Long-term Memory) #

目前的 Agent 大多依赖有限的 Context Window(上下文窗口)。一旦对话结束,记忆即消失。

  • 未来 Agent 将标配向量数据库 (Vector DB)知识图谱作为海马体。
  • 它们不仅能记住几分钟前的对话,还能记住用户的偏好、过往的交互历史以及特定领域的知识库,实现类似 MemGPT 的无限上下文体验。

2.4 从“对话框”到“操作系统” (Computer Use / Embodied AI) #

目前的 Agent 主要存在于聊天窗口中。未来的 Agent 将具备具身性 (Embodiment)OS 操作能力

  • Computer Use: 如 Anthropic 的 Claude Computer Use,Agent 可以像人类一样控制鼠标、键盘,浏览网页、操作 Excel、使用 Photoshop,直接在 GUI 层面解决问题,而不仅仅是调用 API。
  • 具身智能: Agent 将接入机器人硬件,感知物理世界并与之交互。

2.5 从“文本生成”到“代码执行” (Code-as-Action) #

对于逻辑计算和精确任务,纯文本生成的可靠性远不如代码。

  • 未来的 Agent 将更加依赖沙箱环境 (Sandbox)。在遇到数学计算、数据分析或复杂逻辑时,Agent 会优先编写并执行 Python 代码来获取结果,而不是靠“脑补”生成答案。这被称为 Program-Aided Language Models (PAL)

总结 #

Agent 的构建本质上是在弥补 LLM 与现实世界之间的鸿沟。

  • ReAct 解决了的问题(工具使用);
  • Plan-and-Solve 解决了的问题(逻辑规划);
  • Reflection 解决了的问题(自我审视);

范式融合则是将这三者合二为一,创造出真正具备解决复杂问题能力的数字实体。接下来的旅程,不仅仅是编写 Prompt,更是设计能够自我演进的智能系统架构。

参考 #