一、ReAct 架构
在早期的大语言模型(LLM)应用中,提示工程(Prompt Engineering)是连接用户意图与模型输出的核心手段。然而,纯提示驱动的方案在多步推理、工具调用和动态环境交互中常显不足。ReAct框架(Reasoning+Acting)应运而生,通过将链式推理(Reasoning) 与环境行动(Acting) 结合,构建出能主动思考、决策并执行复杂任务的智能体(Agent)。
ReAct(Reasoning and Action)是一个框架,其概念来源于一篇论文,其核心思想,就是通过思维链的方式,引导模型将复杂问题进行拆分,一步一步地进行推理(Reasoning)和行动(Action),同时还引入了观察(Observation)环节,在每次执行(Action)之后,都会先观察(Observation)当前现状,然后再进行下一步的推理(Reason)。
ReAct这个框架,就是要让LLM,进行推理,然后采取行动与外界环境互动。
ReAct这个框架,就是要让开发者一步步引导LLM进行推理,然后根据推理的结果,判断采取哪个行动。
ReAct 架构:AI 代理的“推理-执行”闭环。
ReAct(Reasoning + Action)并非简单的技术模块,而是为大语言模型(LLM)赋予自主任务解决能力的核心范式。区别于传统 LLM 直接输出答案的“黑箱模式”,ReAct 强制代理在“思考”与“行动”之间形成循环闭环,具体表现为两步交替执行:
推理(Reasoning):基于当前任务目标与已有信息,分析缺口、规划下一步操作方向,相当于代理的“大脑决策”环节;
行动(Action):根据推理结论调用外部工具(如搜索引擎、计算器),获取新数据或执行特定操作,是代理与外部环境交互的“执行环节”。
Thought → Act → Observation → Thought → ... → Final Answer
ReAct框架的核心组件包括:
- 思维链(Chain of Thought):将一个大的复杂任务进行拆解,拆解成多个思维步骤。
- 推理(Reasoning):负责分析和处理输入的数据,生成有效的决策。
- 行动(Action):执行具体的操作,比如搜索、执行代码,或者其余自定义的行动。
- 观察(Observation):监控和收集环境反馈的数据,为下一步的推理和行动提供依据。
核心组件拆解
| 模块 | 功能说明 | 实现示例 |
|---|---|---|
| LLM Core | 生成推理与行动指令 | GPT-4、Claude 3、Llama 3 |
| Tool Engine | 工具调度与执行 | LangChain Tools, LlamaIndex |
| Memory | 存储历史观察与推理链 | Redis、向量数据库 |
| Parser | 解析LLM输出为结构化操作 | Pydantic + 正则表达式 |

二、Java 开发实现 ReActAgent
1.prompt
你需要解决一个问题。为此,你需要将问题分解为多个步骤。对于每个步骤,首先使用 <thought> 思考要做什么,然后使用可用工具之一决定一个 <action>。接着,你将根据你的行动从环境/工具中收到一个 <observation>。持续这个思考和行动的过程,直到你有足够的信息来提供 <final_answer>。
所有步骤请严格使用以下 XML 标签格式输出:
- <question> 用户问题
- <thought> 思考
- <action> 采取的工具操作
- <observation> 工具或环境返回的结果
- <final_answer> 最终答案
⸻
例子 1:
<question>埃菲尔铁塔有多高?</question>
<thought>我需要找到埃菲尔铁塔的高度。可以使用搜索工具。</thought>
<action>get_height("埃菲尔铁塔")</action>
<observation>埃菲尔铁塔的高度约为330米(包含天线)。</observation>
<thought>搜索结果显示了高度。我已经得到答案了。</thought>
<final_answer>埃菲尔铁塔的高度约为330米。</final_answer>
⸻
请严格遵守:
- 你每次回答都必须包括两个标签,第一个是 <thought>,第二个是 <action> 或 <final_answer>
- 输出 <action> 后立即停止生成,等待真实的 <observation>,擅自生成 <observation> 将导致错误
- 如果 <action> 中的某个工具参数有多行的话,请使用 \n 来表示,如:<action>write_to_file("/tmp/test.txt", "a\nb\nc")</action>
- 工具参数中的文件路径请使用绝对路径,不要只给出一个文件名。比如要写 write_to_file("/tmp/test.txt", "内容"),而不是 write_to_file("test.txt", "内容")
⸻
本次任务可用工具:
- writeToFile(String, String): 工具方法
- readFile(String): 工具方法
- runTerminalCommand(String): 工具方法
⸻
环境信息:
操作系统:Linux
工作目录在:/home/zxm/test
2.pom
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
<optional>true</optional>
</dependency>
</dependencies>
3.工具类
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 工具函数集合,提供文件操作和终端命令执行功能
*/
public class ToolFunctions {
/**
* 读取文件内容
* @param filePath 文件路径
* @return 文件内容
*/
public static String readFile(String filePath) {
try {
Path path = Paths.get(filePath);
return Files.readString(path);
} catch (IOException e) {
return "读取文件错误:" + e.getMessage();
}
}
/**
* 将指定内容写入指定文件
* @param filePath 文件路径
* @param content 要写入的内容
* @return 操作结果
*/
public static String writeToFile(String filePath, String content) {
try {
Path path = Paths.get(filePath);
Files.createDirectories(path.getParent());
Files.writeString(path, content.replace("\\n", "\n"));
return "写入成功";
} catch (IOException e) {
return "写入文件错误:" + e.getMessage();
}
}
/**
* 执行终端命令
* @param command 要执行的命令
* @return 执行结果
*/
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();
}
}
}
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ToolUtils {
private final static Map<String, Method> tools;
static {
tools = new HashMap<>();
try {
tools.put("readFile", ToolFunctions.class.getMethod("readFile", String.class));
tools.put("writeToFile", ToolFunctions.class.getMethod("writeToFile", String.class, String.class));
tools.put("runTerminalCommand", ToolFunctions.class.getMethod("runTerminalCommand", String.class));
} catch (NoSuchMethodException e) {
throw new RuntimeException("无法找到工具方法", e);
}
}
/**
* 执行工具方法
*/
public static String executeTool(String toolName, List<String> args) throws Exception {
Method method = tools.get(toolName);
if (method == null) {
throw new IllegalArgumentException("未知工具: " + toolName);
}
Object[] methodArgs = new Object[args.size()];
Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < args.size(); i++) {
String arg = args.get(i);
Class<?> paramType = paramTypes[i];
if (paramType == String.class) {
methodArgs[i] = arg;
} else if (paramType == int.class || paramType == Integer.class) {
methodArgs[i] = Integer.parseInt(arg);
} else if (paramType == long.class || paramType == Long.class) {
methodArgs[i] = Long.parseLong(arg);
} else if (paramType == double.class || paramType == Double.class) {
methodArgs[i] = Double.parseDouble(arg);
} else if (paramType == boolean.class || paramType == Boolean.class) {
methodArgs[i] = Boolean.parseBoolean(arg);
} else {
methodArgs[i] = arg;
}
}
Object result = method.invoke(null, methodArgs);
return result != null ? result.toString() : "null";
}
/**
* 生成工具列表字符串
*/
public static String getToolList() {
List<String> toolDescriptions = new ArrayList<>();
for (Method method : tools.values()) {
String name = method.getName();
String signature = getMethodSignature(method);
toolDescriptions.add("- " + name + signature + ": 工具方法");
}
return String.join("\n", toolDescriptions);
}
/**
* 获取方法签名
*/
private static String getMethodSignature(Method method) {
StringBuilder sb = new StringBuilder("(");
Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
if (i > 0) sb.append(", ");
sb.append(paramTypes[i].getSimpleName());
}
sb.append(")");
return sb.toString();
}
}
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.util.Timeout;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CallUtils {
public static String callQwenAPI(List<Map<String, String>> messages,
String apiUrl, String apiModel, String apiKey) {
RequestConfig config = RequestConfig.custom()
.setConnectionKeepAlive(Timeout.ofSeconds(600))
.setConnectTimeout(Timeout.ofSeconds(600))
.setConnectionRequestTimeout(Timeout.ofSeconds(600))
.setResponseTimeout(Timeout.ofSeconds(600))
.build();
try (CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(config).build()) {
HttpPost httpPost = new HttpPost(apiUrl);
// 设置请求头
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("Authorization", "Bearer " + apiKey);
// 构建请求体
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", apiModel);
requestBody.put("messages", messages);
requestBody.put("temperature", 0.7);
requestBody.put("max_tokens", 2000);
// 使用Jackson序列化JSON
ObjectMapper objectMapper = new ObjectMapper();
String jsonRequest = objectMapper.writeValueAsString(requestBody);
// 设置请求体
StringEntity entity = new StringEntity(jsonRequest, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 发送请求
try (CloseableHttpResponse response = httpClient.execute(httpPost, (HttpContext) null)) {
int statusCode = response.getCode();
if (statusCode == 200) {
// 读取响应
String responseBody = new String(response.getEntity().getContent().readAllBytes());
// 使用Jackson解析JSON响应
JsonNode rootNode = objectMapper.readTree(responseBody);
JsonNode choicesNode = rootNode.get("choices");
JsonNode messageNode = choicesNode.get(0).get("message");
return messageNode.get("content").asText();
} else {
System.err.println("API请求失败,响应码: " + statusCode);
return null;
}
}
} catch (Exception e) {
System.err.println("调用通义千问API失败: " + e.getMessage());
return null;
}
}
}
4.ReActAgent
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ReActAgent {
private final String apiKey;
private final String apiUrl;
private final String apiModel;
public ReActAgent(String apiKey, String apiUrl, String apiModel) {
this.apiKey = apiKey;
this.apiUrl = apiUrl;
this.apiModel = apiModel;
}
public String run(String task) {
System.out.println("开始执行任务: " + task);
List<Map<String, String>> messages = new ArrayList<>();
messages.add(Map.of("role", "system", "content", getDefaultSystemPrompt()));
messages.add(Map.of("role", "user", "content", "<question>" + task + "</question>"));
// 防止无限循环
int maxIterations = 10;
int iteration = 0;
while (iteration < maxIterations) {
iteration++;
System.out.println("--- 第 " + iteration + " 轮 ---");
long begin = System.currentTimeMillis();
String content = CallUtils.callQwenAPI(messages, apiUrl, apiModel, apiKey);
messages.add(Map.of("role", "assistant", "content", content));
long end = System.currentTimeMillis();
System.out.println(String.format("大模型调用耗时:%s 秒", (end - begin) / 1000));
if (content == null) {
return "API调用失败";
}
// 检测 Thought
Pattern thoughtPattern = Pattern.compile("<thought>(.*?)</thought>", Pattern.DOTALL);
Matcher thoughtMatcher = thoughtPattern.matcher(content);
if (thoughtMatcher.find()) {
String thought = thoughtMatcher.group(1);
System.out.println("Thought: " + thought);
}
// 检测是否输出 Final Answer
if (content.contains("<final_answer>")) {
Pattern finalAnswerPattern = Pattern.compile("<final_answer>(.*?)</final_answer>", Pattern.DOTALL);
Matcher finalAnswerMatcher = finalAnswerPattern.matcher(content);
if (finalAnswerMatcher.find()) {
String finalAnswer = finalAnswerMatcher.group(1);
System.out.println("Final Answer: " + finalAnswer);
return finalAnswer;
}
}
// 检测 Action
Pattern actionPattern = Pattern.compile("<action>(.*?)</action>", Pattern.DOTALL);
Matcher actionMatcher = actionPattern.matcher(content);
if (!actionMatcher.find()) {
System.out.println("模型未输出有效的action");
return "执行失败:模型未输出有效的action";
}
String action = actionMatcher.group(1);
ActionResult actionResult = parseAction(action);
System.out.println("Action: " + actionResult.getToolName() + "(" +
String.join(", ", actionResult.getArgs()) + ")");
try {
String observation = ToolUtils.executeTool(actionResult.getToolName(), actionResult.getArgs());
System.out.println("Observation: " + observation);
messages.add(Map.of("role", "user", "content", "<observation>" + observation + "</observation>"));
} catch (Exception e) {
String observation = "工具执行错误:" + e.getMessage();
System.out.println("Observation: " + observation);
messages.add(Map.of("role", "user", "content", "<observation>" + observation + "</observation>"));
}
}
return "执行超时:达到最大迭代次数";
}
/**
* 默认系统提示
*/
private String getDefaultSystemPrompt() {
return """
你需要解决一个问题。为此,你需要将问题分解为多个步骤。对于每个步骤,首先使用 <thought> 思考要做什么,然后使用可用工具之一决定一个 <action>。接着,你将根据你的行动从环境/工具中收到一个 <observation>。持续这个思考和行动的过程,直到你有足够的信息来提供 <final_answer>。
所有步骤请严格使用以下 XML 标签格式输出:
- <question> 用户问题
- <thought> 思考
- <action> 采取的工具操作
- <observation> 工具或环境返回的结果
- <final_answer> 最终答案
⸻
例子 1:
<question>埃菲尔铁塔有多高?</question>
<thought>我需要找到埃菲尔铁塔的高度。可以使用搜索工具。</thought>
<action>get_height("埃菲尔铁塔")</action>
<observation>埃菲尔铁塔的高度约为330米(包含天线)。</observation>
<thought>搜索结果显示了高度。我已经得到答案了。</thought>
<final_answer>埃菲尔铁塔的高度约为330米。</final_answer>
⸻
请严格遵守:
- 你每次回答都必须包括两个标签,第一个是 <thought>,第二个是 <action> 或 <final_answer>
- 输出 <action> 后立即停止生成,等待真实的 <observation>,擅自生成 <observation> 将导致错误
- 如果 <action> 中的某个工具参数有多行的话,请使用 \\n 来表示,如:<action>write_to_file("/tmp/test.txt", "a\\nb\\nc")</action>
- 工具参数中的文件路径请使用绝对路径,不要只给出一个文件名。比如要写 write_to_file("/tmp/test.txt", "内容"),而不是 write_to_file("test.txt", "内容")
⸻
本次任务可用工具:
${tool_list}
⸻
环境信息:
操作系统:${operating_system}
工作目录在:${work_path}
""".replace("${tool_list}", ToolUtils.getToolList())
.replace("${operating_system}", System.getProperty("os.name"))
.replace("${work_path}", "/home/zxm/test");
}
/**
* 解析Action字符串
*/
private ActionResult parseAction(String codeStr) {
Pattern pattern = Pattern.compile("(\\w+)\\((.*)\\)", Pattern.DOTALL);
Matcher matcher = pattern.matcher(codeStr);
if (!matcher.find()) {
throw new IllegalArgumentException("Invalid function call syntax");
}
String funcName = matcher.group(1);
String argsStr = matcher.group(2).trim();
List<String> args = parseArguments(argsStr);
return new ActionResult(funcName, args);
}
/**
* 解析参数列表
*/
private List<String> parseArguments(String argsStr) {
List<String> args = new ArrayList<>();
StringBuilder currentArg = new StringBuilder();
boolean inString = false;
char stringChar = 0;
int parenDepth = 0;
for (int i = 0; i < argsStr.length(); i++) {
char c = argsStr.charAt(i);
if (!inString) {
if (c == '"' || c == '\'') {
inString = true;
stringChar = c;
currentArg.append(c);
} else if (c == '(') {
parenDepth++;
currentArg.append(c);
} else if (c == ')') {
parenDepth--;
currentArg.append(c);
} else if (c == ',' && parenDepth == 0) {
args.add(parseSingleArg(currentArg.toString().trim()));
currentArg = new StringBuilder();
} else {
currentArg.append(c);
}
} else {
currentArg.append(c);
if (c == stringChar && (i == 0 || argsStr.charAt(i - 1) != '\\')) {
inString = false;
stringChar = 0;
}
}
}
if (currentArg.length() > 0) {
args.add(parseSingleArg(currentArg.toString().trim()));
}
return args;
}
/**
* 解析单个参数
*/
private String parseSingleArg(String argStr) {
argStr = argStr.trim();
// 如果是字符串字面量
if ((argStr.startsWith("\"") && argStr.endsWith("\"")) ||
(argStr.startsWith("'") && argStr.endsWith("'"))) {
String innerStr = argStr.substring(1, argStr.length() - 1);
// 处理常见的转义字符
innerStr = innerStr.replace("\\\"", "\"").replace("\\'", "'");
innerStr = innerStr.replace("\\n", "\n").replace("\\t", "\t");
innerStr = innerStr.replace("\\r", "\r").replace("\\\\", "\\");
return innerStr;
}
return argStr;
}
/**
* Action解析结果
*/
@Getter
@AllArgsConstructor
private static class ActionResult {
private final String toolName;
private final List<String> args;
}
}
4.测试类
public class Main {
private static final String QWEN_API_KEY = "sk-123456";
private static final String QWEN_API_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
private static final String QWEN_MODEL = "qwen-turbo";
private static final String TASK = "帮我创建一个简单的Hello World Java程序,并运行它";
public static void main(String[] args) {
System.out.println("任务: " + TASK);
try {
// 创建Agent
ReActAgent agent = new ReActAgent(QWEN_API_KEY, QWEN_API_URL, QWEN_MODEL);
// 执行任务
String result = agent.run(TASK);
System.out.println("最终结果: " + result);
} catch (Exception e) {
System.err.println("执行失败: " + e.getMessage());
}
}
}
5.结论
调用步骤:
第一次发送:
1) role: system
content:你需要解决一个问题。为此,你需要将问题分解为多个步骤...
2) role: user
content:<question>帮我创建一个简单的Hello World Java程序,并运行它</question>
第一次返回:
3) role: assistant
content:<thought>我需要先创建一个Java源文件,然后编译并运行它。首先使用writeToFile工具创建HelloWorld.java文件。</thought>
<action>writeToFile("/home/zxm/test/HelloWorld.java", "public class HelloWorld")</action>
第二次发送:
1) 2) 3)
4) role: user
content:<observation>写入成功</observation>
第二次返回:
5) role: assistant
content: <thought>现在需要编译并运行Java程序。使用runTerminalCommand工具执行编译和运行命令。</thought>
<action>runTerminalCommand("cd /home/zxm/test && javac HelloWorld.java && java HelloWorld")</action>
第三次发送:
1) 2) 3) 4) 5)
6) role: user
content:<observation>执行成功 Hello World </observation>
第三次返回:
7) role: assistant
content: <thought>程序已成功编译并运行,输出了"Hello World"。任务完成。</thought>
<final_answer>Java程序已创建并成功运行,输出结果为:Hello World</final_answer>
三、langchain4j 框架实现 ReActAgent
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>
<!-- LangChain4j Core -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- LangChain4j OpenAI -->
<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.工具类
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;
/**
* 工具函数集合,提供文件操作和终端命令执行功能
* 使用 langchain4j @Tool 注解标记为工具函数
*/
public class ToolFunctions {
/**
* 读取文件内容
* @param filePath 文件路径
* @return 文件内容
*/
@Tool("读取指定路径的文件内容")
public static String readFile(String filePath) {
try {
Path path = Paths.get(filePath);
return Files.readString(path);
} catch (IOException e) {
return "读取文件错误:" + e.getMessage();
}
}
/**
* 将指定内容写入指定文件
* @param filePath 文件路径
* @param content 要写入的内容
* @return 操作结果
*/
@Tool("将内容写入指定路径的文件")
public static String writeToFile(String filePath, String content) {
try {
Path path = Paths.get(filePath);
Files.createDirectories(path.getParent());
Files.writeString(path, content.replace("\\n", "\n"));
return "写入成功";
} catch (IOException e) {
return "写入文件错误:" + e.getMessage();
}
}
/**
* 执行终端命令
* @param command 要执行的命令
* @return 执行结果
*/
@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.ReActAgent
LangChain4j 实现了 function calling,可以把工具信息提供给 LangChain4j 即可。
import dev.langchain4j.model.openai.internal.chat.ChatCompletionRequest;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.model.openai.OpenAiChatModel;
import java.io.IOException;
import java.time.Duration;
public class ReActAgent {
private final AiService aiService;
/**
* @see dev.langchain4j.model.openai.internal.DefaultOpenAiClient#chatCompletion(ChatCompletionRequest)
*/
public ReActAgent(String baseApiUrl, String apiModel, String apiKey) throws IOException {
OpenAiChatModel chatModel = OpenAiChatModel.builder()
.apiKey(apiKey)
.baseUrl(baseApiUrl)
.modelName(apiModel)
.temperature(0.7)
.maxTokens(2000)
.timeout(Duration.ofMinutes(10))
.build();
String systemPrompt = String.format("工作目录在:/home/zxm/test;当前操作系统是:%s;", System.getProperty("os.name"));
this.aiService = AiServices.builder(AiService.class)
.chatModel(chatModel)
.tools(new ToolFunctions())
.systemMessageProvider(chatRequest -> systemPrompt)
.build();
}
public String chat(String userQuestion) {
try {
return aiService.generateResponse(userQuestion);
} catch (Exception e) {
return "处理问题时发生错误: " + e.getMessage();
}
}
// 定义 AI 服务接口
public interface AiService {
String generateResponse(String userQuestion);
}
}
4.测试类
public class Main {
public static void main(String[] args) {
try {
// 创建 ReAct Agent 服务
ReActAgent agentService = new ReActAgent(
"https://dashscope.aliyuncs.com/compatible-mode/v1",
"qwen-turbo",
"sk-123456");
// 固定的测试问题
String question = "帮我创建一个简单的Hello World Java程序,并运行它";
System.out.println("测试问题: " + question);
String response = agentService.chat(question);
System.out.println("结果:" + response);
} catch (Exception e) {
System.err.println("处理问题时发生错误: " + e.getMessage());
}
}
}
5.结论
调用步骤:
发起大模型的请求如下:
{
"model" : "qwen-turbo",
"messages" : [ {
"role" : "system",
"content" : "工作目录在:/home/zxm/test;当前操作系统是:Linux;"
}, {
"role" : "user",
"content" : "帮我创建一个简单的Hello World Java程序,并运行它"
}, {
"role" : "assistant",
"tool_calls" : [ {
"id" : "call_684add391e414f1683631d",
"type" : "function",
"function" : {
"name" : "writeToFile",
"arguments" : "{\"arg0\": \"/home/zxm/test/HelloWorld.java\", \"arg1\": \"public class HelloWorld {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello, World!\\\");\\n }\\n}\"}"
}
} ]
}, {
"role" : "tool",
"tool_call_id" : "call_684add391e414f1683631d",
"content" : "写入成功"
}, {
"role" : "assistant",
"tool_calls" : [ {
"id" : "call_1b4f157945474fb7bc28ea",
"type" : "function",
"function" : {
"name" : "runTerminalCommand",
"arguments" : "{\"arg0\": \"javac /home/zxm/test/HelloWorld.java && java -cp /home/zxm/test HelloWorld\"}"
}
} ]
}, {
"role" : "tool",
"tool_call_id" : "call_1b4f157945474fb7bc28ea",
"content" : "执行成功\nHello, World!\n"
} ],
"temperature" : 0.7,
"stream" : false,
"max_tokens" : 2000,
"tools" : [ {
"type" : "function",
"function" : {
"name" : "writeToFile",
"description" : "将内容写入指定路径的文件",
"parameters" : {
"type" : "object",
"properties" : {
"arg0" : {
"type" : "string"
},
"arg1" : {
"type" : "string"
}
},
"required" : [ "arg0", "arg1" ]
}
}
}, {
"type" : "function",
"function" : {
"name" : "runTerminalCommand",
"description" : "执行终端命令并返回结果",
"parameters" : {
"type" : "object",
"properties" : {
"arg0" : {
"type" : "string"
}
},
"required" : [ "arg0" ]
}
}
}, {
"type" : "function",
"function" : {
"name" : "readFile",
"description" : "读取指定路径的文件内容",
"parameters" : {
"type" : "object",
"properties" : {
"arg0" : {
"type" : "string"
}
},
"required" : [ "arg0" ]
}
}
} ]
}
现在关注messages:
第一次发送:
1) role: system
content:工作目录在:/home/zxm/test
2) role: user
content:帮我创建一个简单的Hello World Java程序,并运行它
第一次返回:
3) role: assistant
content: ""
tool_calls: id: call_fcbb88e694c54b619df4aa
type: function
function: arguments: "{\"arg0\": \"/home/zxm/test/HelloWorld.java\", \"arg1\": \"public class HelloWorld {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello, World!\\\");\\n }\\n}\"}"
name: writeToFile
第二次发送:
1) 2) 3)
4) role: tool
content: 写入成功
tool_call_id: call_fcbb88e694c54b619df4aa
第二次返回:
5) role: assistant
content: ""
tool_calls: id: call_bf240bb2c6164d01ad4042
type: function
function: arguments: "{\"arg0\": \"javac /home/zxm/test/HelloWorld.java && java -cp /home/zxm/test HelloWorld\"}"
name: runTerminalCommand
第三次发送:
1) 2) 3) 4) 5)
6) role: tool
tool_call_id: call_bf240bb2c6164d01ad4042
content: "执行成功\nHello, World!\n"
第三次返回:
7) role: assistant
content: "以下为您创建的简单 Hello World Java 程序的运行结果:\n\n```\nHello, World!\n```\n\n程序已成功编译并运行,输出了 \"Hello, World!\"。如果您需要进一步的帮助,请随时告诉我!"