作者:逆境不可逃
技术永无止境
希望我的内容可以帮助到你!!!!!
大家吼 ! 我是 逆境不可逃 今天给大家带来文章**《Hello-Agents 第二部分-第四章总结:智能体经典范式构建》.**
Hello-Agents 官方地址:datawhalechina/hello-agents: 📚 《从零开始构建智能体》------从零开始的智能体原理与实践教程
1. 本章总览
现代智能体的关键,不只是 "让 LLM 回答问题",而是让 LLM 能够:
- 理解用户目标;
- 拆解任务;
- 根据中间结果调整路径;
- 调用外部工具获取信息或执行动作;
- 在必要时自我检查、修正和优化。
第四章围绕三种经典智能体范式展开:
| 范式 | 核心组织方式 | 适合任务 | 主要风险 |
|---|---|---|---|
ReAct |
思考、行动、观察交替循环 | 需要工具、搜索、API、动态探索的任务 | 多轮调用成本高,格式解析脆弱,可能循环 |
Plan-and-Solve |
先生成完整计划,再逐步执行 | 逻辑路径较清晰、可拆解的复杂任务 | 初始计划一旦错,后续会沿着错误计划执行 |
Reflection |
执行、反思、优化迭代 | 高质量代码、报告、推理、决策支持 | 调用成本和延迟显著增加,提示词设计更复杂 |
可以把三者理解成三种不同的 "思考与行动编排方式":
ReAct:
Thought -> Action -> Observation -> Thought -> ...
Plan-and-Solve:
Question -> Plan -> Step 1 -> Step 2 -> ... -> Final Answer
Reflection:
Draft -> Critique -> Refine -> Critique -> ... -> Final Output
2. 基础环境与 LLM 封装
本章所有范式都依赖一个统一的 LLM 客户端。这样做的工程价值是:把模型调用细节隔离起来,让后续 Agent 代码只关心 "传入 messages,拿到文本结果"。
2.1 依赖与配置
pip install openai python-dotenv
.env 示例:
LLM_API_KEY="YOUR-API-KEY" 例如 "sk-abcdeewe465512wewewa"
LLM_MODEL_ID="YOUR-MODEL"
LLM_BASE_URL="YOUR-URL"
LLM_TIMEOUT=60
2.2 LLM 客户端
下面的封装支持任何兼容 OpenAI Chat Completions 接口的服务。默认使用流式输出,但最终仍返回完整字符串,方便 Agent 解析。
import os
from typing import Dict, List, Optional
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
class HelloAgentsLLM:
"""统一封装 LLM 调用,屏蔽模型服务、API Key、Base URL 等细节。"""
def __init__(
self,
model: Optional[str] = None,
api_key: Optional[str] = None,
base_url: Optional[str] = None,
timeout: Optional[int] = 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")
timeout = timeout or int(os.getenv("LLM_TIMEOUT", "60"))
if not all([self.model, api_key, base_url]):
raise ValueError("LLM_MODEL_ID、LLM_API_KEY、LLM_BASE_URL 必须配置。")
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:
"""调用模型并返回完整响应文本。"""
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
)
chunks = []
for chunk in response:
content = chunk.choices[0].delta.content or ""
print(content, end="", flush=True)
chunks.append(content)
print()
return "".join(chunks)
这个基础类有三个重点:
- 配置集中:模型 ID、密钥、服务地址不写死在代码里;
- 接口统一:后续所有 Agent 都只依赖
think(messages); - 温度默认设为
0:教学范式里更需要稳定、可复现、易解析的输出。
3. ReAct:边想边做的动态循环
ReAct 是 Reasoning and Acting 的缩写,核心是把推理和行动显式交替起来。模型不是一次性给出答案,而是每轮输出:
Thought:分析当前情况,决定下一步;Action:调用某个工具,或输出最终答案;Observation:工具执行结果,回填到上下文中。
3.1 ReAct 的核心循环
用户问题
↓
LLM 生成 Thought + Action
↓
系统解析 Action
↓
如果 Action 是工具调用:执行工具,得到 Observation
↓
把 Action 和 Observation 追加到 history
↓
进入下一轮
ReAct 的价值在于:推理指导行动,行动结果又修正推理。它特别适合处理模型自身知识库无法直接覆盖的任务,例如实时搜索、计算、数据库 / API 查询。
3.2 工具的三要素
一个可被 Agent 使用的工具至少要包含:
| 要素 | 作用 |
|---|---|
Name |
工具唯一名称,供 Action 调用,例如 Search |
Description |
给 LLM 看的自然语言说明,决定模型是否会正确选择工具 |
Execution Logic |
真正执行任务的函数 |
书中使用 SerpApi 做搜索工具。安装:
pip install google-search-results
**.env**增加:
SERPAPI_API_KEY="YOUR_SERPAPI_API_KEY"
搜索工具示例:
import os
from serpapi import SerpApiClient
def search(query: str) -> str:
"""基于 SerpApi 的网页搜索工具,优先返回直接答案或知识图谱信息。"""
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",
}
try:
results = SerpApiClient(params).get_dict()
if "answer_box_list" in results:
return "\n".join(map(str, results["answer_box_list"]))
answer_box = results.get("answer_box", {})
if answer_box.get("answer"):
return answer_box["answer"]
knowledge_graph = results.get("knowledge_graph", {})
if knowledge_graph.get("description"):
return knowledge_graph["description"]
organic_results = results.get("organic_results", [])
if organic_results:
snippets = []
for i, item in enumerate(organic_results[:3], start=1):
title = item.get("title", "")
snippet = item.get("snippet", "")
snippets.append(f"[{i}] {title}\n{snippet}")
return "\n\n".join(snippets)
return f"没有找到关于 {query!r} 的信息。"
except Exception as exc:
return f"搜索时发生错误:{exc}"
3.3 工具执行器
当工具变多时,不能让 Agent 到处写if tool_name == ...。更好的方式是用一个注册中心统一管理工具。
from typing import Any, Callable, Dict
class ToolExecutor:
"""负责注册、描述和调用工具。"""
def __init__(self):
self.tools: Dict[str, Dict[str, Any]] = {}
def register_tool(self, name: str, description: str, func: Callable[[str], str]):
if name in self.tools:
print(f"警告:工具 {name!r} 已存在,将被覆盖。")
self.tools[name] = {"description": description, "func": func}
def get_tool(self, name: str) -> Callable[[str], str] | None:
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()
)
工具注册示例:
tool_executor = ToolExecutor()
tool_executor.register_tool(
"Search",
"网页搜索引擎。适合回答时事、事实查询、模型知识库中找不到的信息。",
search,
)
3.4 ReAct 提示词模板
ReAct 能跑起来,关键在于让模型稳定输出可解析结构。
REACT_PROMPT_TEMPLATE = """
你是一个有能力调用外部工具的智能助手。
可用工具如下:
{tools}
请严格按照以下格式回应:
Thought: 你的思考过程,用于分析问题、拆解任务和规划下一步行动。
Action: 你决定采取的行动,必须是以下格式之一:
- {{tool_name}}[{{tool_input}}]: 调用一个可用工具。
- Finish[最终答案]: 当你认为已经获得最终答案时。
当你收集到足够信息后,必须使用 Finish[最终答案] 输出最终答案。
Question: {question}
History:
{history}
"""
这里包含四类信息:
- 角色定义:告诉模型它是 "可调用工具的助手";
- 工具清单:告诉模型有哪些工具、何时用;
- 输出格式:让程序能解析
Thought和Action; - 动态上下文:注入用户问题和历史
Action/Observation。
3.5 ReActAgent 实现
import re
class ReActAgent:
"""最小可用的 ReAct 智能体。"""
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: list[str] = []
def run(self, question: str) -> str | None:
self.history = []
for step in range(1, self.max_steps + 1):
print(f"\n--- 第 {step} 步 ---")
prompt = REACT_PROMPT_TEMPLATE.format(
tools=self.tool_executor.get_available_tools(),
question=question,
history="\n".join(self.history) or "无",
)
response_text = self.llm_client.think(
messages=[{"role": "user", "content": prompt}]
)
if not response_text:
print("错误:LLM 未返回有效响应。")
return None
thought, action = self._parse_output(response_text)
if thought:
print(f"思考:{thought}")
if not action:
print("警告:未解析到有效 Action,流程终止。")
return None
if action.startswith("Finish"):
match = re.match(r"Finish\[(.*)\]", action, re.DOTALL)
final_answer = match.group(1).strip() if match else action
print(f"最终答案:{final_answer}")
return final_answer
tool_name, tool_input = self._parse_action(action)
if not tool_name or tool_input is None:
observation = f"错误:Action 格式无效:{action}"
else:
tool = self.tool_executor.get_tool(tool_name)
if not tool:
observation = f"错误:未找到名为 {tool_name!r} 的工具。"
else:
observation = tool(tool_input)
print(f"观察:{observation}")
self.history.append(f"Action: {action}")
self.history.append(f"Observation: {observation}")
print("达到最大步数,流程终止。")
return None
def _parse_output(self, text: str) -> tuple[str | None, str | None]:
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) -> tuple[str | None, str | None]:
match = re.match(r"(\w+)\[(.*)\]", action_text, re.DOTALL)
if not match:
return None, None
return match.group(1), match.group(2).strip()
运行示例:
llm = HelloAgentsLLM()
tools = ToolExecutor()
tools.register_tool(
"Search",
"网页搜索工具。适合查询最新事实、新闻、产品信息和模型不知道的内容。",
search,
)
agent = ReActAgent(llm, tools, max_steps=5)
agent.run("华为最新的手机是哪一款?它的主要卖点是什么?")
3.6 ReAct 的优点、局限和调试
优点:
- 可解释:
Thought展示每一步为什么这么做; - 动态纠错:每轮
Observation都能改变下一步行动; - 工具协同:把 LLM 的语言推理和搜索、计算、API 等外部能力接起来。
局限:
- 强依赖模型能力:模型如果不遵守格式,解析会失败;
- 成本和延迟高:每一步通常都要调用一次 LLM;
- 提示词脆弱:格式说明稍有变化,输出可能不稳定;
- 可能原地循环:步进式决策缺少全局规划,容易被局部观察牵引。
调试顺序:
- 打印完整 prompt,确认工具、问题、历史记录是否正确注入;
- 打印模型原始输出,判断是模型没按格式输出,还是解析器有问题;
- 检查
tool_input是否符合工具函数预期; - 检查
observation是否足够清晰,能被模型继续利用; - 在 prompt 中加入 few-shot 示例,强化
Thought -> Action -> Observation格式; - 对需要稳定解析的场景,优先使用 JSON Schema 或函数调用,而不是纯正则。
4. Plan-and-Solve:先规划,再执行
Plan-and-Solve 把任务分为两个阶段:
Plan:先把问题拆成结构化步骤;Solve:再按照计划逐步执行。
它解决的是 "模型一边想一边答时容易偏离轨道" 的问题。相比 ReAct,它更强调全局目标一致性。
4.1 工作流
Question
↓
Planner 生成步骤列表
↓
Executor 逐步执行
↓
每一步结果写入 history
↓
最后一步输出作为最终答案
适合场景:
- 多步数学题;
- 结构化报告写作;
- 代码生成前的模块 / 函数规划;
- 流程明确、依赖顺序清晰的任务。
不适合场景:
- 外部环境变化快,需要边做边查;
- 初始条件不完整,需要不断探索;
- 计划执行中很可能失败且需要频繁重规划。
4.2 Planner
规划阶段的关键是输出结构化计划。书中要求模型输出 Python 列表,这样可以用 ast.literal_eval 安全解析。
import ast
PLANNER_PROMPT_TEMPLATE = """
你是一个顶级的 AI 规划专家。你的任务是将用户提出的复杂问题分解成多个简单步骤。
要求:
- 每个步骤都是一个独立、可执行的子任务;
- 步骤必须按逻辑顺序排列;
- 输出必须是一个 Python 列表;
- 必须使用 ```python 和 ``` 包裹列表。
问题: {question}
输出格式:
```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)
response_text = self.llm_client.think(
messages=[{"role": "user", "content": prompt}]
) or ""
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 (IndexError, SyntaxError, ValueError) as exc:
print (f"解析计划失败:{exc}")
print (f"原始响应:{response_text}")
return []
### 4.3 Executor 与状态管理
执行器的关键不是简单遍历步骤,而是每一步都要带上:
- 原始问题;
- 完整计划;
- 已完成步骤和结果;
- 当前步骤。
这样后续步骤才能使用前面的中间结果。
```python
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 = ""
last_result = ""
for i, step in enumerate(plan, start=1):
print(f"\n-> 执行步骤 {i}/{len(plan)}: {step}")
prompt = EXECUTOR_PROMPT_TEMPLATE.format(
question=question,
plan=plan,
history=history or "无",
current_step=step,
)
last_result = self.llm_client.think(
messages=[{"role": "user", "content": prompt}]
) or ""
history += f"步骤 {i}: {step}\n结果: {last_result}\n\n"
print(f"步骤 {i} 结果:{last_result}")
return last_result
4.4 PlanAndSolveAgent
class PlanAndSolveAgent:
"""协调 Planner 和 Executor 的两阶段智能体。"""
def __init__(self, llm_client: HelloAgentsLLM):
self.planner = Planner(llm_client)
self.executor = Executor(llm_client)
def run(self, question: str) -> str | None:
print(f"\n--- 开始处理问题 ---\n{question}")
plan = self.planner.plan(question)
if not plan:
print("无法生成有效计划,任务终止。")
return None
print("\n计划:")
for i, step in enumerate(plan, start=1):
print(f"{i}. {step}")
final_answer = self.executor.execute(question, plan)
print(f"\n最终答案:{final_answer}")
return final_answer
运行示例:
question = (
"一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。"
"周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果?"
)
agent = PlanAndSolveAgent(HelloAgentsLLM())
agent.run(question)
4.5 Plan-and-Solve 的工程注意点
优点:
- 全局计划清晰,适合复杂任务;
- 每一步目标明确,执行过程稳定;
- 状态管理简单,历史结果按步骤累积即可;
- 比 ReAct 更不容易被单个观察结果带偏。
局限:
- 初始计划质量决定上限;
- 计划是静态的,执行中遇到问题时不一定能调整;
- 对开放式、探索式任务不如 ReAct 灵活;
- 每个步骤都调用 LLM 时,成本仍然不低。
改进方向:
- 增加 "执行失败时重规划";
- 采用分层规划:先高层计划,再为每个高层步骤生成子计划;
- 对计划输出使用 JSON Schema,避免解析 Python 列表失败;
- 对关键步骤引入工具,例如计算器、检索器、数据库查询器。
5. Reflection:执行、反思、优化
Reflection 给智能体增加事后自我校正能力。它的核心循环是:
Execution: 生成初稿
Reflection: 批判性审查初稿
Refinement: 根据反馈优化
重复直到无需改进或达到最大轮数
这类机制适合 "结果质量比响应速度更重要" 的任务,例如代码生成、技术报告、复杂推理、决策支持。
5.1 Reflection 的价值
相比 ReAct 和 Plan-and-Solve,Reflection 的重点不在 "下一步做什么",而在 "已经做出的结果是否足够好"。
它能解决的问题包括:
- 初始答案有事实错误;
- 推理链条不严谨;
- 代码能跑但效率低;
- 忽略边界条件;
- 生成内容可读性差、结构差或不符合规范。
它的代价也很明显:
- 每轮至少多两次模型调用:一次反思,一次优化;
- 串行迭代导致延迟增加;
- 需要分别设计执行、反思、优化提示词;
- 终止条件需要谨慎设计,否则可能过早停止或无意义迭代。
5.2 短期记忆模块
Reflection 需要记住每轮生成结果和反馈,否则后续优化没有上下文。
from typing import Any, Dict, List, Optional
class Memory:
"""存储执行与反思轨迹的短期记忆。"""
def __init__(self):
self.records: List[Dict[str, Any]] = []
def add_record(self, record_type: str, content: str):
if record_type not in {"execution", "reflection"}:
raise ValueError("record_type 必须是 execution 或 reflection。")
self.records.append({"type": record_type, "content": content})
def get_trajectory(self) -> str:
parts = []
for record in self.records:
if record["type"] == "execution":
parts.append(f"--- 上一轮尝试 ---\n{record['content']}")
elif record["type"] == "reflection":
parts.append(f"--- 评审反馈 ---\n{record['content']}")
return "\n\n".join(parts)
def get_last_execution(self) -> Optional[str]:
for record in reversed(self.records):
if record["type"] == "execution":
return record["content"]
return None
5.3 三类提示词
初始执行提示词:
INITIAL_PROMPT_TEMPLATE = """
你是一位资深的 Python 程序员。请根据以下要求编写一个 Python 函数。
要求:
- 包含完整函数签名;
- 包含文档字符串;
- 遵循 PEP 8;
- 直接输出代码,不要额外解释。
任务: {task}
"""
反思提示词:
REFLECT_PROMPT_TEMPLATE = """
你是一位极其严格的代码评审专家和资深算法工程师,对代码性能有很高要求。
# 原始任务
{task}
# 待审查代码
```python
{code}
请分析该代码的时间复杂度,并判断是否存在算法上更优的解决方案。 如果存在,请指出当前算法瓶颈,并提出具体可行的改进建议。 如果算法层面已经足够好,才能回答 "无需改进"。
请直接输出反馈,不要输出额外解释。
优化提示词:
优化提示词:
```python
REFINE_PROMPT_TEMPLATE = """
你是一位资深的 Python 程序员。请根据代码评审反馈优化上一版代码。
# 原始任务
{task}
# 上一轮代码
{last_code_attempt}
# 评审反馈
{feedback}
请输出优化后的新版本代码。
要求:
- 包含完整函数签名;
- 包含文档字符串;
- 遵循 PEP 8;
- 直接输出代码,不要额外解释。
"""
这三个提示词分别对应三个角色:
| 阶段 | 模型角色 | 输出 |
|---|---|---|
| 初始执行 | 程序员 | 初版代码 |
| 反思 | 严格评审专家 | 缺陷与改进建议 |
| 优化 | 程序员 | 修订代码 |
5.4 ReflectionAgent
class ReflectionAgent:
"""执行、反思、优化循环。"""
def __init__(self, llm_client: HelloAgentsLLM, max_iterations: int = 3):
self.llm_client = llm_client
self.max_iterations = max_iterations
self.memory = Memory()
def run(self, task: str) -> str:
print(f"\n--- 开始任务 ---\n{task}")
initial_prompt = INITIAL_PROMPT_TEMPLATE.format(task=task)
initial_output = self._get_llm_response(initial_prompt)
self.memory.add_record("execution", initial_output)
for i in range(1, self.max_iterations + 1):
print(f"\n--- 第 {i}/{self.max_iterations} 轮反思 ---")
last_output = self.memory.get_last_execution() or ""
reflect_prompt = REFLECT_PROMPT_TEMPLATE.format(
task=task,
code=last_output,
)
feedback = self._get_llm_response(reflect_prompt)
self.memory.add_record("reflection", feedback)
if "无需改进" in feedback:
print("反思结果:无需改进,停止迭代。")
break
refine_prompt = REFINE_PROMPT_TEMPLATE.format(
task=task,
last_code_attempt=last_output,
feedback=feedback,
)
refined_output = self._get_llm_response(refine_prompt)
self.memory.add_record("execution", refined_output)
final_output = self.memory.get_last_execution() or ""
print(f"\n--- 最终结果 ---\n{final_output}")
return final_output
def _get_llm_response(self, prompt: str) -> str:
return self.llm_client.think(
messages=[{"role": "user", "content": prompt}]
) or ""
运行示例:
task = "编写一个 Python 函数,找出 1 到 n 之间所有的素数。"
agent = ReflectionAgent(HelloAgentsLLM(), max_iterations=2)
agent.run(task)
6. 三种范式的选择策略
| 任务特征 | 推荐范式 | 原因 |
|---|---|---|
| 需要实时信息、搜索、API 调用 | ReAct |
可以根据工具返回结果动态调整 |
| 任务步骤清晰、可提前拆解 | Plan-and-Solve |
先形成全局路线,再逐步执行 |
| 结果质量要求高、允许更慢 | Reflection |
通过反思和优化提高可靠性 |
| 需要处理未知环境 | ReAct |
观察结果能反馈到下一步决策 |
| 需要数学 / 逻辑多步推理 | Plan-and-Solve |
步骤化能降低中途跑偏概率 |
| 代码优化、报告润色、方案审查 | Reflection |
自我批判能发现初稿缺陷 |
实际工程中三者可以组合:
复杂任务
↓
Plan-and-Solve 先生成总体计划
↓
每个步骤内部用 ReAct 调用工具
↓
关键输出再用 Reflection 审查和优化
例如 "自动生成一份行业研究报告":
- 用
Plan-and-Solve规划报告结构; - 用
ReAct搜索数据、调用数据库、查询论文; - 用
Reflection审查事实一致性、逻辑连贯性、引用规范和表达质量。
7. 本章容易忽略但很关键的工程点
7.1 输出格式比提示词文采更重要
Agent 不是普通聊天。普通聊天只要 "看起来回答对了" 即可,但 Agent 需要程序解析模型输出。因此格式必须稳定:
- ReAct 需要稳定解析
Thought和Action; - Plan-and-Solve 需要稳定解析计划列表;
- Reflection 需要稳定识别 "无需改进" 等终止信号。
正则表达式适合教学,但生产环境更建议:
- JSON 输出;
- JSON Schema;
- 函数调用 /tool calling;
- Pydantic 校验;
- 失败重试与格式修复器。
7.2 max_steps 和 max_iterations 是安全阀
Agent Loop 必须有上限。否则一旦模型重复调用无效工具、无法完成计划、或一直反思,就会无限消耗成本。
常见限制包括:
- 最大步骤数;
- 最大迭代轮数;
- 最大工具调用次数;
- 最大 token 成本;
- 最大运行时间;
- 连续失败次数上限。
7.3 工具描述决定工具选择质量
很多工具调用失败不是代码问题,而是工具描述不清楚。描述应该说明:
- 工具能做什么;
- 什么时候应该用;
- 输入格式是什么;
- 输出结果代表什么;
- 不能做什么。
例如 Search 不应只写 "搜索工具",而应写 "当需要最新事实、新闻、产品信息、模型知识库中找不到的信息时使用"。
7.4 状态管理是 Agent 的核心
本章三个范式都在管理状态,只是状态形态不同:
| 范式 | 状态内容 |
|---|---|
ReAct |
历史 Action 和 Observation |
Plan-and-Solve |
已执行步骤和每步结果 |
Reflection |
每轮执行结果和反思反馈 |
没有状态,Agent 就只能单轮回答;有状态,Agent 才能根据过去的行动继续推进任务。
7.5 幻觉风险会被工具调用放大
LLM 幻觉本来只是 "说错"。在 Agent 中,幻觉可能变成 "做错":
- 生成不存在的工具名;
- 给工具传错参数;
- 错误解读工具返回结果;
- 基于错误观察继续执行;
- 误判任务已经完成。
因此 Agent 需要:
- 工具参数校验;
- 工具调用错误反馈;
- 输出结构校验;
- 高风险动作人工确认;
- 关键结论二次验证。
8. 总结
第四章的重点不是记住三个名词,而是掌握三种 Agent Loop 的工程差异:
ReAct解决 "我需要边查边做" 的问题;Plan-and-Solve解决 "我需要先有全局步骤" 的问题;Reflection解决 "我需要把初稿变得更好" 的问题。
真正构建智能体时,往往不是三选一,而是围绕任务特点组合它们:先规划,再用工具执行,最后反思优化。
附 习题解析 和 JAVA版学习参考
习题解析:智能体经典范式构建
习题 1 三种范式本质区别与混合架构设计
一、思考与行动组织方式核心区别
三者核心差异在于思考、行动、复盘在任务流程中的排布位置。
| 范式 | 思考行动关系 | 执行流程 | 核心优势 |
|---|---|---|---|
| ReAct | 边思考边行动,行动结果反向修正思考 | 思考→行动→观察→循环 | 实时交互、动态纠错、灵活调用工具 |
| Plan-and-Solve | 一次性全局思考制定计划,再按序执行 | 整体规划→分步执行 | 流程规整、逻辑清晰、不易偏离目标 |
| Reflection | 先完成行动,事后反思复盘迭代优化 | 生成初稿→批判审查→迭代优化 | 自我修正、提升内容质量、补齐逻辑漏洞 |
极简流程示意
ReAct: Think -> Act -> Observe -> Loop
Plan-and-Solve: MakePlan -> Step1 -> Step2 -> End
Reflection: Draft -> Review -> Optimize -> Final
二、智能家居控制助手架构选型
首选架构:以 ReAct 为主,叠加 Plan-and-Solve 批量规划 + Reflection 安全校验 选型理由:
- 智能家居属于强设备交互场景,需实时获取灯光、空调、窗帘、温湿度传感器状态,必须依靠 ReAct 实时观察反馈;
- 用户口语化指令模糊,如 "睡前营造居家环境",需要动态拆解调整,静态计划无法适配;
- 定时场景、全屋场景联动可提前用 Plan-and-Solve 生成批量控制流程;
- 开关门锁、大功率电器等高风险操作,依靠 Reflection 做安全合规校验。
三、混合范式智能体整体架构
用户自然语言指令
↓
意图识别解析
↓
Plan-and-Solve 生成全屋场景高层控制计划
↓
ReAct 循环调用设备工具,实时获取设备状态并执行控制
↓
Reflection 安全风控校验,拦截违规、危险操作
↓
执行结果状态反馈用户
配套核心控制工具
| 工具名称 | 功能作用 |
|---|---|
| GetDeviceState | 批量查询所有智能设备在线状态、运行参数 |
| ControlLight | 灯光开关、亮度色温调节 |
| ControlAirCondition | 调节温度、运行模式、风速 |
| ControlCurtain | 窗帘开合、比例调节 |
| UserHabitQuery | 调取用户日常使用习惯,自动适配偏好 |
| SafetyCheckTool | 校验设备操作是否存在安全隐患 |
习题 2 ReAct 正则解析缺陷与高可靠输出方案
一、正则表达式解析存在的脆弱性
- 极度依赖模型严格遵守固定格式,出现多余文字、换行、空格直接解析失败;
- 中英文标签混用、自定义符号嵌套,会导致正则匹配错位;
- 工具参数包含括号、方括号等特殊字符时,切割逻辑失效;
- 多轮连续行动、长文本答案会造成解析逻辑混乱;
- 不同大模型输出风格差异大,正则不具备通用性。
二、更鲁棒的输出解析方案
- JSON 结构化输出:轻量易实现,自带字段校验,适配绝大多数场景;
- JSON Schema 约束:强制限定字段类型、枚举值,生产环境首选;
- 原生 Function Calling:大模型官方工具调用格式,稳定性最强;
- Pydantic 实体类校验:Python 工程化强校验,自动过滤异常数据;
- 格式失败自动重试:识别格式错误后,下发修正提示词重新生成。
三、改造 ReAct 为 JSON 输出格式
1. 新版提示词模板
REACT_JSON_PROMPT = """
你是可调用外部工具的智能助手。
可用工具:
{tools}
严格仅输出标准JSON,禁止额外解释、多余文字、markdown格式。
JSON固定格式:
{
"thought": "本轮思考分析内容",
"action": {
"type": "tool/finish",
"name": "工具名称,结束则为空",
"input": "工具入参/最终回答"
}
}
用户问题:{question}
历史交互记录:{history}
"""
2. JSON 解析代码
import json
from dataclasses import dataclass
@dataclass
class AgentAction:
thought: str
action_type: str
tool_name: str
action_input: str
def parse_json_response(content:str) -> AgentAction:
try:
data = json.loads(content)
thought = data.get("thought","")
action = data.get("action",{})
act_type = action.get("type")
name = action.get("name","")
inp = action.get("input","")
return AgentAction(thought,act_type,name,inp)
except json.JSONDecodeError:
raise ValueError("模型输出非标准JSON格式,请重新生成")
四、两种解析方案优缺点对比
| 解析方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 正则解析 | 代码简洁、上手简单、教学易懂 | 容错率极低、极易报错、扩展性差 | 课堂演示、入门学习 |
| JSON 解析 | 结构清晰、支持校验、兼容性强 | 需规范模型输出格式 | 课程作业、中小型项目 |
| 函数调用 | 官方原生支持、稳定性拉满 | 对接成本高、依赖模型接口 | 企业级线上业务 |
习题 3 工具扩展、调用失败处理与海量工具优化
一、为 ReAct 智能体新增安全计算器工具
采用 AST 语法树实现安全数学运算,禁止恶意代码执行,仅支持算术运算。
import ast
import operator as op
ALLOW_CALC_OP = {
ast.Add:op.add,ast.Sub:op.sub,ast.Mult:op.mul,
ast.Div:op.truediv,ast.Pow:op.pow,ast.USub:op.neg
}
def safe_calc_node(node):
if isinstance(node,ast.Constant) and isinstance(node.value,(int,float)):
return node.value
if isinstance(node,ast.BinOp) and type(node.op) in ALLOW_CALC_OP:
left = safe_calc_node(node.left)
right = safe_calc_node(node.right)
return ALLOW_CALC_OP[type(node.op)](left,right)
if isinstance(node,ast.UnaryOp) and type(node.op) in ALLOW_CALC_OP:
return ALLOW_CALC_OP[type(node.op)](safe_calc_node(node.operand))
raise ValueError("仅支持数字与基础算术运算")
def calculator_tool(expression:str) -> str:
expr = expression.replace("×","*").replace("÷","/").strip()
try:
tree = ast.parse(expr,mode="eval")
res = safe_calc_node(tree.body)
return f"计算结果:{res}"
except Exception as e:
return f"计算失败:{str(e)},请输入规范数学表达式"
工具注册后可直接处理 (123+456)*789/12 等复杂运算。
二、工具选择失败纠错机制设计
- 建立调用失败计数器,统计同一工具连续调用错误次数;
- 单次失败:返回错误原因,引导模型自查参数与工具名称;
- 多次连续失败:推送全部可用工具列表,强制重新选择;
- 参数格式错误:下发工具入参标准示例;
- 高危工具调用错误:直接终止自动执行,转为人工确认。
三、海量工具场景工程优化方案
当可调用工具数量达到数十、上百个时,直接全量写入提示词会造成冗余臃肿,优化策略:
- 工具领域分类:按业务划分为查询类、计算类、控制类、办公类;
- 向量检索筛选:通过用户问题语义匹配,仅推送 Top3-5 高相关工具;
- 统一工具 Schema:标准化入参、出参、功能描述,降低模型理解成本;
- 分级权限管控:普通智能体仅调用基础工具,复杂工具单独授权调用;
- 工具路由分发:增设专用工具选择子智能体,专职完成工具匹配。
习题 4 Plan-and-Solve 动态重规划与分层规划
一、静态计划缺陷与动态重规划机制
原生 Plan-and-Solve 为一次性静态计划,执行途中出现步骤失败、资源不足、条件变更时,无法自适应调整。
动态重规划执行流程
初始全局计划生成
↓
分步执行+结果状态监测
↓
执行成功:顺序执行下一流程
↓
执行失败:采集失败原因、当前进度、已有结果
↓
重规划模块基于现状重构剩余任务流程
↓
重启执行直至任务完成/达到最大重规划次数
二、商务旅行预订任务范式选型
预订北京至上海商务旅行(机票 + 酒店 + 租车):优先采用 Plan-and-Solve 顶层规划 + ReAct 分步执行 选型原因:
- 出行任务具备固定业务流程,适合提前拆分整体步骤;
- 机票余票、酒店房源、车辆状态为实时变动数据,必须依靠 ReAct 调用外部接口实时查询;
- 行程方案可多维度对比筛选,动态调整出行时间、住宿地点;
- 下单支付属于高风险操作,分步执行更易管控风险。
三、分层规划系统设计
- 高层抽象规划:拆分交通出行、住宿预订、市内通勤、行程确认四大核心模块;
- 底层子级规划:针对每一个高层模块,拆分精细化执行子步骤;
- 优势:
- 巨型复杂任务拆解轻量化,逻辑层级清晰;
- 单个子计划执行失败,仅局部重规划,不影响整体流程;
- 支持多子任务并行执行,提升整体运行效率;
- 便于权限拆分、模块解耦,后期维护拓展更便捷。
习题 5 Reflection 反思机制深度设计
一、双模型分离执行架构影响
架构设计:轻量快速模型负责内容生成,高性能大模型负责反思评审
- 优势:降低整体调用成本,兼顾响应速度与内容质量,模拟 "作者撰写 + 专家审稿" 真实工作模式;
- 弊端:两类模型输出风格存在偏差,容易出现优化内容风格割裂;初稿质量过低会大幅提升反思优化难度;
- 适配场景:代码编写、论文创作、方案撰写等重质量、低时效需求场景。
二、智能迭代终止条件优化
摒弃单一固定次数终止、文字匹配终止,采用多维度复合终止判定
- 内容综合评分达到预设合格阈值;
- 连续两轮迭代优化幅度趋近于 0,无改进空间;
- 所有维度缺陷全部修复完毕;
- 达到最大迭代轮数、最大调用成本上限强制停止;
- 功能测试、逻辑校验全部通过自动终止。
三、学术论文助手多维 Reflection 机制
搭建多维度评审反思面板,全方位优化论文内容
| 反思评审维度 | 核查优化内容 |
|---|---|
| 逻辑结构性 | 梳理段落论点、论据、结论,修正行文逻辑断层 |
| 方法创新性 | 对比现有研究,强化创新点表述,补齐创新依据 |
| 实验严谨性 | 补充实验数据、完善实验流程,佐证论文核心观点 |
| 学术语言 | 精简冗余语句,修正口语化表达,贴合学术文风 |
| 引用规范性 | 补齐缺失参考文献,统一引用格式,规范文献排版 |
| 事实准确性 | 核查行业常识、研究结论,杜绝主观臆断与事实错误 |
习题 6 提示词工程核心差异与角色设定
一、ReAct 与 Plan-and-Solve 提示词结构差异
- ReAct 提示词:核心强调循环决策、工具调用、历史观察记录,重点约束思考内容与行动格式,适配动态交互;
- Plan-and-Solve 提示词:核心强调任务拆分、逻辑顺序、结构化步骤输出,无需历史交互上下文,聚焦一次性全局规划;
- 本质区别:提示词结构完全服务于智能体核心运行逻辑。
二、反思角色设定对输出结果的影响
- 严格算法评审专家:侧重代码性能、时间复杂度、边界漏洞,反馈偏严苛,优先优化算法底层逻辑;
- 开源项目维护者:侧重代码可读性、命名规范、注释文档、项目可维护性,风格温和,偏向工程规范优化;
- 学术审稿人:侧重研究价值、实验论据、行文严谨性,适合论文、学术方案优化; 总结:角色设定直接决定模型评判标准、优化方向与输出语气。
三、Few-Shot 示例实战作用
在提示词中加入标准问答 + 行动示例,可大幅提升三大能力:
- 强化模型格式遵循度,杜绝输出混乱;
- 规范工具选择逻辑,减少工具误用;
- 明确任务终止时机,避免无效循环调用; 缺点:增加提示词长度,提升调用 Token 成本,示例偏差会误导模型行为。
习题 7 电商退款客服智能体完整落地设计
一、整体架构范式组合
核心架构:Plan-and-Solve 流程梳理 + ReAct 多工具信息查询 + Reflection 争议决策复核
- Plan-and-Solve:固化售后退款标准处理流程;
- ReAct:调取订单、物流、用户画像、退款政策等外部数据;
- Reflection:低置信度退款、大额订单、纠纷订单自动复盘复核;
- 最终兜底:高风险场景自动转接人工客服。
二、核心必备工具列表
| 工具名称 | 具体功能 |
|---|---|
| 订单信息查询工具 | 查询订单商品、金额、下单时间、售后记录、支付状态 |
| 物流状态查询工具 | 核查签收时间、物流异常、配送延迟记录 |
| 退款政策检索工具 | 调取平台售后规则,区分七天无理由、破损、定制商品退款限制 |
| 用户画像查询工具 | 统计用户历史退款频次、会员等级、风控标签 |
| 退款决策工具 | 结合多维度信息生成初步退款审批意见 |
| 邮件自动生成工具 | 输出礼貌合规的用户售后回复邮件 |
| 人工转接工具 | 争议订单、欺诈嫌疑订单流转人工坐席 |
三、合规友好型提示词设计
核心约束原则:兼顾平台商家利益 + 用户消费体验
- 所有决策必须依托工具查询真实数据,禁止编造订单、政策信息;
- 严格遵循平台售后退款规则,不私自放宽审批权限;
- 拒绝退款时语气共情委婉,清晰告知原因与解决办法;
- 信息缺失、依据不足、决策置信度低于阈值时,自动启动反思复核;
- 禁止向用户做出平台未授权的售后承诺。
四、上线风险与技术规避方案
| 业务风险 | 规避优化手段 |
|---|---|
| LLM 幻觉编造售后政策 | 所有决策依据强制绑定工具返回数据,输出标注信息来源 |
| 违规审批不合理退款 | LLM 决策 + 平台规则引擎双层校验,大额订单强制人工审核 |
| 客服回复语气生硬引发投诉 | 独立话术生成提示词,统一客服沟通语气 |
| 恶意用户频繁恶意退款 | 调取用户风控标签,限制高频异常退款申请 |
| 接口查询失败中断流程 | 配置工具调用重试机制,查询异常直接转接人工 |
| 用户隐私信息泄露 | 回复内容自动脱敏,屏蔽手机号、地址等敏感数据 |
习题总结
第四章三大经典智能体范式并非非此即彼,真实工业级智能体开发均以混合架构为主:
- 实时交互、外部信息查询优先选用ReAct;
- 流程固定、任务拆分场景优先选用Plan-and-Solve;
- 高质量内容生成、决策纠错场景引入Reflection;
- 落地项目必须配套格式校验、错误重试、工具管控、人工兜底四大工程化能力,兼顾智能性与稳定性。
JAVA版学习参考
1. 项目配置与 Maven 依赖
在pom.xml中引入所需依赖
<dependencies>
<!-- OpenAI 官方Java客户端 -->
<dependency>
<groupId>com.openai</groupId>
<artifactId>openai-java</artifactId>
<version>0.21.0</version>
</dependency>
<!-- 本地环境变量读取 -->
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.0.0</version>
</dependency>
<!-- JSON序列化解析 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
本地.env 环境配置文件
LLM_MODEL_ID=你的大模型名称
LLM_API_KEY=你的API密钥
LLM_BASE_URL=大模型接口地址
SERPAPI_API_KEY= serpapi搜索密钥
可以用application.yml等配置文件配置,但下面内容就要做出对应修改
2. 大模型通用调用封装 HelloAgentsLLM.java
对标 Python 版本 llm_client,实现流式大模型统一调用
package com.helloagents;
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.chat.completions.*;
import io.github.cdimascio.dotenv.Dotenv;
import java.util.List;
public class HelloAgentsLLM {
private final OpenAIClient client;
private final String model;
public HelloAgentsLLM() {
this(null, null, null, 60);
}
public HelloAgentsLLM(String model, String apiKey, String baseUrl, int timeout) {
Dotenv dotenv;
try {
dotenv = Dotenv.load();
} catch (Exception e) {
System.out.println("警告:未找到 .env 文件,将使用系统环境变量。");
dotenv = null;
}
this.model = model != null ? model
: (dotenv != null ? dotenv.get("LLM_MODEL_ID") : System.getenv("LLM_MODEL_ID"));
String key = apiKey != null ? apiKey
: (dotenv != null ? dotenv.get("LLM_API_KEY") : System.getenv("LLM_API_KEY"));
String url = baseUrl != null ? baseUrl
: (dotenv != null ? dotenv.get("LLM_BASE_URL") : System.getenv("LLM_BASE_URL"));
if (this.model == null || key == null || url == null) {
throw new IllegalArgumentException(
"模型ID、API密钥和服务地址必须被提供或在.env文件中定义。");
}
this.client = OpenAIOkHttpClient.builder()
.apiKey(key)
.baseUrl(url)
.build();
}
/**
* 调用大语言模型进行思考,并返回其响应(流式输出)。
*/
public String think(List<ChatCompletionMessage> messages, double temperature) {
System.out.println("🧠 正在调用 " + model + " 模型...");
try {
ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
.model(model)
.messages(messages)
.temperature(temperature)
.build();
// 流式调用
StringBuilder collected = new StringBuilder();
System.out.println("✅ 大语言模型响应成功:");
client.chat().completions().createStream(params)
.forEach(chunk -> {
String content = chunk.choices().stream()
.filter(c -> c.delta().content().isPresent())
.map(c -> c.delta().content().get())
.reduce("", String::concat);
System.out.print(content);
collected.append(content);
});
System.out.println();
return collected.toString();
} catch (Exception e) {
System.out.println("❌ 调用LLM API时发生错误: " + e.getMessage());
return null;
}
}
// 便捷重载:默认 temperature=0
public String think(List<ChatCompletionMessage> messages) {
return think(messages, 0.0);
}
// ===== 使用示例 =====
public static void main(String[] args) {
HelloAgentsLLM llm = new HelloAgentsLLM();
List<ChatCompletionMessage> messages = List.of(
ChatCompletionMessage.builder()
.role(ChatCompletionMessage.Role.SYSTEM)
.content("You are a helpful assistant that writes Python code.")
.build(),
ChatCompletionMessage.builder()
.role(ChatCompletionMessage.Role.USER)
.content("写一个快速排序算法")
.build()
);
System.out.println("--- 调用LLM ---");
String response = llm.think(messages);
if (response != null) {
System.out.println("\n\n--- 完整模型响应 ---");
System.out.println(response);
}
}
}
3. 工具统一管理器 ToolExecutor.java
对标 tools.py,实现工具注册、工具查询、网页搜索工具封装
package com.helloagents;
import com.google.gson.*;
import okhttp3.*;
import io.github.cdimascio.dotenv.Dotenv;
import java.util.*;
import java.util.function.Function;
public class ToolExecutor {
private final Map<String, ToolInfo> tools = new LinkedHashMap<>();
private static class ToolInfo {
String description;
Function<String, String> func;
ToolInfo(String description, Function<String, String> func) {
this.description = description;
this.func = func;
}
}
/** 注册一个工具 */
public void registerTool(String name, String description, Function<String, String> func) {
if (tools.containsKey(name)) {
System.out.println("警告:工具 '" + name + "' 已存在,将被覆盖。");
}
tools.put(name, new ToolInfo(description, func));
System.out.println("工具 '" + name + "' 已注册。");
}
/** 根据名称获取工具的执行函数 */
public Function<String, String> getTool(String name) {
ToolInfo info = tools.get(name);
return info != null ? info.func : null;
}
/** 获取所有可用工具的格式化描述字符串 */
public String getAvailableTools() {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, ToolInfo> entry : tools.entrySet()) {
sb.append("- ").append(entry.getKey())
.append(": ").append(entry.getValue().description).append("\n");
}
return sb.toString();
}
// ===== 内置搜索工具(基于 SerpApi) =====
private static final OkHttpClient httpClient = new OkHttpClient();
private static final Gson gson = new Gson();
/**
* 一个基于 SerpApi 的网页搜索引擎工具。
* 它会智能地解析搜索结果,优先返回直接答案或知识图谱信息。
*/
public static String search(String query) {
System.out.println("🔍 正在执行 [SerpApi] 网页搜索: " + query);
Dotenv dotenv;
try {
dotenv = Dotenv.load();
} catch (Exception e) {
dotenv = null;
}
String apiKey = dotenv != null ? dotenv.get("SERPAPI_API_KEY")
: System.getenv("SERPAPI_API_KEY");
if (apiKey == null) {
return "错误:SERPAPI_API_KEY 未在 .env 文件中配置。";
}
try {
String url = "https://serpapi.com/search"
+ "?engine=google"
+ "&q=" + query
+ "&api_key=" + apiKey
+ "&gl=cn"
+ "&hl=zh-cn";
Request request = new Request.Builder().url(url).build();
Response response = httpClient.newCall(request).execute();
JsonObject results = gson.fromJson(response.body().string(), JsonObject.class);
// 智能解析:优先寻找最直接的答案
if (results.has("answer_box_list")) {
JsonArray arr = results.getAsJsonArray("answer_box_list");
StringBuilder sb = new StringBuilder();
for (JsonElement e : arr) {
if (sb.length() > 0) sb.append("\n");
sb.append(e.getAsString());
}
return sb.toString();
}
if (results.has("answer_box")) {
JsonObject box = results.getAsJsonObject("answer_box");
if (box.has("answer")) return box.get("answer").getAsString();
}
if (results.has("knowledge_graph")) {
JsonObject kg = results.getAsJsonObject("knowledge_graph");
if (kg.has("description")) return kg.get("description").getAsString();
}
if (results.has("organic_results")) {
JsonArray organic = results.getAsJsonArray("organic_results");
StringBuilder sb = new StringBuilder();
int count = Math.min(organic.size(), 3);
for (int i = 0; i < count; i++) {
JsonObject res = organic.get(i).getAsJsonObject();
String title = res.has("title") ? res.get("title").getAsString() : "";
String snippet = res.has("snippet") ? res.get("snippet").getAsString() : "";
sb.append("[").append(i + 1).append("] ").append(title).append("\n")
.append(snippet).append("\n\n");
}
return sb.toString();
}
return "对不起,没有找到关于 '" + query + "' 的信息。";
} catch (Exception e) {
return "搜索时发生错误: " + e.getMessage();
}
}
// ===== 使用示例 =====
public static void main(String[] args) {
ToolExecutor executor = new ToolExecutor();
String searchDesc = "一个网页搜索引擎。当你需要回答关于时事、事实以及在你的知识库中找不到的信息时,应使用此工具。";
executor.registerTool("Search", searchDesc, ToolExecutor::search);
System.out.println("\n--- 可用的工具 ---");
System.out.println(executor.getAvailableTools());
System.out.println("\n--- 执行 Action: Search['英伟达最新的GPU型号是什么'] ---");
Function<String, String> func = executor.getTool("Search");
if (func != null) {
String observation = func.apply("英伟达最新的GPU型号是什么");
System.out.println("--- 观察 (Observation) ---");
System.out.println(observation);
} else {
System.out.println("错误:未找到名为 'Search' 的工具。");
}
}
}
4. 先规划后执行智能体 PlanAndSolveAgent.java
实现 Plan-and-Solve 经典范式,拆分任务 + 分步执行
package com.helloagents;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.openai.models.chat.completions.ChatCompletionMessage;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PlanAndSolveAgent {
private final HelloAgentsLLM llmClient;
private final Planner planner;
private final Executor executor;
// ===== 规划器提示词 =====
private static final String PLANNER_PROMPT_TEMPLATE = """
你是一个顶级的AI规划专家。你的任务是将用户提出的复杂问题分解成一个由多个简单步骤组成的行动计划。
请确保计划中的每个步骤都是一个独立的、可执行的子任务,并且严格按照逻辑顺序排列。
你的输出必须是一个Python列表,其中每个元素都是一个描述子任务的字符串。
问题: {question}
请严格按照以下格式输出你的计划,```python与```作为前后缀是必要的:
```python
["步骤1", "步骤2", "步骤3", ...]
// ===== 执行器提示词 =====
private static final String EXECUTOR_PROMPT_TEMPLATE = """
你是一位顶级的AI执行专家。你的任务是严格按照给定的计划,一步步地解决问题。
你将收到原始问题、完整的计划、以及到目前为止已经完成的步骤和结果。
请你专注于解决"当前步骤",并仅输出该步骤的最终答案,不要输出任何额外的解释或对话。
# 原始问题:
{question}
# 完整计划:
{plan}
# 历史步骤与结果:
{history}
# 当前步骤:
{current_step}
请仅输出针对"当前步骤"的回答:
""";
public PlanAndSolveAgent(HelloAgentsLLM llmClient) {
this.llmClient = llmClient;
this.planner = new Planner(llmClient);
this.executor = new Executor(llmClient);
}
// ----- 规划器 -----
static class Planner {
private final HelloAgentsLLM llmClient;
private final Gson gson = new Gson();
Planner(HelloAgentsLLM llmClient) {
this.llmClient = llmClient;
}
List<String> plan(String question) {
String prompt = PLANNER_PROMPT_TEMPLATE.replace("{question}", question);
List<ChatCompletionMessage> messages = List.of(
ChatCompletionMessage.builder()
.role(ChatCompletionMessage.Role.USER)
.content(prompt)
.build()
);
System.out.println("--- 正在生成计划 ---");
String responseText = llmClient.think(messages);
if (responseText == null) responseText = "";
System.out.println("✅ 计划已生成:\n" + responseText);
try {
// 提取 ```python ... ``` 之间的内容
Pattern pattern = Pattern.compile("```python\\s*(.*?)\\s*```", Pattern.DOTALL);
Matcher matcher = pattern.matcher(responseText);
if (matcher.find()) {
String planStr = matcher.group(1).trim();
List<String> plan = gson.fromJson(planStr,
new TypeToken<List<String>>() {}.getType());
return plan != null ? plan : Collections.emptyList();
}
return Collections.emptyList();
} catch (Exception e) {
System.out.println("❌ 解析计划时出错: " + e.getMessage());
System.out.println("原始响应: " + responseText);
return Collections.emptyList();
}
}
}
// ----- 执行器 -----
static class Executor {
private final HelloAgentsLLM llmClient;
Executor(HelloAgentsLLM llmClient) {
this.llmClient = llmClient;
}
String execute(String question, List<String> plan) {
StringBuilder history = new StringBuilder();
String finalAnswer = "";
System.out.println("\n--- 正在执行计划 ---");
for (int i = 0; i < plan.size(); i++) {
String step = plan.get(i);
System.out.println("\n-> 正在执行步骤 " + (i + 1) + "/" + plan.size() + ": " + step);
String prompt = EXECUTOR_PROMPT_TEMPLATE
.replace("{question}", question)
.replace("{plan}", plan.toString())
.replace("{history}", history.length() > 0 ? history.toString() : "无")
.replace("{current_step}", step);
List<ChatCompletionMessage> messages = List.of(
ChatCompletionMessage.builder()
.role(ChatCompletionMessage.Role.USER)
.content(prompt)
.build()
);
String responseText = llmClient.think(messages);
if (responseText == null) responseText = "";
history.append("步骤 ").append(i + 1).append(": ").append(step)
.append("\n结果: ").append(responseText).append("\n\n");
finalAnswer = responseText;
System.out.println("✅ 步骤 " + (i + 1) + " 已完成,结果: " + finalAnswer);
}
return finalAnswer;
}
}
// ----- 主运行入口 -----
public void run(String question) {
System.out.println("\n--- 开始处理问题 ---\n问题: " + question);
List<String> plan = planner.plan(question);
if (plan.isEmpty()) {
System.out.println("\n--- 任务终止 ---\n无法生成有效的行动计划。");
return;
}
String finalAnswer = executor.execute(question, plan);
System.out.println("\n--- 任务完成 ---\n最终答案: " + finalAnswer);
}
// ===== 使用示例 =====
public static void main(String[] args) {
HelloAgentsLLM llm = new HelloAgentsLLM();
PlanAndSolveAgent agent = new PlanAndSolveAgent(llm);
String question = "一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。"
+ "周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果?";
agent.run(question);
}
}
5. 思考行动观察智能体 ReActAgent.java
实现 ReAct 循环范式,Thought-Action-Observation 闭环
package com.helloagents;
import com.openai.models.chat.completions.ChatCompletionMessage;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ReActAgent {
private final HelloAgentsLLM llmClient;
private final ToolExecutor toolExecutor;
private final int maxSteps;
private final List<String> history = new ArrayList<>();
private static final String REACT_PROMPT_TEMPLATE = """
请注意,你是一个有能力调用外部工具的智能助手。
可用工具如下:
{tools}
请严格按照以下格式进行回应:
Thought: 你的思考过程,用于分析问题、拆解任务和规划下一步行动。
Action: 你决定采取的行动,必须是以下格式之一:
- `{{tool_name}}[{{tool_input}}]`:调用一个可用工具。
- `Finish[最终答案]`:当你认为已经获得最终答案时。
- 当你收集到足够的信息,能够回答用户的最终问题时,你必须在`Action:`字段后使用 `Finish[最终答案]` 来输出最终答案。
现在,请开始解决以下问题:
Question: {question}
History: {history}
""";
public ReActAgent(HelloAgentsLLM llmClient, ToolExecutor toolExecutor, int maxSteps) {
this.llmClient = llmClient;
this.toolExecutor = toolExecutor;
this.maxSteps = maxSteps;
}
public String run(String question) {
history.clear();
for (int step = 1; step <= maxSteps; step++) {
System.out.println("\n--- 第 " + step + " 步 ---");
String toolsDesc = toolExecutor.getAvailableTools();
String historyStr = String.join("\n", history);
String prompt = REACT_PROMPT_TEMPLATE
.replace("{tools}", toolsDesc)
.replace("{question}", question)
.replace("{history}", historyStr);
List<ChatCompletionMessage> messages = List.of(
ChatCompletionMessage.builder()
.role(ChatCompletionMessage.Role.USER)
.content(prompt)
.build()
);
String responseText = llmClient.think(messages);
if (responseText == null || responseText.isEmpty()) {
System.out.println("错误:LLM未能返回有效响应。");
break;
}
// 解析 Thought 和 Action
ParsedOutput parsed = parseOutput(responseText);
if (parsed.thought != null) {
System.out.println("🤔 思考: " + parsed.thought);
}
if (parsed.action == null) {
System.out.println("警告:未能解析出有效的Action,流程终止。");
break;
}
// 检查是否是 Finish 指令
if (parsed.action.startsWith("Finish")) {
String finalAnswer = parseActionInput(parsed.action);
System.out.println("🎉 最终答案: " + finalAnswer);
return finalAnswer;
}
// 解析工具名和输入
String[] toolParts = parseAction(parsed.action);
if (toolParts == null) {
history.add("Observation: 无效的Action格式,请检查。");
continue;
}
String toolName = toolParts[0];
String toolInput = toolParts[1];
System.out.println("🎬 行动: " + toolName + "[" + toolInput + "]");
Function<String, String> toolFunc = toolExecutor.getTool(toolName);
String observation;
if (toolFunc != null) {
observation = toolFunc.apply(toolInput);
} else {
observation = "错误:未找到名为 '" + toolName + "' 的工具。";
}
System.out.println("👀 观察: " + observation);
history.add("Action: " + parsed.action);
history.add("Observation: " + observation);
}
System.out.println("已达到最大步数,流程终止。");
return null;
}
// ---- 解析辅助方法 ----
private static class ParsedOutput {
String thought;
String action;
ParsedOutput(String thought, String action) {
this.thought = thought;
this.action = action;
}
}
private ParsedOutput parseOutput(String text) {
Pattern thoughtPattern = Pattern.compile("Thought:\\s*(.*?)(?=\nAction:|$)", Pattern.DOTALL);
Pattern actionPattern = Pattern.compile("Action:\\s*(.*?)$", Pattern.DOTALL);
Matcher thoughtMatcher = thoughtPattern.matcher(text);
Matcher actionMatcher = actionPattern.matcher(text);
String thought = thoughtMatcher.find() ? thoughtMatcher.group(1).trim() : null;
String action = actionMatcher.find() ? actionMatcher.group(1).trim() : null;
return new ParsedOutput(thought, action);
}
/** 解析 "ToolName[input]" 或 "Finish[answer]" 中的输入部分 */
private String parseActionInput(String actionText) {
Pattern pattern = Pattern.compile("\\w+\\[(.*)\\]", Pattern.DOTALL);
Matcher matcher = pattern.matcher(actionText);
return matcher.find() ? matcher.group(1) : "";
}
/** 返回 [toolName, toolInput],解析失败返回 null */
private String[] parseAction(String actionText) {
Pattern pattern = Pattern.compile("(\\w+)\\[(.*)\\]", Pattern.DOTALL);
Matcher matcher = pattern.matcher(actionText);
if (matcher.find()) {
return new String[]{matcher.group(1), matcher.group(2)};
}
return null;
}
// ===== 使用示例 =====
public static void main(String[] args) {
HelloAgentsLLM llm = new HelloAgentsLLM();
ToolExecutor toolExecutor = new ToolExecutor();
String searchDesc = "一个网页搜索引擎。当你需要回答关于时事、事实以及在你的知识库中找不到的信息时,应使用此工具。";
toolExecutor.registerTool("Search", searchDesc, ToolExecutor::search);
ReActAgent agent = new ReActAgent(llm, toolExecutor, 5);
String question = "华为最新的手机是哪一款?它的主要卖点是什么?";
agent.run(question);
}
}
6. 反思迭代智能体 ReflectionAgent.java
实现反思优化范式,生成 - 评审 - 迭代优化
package com.helloagents;
import com.openai.models.chat.completions.ChatCompletionMessage;
import java.util.*;
public class ReflectionAgent {
private final HelloAgentsLLM llmClient;
private final Memory memory;
private final int maxIterations;
// ===== 1. 初始执行提示词 =====
private static final String INITIAL_PROMPT_TEMPLATE = """
你是一位资深的Python程序员。请根据以下要求,编写一个Python函数。
你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
要求: {task}
请直接输出代码,不要包含任何额外的解释。
""";
// ===== 2. 反思提示词 =====
private static final String REFLECT_PROMPT_TEMPLATE = """
你是一位极其严格的代码评审专家和资深算法工程师,对代码的性能有极致的要求。
你的任务是审查以下Python代码,并专注于找出其在**算法效率**上的主要瓶颈。
# 原始任务:
{task}
# 待审查的代码:
```python
{code}
```
请分析该代码的时间复杂度,并思考是否存在一种**算法上更优**的解决方案来显著提升性能。
如果存在,请清晰地指出当前算法的不足,并提出具体的、可行的改进算法建议(例如,使用筛法替代试除法)。
如果代码在算法层面已经达到最优,才能回答"无需改进"。
请直接输出你的反馈,不要包含任何额外的解释。
""";
// ===== 3. 优化提示词 =====
private static final String REFINE_PROMPT_TEMPLATE = """
你是一位资深的Python程序员。你正在根据一位代码评审专家的反馈来优化你的代码。
# 原始任务:
{task}
# 你上一轮尝试的代码:
{last_code_attempt}
# 评审员的反馈:
{feedback}
请根据评审员的反馈,生成一个优化后的新版本代码。
你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
请直接输出优化后的代码,不要包含任何额外的解释。
""";
public ReflectionAgent(HelloAgentsLLM llmClient, int maxIterations) {
this.llmClient = llmClient;
this.memory = new Memory();
this.maxIterations = maxIterations;
}
// ======== 记忆模块 ========
/**
* 一个简单的短期记忆模块,用于存储智能体的行动与反思轨迹。
*/
static class Memory {
private final List<Record> records = new ArrayList<>();
private static class Record {
String type; // "execution" 或 "reflection"
String content;
Record(String type, String content) {
this.type = type;
this.content = content;
}
}
/** 向记忆中添加一条新记录 */
void addRecord(String recordType, String content) {
records.add(new Record(recordType, content));
System.out.println("📝 记忆已更新,新增一条 '" + recordType + "' 记录。");
}
/** 将所有记忆记录格式化为一个连贯的字符串文本 */
String getTrajectory() {
StringBuilder sb = new StringBuilder();
for (Record r : records) {
if ("execution".equals(r.type)) {
sb.append("--- 上一轮尝试 (代码) ---\n").append(r.content).append("\n\n");
} else if ("reflection".equals(r.type)) {
sb.append("--- 评审员反馈 ---\n").append(r.content).append("\n\n");
}
}
return sb.toString().trim();
}
/** 获取最近一次的执行结果(最新生成的代码) */
String getLastExecution() {
for (int i = records.size() - 1; i >= 0; i--) {
if ("execution".equals(records.get(i).type)) {
return records.get(i).content;
}
}
return null;
}
}
// ======== 主运行方法 ========
public String run(String task) {
System.out.println("\n--- 开始处理任务 ---\n任务: " + task);
// 1. 初始执行
System.out.println("\n--- 正在进行初始尝试 ---");
String initialPrompt = INITIAL_PROMPT_TEMPLATE.replace("{task}", task);
String initialCode = getLlmResponse(initialPrompt);
memory.addRecord("execution", initialCode);
// 2. 迭代循环:反思与优化
for (int i = 0; i < maxIterations; i++) {
System.out.println("\n--- 第 " + (i + 1) + "/" + maxIterations + " 轮迭代 ---");
// a. 反思
System.out.println("\n-> 正在进行反思...");
String lastCode = memory.getLastExecution();
String reflectPrompt = REFLECT_PROMPT_TEMPLATE
.replace("{task}", task)
.replace("{code}", lastCode);
String feedback = getLlmResponse(reflectPrompt);
memory.addRecord("reflection", feedback);
// b. 检查停止条件
if (feedback.contains("无需改进")
|| feedback.toLowerCase().contains("no need for improvement")) {
System.out.println("\n✅ 反思认为代码已无需改进,任务完成。");
break;
}
// c. 优化
System.out.println("\n-> 正在进行优化...");
String refinePrompt = REFINE_PROMPT_TEMPLATE
.replace("{task}", task)
.replace("{last_code_attempt}", lastCode)
.replace("{feedback}", feedback);
String refinedCode = getLlmResponse(refinePrompt);
memory.addRecord("execution", refinedCode);
}
String finalCode = memory.getLastExecution();
System.out.println("\n--- 任务完成 ---\n最终生成的代码:\n" + finalCode);
return finalCode;
}
/** 辅助方法:调用LLM并获取完整响应 */
private String getLlmResponse(String prompt) {
List<ChatCompletionMessage> messages = List.of(
ChatCompletionMessage.builder()
.role(ChatCompletionMessage.Role.USER)
.content(prompt)
.build()
);
String response = llmClient.think(messages);
return response != null ? response : "";
}
// ===== 使用示例 =====
public static void main(String[] args) {
try {
HelloAgentsLLM llmClient = new HelloAgentsLLM();
ReflectionAgent agent = new ReflectionAgent(llmClient, 2);
String task = "编写一个Python函数,找出1到n之间所有的素数 (prime numbers)。";
agent.run(task);
} catch (Exception e) {
System.out.println("初始化LLM客户端时出错: " + e.getMessage());
}
}
}
7.无 SDK 纯 Http 轻量 LLM 客户端
无需引入 OpenAI 官方依赖,仅 OkHttp+Gson 实现调用
package com.helloagents;
import com.google.gson.*;
import io.github.cdimascio.dotenv.Dotenv;
import okhttp3.*;
import okhttp3.MediaType;
import java.util.*;
public class HelloAgentsLLM {
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private static final OkHttpClient httpClient = new OkHttpClient();
private static final Gson gson = new Gson();
private final String model;
private final String apiKey;
private final String baseUrl;
public HelloAgentsLLM() {
this(null, null, null);
}
public HelloAgentsLLM(String model, String apiKey, String baseUrl) {
Dotenv dotenv;
try {
dotenv = Dotenv.load();
} catch (Exception e) {
System.out.println("警告:未找到 .env 文件,将使用系统环境变量。");
dotenv = null;
}
this.model = model != null ? model
: (dotenv != null ? dotenv.get("LLM_MODEL_ID") : System.getenv("LLM_MODEL_ID"));
this.apiKey = apiKey != null ? apiKey
: (dotenv != null ? dotenv.get("LLM_API_KEY") : System.getenv("LLM_API_KEY"));
this.baseUrl = baseUrl != null ? baseUrl
: (dotenv != null ? dotenv.get("LLM_BASE_URL") : System.getenv("LLM_BASE_URL"));
if (this.model == null || this.apiKey == null || this.baseUrl == null) {
throw new IllegalArgumentException(
"模型ID、API密钥和服务地址必须被提供或在.env文件中定义。");
}
}
/**
* 调用 LLM(非流式),返回完整的响应文本。
*/
public String think(List<Map<String, String>> messages, double temperature) {
System.out.println("🧠 正在调用 " + model + " 模型...");
try {
// 构建请求体
JsonArray msgs = new JsonArray();
for (Map<String, String> msg : messages) {
JsonObject obj = new JsonObject();
obj.addProperty("role", msg.get("role"));
obj.addProperty("content", msg.get("content"));
msgs.add(obj);
}
JsonObject requestBody = new JsonObject();
requestBody.addProperty("model", model);
requestBody.add("messages", msgs);
requestBody.addProperty("temperature", temperature);
requestBody.addProperty("stream", false); // 非流式
Request request = new Request.Builder()
.url(baseUrl + "/chat/completions")
.addHeader("Authorization", "Bearer " + apiKey)
.addHeader("Content-Type", "application/json")
.post(RequestBody.create(requestBody.toString(), JSON))
.build();
System.out.println("✅ 大语言模型响应成功:");
Response response = httpClient.newCall(request).execute();
String body = response.body().string();
JsonObject result = gson.fromJson(body, JsonObject.class);
String content = result.getAsJsonArray("choices")
.get(0).getAsJsonObject()
.getAsJsonObject("message")
.get("content").getAsString();
System.out.println(content);
return content;
} catch (Exception e) {
System.out.println("❌ 调用LLM API时发生错误: " + e.getMessage());
return null;
}
}
// 默认 temperature=0
public String think(List<Map<String, String>> messages) {
return think(messages, 0.0);
}
}
此版本返回 `List<Map<String, String>>` 而非 OpenAI SDK 的强类型消息对象,使用时请注意调整 Agent 类中的消息构造方式。

