AI Agent设计模式:ReAct、Plan-and-Execute、Multi-Agent的Java实现
AI Agent设计模式:ReAct、Plan-and-Execute、Multi-Agent的Java实现
适读人群:Java后端工程师、AI架构师 | 阅读时长:约22分钟 | 依赖:Spring AI 1.0、LangChain4j 0.36
开篇故事
去年做了一个投资研究自动化的项目,需求是:用户输入一只股票代码,系统自动生成一份完整的研究报告,包括公司基本面分析、最新新闻摘要、财务指标计算、行业对比、风险提示。
一开始我用的是最简单的方案——一个大Prompt,把所有任务描述清楚,让GPT-4一次性完成。效果惨不忍睹:要么遗漏某些分析维度,要么把真实财务数据和自己"编"的数据混在一起,要么分析流于表面,看起来像截图上网搜的摘抄。
真正让项目转折的是我引入了Plan-and-Execute模式:先让LLM制定一个详细的分析计划,然后按计划逐步执行每一步——调用财务数据API、搜索新闻、计算指标、生成各节分析——最后把各步结果整合成完整报告。
这个架构调整之后,报告质量大幅提升。每步任务更聚焦,数据来源明确,LLM的幻觉率显著降低。这让我对Agent设计模式产生了浓厚兴趣,系统性地把ReAct、Plan-and-Execute、Multi-Agent都研究了一遍,今天整理出来分享。
一、核心问题分析
从"直接调用LLM"到"Agent",本质的变化是引入了循环和决策:
简单LLM调用:输入 → 单次LLM调用 → 输出
Agent:输入 → [思考 → 行动 → 观察] × N次 → 输出
这个循环让Agent能够处理需要多步骤、动态调整的复杂任务。三种主要模式的区别:
| 模式 | 计划方式 | 执行方式 | 适用场景 |
|---|---|---|---|
| ReAct | 边想边做 | 每步决定下一步 | 探索性任务、不确定路径 |
| Plan-and-Execute | 先全局计划 | 按计划顺序执行 | 已知步骤的复杂任务 |
| Multi-Agent | 分工计划 | 多Agent并行/协作 | 大型、可分解的任务 |
二、原理深度解析
2.1 三种模式架构对比
2.2 ReAct的循环机制
ReAct(Reasoning + Acting)的核心是一个特殊的Prompt格式:
Thought: 我需要先查找公司的最新财报数据...
Action: search_financial_data
Action Input: {"symbol": "AAPL", "year": 2024}
Observation: 苹果2024年营收3892亿美元,净利润937亿美元...
Thought: 有了营收数据,接下来需要计算PE估值...
Action: calculate_pe_ratio
Action Input: {"market_cap": 3200000000000, "net_income": 93700000000}
Observation: PE = 34.15
Thought: 数据已收集完整,可以生成分析报告了
Final Answer: ...Java实现需要解析这个格式,提取Action和Action Input,调用对应工具,把Observation追加到对话中,循环直到出现"Final Answer"。
三、完整代码实现
3.1 ReAct Agent实现
@Service
public class ReactAgent {
private static final Logger log = LoggerFactory.getLogger(ReactAgent.class);
private static final int MAX_ITERATIONS = 10; // 防止无限循环
private final ChatClient chatClient;
private final Map<String, AgentTool> tools;
private static final String REACT_SYSTEM_PROMPT = """
你是一个智能分析助手,通过"思考-行动-观察"循环解决复杂问题。
可用工具:
{tools_description}
回复格式(严格遵守):
思考:[你对当前情况的分析和下一步计划]
行动:[工具名称]
行动输入:[JSON格式的工具参数]
收到观察结果后继续:
思考:[基于观察结果的新分析]
...
当你有了足够信息可以回答时:
思考:[最终推理]
最终答案:[给用户的完整答案]
""";
public ReactAgent(ChatClient.Builder builder, List<AgentTool> toolList) {
this.chatClient = builder.build();
this.tools = toolList.stream()
.collect(Collectors.toMap(AgentTool::getName, t -> t));
}
public String run(String userTask) {
String toolsDesc = tools.values().stream()
.map(t -> "- " + t.getName() + ": " + t.getDescription())
.collect(Collectors.joining("\n"));
String systemPrompt = REACT_SYSTEM_PROMPT.replace("{tools_description}", toolsDesc);
List<Map<String, String>> messages = new ArrayList<>();
messages.add(Map.of("role", "system", "content", systemPrompt));
messages.add(Map.of("role", "user", "content", "任务:" + userTask));
for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
// 调用LLM
String llmResponse = callLLM(messages);
messages.add(Map.of("role", "assistant", "content", llmResponse));
log.debug("ReAct迭代{}:\n{}", iteration + 1, llmResponse);
// 检查是否有最终答案
if (llmResponse.contains("最终答案:")) {
return extractFinalAnswer(llmResponse);
}
// 解析行动
ActionPlan action = parseAction(llmResponse);
if (action == null) {
log.warn("无法解析行动,强制结束");
return llmResponse;
}
// 执行工具
String observation = executeAction(action);
log.debug("工具[{}]返回:{}", action.getToolName(), observation);
// 把观察结果追加到消息
messages.add(Map.of("role", "user", "content", "观察:" + observation));
}
return "达到最大迭代次数,当前进度:" +
messages.get(messages.size() - 1).get("content");
}
private ActionPlan parseAction(String llmResponse) {
// 解析"行动:工具名"和"行动输入:{...}"
Pattern actionPattern = Pattern.compile("行动:(\\S+)");
Pattern inputPattern = Pattern.compile("行动输入:(\\{[^}]+\\})");
Matcher actionMatcher = actionPattern.matcher(llmResponse);
Matcher inputMatcher = inputPattern.matcher(llmResponse);
if (!actionMatcher.find()) return null;
String toolName = actionMatcher.group(1);
String inputJson = inputMatcher.find() ? inputMatcher.group(1) : "{}";
return new ActionPlan(toolName, inputJson);
}
private String executeAction(ActionPlan action) {
AgentTool tool = tools.get(action.getToolName());
if (tool == null) {
return "工具不存在:" + action.getToolName();
}
try {
return tool.execute(action.getInputJson());
} catch (Exception e) {
return "工具执行失败:" + e.getMessage();
}
}
private String callLLM(List<Map<String, String>> messages) {
// 构建消息列表并调用
StringBuilder promptBuilder = new StringBuilder();
for (Map<String, String> msg : messages) {
promptBuilder.append(msg.get("role")).append(": ")
.append(msg.get("content")).append("\n\n");
}
return chatClient.prompt(promptBuilder.toString()).call().content();
}
private String extractFinalAnswer(String response) {
int idx = response.lastIndexOf("最终答案:");
return idx >= 0 ? response.substring(idx + 5).trim() : response;
}
}3.2 Plan-and-Execute Agent实现
@Service
public class PlanAndExecuteAgent {
private static final Logger log = LoggerFactory.getLogger(PlanAndExecuteAgent.class);
private final ChatClient plannerClient; // 规划用,较大模型
private final ChatClient executorClient; // 执行用,较小模型省成本
private final Map<String, AgentTool> tools;
public PlanAndExecuteAgent(ChatClient.Builder builder,
List<AgentTool> toolList) {
// 规划器用更强的模型
this.plannerClient = builder
.defaultOptions(ChatOptions.builder()
.model("gpt-4o")
.temperature(0.3)
.build())
.build();
// 执行器可以用更小的模型
this.executorClient = builder
.defaultOptions(ChatOptions.builder()
.model("gpt-4o-mini")
.temperature(0.1)
.build())
.build();
this.tools = toolList.stream()
.collect(Collectors.toMap(AgentTool::getName, t -> t));
}
public String run(String userTask) {
// 阶段1:制定执行计划
List<ExecutionStep> plan = createPlan(userTask);
log.info("执行计划制定完成,共{}个步骤", plan.size());
// 阶段2:按顺序执行每个步骤
Map<Integer, String> stepResults = new LinkedHashMap<>();
for (ExecutionStep step : plan) {
log.info("执行步骤{}: {}", step.getStepId(), step.getDescription());
String result = executeStep(step, stepResults);
stepResults.put(step.getStepId(), result);
log.info("步骤{}完成: {}", step.getStepId(),
result.length() > 100 ? result.substring(0, 100) + "..." : result);
}
// 阶段3:整合所有步骤结果
return integrateResults(userTask, plan, stepResults);
}
private List<ExecutionStep> createPlan(String task) {
String plannerPrompt = """
请为以下任务制定详细的执行计划,输出JSON数组格式。
任务:%s
可用工具:
%s
输出格式(JSON数组):
[
{
"stepId": 1,
"description": "步骤描述",
"toolName": "工具名称或null(如果是纯LLM分析)",
"toolInput": {"参数": "值"},
"dependsOn": []
}
]
""".formatted(task, getToolsDescription());
String planJson = plannerClient.prompt(plannerPrompt).call().content();
return parsePlanJson(planJson);
}
private String executeStep(ExecutionStep step,
Map<Integer, String> previousResults) {
if (step.getToolName() != null && tools.containsKey(step.getToolName())) {
// 使用工具执行
AgentTool tool = tools.get(step.getToolName());
String inputWithContext = interpolateContext(
step.getToolInputJson(), previousResults);
return tool.execute(inputWithContext);
} else {
// 纯LLM分析步骤
String contextSummary = buildContextSummary(
step.getDependsOn(), previousResults);
String executorPrompt = """
请完成以下分析任务:
任务:%s
前置步骤结果:
%s
请给出详细分析:
""".formatted(step.getDescription(), contextSummary);
return executorClient.prompt(executorPrompt).call().content();
}
}
private String integrateResults(String originalTask,
List<ExecutionStep> plan,
Map<Integer, String> results) {
StringBuilder integrationPrompt = new StringBuilder();
integrationPrompt.append("请根据以下各步骤分析结果,生成对任务「")
.append(originalTask)
.append("」的完整综合报告:\n\n");
for (ExecutionStep step : plan) {
integrationPrompt.append("=== ")
.append(step.getDescription())
.append(" ===\n");
integrationPrompt.append(results.getOrDefault(step.getStepId(), "无结果"))
.append("\n\n");
}
return plannerClient.prompt(integrationPrompt.toString()).call().content();
}
private String getToolsDescription() {
return tools.values().stream()
.map(t -> t.getName() + ": " + t.getDescription())
.collect(Collectors.joining("\n"));
}
private List<ExecutionStep> parsePlanJson(String json) {
// 解析JSON,提取步骤列表(简化实现)
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(extractJsonArray(json),
new TypeReference<List<ExecutionStep>>() {});
} catch (Exception e) {
log.error("计划解析失败: {}", e.getMessage());
// 降级:单步执行
return List.of(new ExecutionStep(1, "直接分析", null, "{}", List.of()));
}
}
private String extractJsonArray(String text) {
int start = text.indexOf('[');
int end = text.lastIndexOf(']');
return start >= 0 && end > start ? text.substring(start, end + 1) : "[]";
}
private String buildContextSummary(List<Integer> dependsOn,
Map<Integer, String> results) {
return dependsOn.stream()
.map(id -> "步骤" + id + "结果:" + results.getOrDefault(id, ""))
.collect(Collectors.joining("\n"));
}
private String interpolateContext(String json, Map<Integer, String> results) {
// 简单的占位符替换,如 {{step1_result}} -> 步骤1的结果
String result = json;
for (Map.Entry<Integer, String> entry : results.entrySet()) {
result = result.replace("{{step" + entry.getKey() + "_result}}",
entry.getValue());
}
return result;
}
}3.3 Multi-Agent协作框架
@Service
public class MultiAgentOrchestrator {
private static final Logger log = LoggerFactory.getLogger(MultiAgentOrchestrator.class);
private final Map<String, SpecializedAgent> agents;
private final ChatClient orchestratorClient;
public MultiAgentOrchestrator(ChatClient.Builder builder,
List<SpecializedAgent> agentList) {
this.orchestratorClient = builder.build();
this.agents = agentList.stream()
.collect(Collectors.toMap(SpecializedAgent::getName, a -> a));
}
/**
* 协调多个专业Agent完成复杂任务
*/
public String orchestrate(String task) {
// 1. 分解任务给各Agent
Map<String, String> subTasks = decomposeTasks(task);
// 2. 并行执行(独立子任务)
Map<String, CompletableFuture<String>> futures = new HashMap<>();
for (Map.Entry<String, String> entry : subTasks.entrySet()) {
String agentName = entry.getKey();
String subTask = entry.getValue();
SpecializedAgent agent = agents.get(agentName);
if (agent != null) {
futures.put(agentName,
CompletableFuture.supplyAsync(() -> agent.execute(subTask)));
}
}
// 3. 等待所有结果
Map<String, String> agentResults = new HashMap<>();
for (Map.Entry<String, CompletableFuture<String>> entry : futures.entrySet()) {
try {
agentResults.put(entry.getKey(), entry.getValue().get(30, TimeUnit.SECONDS));
} catch (Exception e) {
log.error("Agent[{}]执行失败: {}", entry.getKey(), e.getMessage());
agentResults.put(entry.getKey(), "执行失败:" + e.getMessage());
}
}
// 4. 综合报告
return synthesize(task, agentResults);
}
private Map<String, String> decomposeTasks(String task) {
String decompositionPrompt = """
请将以下任务分解给各专业Agent。输出JSON对象,key是Agent名称,value是该Agent的具体任务。
可用Agent:
%s
主任务:%s
输出JSON:
""".formatted(getAgentsDescription(), task);
String json = orchestratorClient.prompt(decompositionPrompt).call().content();
try {
String jsonPart = extractJsonObject(json);
return new ObjectMapper().readValue(jsonPart, new TypeReference<>() {});
} catch (Exception e) {
// 降级:所有Agent处理同一任务
Map<String, String> fallback = new HashMap<>();
agents.keySet().forEach(name -> fallback.put(name, task));
return fallback;
}
}
private String synthesize(String originalTask, Map<String, String> agentResults) {
StringBuilder prompt = new StringBuilder();
prompt.append("请综合以下各专业Agent的分析结果,生成针对任务「")
.append(originalTask).append("」的最终结论:\n\n");
agentResults.forEach((agent, result) -> {
prompt.append("[").append(agent).append("分析结果]\n");
prompt.append(result).append("\n\n");
});
return orchestratorClient.prompt(prompt.toString()).call().content();
}
private String getAgentsDescription() {
return agents.values().stream()
.map(a -> "- " + a.getName() + ": " + a.getDescription())
.collect(Collectors.joining("\n"));
}
private String extractJsonObject(String text) {
int start = text.indexOf('{');
int end = text.lastIndexOf('}');
return start >= 0 && end > start ? text.substring(start, end + 1) : "{}";
}
}四、效果评估与优化
在投研报告生成任务(50份标准化测试报告)上的对比:
| Agent模式 | 报告完整度 | 数据准确率 | 平均生成时间 | 幻觉率 |
|---|---|---|---|---|
| 单次大Prompt | 62% | 71% | 45秒 | 28% |
| ReAct | 81% | 84% | 2分10秒 | 12% |
| Plan-and-Execute | 89% | 91% | 3分40秒 | 8% |
| Multi-Agent并行 | 87% | 88% | 1分50秒 | 10% |
Plan-and-Execute的报告完整度和准确率最高,因为全局规划让每个步骤目标明确、来源可追溯。Multi-Agent虽然速度更快(并行执行),但Agent间的信息共享不足,偶尔出现各自分析视角割裂的问题。
五、踩坑实录
坑1:ReAct的循环上限必须设,而且要真的能触发停止
第一版没有设最大迭代次数,LLM在某次推理时进入了"分析-补充分析-再补充分析"的循环,调用了40多次工具,花了将近200块API费用。现在所有ReAct实现都强制设MAX_ITERATIONS,通常10次以内能完成合理的任务,超过了基本都是某个步骤进入了死循环。
坑2:Plan-and-Execute的计划质量严重依赖Planner的模型能力
用GPT-4o做Planner,计划制定得很合理,步骤清晰,依赖关系正确。换成GPT-4o-mini,计划常常遗漏关键步骤,或者把两个相互依赖的步骤设计成并行,导致执行时数据不完整。最终决策:Planner必须用高质量模型,成本在整体成本里占比不高但对最终效果影响极大。
坑3:Multi-Agent之间的信息共享是最大的工程挑战
各Agent并行跑,各自拿到的是子任务,但有些分析需要引用其他Agent的结论(比如写作Agent的风险分析需要知道财务Agent发现了哪些异常)。最终我用了一个"共享黑板"模式:所有Agent都能读写一个共享状态对象,在执行前先读已完成Agent的结论。但这要求任务分解时要明确定义信息依赖图,实现复杂了不少。
六、总结
三种Agent模式各有其适用场景:ReAct适合探索性、不确定路径的任务;Plan-and-Execute适合步骤相对固定、需要高质量结果的任务;Multi-Agent适合任务规模大且可以分解为相对独立子任务的场景。
在实际工程中,不要盲目上Multi-Agent——复杂的协调本身就是成本,单Agent的Plan-and-Execute往往能满足大部分需求。只有当单Agent无法完成的超大任务(比如完整的年度报告生成,需要不同专家视角),才真的需要Multi-Agent协作。
