一、Plan-And-Execute 架构
当任务变得更加复杂、步骤繁多时,「规划-执行」架构开始展现威力。
这种模式将 Agent 的流程明确分为两个阶段:先规划(Plan),再执行(Execute) 先让 Agent 想出一整套方案,然后按照方案逐步落实。
与 ReAct 不同,Plan-and-Execute 会强制 LLM 做全局思考。它通常涉及两个子 Agent 或子模块:一个 Planner(规划者)和一个Executor(执行者)。两者分工如下:
Planner:由一个 LLM 来承担,它的任务是分析目标,产出详细的执行计划。Planner 会接收用户的最终任务描述,然后以列表形式生成需要完成的子任务序列。
Planner 在这一步可以充分利用 LLM 的链式思考能力,将模糊的目标细化为可执行的步骤,并考虑步骤间的依赖、先后顺序等 。
Executor(s):执行者负责按照 Planner 给出的每个子任务,逐条执行 。Executor 本质上也是一个 Agent,可以针对不同子任务切换工具或 API,也可以调用一个内部 ReAct Agent 来完成。
Executor 会读取任务清单的某一条,比如「第1步:搜索 X 信息」,然后实际调用对应的工具完成它,将结果记录下来,再执行下一步。
二、langchain4j 框架实现 Plan-And-Execute
1.pom
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<langchain4j.version>1.7.1</langchain4j.version>
</properties>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
<optional>true</optional>
</dependency>
</dependencies>
2.Tool
import dev.langchain4j.agent.tool.Tool;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
/**
* 工具函数集合,提供文件操作和终端命令执行功能
* 使用 langchain4j @Tool 注解标记为工具函数
*/
public class ToolFunctions {
/**
* 读取文件内容
*/
@Tool("读取指定路径的文件内容")
public static String readFile(String filePath) {
try {
Path path = Paths.get(filePath);
return Files.readString(path);
} catch (IOException e) {
return "读取文件错误:" + e.getMessage();
}
}
/**
* 将指定内容写入指定文件
*/
@Tool("将内容写入指定路径的文件")
public static String writeToFile(String filePath, String content) {
try {
Path path = Paths.get(filePath);
// 如果目录不存在,创建目录
Path parent = path.getParent();
if (parent != null && !Files.exists(parent)) {
Files.createDirectories(parent);
}
// 写入文件(覆盖模式)
Files.writeString(path, content, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
return "写入成功";
} catch (IOException e) {
return "写入文件失败:" + e.getMessage();
}
}
/**
* 执行终端命令
*/
@Tool("执行终端命令并返回结果")
public static String runTerminalCommand(String command) {
try {
ProcessBuilder processBuilder = new ProcessBuilder();
// 根据操作系统选择shell
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
processBuilder.command("cmd.exe", "/c", command);
} else {
processBuilder.command("sh", "-c", command);
}
Process process = processBuilder.start();
// 读取输出
StringBuilder output = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
}
// 读取错误输出
StringBuilder error = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
error.append(line).append("\n");
}
}
int exitCode = process.waitFor();
if (exitCode == 0) {
return "执行成功\n" + output.toString();
} else {
return "执行失败\n" + error.toString();
}
} catch (Exception e) {
return "工具执行错误:" + e.getMessage();
}
}
}
3.PlanAndExecuteAgent
PlannerService 作为任务规划器
ExecutorService 作为任务执行器
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.UserMessage;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class PlanAndExecuteAgent {
private final PlannerService plannerService;
private final ExecutorService executorService;
private final int maxReplanAttempts;
public PlanAndExecuteAgent(String baseApiUrl, String apiModel, String apiKey) throws IOException {
this.maxReplanAttempts = 3;
OpenAiChatModel chatModel = OpenAiChatModel.builder()
.baseUrl(baseApiUrl)
.modelName(apiModel)
.apiKey(apiKey)
.temperature(0.7)
.maxTokens(2000)
.timeout(Duration.ofMinutes(10))
.build();
// 规划器:负责将复杂任务分解为多个步骤
this.plannerService = AiServices.builder(PlannerService.class)
.chatModel(chatModel)
.systemMessageProvider(request ->
"""
你是一个任务规划器。请将用户的复杂任务分解为一系列清晰的步骤。
可用工具:
- readFile(path): 读取文件内容
- writeToFile(path, content): 将内容写入文件
- runTerminalCommand(command): 执行终端命令
规划原则:
1. 每个步骤应该具体可执行
2. 步骤按顺序排列
3. 步骤之间相互依赖
4. 如果需要读取/写入文件或执行命令,请明确标注需要使用哪个工具
请用以下格式输出计划:
目标:[总体目标]
步骤1: [具体操作]
步骤2: [具体操作]
...
""")
.build();
// 执行器:负责执行每个步骤
this.executorService = AiServices.builder(ExecutorService.class)
.chatModel(chatModel)
.tools(new ToolFunctions())
.systemMessageProvider(request ->
"""
你是一个任务执行器。使用提供的工具函数执行步骤。
你可以使用以下工具:
- readFile(文件路径): 读取文件内容
- writeToFile(文件路径, 内容): 写入文件
- runTerminalCommand(命令): 执行终端命令
请仔细执行每个步骤,需要时使用工具。
执行结果要求:
- 如果步骤执行成功,返回执行结果
- 如果步骤执行失败(工具调用失败、命令执行出错、文件操作失败等),返回以"失败:"开头的错误信息
""")
.build();
}
/**
* 执行任务:先制定计划,然后逐步执行,必要时重新规划
*/
public String execute(String task) {
return executeWithReplan(task, task, 0);
}
/**
* 带重新规划功能的执行方法
*/
private String executeWithReplan(String originTask, String task, int replanCount) {
if (replanCount > maxReplanAttempts) {
return " 达到最大重新规划次数限制(%d),停止执行。 ".formatted(maxReplanAttempts);
}
try {
// 第一步:制定计划
String plan = plannerService.createPlan(task);
// 解析计划为步骤列表
List<String> steps = parsePlan(plan);
// 第二步:执行计划
StringBuilder results = new StringBuilder();
for (int i = 0; i < steps.size(); i++) {
String step = steps.get(i);
String result = executorService.executeStep(step);
results.append("""
步骤 %d: %s
结果: %s
""".formatted(i + 1, step, result));
// 如果需要重新规划
if (result.trim().toUpperCase().startsWith("失败")) {
String updatedTask = """
原始任务:%s
之前的执行失败了,需要你重新制定一个完整的计划。
之前的规划步骤:
%s
已执行的步骤及结果:
%s
请分析失败原因,制定一个新计划。新计划应该:
1. 避免之前失败的错误
2. 可以采用不同的方法或工具
3. 如果需要,可以调整步骤的顺序或添加额外的步骤
4. 确保新计划能够完成原始任务
""".formatted(task, plan, results);
return executeWithReplan(originTask, updatedTask, replanCount + 1);
}
}
return """
计划:
%s
执行结果:
%s
""".formatted(originTask, results.toString());
} catch (Exception e) {
return "执行任务时发生错误: %s ".formatted(e.getMessage());
}
}
/**
* 解析计划文本,提取步骤列表
*/
private List<String> parsePlan(String plan) {
List<String> steps = new ArrayList<>();
// 匹配每个步骤:从步骤标题到下一个步骤标题之间的所有内容
Pattern pattern = Pattern.compile("^\\s*(?:步骤\\d+|Step\\s*\\d+|\\d+)[.::]\\s*(.*?)(?=^\\s*(?:步骤\\d+|Step\\s*\\d+|\\d+)[.::]|\\z)", Pattern.MULTILINE | Pattern.DOTALL);
Matcher matcher = pattern.matcher(plan);
while (matcher.find()) {
String stepContent = matcher.group(1).trim();
if (!stepContent.isEmpty()) {
steps.add(stepContent);
}
}
return steps.isEmpty() ? List.of(plan) : steps;
}
// 规划器接口
public interface PlannerService {
String createPlan(@UserMessage String task);
}
// 执行器接口
public interface ExecutorService {
String executeStep(@UserMessage String step);
}
}
4.测试类
public class Main {
public static void main(String[] args) {
try {
// 创建 Plan-and-Execute Agent 服务
PlanAndExecuteAgent agentService = new PlanAndExecuteAgent(
"https://dashscope.aliyuncs.com/compatible-mode/v1",
"qwen-turbo",
"sk-123456");
// 固定的测试问题
String question = "帮我创建一个简单的Hello World Java程序,并运行它";
String response = agentService.execute(question);
System.out.println("最终结果:" + response);
} catch (Exception e) {
System.err.println("处理问题时发生错误: " + e.getMessage());
}
}
}