1.封装基础LLM调用函数
在新手教程那已经写过配置api等等。这里,我们先创建一个.env,把配置信息放里面。
在终端安装一下python-dotenv 库,用于安全地管理我们的 API 密钥:
pip install python-dotenv openai
然后创建一个question_agent.py
import os
from openai import OpenAI
from dotenv import load_dotenv
from typing import List, Dict
# 加载 .env 文件中的环境变量
load_dotenv()
class HelloAgentsLLM:
"""
为本书 "Hello Agents" 定制的LLM客户端。
它用于调用任何兼容OpenAI接口的服务,并默认使用流式响应。
"""
def __init__(self, model: str = None, apiKey: str = None, baseUrl: str = None, timeout: int = None):
"""
初始化客户端。优先使用传入参数,如果未提供,则从环境变量加载。
"""
self.model = model or os.getenv("LLM_MODEL_ID")
apiKey = apiKey or os.getenv("LLM_API_KEY")
baseUrl = baseUrl or os.getenv("LLM_BASE_URL")
timeout = timeout or int(os.getenv("LLM_TIMEOUT", 60))
if not all([self.model, apiKey, baseUrl]):
raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")
self.client = OpenAI(api_key=apiKey, base_url=baseUrl, 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:
if not chunk.choices:
continue
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)
运行效果:

2.ReAct
将推理 (Reasoning) 与行动 (Acting) 显式地结合起来,形成一个“思考-行动-观察”的循环。这个过程形成了一个强大的协同效应:推理使得行动更具目的性,而行动则为推理提供了事实依据。
2.1 工具实现
这里需要选用 SerpApi,它通过API提供结构化的Google搜索结果,能直接返回“答案摘要框”或精确的知识图谱信息。
安装:
pip install google-search-results

同时,你需要前往 SerpApi官网 注册一个免费账户,获取你的API密钥,并将其添加到我们项目根目录下的 .env 文件中。注册需要有验证码,因为是境外的短信,所以记得把手机的智能拦截给关掉,不然接收不到短信。
SERPAPI_API_KEY="xxxx"
编写代码,创建survey_agent.py
from serpapi import SerpApiClient
import os
from dotenv import load_dotenv
load_dotenv()
def search(query: str) -> str:
"""
一个基于SerpApi的实战网页搜索引擎工具。
它会智能地解析搜索结果,优先返回直接答案或知识图谱信息。
"""
print(f"🔍 正在执行 [SerpApi] 网页搜索: {query}")
try:
api_key = os.getenv("SERPAPI_API_KEY")
if not api_key:
return "错误:SERPAPI_API_KEY 未在 .env 文件中配置。"
params = {
"engine": "google",
"q": query,
"api_key": api_key,
"gl": "cn", # 国家代码
"hl": "zh-cn", # 语言代码
}
client = SerpApiClient(params)
results = client.get_dict()
# 智能解析:优先寻找最直接的答案
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}"
from typing import Dict, Any
class ToolExecutor:
"""
一个工具执行器,负责管理和执行工具。
"""
def __init__(self):
self.tools: Dict[str, Dict[str, Any]] = {}
def registerTool(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 getTool(self, name: str) -> callable:
"""
根据名称获取一个工具的执行函数。
"""
return self.tools.get(name, {}).get("func")
def getAvailableTools(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.registerTool("Search", search_description, search)
# 3. 打印可用的工具
print("\n--- 可用的工具 ---")
print(toolExecutor.getAvailableTools())
# 4. 智能体的Action调用,这次我们问一个实时性的问题
print("\n--- 执行 Action: Search['英伟达最新的GPU型号是什么'] ---")
tool_name = "Search"
tool_input = "英伟达最新的GPU型号是什么"
tool_function = toolExecutor.getTool(tool_name)
if tool_function:
observation = tool_function(tool_input)
print("--- 观察 (Observation) ---")
print(observation)
else:
print(f"错误:未找到名为 '{tool_name}' 的工具。")
运行效果:

2.2 智能体的编码实现
创建react_agent.py:
import os
import re
from dotenv import load_dotenv
from openai import OpenAI
from typing import List, Dict, Any
from serpapi import SerpApiClient
# 1.加载环境变量
load_dotenv()
# ====================== 第一部分:LLM客户端 HelloAgentsLLM ======================
class HelloAgentsLLM:
def __init__(self, model: str = None, apiKey: str = None, baseUrl: str = None, timeout: int = None):
self.model = model or os.getenv("LLM_MODEL_ID")
apiKey = apiKey or os.getenv("LLM_API_KEY")
baseUrl = baseUrl or os.getenv("LLM_BASE_URL")
timeout = timeout or int(os.getenv("LLM_TIMEOUT", 60))
if not all([self.model, apiKey, baseUrl]):
raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")
self.client = OpenAI(api_key=apiKey, base_url=baseUrl, 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:
if not chunk.choices:
continue
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
# ====================== 第二部分:Search工具 + ToolExecutor ======================
def search(query: str) -> str:
print(f"🔍 正在执行 [SerpApi] 网页搜索: {query}")
try:
api_key = os.getenv("SERPAPI_API_KEY")
if not api_key:
return "错误:SERPAPI_API_KEY 未在 .env 文件中配置。"
params = {
"engine": "google",
"q": query,
"api_key": api_key,
"gl": "cn",
"hl": "zh-cn",
}
client = SerpApiClient(params)
results = client.get_dict()
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 registerTool(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 getTool(self, name: str) -> callable:
return self.tools.get(name, {}).get("func")
def getAvailableTools(self) -> str:
return "\n".join([
f"- {name}: {info['description']}"
for name, info in self.tools.items()
])
# ====================== 第三部分:ReAct提示词 + ReActAgent主体 ======================
# ReAct 提示词模板
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):
"""
运行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.getAvailableTools()
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 or not tool_input:
# ... 处理无效Action格式 ...
continue
print(f"🎬 行动: {tool_name}[{tool_input}]")
tool_function = self.tool_executor.getTool(tool_name)
if not tool_function:
observation = f"错误:未找到名为 '{tool_name}' 的工具。"
else:
observation = tool_function(tool_input) # 调用真实工具
print(f"👀 观察: {observation}")
# 将本轮的Action和Observation添加到历史记录中
self.history.append(f"Action: {action}")
self.history.append(f"Observation: {observation}")
# 循环结束
print("已达到最大步数,流程终止。")
return None
def _parse_output(self, text: str):
"""解析LLM的输出,提取Thought和Action。
"""
# Thought: 匹配到 Action: 或文本末尾
thought_match = re.search(r"Thought:\s*(.*?)(?=\nAction:|$)", text, re.DOTALL)
# Action: 匹配到文本末尾
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):
"""解析Action字符串,提取工具名称和输入。
"""
match = re.match(r"(\w+)\[(.*)\]", action_text, re.DOTALL)
if match:
return match.group(1), match.group(2)
return None, None
# ====================== 程序入口:启动智能体 ======================
if __name__ == '__main__':
# 1.初始化LLM
llm = HelloAgentsLLM()
# 2.注册搜索工具
tool_exec = ToolExecutor()
desc = "一个网页搜索引擎。当你需要回答关于时事、事实以及在你的知识库中找不到的信息时,应使用此工具。"
tool_exec.registerTool("Search", desc, search)
# 3.创建ReAct智能体
agent = ReActAgent(llm_client=llm, tool_executor=tool_exec, max_steps=5)
# 4.提问测试
res = agent.run("华为最新手机型号及主要卖点")
运行效果:
(ai-agent) PS D:\Project\ai-agent> & D:\conda\.conda\envs\ai-agent\python.exe d:/Project/ai-agent/react_agent.py
工具 'Search' 已注册。
--- 第 1 步 ---
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
Thought: 我需要查找华为最新的手机型号以及它们的主要卖点。
Action: Search[华为 最新手机型号 主要卖点]
思考: 我需要查找华为最新的手机型号以及它们的主要卖点。
🎬 行动: Search[华为 最新手机型号 主要卖点]
🔍 正在执行 [SerpApi] 网页搜索: 华为 最新手机型号 主要卖点
👀 观察: [1] 华为手机- 华为官网
探索并选购华为最新手机,了解Mate 系列、Pura 系列、Pocket 系列、nova 系列、畅享系列及相关配件,体验鸿蒙AI 、影像、通信等功能。
[2] 2026年华为手机哪一款性价比高?华为手机推荐与市场分析
现在华为手机最大的卖点只剩下鸿蒙HarmonyOS系统,以及饱受争议的品牌信仰。 这里推荐目前值得入手的几款华为系列手机,根据不同预算自行选择:. 华为目前最受欢迎,也是搭载 ...
[3] 华为手机馆
Mate 80 Pro Max · 12期0分期利息 · 全金属玄武架构 ; Pura 90 Pro Max · 12期0分期利息 · 超大底2 亿长焦 ; nova 16 Pro · 6期0分期利息 · 2 亿红枫影像 ; 华为畅享90 Pro Max · 6期 ...
--- 第 2 步 ---
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
Thought: 我需要进一步搜索华为最新手机型号及其主要卖点,以提供更详细的信息。
Action: Search[华为 最新手机型号 主要卖点]
思考: 我需要进一步搜索华为最新手机型号及其主要卖点,以提供更详细的信息。
🎬 行动: Search[华为 最新手机型号 主要卖点]
🔍 正在执行 [SerpApi] 网页搜索: 华为 最新手机型号 主要卖点
👀 观察: [1] 华为手机- 华为官网
探索并选购华为最新手机,了解Mate 系列、Pura 系列、Pocket 系列、nova 系列、畅享系列及相关配件,体验鸿蒙AI 、影像、通信等功能。
[2] 2026年华为手机哪一款性价比高?华为手机推荐与市场分析
现在华为手机最大的卖点只剩下鸿蒙HarmonyOS系统,以及饱受争议的品牌信仰。 这里推荐目前值得入手的几款华为系列手机,根据不同预算自行选择:. 华为目前最受欢迎,也是搭载 ...
[3] 华为手机馆
Mate 80 Pro Max · 12期0分期利息 · 全金属玄武架构 ; Pura 90 Pro Max · 12期0分期利息 · 超大底2 亿长焦 ; nova 16 Pro · 6期0分期利息 · 2 亿红枫影像 ; 华为畅享90 Pro Max · 6期 ...
--- 第 3 步 ---
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
Thought: 从搜索结果来看,华为最新手机型号包括Mate系列、Pura系列、Pocket系列、nova系列和畅享系列。主要卖点包括鸿蒙HarmonyOS系统、影像功能、通信技术等。但具体型号和详细卖点需要进一步确认。
Action: Search[华为 最新手机型号 主要卖点]
思考: 从搜索结果来看,华为最新手机型号包括Mate系列、Pura系列、Pocket系列、nova系列和畅享系列。主要卖点包括鸿蒙HarmonyOS系统、影像功能、通信技术等。但具体型号和详细卖点需要进一步确认。
🎬 行动: Search[华为 最新手机型号 主要卖点]
🔍 正在执行 [SerpApi] 网页搜索: 华为 最新手机型号 主要卖点
👀 观察: [1] 华为手机- 华为官网
探索并选购华为最新手机,了解Mate 系列、Pura 系列、Pocket 系列、nova 系列、畅享系列及相关配件,体验鸿蒙AI 、影像、通信等功能。
[2] 2026年华为手机哪一款性价比高?华为手机推荐与市场分析
现在华为手机最大的卖点只剩下鸿蒙HarmonyOS系统,以及饱受争议的品牌信仰。 这里推荐目前值得入手的几款华为系列手机,根据不同预算自行选择:. 华为目前最受欢迎,也是搭载 ...
[3] 华为手机馆
Mate 80 Pro Max · 12期0分期利息 · 全金属玄武架构 ; Pura 90 Pro Max · 12期0分期利息 · 超大底2 亿长焦 ; nova 16 Pro · 6期0分期利息 · 2 亿红枫影像 ; 华为畅享90 Pro Max · 6期 ...
--- 第 4 步 ---
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
Thought: 从搜索结果来看,目前华为的最新手机型号包括Mate系列、Pura系列、Pocket系列、nova系列和畅享系列。其中,Mate 80 Pro Max、Pura 90 Pro Max、nova 16 Pro和华为畅享90 Pro Max是被提及的具体型号。主要卖点包括鸿蒙HarmonyOS系统、影像功能(如2亿像素长焦、红枫影像)、通信技术以及全金属玄武架构等。
Action: Finish[华为最新手机型号包括Mate系列、Pura系列、Pocket系列、nova系列和畅享系列。具体型号如Mate 80 Pro Max、Pura 90 Pro Max、nova 16 Pro和华为畅享90 Pro Max。主要卖点包括鸿蒙HarmonyOS系统、影像功能(如2亿像素长焦、红枫影像)、通信技术以及全金属玄武架构等。]
思考: 从搜索结果来看,目前华为的最新手机型号包括Mate系列、Pura系列、Pocket系列、nova系列和畅享系列。其中,Mate 80 Pro Max、Pura 90 Pro Max、nova 16 Pro和华为畅享90 Pro Max是被提及的具体型号。主要卖点包括鸿蒙HarmonyOS系统、影像功能(如2亿像素长焦、红枫影像)、通信技术以及全金属玄武架构等。
🎉 最终答案: 华为最新手机型号包括Mate系列、Pura系列、Pocket系列、nova系列和畅享系列。具体型号如Mate 80 Pro Max、Pura 90 Pro Max、nova 16 Pro和华为畅享90 Pro Max。主要卖点包括鸿蒙HarmonyOS系统、影像功能(如2亿像素长焦、红枫影像)、通信技术以及全金属玄武架构等。
3.Plan And Slove
将任务处理明确地分为两个阶段:先规划 (Plan),后执行 (Solve)。
import os
import re
import ast
from dotenv import load_dotenv
from openai import OpenAI
from typing import List, Dict, Any
# 1.加载环境变量
load_dotenv()
# ====================== 第一部分:LLM客户端 HelloAgentsLLM ======================
class HelloAgentsLLM:
def __init__(self, model: str = None, apiKey: str = None, baseUrl: str = None, timeout: int = None):
self.model = model or os.getenv("LLM_MODEL_ID")
apiKey = apiKey or os.getenv("LLM_API_KEY")
baseUrl = baseUrl or os.getenv("LLM_BASE_URL")
timeout = timeout or int(os.getenv("LLM_TIMEOUT", 60))
if not all([self.model, apiKey, baseUrl]):
raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")
self.client = OpenAI(api_key=apiKey, base_url=baseUrl, 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:
if not chunk.choices:
continue
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
# ====================== 第二部分:提示词 + Agent主体 ======================
PLANNER_PROMPT_TEMPLATE = """
你是一个顶级的AI规划专家。你的任务是将用户提出的复杂问题分解成一个由多个简单步骤组成的行动计划。
请确保计划中的每个步骤都是一个独立的、可执行的子任务,并且严格按照逻辑顺序排列。
你的输出必须是一个Python列表,其中每个元素都是一个描述子任务的字符串。
问题: {question}
请严格按照以下格式输出你的计划,```python与```作为前后缀是必要的:
```python
["步骤1", "步骤2", "步骤3", ...]
```
"""
EXECUTOR_PROMPT_TEMPLATE = """
你是一位顶级的AI执行专家。你的任务是严格按照给定的计划,一步步地解决问题。
你将收到原始问题、完整的计划、以及到目前为止已经完成的步骤和结果。
请你专注于解决“当前步骤”,并仅输出该步骤的最终答案,不要输出任何额外的解释或对话。
# 原始问题:
{question}
# 完整计划:
{plan}
# 历史步骤与结果:
{history}
# 当前步骤:
{current_step}
请仅输出针对“当前步骤”的回答:
"""
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 []
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
class PlanAndSolveAgent:
def __init__(self, llm_client):
"""
初始化智能体,同时创建规划器和执行器实例。
"""
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__':
# 1.初始化LLM
llm = HelloAgentsLLM()
# 2.创建PlanAndSolve智能体
agent = PlanAndSolveAgent(llm_client=llm)
# 4.提问测试
res = agent.run("一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果?")
运行效果:

4.Reflection
引入一种事后(post-hoc)的自我校正循环,三步循环:执行 -> 反思 -> 优化
import os
import re
import ast
from dotenv import load_dotenv
from openai import OpenAI
from typing import List, Dict, Any, Optional
# 1.加载环境变量
load_dotenv()
# ====================== 第一部分:LLM客户端 HelloAgentsLLM ======================
class HelloAgentsLLM:
def __init__(self, model: str = None, apiKey: str = None, baseUrl: str = None, timeout: int = None):
self.model = model or os.getenv("LLM_MODEL_ID")
apiKey = apiKey or os.getenv("LLM_API_KEY")
baseUrl = baseUrl or os.getenv("LLM_BASE_URL")
timeout = timeout or int(os.getenv("LLM_TIMEOUT", 60))
if not all([self.model, apiKey, baseUrl]):
raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")
self.client = OpenAI(api_key=apiKey, base_url=baseUrl, 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:
if not chunk.choices:
continue
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
# ====================== 第二部分:提示词 + Agent主体 ======================
INITIAL_PROMPT_TEMPLATE = """
你是一位资深的Python程序员。请根据以下要求,编写一个Python函数。
你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
要求: {task}
请直接输出代码,不要包含任何额外的解释。
"""
REFLECT_PROMPT_TEMPLATE = """
你是一位极其严格的代码评审专家和资深算法工程师,对代码的性能有极致的要求。
从三点审查:1.冗余无用代码 2.逻辑错误 3.性能可优化点
# 原始任务:
{task}
# 待审查的代码:
```python
{code}
```
输出固定两段:
1.问题说明:逐条列出现存问题,没有就写无
2.Verdict:只能二选一
CONTINUE:存在问题需要继续修改优化
FINISH:代码无冗余、无bug、无优化空间,结束迭代
"""
REFINE_PROMPT_TEMPLATE = """
你是一位资深的Python程序员。你正在根据一位代码评审专家的反馈来优化你的代码。
# 原始任务:
{task}
# 你上一轮尝试的代码:
{last_code_attempt}
评审员的反馈:
{feedback}
请根据评审员的反馈,生成一个优化后的新版本代码。
你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
请直接输出优化后的代码,不要包含任何额外的解释。
"""
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
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 "FINISH" 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__':
# 1.初始化LLM
llm = HelloAgentsLLM()
# 2.创建Reflection智能体
agent = ReflectionAgent(llm_client=llm)
# 4.提问测试
res = agent.run("编写一个Python函数,找出1到n之间所有的素数 (prime numbers)。")
运行效果:
(ai-agent) PS D:\Project\ai-agent> & D:\conda\.conda\envs\ai-agent\python.exe d:/Project/ai-agent/demo/reflection_agent.py
--- 开始处理任务 ---
任务: 编写一个Python函数,找出1到n之间所有的素数 (prime numbers)。
--- 正在进行初始尝试 ---
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
def is_prime(num: int) -> bool:
"""判断一个数是否为素数。
Args:
num: 需要判断的整数。
Returns:
如果num是素数,返回True;否则返回False。
"""
if num < 2:
return False
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return True
def find_primes(n: int) -> list[int]:
"""找出1到n之间所有的素数。
Args:
n: 上限,包含在结果中。
Returns:
1到n之间的所有素数的列表。
"""
return [i for i in range(2, n + 1) if is_prime(i)]
📝 记忆已更新,新增一条 'execution' 记录。
--- 第 1/3 轮迭代 ---
-> 正在进行反思...
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
问题说明:
1. 代码逻辑正确,没有冗余无用代码。
2. 没有发现逻辑错误。
3. 性能可优化点:
- `find_primes` 函数使用了列表推导式和 `is_prime` 函数逐个判断每个数是否为素数,对于大范围的 `n`(如 n > 10^6),这会导致重复计算,效率较低。
- 可以考虑使用埃拉托斯特尼筛法(Sieve of Eratosthenes)来更高效地生成素数列表。
Verdict:CONTINUE
📝 记忆已更新,新增一条 'reflection' 记录。
-> 正在进行优化...
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
def is_prime(num: int) -> bool:
"""判断一个数是否为素数。
Args:
num: 需要判断的整数。
Returns:
如果num是素数,返回True;否则返回False。
"""
if num < 2:
return False
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return True
def find_primes(n: int) -> list[int]:
"""找出1到n之间所有的素数。
Args:
n: 上限,包含在结果中。
Returns:
1到n之间的所有素数的列表。
"""
if n < 2:
return []
sieve = [True] * (n + 1)
sieve[0] = sieve[1] = False
for i in range(2, int(n ** 0.5) + 1):
if sieve[i]:
for j in range(i * i, n + 1, i):
sieve[j] = False
return [i for i, is_p in enumerate(sieve) if is_p]
📝 记忆已更新,新增一条 'execution' 记录。
--- 第 2/3 轮迭代 ---
-> 正在进行反思...
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
问题说明:
1. 代码中没有冗余无用代码,逻辑结构清晰。
2. 代码中没有逻辑错误,is_prime函数和find_primes函数都正确实现了素数判断和筛选功能。
3. 性能可优化点:
- 在find_primes函数中,外层循环的范围是range(2, int(n ** 0.5) + 1),但可以进一步优化为range(2, int(n ** 0.5) + 1) if n >= 2 else range(0),避免在n < 2时执行不必要的循环。
- 在find_primes函数中,当i*i超过n时,内层循环不会执行,因此可以将外层循环的上限改为range(2, int(n ** 0.5) + 1) if n >= 2 else range(0),以减少不必要的迭代。
Verdict:CONTINUE
📝 记忆已更新,新增一条 'reflection' 记录。
-> 正在进行优化...
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
def is_prime(num: int) -> bool:
"""判断一个数是否为素数。
Args:
num: 需要判断的整数。
Returns:
如果num是素数,返回True;否则返回False。
"""
if num < 2:
return False
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return True
def find_primes(n: int) -> list[int]:
"""找出1到n之间所有的素数。
Args:
n: 上限,包含在结果中。
Returns:
1到n之间的所有素数的列表。
"""
if n < 2:
return []
sieve = [True] * (n + 1)
sieve[0] = sieve[1] = False
for i in range(2, int(n ** 0.5) + 1):
if sieve[i]:
for j in range(i * i, n + 1, i):
sieve[j] = False
return [i for i, is_p in enumerate(sieve) if is_p]
📝 记忆已更新,新增一条 'execution' 记录。
--- 第 3/3 轮迭代 ---
-> 正在进行反思...
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
问题说明:
1. 代码中没有冗余无用代码,逻辑结构清晰。
2. 代码中没有逻辑错误,is_prime函数和find_primes函数都正确实现了素数判断和筛选功能。
3. 性能可优化点:
- 在find_primes函数中,外层循环的范围可以优化为range(2, int(n ** 0.5) + 1),但当前实现已经是最优的。
- 当n非常大时,使用列表sieve可能会占用较多内存,可以考虑使用位数组或其他更节省内存的方式。
Verdict:CONTINUE
📝 记忆已更新,新增一条 'reflection' 记录。
-> 正在进行优化...
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
```python
def is_prime(num: int) -> bool:
"""判断一个数是否为素数。
Args:
num: 需要判断的整数。
Returns:
如果num是素数,返回True;否则返回False。
"""
if num < 2:
return False
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return True
def find_primes(n: int) -> list[int]:
"""找出1到n之间所有的素数。
Args:
n: 上限,包含在结果中。
Returns:
1到n之间的所有素数的列表。
"""
if n < 2:
return []
sieve = [True] * (n + 1)
sieve[0] = sieve[1] = False
for i in range(2, int(n ** 0.5) + 1):
if sieve[i]:
for j in range(i * i, n + 1, i):
sieve[j] = False
return [i for i, is_p in enumerate(sieve) if is_p]
```
📝 记忆已更新,新增一条 'execution' 记录。
--- 任务完成 ---
最终生成的代码:
```python
```python
def is_prime(num: int) -> bool:
"""判断一个数是否为素数。
Args:
num: 需要判断的整数。
Returns:
如果num是素数,返回True;否则返回False。
"""
if num < 2:
return False
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return True
def find_primes(n: int) -> list[int]:
"""找出1到n之间所有的素数。
Args:
n: 上限,包含在结果中。
Returns:
1到n之间的所有素数的列表。
"""
if n < 2:
return []
sieve = [True] * (n + 1)
sieve[0] = sieve[1] = False
for i in range(2, int(n ** 0.5) + 1):
if sieve[i]:
for j in range(i * i, n + 1, i):
sieve[j] = False
return [i for i, is_p in enumerate(sieve) if is_p]
```
```
Reflection 机制是一种典型的“以成本换质量”的策略。它非常适合那些对最终结果的质量、准确性和可靠性有极高要求,且对任务完成的实时性要求相对宽松的场景。例如:生成关键的业务代码或技术报告;在科学研究中进行复杂的逻辑推演;需要深度分析和规划的决策支持系统。
5.总结
ReAct(思考→单次行动→观测→循环)
Plan-and-Solve(先全局全量规划→分步串行执行)
Reflection 反思型(执行→复盘反思→迭代优化,循环改良)
6.架构选型案例
智能家居控制助手选型:优先以 Plan-and-Solve 为主体架构,ReAct 做实时微调
- 智能家居任务具备固定设备清单(灯 / 空调 / 窗帘 / 加湿器)、结构化动作,用户指令可提前拆解成固定步骤,适合前置全局规划。例:用户说「睡前睡眠模式」→ Planner 一次性拆解:①关闭客厅主灯→②窗帘全闭合→③空调调到 26℃睡眠风→④卧室小夜灯开启,一次性生成完整执行清单;
- 家居场景需要流程可控,Plan 预先规划步骤避免设备乱启停;若用纯 ReAct 一步一想,容易频繁来回开关设备;
- 结合用户习惯(历史作息、温湿度偏好),规划器生成计划时可直接嵌入历史习惯参数,一次性完成整套联动。
-
遇到突发动态条件(室温突降、有人开门),用 ReAct 单步实时思考临时修正单步动作,弥补静态计划无法应对实时环境变化的短板。
不适合纯 Reflection:日常家居控制不需要反复迭代优化控制逻辑;Reflection 只适合离线优化控制规则库。
也可以三种架构组合使用:
- Plan 层(全局规划)接收用户一句话指令(回家模式 / 睡眠模式 / 离家模式),一次性拆解整套设备联动步骤,生成基础执行方案;
- ReAct 执行层(实时运行)按计划逐条执行设备控制;遇到环境传感器实时数据变动(开窗导致室温骤降、人体传感器检测房间无人),利用ReAct单步思考临时微调当前单步动作,不改动整体大计划;
- Reflection 反思层(离线每日定时运行,非实时)汇总全天所有控制记录、用户手动修正记录,反思现存问题:
- 例:凌晨空调频繁启停、窗帘开合时间不符合用户作息;反思后优化Planner的规划规则、用户习惯参数,反向更新下一日Plan的生成逻辑。
适用场景
- 核心场景:全屋智能家居长期自动化控制实时:用户语音一键场景联动 + 传感器动态环境适配;离线:每日自动学习用户习惯,持续优化控制方案。
- 延伸场景:酒店客房智能管家、公寓全屋自动化系统。
混合架构优势
- Plan 保证整体流程规整、设备联动有序;
- ReAct 保证实时突发环境灵活应变;
- Reflection 实现智能体自主进化,越用越贴合用户习惯。
7.ReAct改进
7.1 由正则解析→JSON 结构化输出解析
import os
import re
import json
from dotenv import load_dotenv
from openai import OpenAI
from typing import List, Dict, Any
from serpapi import SerpApiClient
# 1.加载环境变量
load_dotenv()
# ====================== 第一部分:LLM客户端 HelloAgentsLLM ======================
class HelloAgentsLLM:
def __init__(self, model: str = None, apiKey: str = None, baseUrl: str = None, timeout: int = None):
self.model = model or os.getenv("LLM_MODEL_ID")
apiKey = apiKey or os.getenv("LLM_API_KEY")
baseUrl = baseUrl or os.getenv("LLM_BASE_URL")
timeout = timeout or int(os.getenv("LLM_TIMEOUT", 60))
if not all([self.model, apiKey, baseUrl]):
raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")
self.client = OpenAI(api_key=apiKey, base_url=baseUrl, 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:
if not chunk.choices:
continue
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
# ====================== 第二部分:Search工具 + ToolExecutor ======================
def search(query: str) -> str:
print(f"🔍 正在执行 [SerpApi] 网页搜索: {query}")
try:
api_key = os.getenv("SERPAPI_API_KEY")
if not api_key:
return "错误:SERPAPI_API_KEY 未在 .env 文件中配置。"
params = {
"engine": "google",
"q": query,
"api_key": api_key,
"gl": "cn",
"hl": "zh-cn",
}
client = SerpApiClient(params)
results = client.get_dict()
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 registerTool(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 getTool(self, name: str) -> callable:
return self.tools.get(name, {}).get("func")
def getAvailableTools(self) -> str:
return "\n".join([
f"- {name}: {info['description']}"
for name, info in self.tools.items()
])
# ====================== 第三部分 ReActAgent主体 ======================
# ReAct 提示词模板:强制纯JSON输出
REACT_PROMPT_TEMPLATE = """
你是可以调用工具的智能助手,可用工具:
{tools}
规则:
1. 你的输出**只允许返回一段标准JSON,禁止任何多余解释、注释、前言后语**;
2. 二选一格式:
①调用工具:{{"thought":"思考内容","action":{{"tool":"工具名","args":"入参内容"}}}}
②结束回答:{{"thought":"思考内容","finish":"最终回答文本"}}
问题:{question}
历史交互:{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 = []
# 【移入类内的JSON解析函数】
def _parse_json_output(self, text: str):
# 预处理:剔除前后多余文本,只提取{}包裹的json
start = text.find("{")
end = text.rfind("}")
if start == -1 or end == -1:
print("无法找到JSON大括号")
return None, None, None
json_str = text[start:end+1].strip()
try:
data = json.loads(json_str)
thought = data.get("thought", "")
finish_content = data.get("finish")
action_info = data.get("action")
return thought, finish_content, action_info
except json.JSONDecodeError as e:
print(f"JSON解析失败:{e},原始片段:{json_str}")
return None, None, None
def run(self, question: str):
"""运行ReAct智能体来回答一个问题。"""
self.history = []
current_step = 0
while current_step < self.max_steps:
current_step += 1
print(f"\n--- 第 {current_step} 步 ---")
tools_desc = self.tool_executor.getAvailableTools()
history_str = "\n".join(self.history) if self.history else "无"
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
# JSON解析
thought, finish_content, action_info = self._parse_json_output(response_text)
if not thought:
print("解析失败,跳过本轮")
continue
# 分支1:直接结束输出答案
if finish_content is not None:
print(f"\n🎉最终答案:\n{finish_content}")
return finish_content
# 分支2:调用工具
if not isinstance(action_info, dict):
print("Action格式错误,无有效工具信息")
continue
tool_name = action_info.get("tool")
tool_input = action_info.get("args")
if not tool_name or not tool_input:
print("工具名或参数为空")
continue
print(f"🎬 行动: {tool_name}[{tool_input}]")
tool_func = self.tool_executor.getTool(tool_name)
if not tool_func:
observation = f"错误:未找到名为 '{tool_name}' 的工具。"
else:
observation = tool_func(tool_input)
print(f"👀 观察: {observation}")
# 写入历史记录
self.history.append(f"Thought:{thought}\nAction:{tool_name}[{tool_input}]\nObservation:{observation}")
print(f"\n已达到最大{self.max_steps}步数,流程终止。")
return None
# ====================== 程序入口:启动智能体 ======================
if __name__ == '__main__':
# 1.初始化LLM
llm = HelloAgentsLLM()
# 2.注册搜索工具
tool_exec = ToolExecutor()
desc = "网页搜索引擎,用于查询实时新闻、产品参数、外部资料,知识库不存在的信息全部使用Search"
tool_exec.registerTool("Search", desc, search)
# 3.创建ReAct智能体
agent = ReActAgent(llm_client=llm, tool_executor=tool_exec, max_steps=5)
# 4.提问测试
res = agent.run("华为最新手机型号及主要卖点")
运行效果:
(ai-agent) PS D:\Project\ai-agent> & D:\conda\.conda\envs\ai-agent\python.exe d:/Project/ai-agent/demo/new_react_agent.py
工具 'Search' 已注册。
--- 第 1 步 ---
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
{"thought":"需要搜索华为最新的手机型号及其主要卖点","action":{"tool":"Search","args":"华为 最新手机型号 主要卖点"}}
🎬 行动: Search[华为 最新手机型号 主要卖点]
🔍 正在执行 [SerpApi] 网页搜索: 华为 最新手机型号 主要卖点
👀 观察: [1] 华为手机- 华为官网
探索并选购华为最新手机,了解Mate 系列、Pura 系列、Pocket 系列、nova 系列、畅享系列及相关配件,体验鸿蒙AI 、影像、通信等功能。
[2] 2026年618华为手机各系列介绍及选购指南( ...
华为今年新机都只有纯血鸿蒙系统版本可选,介意者可以考虑荣耀等其他品牌。 近期更新:已更新6月1日发布的华为nova16系列。已更新4月20日发布的华为Pura90系列。已更新 ...
[3] HUAWEI Mate X6 - 华为官网
全新四摄, 个个都是专业大咖 ; 长焦微距摄像头. 4800 万像素. 4 倍光学变焦 · F3.0 光圈. OIS 光学防抖. RYYB 超感光传感器 ; 超广角摄像头. 4000 万像素. F2.2 光圈. RYYB ...
--- 第 2 步 ---
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
{"thought":"需要进一步搜索华为最新手机型号及主要卖点","action":{"tool":"Search","args":"华为 最新手机型号 主要卖点 2023"}}
🎬 行动: Search[华为 最新手机型号 主要卖点 2023]
🔍 正在执行 [SerpApi] 网页搜索: 华为 最新手机型号 主要卖点 2023
👀 观察: [1] 华为手机- 华为官网
探索并选购华为最新手机,了解Mate 系列、Pura 系列、Pocket 系列、nova 系列、畅享系列及相关配件,体验鸿蒙AI 、影像、通信等功能。
[2] 2026年618华为手机各系列介绍及选购指南( ...
华为今年新机都只有纯血鸿蒙系统版本可选,介意者可以考虑荣耀等其他品牌。 近期更新:已更新6月1日发布的华为nova16系列。已更新4月20日发布的华为Pura90系列。已更新 ...
[3] 【华为2023年手机大全】华为2023年手机报价及图片大全
2023年手机 · HUAWEI Mate 60 Pro(12GB/256GB) · 华为Mate X5 (12GB/256GB) · HUAWEI Mate 60(12GB/256GB) · HUAWEI P60(8GB+256GB) · 华为Mate 60 Pro+(16GB/512GB) · HUAWEI ...
--- 第 3 步 ---
🧠 正在调用 qwen-turbo 模型...
✅ 大语言模型响应成功:
{"thought":"通过搜索结果可知,华为最新手机型号包括Mate系列、Pura系列、Pocket系列、nova系列和畅享系列。主要卖点包括鸿蒙AI、影像、通信等功能,以及具体型号如HUAWEI Mate X6和HUAWEI Pura90系列等。","finish":"华为最新手机型号包括Mate系列、Pura系列、Pocket系列、nova系列和畅享系列。主要卖点包括鸿蒙AI、影像、通信等功能,具体型号如HUAWEI Mate X6(配备四摄,4800万像素长焦微距摄像头等)和HUAWEI Pura90系列。"}
🎉最终答案:
华为最新手机型号包括Mate系列、Pura系列、Pocket系列、nova系列和畅享系列。主要卖点包括鸿蒙AI、影像、通信等功能,具体型号如HUAWEI Mate X6(配备四摄,4800万像素长焦微距摄像头等)和HUAWEI Pura90系列。
7.2 添加工具
借助 eval 安全限制数学表达式(只允许数字、四则、括号),接收数学字符串 "(123+456)*789/12" 直接运算
- 新增全局错误计数器:连续3 次工具名错误 / 参数非法,在下一轮 Prompt 追加错误历史提醒;
- Prompt 自动注入历史错误记录,告诉模型之前错在哪、可用工具只有
Search、Calculator; - 超限后强制提示禁止编造不存在工具。
import os
import re
import json
from dotenv import load_dotenv
from openai import OpenAI
from typing import List, Dict, Any
from serpapi import SerpApiClient
load_dotenv()
# ====================== LLM客户端 ======================
class HelloAgentsLLM:
def __init__(self, model: str = None, apiKey: str = None, baseUrl: str = None, timeout: int = None):
self.model = model or os.getenv("LLM_MODEL_ID")
apiKey = apiKey or os.getenv("LLM_API_KEY")
baseUrl = baseUrl or os.getenv("LLM_BASE_URL")
timeout = timeout or int(os.getenv("LLM_TIMEOUT", 60))
if not all([self.model, apiKey, baseUrl]):
raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")
self.client = OpenAI(api_key=apiKey, base_url=baseUrl, 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:
if not chunk.choices: continue
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
# ====================== 工具定义:Search + 新增Calculator计算器 ======================
def search(query: str) -> str:
print(f"🔍 正在执行 [SerpApi] 网页搜索: {query}")
try:
api_key = os.getenv("SERPAPI_API_KEY")
if not api_key: return "错误:SERPAPI_API_KEY未配置"
params = {"engine":"google","q":query,"api_key":api_key,"gl":"cn","hl":"zh-cn"}
res = SerpApiClient(params).get_dict()
if "organic_results" in res and res["organic_results"]:
lst = [f"[{i+1}] {x['title']}\n{x['snippet']}" for i,x in enumerate(res["organic_results"][:3])]
return "\n\n".join(lst)
return "未查询到内容"
except Exception as e:
return f"搜索异常:{str(e)}"
def calculator(expr: str) -> str:
"""数学计算器:输入四则运算字符串,例如(123+456)*789/12"""
print(f"🧮 执行计算器运算:{expr}")
# 白名单:只允许数字、四则、括号
if not re.fullmatch(r"[\d\.\+\-\*\/\(\)\s]+", expr):
return f"表达式非法,仅支持数字 +-*/(),输入:{expr}"
try:
res = eval(expr, {"__builtins__": None}, {})
return f"计算结果 = {res}"
except Exception as e:
return f"计算失败:{str(e)},检查表达式格式"
class ToolExecutor:
def __init__(self):
self.tools: Dict[str, Dict[str, Any]] = {}
def registerTool(self, name: str, desc: str, func):
self.tools[name] = {"desc":desc,"func":func}
print(f"工具 '{name}' 已注册。")
def getTool(self,name):
return self.tools.get(name,{}).get("func")
def getAvailableTools(self):
return "\n".join([f"-{k}:{v['desc']}" for k,v in self.tools.items()])
def getAllToolNames(self):
return list(self.tools.keys())
# ====================== ReAct模板({{}}转义JSON大括号) ======================
BASE_PROMPT = """
你是工具调用助手,可用工具列表:
{tools}
规则:
1. 仅输出JSON,无任何多余文字;
2. 二选一:
调用工具:{{"thought":"思考","action":{{"tool":"工具名","args":"参数"}}}}
结束答案:{{"thought":"思考","finish":"答案"}}
{error_tip}
问题:{question}
历史:{history}
"""
class ReActAgent:
def __init__(self, llm_client, tool_executor, max_steps=5):
self.llm = llm_client
self.tool = tool_executor
self.max_steps = max_steps
self.history = []
self.err_count = 0 # 连续错误计数
self.err_msg = "" # 错误提醒文案
def _parse_json_output(self, text):
start = text.find("{")
end = text.rfind("}")
if start == -1 or end == -1:
return None,None,None
try:
d = json.loads(text[start:end+1])
return d.get("thought"),d.get("finish"),d.get("action")
except:
return None,None,None
def run(self, question):
self.history.clear()
self.err_count = 0
self.err_msg = ""
tools_text = self.tool.getAvailableTools()
tool_names = self.tool.getAllToolNames()
for step in range(1,self.max_steps+1):
print(f"\n---第{step}步---")
his_str = "\n".join(self.history) if self.history else "无"
# 错误超限则注入错误提示
err_tip = f"【重要提醒】你之前多次调用错误工具,可用工具只有{tool_names},禁止自定义不存在工具!{self.err_msg}" if self.err_count >=3 else ""
prompt = BASE_PROMPT.format(tools=tools_text,error_tip=err_tip,question=question,history=his_str)
resp = self.llm.think([{"role":"user","content":prompt}])
if not resp: continue
thought,finish,action = self._parse_json_output(resp)
if not thought:
self.err_count +=1
self.err_msg += f"第{step}轮JSON格式错误\n"
continue
if finish is not None:
print(f"🎉最终答案:{finish}")
return finish
# 解析工具
if not isinstance(action,dict):
self.err_count +=1
self.err_msg += f"第{step}轮action格式错误\n"
continue
t_name = action.get("tool")
t_args = action.get("args")
# 工具名校验:不存在=错误
if t_name not in tool_names:
self.err_count +=1
self.err_msg += f"错误:不存在工具[{t_name}],合法工具:{tool_names}\n"
continue
# 合法工具,重置错误计数
self.err_count = 0
self.err_msg = ""
print(f"🎬行动:{t_name}[{t_args}]")
func = self.tool.getTool(t_name)
obs = func(t_args)
print(f"👀观察:{obs}")
self.history.append(f"Thought:{thought}\nAction:{t_name}[{t_args}]\nObs:{obs}")
print("达到最大步数终止")
# ================== 入口 ==================
if __name__=="__main__":
llm = HelloAgentsLLM()
te = ToolExecutor()
# 注册两个工具
te.registerTool("Search","联网搜索实时资讯、产品信息",search)
te.registerTool("Calculator","数学四则运算,传入运算字符串,例:(123+456)*789/12",calculator)
agent = ReActAgent(llm,te,max_steps=5)
# 测试数学题
agent.run("计算 (123 + 456) × 789 ÷ 12 = ?")
运行效果:

如果工具扩充至 50~100 个,如果按原有方案,全部工具一次性塞进 Prompt,会导致上下文超限,信息冗余,干扰 LLM 思考、选错工具,推理变慢。
可以采用:
(1)工具分类 + 路由检索(分类树)
把工具按领域分组:
- 数学类:Calculator、求平方根、解方程
- 搜索类:通用搜索、新闻搜索、商品搜索
- 设备控制类:开灯、开空调、窗帘控制第一步先用分类器筛选领域,只把本领域工具放入 Prompt,不用全量灌入。
(2)向量库召回(工业主流)
- 每个工具名称 + 描述转为 Embedding 存入向量数据库(FAISS/Chroma);
- 根据用户问题语义相似度Top-K 召回少量相关工具(只召回 3~5 个);
- 仅把召回后的工具描述拼入 Prompt,海量工具不用全传给 LLM。
(3)工具注册中心 + 懒加载
搭建统一工具注册中心,统一管理名称、描述、参数、分类;运行时按需动态加载,不一次性加载全部函数。
(4)两级 Agent 架构:路由 Agent + 执行 Agent
- 路由 Agent:接收问题→筛选所需工具子集;
- 执行 ReAct Agent:只在筛选后的小工具集里做思考调用。
8.Plan-and-Solve改进
8.1 静态→动态重规划 机制设计
原版 Plan-and-Solve:一次性全局规划→顺序串行执行,计划固化不变;某一步执行失败(参数错误、接口异常、信息缺失)、结果不符合预期时,后续步骤全部无效,无法修正任务方案。
(1)执行结果校验器 Checker
每一步结束后自动校验 3 类异常,满足任一即触发重规划:
-
工具执行报错(无搜索结果、接口异常、参数非法);
-
返回内容无法支撑后续步骤(信息缺失,如查不到北京→上海机票价格);
-
结果与任务目标逻辑冲突(如订酒店选了广州,偏离上海目的地)。
(2)重规划决策器
-
局部重规划(优先):仅删除失败步骤,基于已完成步骤历史,重新生成失败步骤 + 后续剩余步骤清单,已成功执行的步骤保留不再重做;
-
全局重规划:连续 2 次局部重规划仍然失败、前置关键信息全部缺失 → 清空全计划,基于完整历史重新生成整套全新任务列表。
(3)重规划专用 Prompt
复用 Planner,但入参增加:已完成步骤、失败步骤 + 失败原因,引导模型针对性改计划,示例 Prompt 片段:
已有已成功执行步骤:[步骤1:查询北京-上海航班日期]
执行失败步骤:[步骤2:预订机票],失败原因:当日无商务舱余票
请只对【未完成+失败步骤】重新生成新子计划,已完成内容不再重复。
8.2 对比 Plan-and-Solve 与 ReAct:在处理"预订一次从北京到上海的商务旅行(包括机票、酒店、租车)"这样的任务时,哪种范式更合适?为什么?
优先选用 Plan-and-Solve + 动态重规划增强版,不适用原生 ReAct
1.Plan-and-Solve 优势
- 商旅是多子任务固定结构化流程:机票→酒店→租车是固定依赖时序,前置完成才能后置,全局规划可以一次性梳理完整业务链路,不会遗漏环节;原生 ReAct 一步一想极易漏订酒店 / 租车、来回反复调用工具。
- 便于流程管控与异常回溯:整体计划清单可直观查看任务进度,某一步订房失败,依靠上面的动态重规划局部修正,整体任务链路清晰。
- 可前置约束规则:规划阶段就限定「商务舱、市中心酒店、5 座商务车」等需求,统一嵌入全计划。
2. 原生 ReAct 短板
ReAct 单步局部思考、边做边想,缺少全局视图:
- 第一步查机票,做完后容易遗忘酒店 / 租车任务;
- 频繁无意义来回搜索,碎片化执行,不适合多环节串联的长流程任务。
8.3 尝试设计一个"分层规划"系统:先生成高层次的抽象计划,然后针对每个高层步骤再生成详细的子计划。这种设计有什么优势?
用户原始需求
↓
【一级高层规划:粗粒度大阶段(抽象任务)】
例:1.出行票务办理;2.目的地住宿预订;3.落地本地用车安排
↓
【二级细分规划:针对每一个高层步骤,单独生成精细化执行子步骤】
高层1「出行票务办理」→子步骤:①筛选北京-上海商务航班 ②核对舱位与价格 ③提交机票预订 高层2「住宿预订」→子步骤:①筛选上海市区商务酒店 ②核对入住退房日期 ③完成酒店下单
优势:
1. 降低单次规划复杂度,规避超长计划出错
一次性生成全量细粒度步骤,任务过长时 LLM 极易遗漏细节;先拆大模块、再逐个拆细节,拆分难度逐层降低,计划准确率大幅提升。
2. 模块化独立重规划,耦合度低
某一大模块执行失败(比如租车环节全部异常),仅针对该高层模块重新细化子计划,其他机票、住宿模块不受影响,不用改动其他模块步骤。
3. 便于工具按需绑定、分域管理
高层步骤天然按领域划分:票务类、住宿类、用车类;对应匹配专属工具集,工具数量上百时可按分层路由筛选工具,适配海量工具场景。
4. 适配复杂超长任务,支持任务嵌套
例如 7 天跨省商务差旅(多城流转:北京→上海→杭州),顶层只拆 3 个城市行程,每个城市再分别拆机票 + 酒店 + 本地用车,天然适配嵌套式复杂业务。
9.Reflection改进
9.1 在代码生成案例中,不同阶段使用的是同一个模型。如果使用两个不同的模型(例如,用一个更强大的模型来做反思,用一个更快的模型来做执行),会带来什么影响?
好处:
- 省钱提速:执行是高频动作,写代码、写论文初稿反复生成内容,用便宜小模型,吞吐快、token 成本低;反思只每轮跑一次,用量少,用贵的强模型成本可控。
- 反思深度变强:小模型自己看不出深层漏洞,比如代码隐性性能缺陷、论文创新不足;更强的反思模型能站在专家视角,揪出小模型注意不到的问题,迭代轮次变少。
- 分工贴合模型特性:小模型擅长快速落地产出内容,大模型擅长批判性审阅、高标准找问题。
缺点:
- 能力断层:反思模型提的优化要求太高,执行小模型能力不够,改不出来,卡在迭代循环。比如反思要求用高阶筛法优化,小模型一直写低效试除代码。
- 行文风格割裂:两个模型用词、行文逻辑不一样,反思建议和执行产出风格不匹配,越改越乱。
- 架构变复杂:需要维护两套模型配置、两套 prompt,还要加降级兜底逻辑。
9.2 Reflection 机制的终止条件是"反馈中包含无需改进"或"达到最大迭代次数"。这种设计是否合理?能否设计一个更智能的终止条件?
LLM 很容易偷懒,随便一句 “无需改进” 就提前结束,但内容还有不少瑕疵;固定最大迭代,简单任务白白多迭代好几轮浪费资源,复杂任务没优化完就被强制终止。
优化三层终止规则(满足任一结束):
- 连续两轮反思无新增问题(核心收敛判断):连续两次评审,指出的缺陷完全一致,说明已经到模型优化上限,没法继续改进;
- 多维打分达标:反思时对各项指标量化打分,总分达标直接结束;
- 最大迭代次数兜底:防止无限死循环,极端异常场景保底。
额外约束:前 2 轮禁止直接写 “无需优化”,强制必须找问题,杜绝模型敷衍提前跑路。
9.3 假设你要搭建一个"学术论文写作助手",它能够生成初稿并不断优化论文内容。请设计一个多维度的Reflection机制,从段落逻辑性、方法创新性、语言表达、引用规范等多个角度进行反思和改进。
整体循环:初稿生成(执行模型)→五维度分项反思(强评审模型)→针对性修改→收敛判断一共 5 个评审维度,对标期刊审稿逻辑:
- 整体结构 & 段落逻辑 检查:章节排布是否符合论文规范、论点论据匹配度、段落衔接有无逻辑断层、论证有没有跳步骤;优化:重构章节顺序、补充缺失论证、理顺段落。
- 方法与创新性 检查:有没有清晰写明研究创新点、是否和过往相关工作做对比、实验设计能不能支撑创新结论;优化:补充相关工作综述、细化创新优势、完善实验说明。
- 学术语言规范 检查:口语化表述、语句冗余啰嗦、用词不严谨;优化:学术书面改写、精简冗余文字。
- 参考文献 & 引用格式 检查:引用格式(GB/T7714/IEEE)不统一、正文引用标注错位、缺领域关键文献、引用老旧无关文献;优化:统一格式、增补必要引文、替换无效参考文献。
- 实验与数据严谨度 检查:公式书写错误、实验描述模糊、结论夸大无法复现;优化:修正公式、细化实验参数、客观收敛结论。
10.提示词改进
在提示词中加入 few-shot 示例往往能显著提升模型对特定格式的遵循能力。用真实样例具象化格式规则,相比纯文字描述规则,LLM 更容易模仿样例的输出范式,大幅降低格式解析脆弱性。
REACT_PROMPT = """
可用工具:
- Search:联网查询实时信息
- Calculator:数学四则运算
规则:仅输出纯JSON,无多余文字
【示例1(调用计算器)】
用户问题:计算10+20*3
输出:{"thought":"需要使用计算器完成算术运算","action":{"tool":"Calculator","args":"10+20*3"}}
【示例2(结束回答)】
用户问题:10+20*3结果是70
输出:{"thought":"已有答案,无需调用工具","finish":"10+20×3=70"}
{tools}
问题:{question}
历史:{history}
"""
11.智能体设计
某电商初创公司现在希望使用"客服智能体"来代替真人客服实现降本增效,它需要具备以下功能:
a. 理解用户的退款申请理由
b. 查询用户的订单信息和物流状态
c. 根据公司政策智能地判断是否应该批准退款
d. 生成一封得体的回复邮件并发送至用户邮箱
e. 如果判断决策存在一定争议(自我置信度低于阈值),能够进行自我反思并给出更审慎的建议
此时作为该产品的负责人:
11.1 选择本章的哪种范式(或哪些范式的组合)作为系统的核心架构?
Plan-and-Solve 主体 + ReAct 局部执行 + Reflection 争议复盘(三范式混合)
Plan-and-Solve(主框架,全局规划)用户发起退款诉求后,一次性生成固定处理流程清单:①提取退款原因→②调用工具查订单 + 物流→③对照退款政策判定结果→④无争议直接生成回复邮件;标准化售后是固定流水线,用前置规划保证流程不会缺步骤、漏查订单,适配绝大多数常规退款单。
ReAct(分支动态执行)查询环节偶尔出现异常:订单号错误、物流接口返回空、数据缺失,依靠 ReAct 单步动态重试、引导用户补充订单信息,灵活处理突发数据异常。
Reflection(争议场景专用):决策置信度不足、退款存在争议时触发反思;反思模块重新复盘:退款理由、订单时效、平台规则,修正初审结论、输出审慎方案;只有存疑订单才走反思,常规订单不走迭代,节省算力。
总结:常规退款走静态规划流水线提速;异常查询用 ReAct 灵活兜底;疑难争议单启用 Reflection 迭代复审。
11.2 这个系统需要哪些工具?请列出至少3个工具及其功能描述。
- 订单查询工具 QueryOrderTool 入参:用户手机号 / 订单编号;功能:调取订单下单时间、商品品类、实付金额、签收状态、售后记录,支撑退款合规校验。
- 物流查询工具 QueryLogisticsTool 入参:快递单号;功能:获取物流轨迹、签收时间、是否丢件 / 破损 / 超时未送达,用来核验用户 “未收到货、货品损坏” 类退款理由真实性。
- 邮件发送工具 SendEmailTool 入参:收件邮箱、邮件正文、审批结果;功能:按照模板生成规范售后邮件,自动触达用户邮箱,完成结果告知。
- 退款政策检索工具 SearchRefundRuleTool(备选第四个)入参:退款场景(七天无理由 / 破损 / 超时);功能:检索公司现行退款规章制度,约束智能体判决不能违背平台规则。
11.3 如何设计提示词来确保智能体的决策既符合公司利益,又能保持对用户的友好态度?
三段式 Prompt 结构
角色定位层
你是电商售后智能客服,优先严格遵守公司官方退款政策、控制不合理退款损耗(保障公司合法利益);沟通全程礼貌温和、换位思考,不生硬拒绝用户诉求(保障用户体验)。
规则约束层
①满足政策明文退款条件:爽快同意退款,安抚用户;
②不符合退款条例:禁止随意赔付,委婉说明拒绝依据,同时提供替代方案(换货、优惠券补偿等),减少用户投诉;
③存疑无法判定时,标记低置信度,进入反思复审流程,不擅自决策。
输出格式层
固定输出:审批结论 + 政策依据 + 邮件正文,方便后续调用发邮件工具。
11.4 这个产品上线后可能面临哪些风险和挑战?如何通过技术手段来降低这些风险?
风险 1:智能体误判,不该退的订单误批准退款,造成平台资金损失
技术对策:
引入置信度打分机制,低分自动进入 Reflection 二次评审,高分常规自动审批;
接入人工兜底开关:大额订单(如 500 元以上)强制流转人工复核,不允许智能体直接退款。
风险 2:用户描述五花八门,智能体理解退款理由错误,查错订单、判错场景
技术对策
工具层增加订单校验:用户信息不匹配时,启用 ReAct 交互式追问,引导用户补充正确订单号;
Prompt 内置少样本示例:标注多类退款典型话术(破损、漏发、个人不想要),提升理由分类准确率。
风险 3:话术生硬,一刀切拒绝退款引发大量用户投诉、差评
技术对策
在提示词内置多套安抚话术库,拒绝退款时强制附带备选补偿方案(优惠券 / 换货),Reflection 复审时重点优化回复文案。
风险 4:接口故障(订单 / 物流服务宕机)导致流程卡死
技术对策
ReAct 增加异常重试逻辑,连续 3 次工具调用失败后,自动生成告知邮件并转交人工客服处理。
风险 5:规则迭代(公司更新退款政策)后智能体判决滞后
技术对策
把退款规则存入知识库,工具实时拉取最新规则,无需修改 Prompt 即可同步更新政策。
&spm=1001.2101.3001.5002&articleId=161628292&d=1&t=3&u=3b0e6a4d6b104b24bbeef7671749fd8f)
1214

被折叠的 条评论
为什么被折叠?



