第2057篇:AI Agent设计模式——ReAct、Plan-and-Execute和反思循环
2026/4/30大约 6 分钟
第2057篇:AI Agent设计模式——ReAct、Plan-and-Execute和反思循环
适读人群:需要构建能自主完成复杂任务的AI Agent的工程师 | 阅读时长:约20分钟 | 核心价值:掌握三种核心Agent设计模式,理解各自的适用场景和实现方式
有人问我Agent和普通的LLM调用有什么区别。
我的回答是:普通LLM调用是"问一个问题,得一个回答";Agent是"给一个目标,AI自主规划和执行多个步骤来完成它"。
"预约下周二下午2点的牙医,并在我的日历上加上这条记录"——这个任务需要查找牙医信息、打电话或者在线预约、确认时间、创建日历事件,是一个多步骤的自主执行过程。这就是Agent适合的场景。
模式一:ReAct(推理-行动循环)
ReAct是最基础的Agent模式,让LLM在"推理"和"执行工具"之间交替循环:
/**
* ReAct Agent实现
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class ReActAgent {
private final ChatLanguageModel llm;
private final Map<String, Tool> availableTools;
private static final int MAX_ITERATIONS = 10; // 防止无限循环
public String run(String task) {
List<Message> conversation = new ArrayList<>();
// System Prompt定义ReAct格式
conversation.add(SystemMessage.from(buildSystemPrompt()));
conversation.add(UserMessage.from(task));
int iterations = 0;
while (iterations < MAX_ITERATIONS) {
iterations++;
// 让LLM推理下一步
AiMessage response = llm.generate(conversation).content();
conversation.add(response);
String responseText = response.text();
log.debug("ReAct迭代{}: {}", iterations, responseText.substring(0,
Math.min(200, responseText.length())));
// 检查是否完成
if (responseText.contains("Final Answer:")) {
return extractFinalAnswer(responseText);
}
// 解析工具调用
ToolCall toolCall = parseToolCall(responseText);
if (toolCall == null) {
// LLM没有按格式输出,强制要求最终答案
conversation.add(UserMessage.from(
"请基于你目前的信息,给出最终答案(格式:Final Answer: 你的回答)"));
continue;
}
// 执行工具
String observation = executeTool(toolCall);
log.info("工具[{}]执行结果: {}", toolCall.name(),
observation.substring(0, Math.min(100, observation.length())));
// 把工具结果加入对话
conversation.add(UserMessage.from("Observation: " + observation));
}
return "达到最大迭代次数,任务未完成";
}
private String buildSystemPrompt() {
String toolDescriptions = availableTools.entrySet().stream()
.map(e -> String.format("- %s: %s", e.getKey(), e.getValue().description()))
.collect(Collectors.joining("\n"));
return String.format("""
你是一个能够使用工具完成任务的AI助手。
可用工具:
%s
执行格式(严格遵守):
Thought: 分析当前情况,决定下一步
Action: 工具名称
Action Input: {"参数名": "参数值"}
获得工具结果后,继续推理:
Thought: 根据结果分析
Action: ...
完成任务后:
Final Answer: 最终结果
""", toolDescriptions);
}
private ToolCall parseToolCall(String text) {
// 解析 Action: xxx 和 Action Input: {...}
Pattern actionPattern = Pattern.compile("Action:\\s*(.+)");
Pattern inputPattern = Pattern.compile("Action Input:\\s*(\\{.+\\})",
Pattern.DOTALL);
Matcher actionMatcher = actionPattern.matcher(text);
Matcher inputMatcher = inputPattern.matcher(text);
if (actionMatcher.find() && inputMatcher.find()) {
return new ToolCall(
actionMatcher.group(1).trim(),
inputMatcher.group(1).trim()
);
}
return null;
}
private String executeTool(ToolCall toolCall) {
Tool tool = availableTools.get(toolCall.name());
if (tool == null) {
return "错误:不存在的工具: " + toolCall.name();
}
try {
return tool.execute(toolCall.inputJson());
} catch (Exception e) {
return "工具执行失败: " + e.getMessage();
}
}
private String extractFinalAnswer(String text) {
Pattern pattern = Pattern.compile("Final Answer:\\s*(.+)", Pattern.DOTALL);
Matcher matcher = pattern.matcher(text);
return matcher.find() ? matcher.group(1).trim() : text;
}
public interface Tool {
String description();
String execute(String inputJson);
}
public record ToolCall(String name, String inputJson) {}
}模式二:Plan-and-Execute(先规划再执行)
ReAct的问题是:每一步都让LLM即兴决定,容易"偏离"目标。Plan-and-Execute先生成完整计划,再按步骤执行:
/**
* Plan-and-Execute Agent
* 先用规划器生成执行计划,再用执行器按步骤执行
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class PlanAndExecuteAgent {
private final ChatLanguageModel plannerLlm; // 规划用(强模型,如GPT-4o)
private final ChatLanguageModel executorLlm; // 执行用(快模型,如GPT-4o-mini)
private final Map<String, ReActAgent.Tool> tools;
/**
* 主入口
*/
public AgentResult run(String objective) {
log.info("开始执行任务: {}", objective);
// 第一阶段:规划
ExecutionPlan plan = createPlan(objective);
log.info("执行计划: {} 个步骤", plan.steps().size());
plan.steps().forEach((i, step) -> log.info(" 步骤{}: {}", i, step.description()));
// 第二阶段:执行
Map<Integer, StepResult> results = new LinkedHashMap<>();
for (Map.Entry<Integer, PlanStep> entry : plan.steps().entrySet()) {
int stepNum = entry.getKey();
PlanStep step = entry.getValue();
log.info("执行步骤{}: {}", stepNum, step.description());
// 构建步骤上下文(之前步骤的结果)
String context = buildContext(results);
StepResult result = executeStep(step, context, objective);
results.put(stepNum, result);
if (!result.success()) {
log.warn("步骤{}执行失败: {}", stepNum, result.error());
// 重新规划(可选)
if (step.critical()) {
return AgentResult.failed("关键步骤失败: " + result.error());
}
}
}
// 第三阶段:汇总结果
return synthesizeResults(objective, results);
}
private ExecutionPlan createPlan(String objective) {
String planningPrompt = String.format("""
你是一个任务规划专家。
任务目标:%s
可用工具:
%s
请将任务分解为有序的执行步骤,每个步骤应该:
1. 只做一件具体的事情
2. 明确指定使用哪个工具(如果需要)
3. 标明是否是关键步骤(失败后是否应该中止)
输出JSON格式:
{
"steps": {
"1": {"description": "步骤描述", "tool": "工具名或null", "critical": true/false},
"2": {...}
}
}
""", objective, getToolDescriptions());
String planJson = plannerLlm.generate(planningPrompt);
return parsePlan(planJson);
}
private StepResult executeStep(PlanStep step, String context, String objective) {
if (step.tool() != null) {
// 需要调用工具
String toolInputPrompt = String.format("""
任务目标:%s
已完成的步骤结果:
%s
当前需要执行:%s
使用工具:%s
请生成工具调用的输入参数(JSON格式):
""", objective, context, step.description(), step.tool());
String toolInputJson = executorLlm.generate(toolInputPrompt);
try {
ReActAgent.Tool tool = tools.get(step.tool());
if (tool == null) {
return new StepResult(false, null, "工具不存在: " + step.tool());
}
String output = tool.execute(extractJson(toolInputJson));
return new StepResult(true, output, null);
} catch (Exception e) {
return new StepResult(false, null, e.getMessage());
}
} else {
// 纯思考步骤
String thinkPrompt = String.format("""
任务目标:%s
已有信息:
%s
请分析:%s
""", objective, context, step.description());
String analysis = executorLlm.generate(thinkPrompt);
return new StepResult(true, analysis, null);
}
}
private AgentResult synthesizeResults(String objective, Map<Integer, StepResult> results) {
String successResults = results.entrySet().stream()
.filter(e -> e.getValue().success())
.map(e -> "步骤" + e.getKey() + ": " + e.getValue().output())
.collect(Collectors.joining("\n"));
String synthesisPrompt = String.format("""
基于以下执行结果,总结任务"%s"的完成情况:
%s
请给出清晰的最终结论:
""", objective, successResults);
String conclusion = plannerLlm.generate(synthesisPrompt);
return AgentResult.success(conclusion);
}
private String buildContext(Map<Integer, StepResult> results) {
return results.entrySet().stream()
.filter(e -> e.getValue().success())
.map(e -> String.format("步骤%d完成:%s", e.getKey(),
e.getValue().output() != null ? e.getValue().output().substring(0,
Math.min(200, e.getValue().output().length())) : ""))
.collect(Collectors.joining("\n"));
}
private String getToolDescriptions() {
return tools.entrySet().stream()
.map(e -> "- " + e.getKey() + ": " + e.getValue().description())
.collect(Collectors.joining("\n"));
}
// 简化的Plan解析和JSON提取(实际需要更健壮的实现)
private ExecutionPlan parsePlan(String json) { /* ... */ return null; }
private String extractJson(String text) { return text; }
public record PlanStep(String description, String tool, boolean critical) {}
public record ExecutionPlan(Map<Integer, PlanStep> steps) {}
public record StepResult(boolean success, String output, String error) {}
public record AgentResult(boolean success, String result, String error) {
public static AgentResult success(String result) { return new AgentResult(true, result, null); }
public static AgentResult failed(String error) { return new AgentResult(false, null, error); }
}
}模式三:反思循环(Reflection)
让Agent对自己的输出进行批评和改进:
/**
* 带反思循环的Agent
* 生成->批评->改进,直到质量达标
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class ReflectionAgent {
private final ChatLanguageModel actorLlm; // 执行角色
private final ChatLanguageModel criticLlm; // 批评角色
private static final int MAX_REFLECTIONS = 3;
/**
* 带反思的任务执行
*/
public String runWithReflection(String task, String qualityCriteria) {
// 初始生成
String output = actorLlm.generate(task);
log.info("初始输出生成完成");
for (int i = 0; i < MAX_REFLECTIONS; i++) {
// 批评
String critique = generateCritique(task, output, qualityCriteria);
log.info("反思{}:{}", i + 1, critique.substring(0, Math.min(100, critique.length())));
// 检查是否需要改进
if (isQualitySatisfied(critique)) {
log.info("质量已满足,停止反思");
break;
}
// 改进
output = improveOutput(task, output, critique);
log.info("输出已改进(反思{})", i + 1);
}
return output;
}
private String generateCritique(String task, String output, String criteria) {
return criticLlm.generate(String.format("""
请评估以下输出的质量,并给出具体的改进建议。
原始任务:%s
质量标准:%s
待评估输出:
%s
评估结果(先说是否满足标准,再列出具体改进点):
""", task, criteria, output));
}
private boolean isQualitySatisfied(String critique) {
String lower = critique.toLowerCase();
return lower.contains("满足") && !lower.contains("不满足")
|| lower.contains("已达标")
|| lower.contains("无需改进");
}
private String improveOutput(String task, String currentOutput, String critique) {
return actorLlm.generate(String.format("""
基于以下反馈,改进你的输出。
原始任务:%s
当前输出:
%s
改进建议:
%s
改进后的输出:
""", task, currentOutput, critique));
}
}三种模式的选择:
- 简单多步骤任务:ReAct(实现简单,足够用)
- 复杂长流程任务:Plan-and-Execute(先规划全局,避免走弯路)
- 需要高质量输出:反思循环(质量比速度更重要时)
实际项目里,这三种模式往往组合使用。
