跳到主要内容 基于 Java 从零实现 ReAct 模式 AI Agent | 极客日志
Java AI java 算法
基于 Java 从零实现 ReAct 模式 AI Agent 使用纯 Java 代码从零构建基于 ReAct 模式的 AI Agent,无需依赖 LangChain 等框架。ReAct 模式通过交替进行推理与行动,使 LLM 具备自主规划任务和调用工具的能力。文章涵盖系统架构设计、工具注解系统、Prompt 模板工程、核心循环逻辑及输出解析器实现。通过文件写入示例演示了思考 - 行动 - 观察的闭环流程。此外还提供工程化改进建议,包括循环计数修复、未知工具处理及自动工具注册,并探讨了多轮对话记忆、并行工具调用等进阶扩展方向,为深入理解 AI Agent 原理及后续工程落地提供实践参考。
Stephaine Walsh 发布于 2026/2/6 0 浏览引言
在大语言模型(LLM)蓬勃发展的今天,AI Agent 已经成为最热门的技术方向之一。与传统的问答式 AI 不同,Agent 能够自主思考、规划任务、调用工具,并最终解决复杂问题。这种能力的核心在于让 AI 具备了"行动力"——它不再只是被动回答问题,而是能够主动采取行动来完成任务。
想象一下这样的场景:你对 AI 说"帮我把 1 到 10 的整数写入一个文件",传统的 ChatGPT 只能告诉你"你可以使用 Python 的文件操作来实现…',而一个真正的 AI Agent 会直接帮你创建文件、写入内容,然后告诉你"已完成,文件在 numbers.txt"。这就是 Agent 与传统 LLM 的本质区别。
在众多 Agent 架构中,ReAct(Reasoning + Acting) 模式因其简洁优雅而备受青睐。其核心思想是让 LLM 交替进行推理(Reasoning)和行动(Acting),通过"思考 - 行动 - 观察"的循环来解决问题。
接下来让我们从零开始,使用纯 Java 代码手写一个完整的 ReAct Agent。我们不依赖 LangChain、Spring AI 等框架,而是直接使用 OpenAI 官方 Java SDK 与大模型交互,深入理解 Agent 的工作原理。
一、什么是 ReAct 模式?
1.1 ReAct 的起源与核心思想
ReAct 由 Yao 等人在 2022 年提出(ICLR 2023 发表),论文标题为《ReAct: Synergizing Reasoning and Acting in Language Models》。其核心洞察是:将推理(Reasoning)和行动(Acting)交织在一起,可显著提升 LLM 解决复杂任务的能力 。
传统的 Chain-of-Thought(CoT)只关注推理,让模型"一步步思考"来提升推理能力;传统的 Action-based 方法只关注行动,让模型直接调用工具。ReAct 的创新在于将两者合一:
Thought → Action → Observation → Thought → Action → Observation → ... → FinalAnswer
这种设计模拟了人类解决问题的认知过程:我们不会一次性想清楚所有步骤,而是边思考边行动,根据行动的反馈调整下一步计划。
1.2 ReAct 与思维链(CoT)的对比
思维链(Chain of Thought, CoT)技术,ReAct 可以看作是 CoT 的增强版:
特性 CoT(思维链) ReAct 推理能力 ✅ 支持 ✅ 支持 外部工具调用 ❌ 不支持 ✅ 支持 信息获取 仅依赖模型已有知识 可从外部获取实时信息 任务执行 只能给出建议 可以实际执行任务 自我修正 较弱 较强(基于观察结果调整)
CoT 的局限在于,模型只能基于训练数据中的知识进行推理,无法获取实时信息或执行实际操作。而 ReAct 通过引入工具调用机制,让模型具备了与外部世界交互的能力。
1.3 ReAct 的核心执行循环
ReAct Agent 的工作流程可以概括为一个迭代循环:
Reason(推理) :分析当前状态,思考下一步应该做什么
Action(行动) :决定调用哪个工具
Action Input(行动输入) :生成工具调用参数
Observation(观察) :系统执行工具并返回结果
重复上述过程,直到模型输出最终答案(Final Answer)
用流程图表示:
┌─────────────────────────────────────────────────────────────┐
│ ReAct 执行循环 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户问题 ──→ 构建 Prompt ──→ 调用 LLM │
│ ↑ │
│ │ │
│ ↓ │
│ 历史记录更新 解析 LLM 输出 │
│ │ │
│ ↓ │
│ ┌─────┴─────┐ 是 FinalAnswer? │
│ │ │ │
│ │ Observation ├── 是 ──→ 返回最终答案 │
│ │ │ │
│ └─────┬─────┘ ↓ 否 │
│ │ 执行工具 │
│ │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
1.4 一个具体的执行示例 让我们通过一个具体例子来理解 ReAct 的执行流程。假设用户提出需求:'将 1 到 10 之间的所有整数写入文件'。
Reason : 用户需要将 1 到 10 的整数写入文件,我需要使用 writeFile 工具来完成这个任务。 我需要指定文件路径和要写入的内容。
Action : writeFile
ActionInput : {"file_path" : "numbers.txt" , "content" : "1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n 10" }
Reason: 文件已经成功写入,任务完成。
FinalAnswer: 我已经成功将 1 到 10 的所有整数写入了 numbers.txt 文件。
这个流程清晰地展示了 ReAct 的核心特点:LLM 负责思考和决策,而具体的文件写入操作则由外部工具完成。模型通过观察工具执行结果来决定下一步行动,形成完整的闭环。
二、项目架构设计 在动手编码之前,让我们先设计整体架构。一个完整的 ReAct Agent 需要以下核心组件。
2.1 系统架构图 ┌─────────────────────────────────────────────────────────────┐
│ ReAct Agent │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Prompt │ │ LLM API │ │ Output Parser │ │
│ │ Template │ │ Client │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Tool System ││
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ ││
│ │ │ @Tool │ │@ToolParam │ │ ToolUtil │ ││
│ │ │ Annotation│ │Annotation│ │ (Reflection- based) │ ││
│ │ └──────────┘ └──────────┘ └──────────────────────┘ ││
│ └─────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Agent Tools ││
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ││
│ │ │writeFile │ │readFile │ │search │ ... ││
│ │ └──────────┘ └──────────┘ └──────────┘ ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
2.2 核心组件职责 组件 职责 关键类 模型配置 管理 API Key、模型名称、服务地址 ModelConfig工具注解 声明式定义工具元信息 @Tool, @ToolParam工具集合 实现具体的工具逻辑 AgentTools工具工具类 通过反射生成工具描述 ToolUtilAgent 核心 实现 ReAct 循环逻辑 ReActAgent
2.3 项目依赖 <dependencies >
<dependency >
<groupId > com.openai</groupId >
<artifactId > openai-java</artifactId >
<version > 0.32.0</version >
</dependency >
<dependency >
<groupId > com.alibaba.fastjson2</groupId >
<artifactId > fastjson2</artifactId >
<version > 2.0.56</version >
</dependency >
</dependencies >
选择 OpenAI 官方 SDK 而非 Spring AI 等框架,是因为我们要能够理解 Agent 的底层实现原理,而非仅仅学会调用封装好的 API。这种方式的优势包括:
原理透明 :每一行代码都可追溯,便于理解 Agent 工作机制
依赖精简 :仅需 OpenAI SDK 和 JSON 库,启动快、包体小
灵活可控 :可根据业务需求自由定制 Prompt、解析逻辑
学习价值 :为后续使用框架打下坚实基础
三、核心代码实现详解
3.1 模型配置(ModelConfig) 首先,我们需要配置与大模型的连接。这里我们使用阿里云的 DashScope 服务(兼容 OpenAI API 格式):
public class ModelConfig {
public static final String API_KEY = System.getenv("DASHSCOPE_API_KEY" );
public static final String BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1" ;
public static final String LLM_NAME = "qwen-max" ;
}
环境变量读取 API Key :避免将敏感信息硬编码到代码中,这是安全最佳实践
OpenAI 兼容接口 :DashScope 提供了与 OpenAI API 兼容的接口,这意味着只需修改 BASE_URL 和 API_KEY,即可无缝切换到 OpenAI、DeepSeek 等其他服务商
模型选择 :qwen-max 是目前通义千问系列中推理能力最强的模型,适合需要复杂推理的 Agent 场景
3.2 工具注解系统(Tool / ToolParam) 为了让 Agent 能够识别和调用工具,我们设计了一套基于注解的工具系统。这套设计借鉴了 Spring 框架的声明式编程理念。
@Tool 注解 :标记一个方法为可被 Agent 调用的工具
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tool {
String description () ;
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ToolParam {
String description () ;
}
声明式定义 :工具的元信息直接标注在方法上,代码即文档
运行时可见 :@Retention(RetentionPolicy.RUNTIME) 确保注解在运行时可被反射读取
自动化集成 :无需手动维护工具列表,系统自动发现和注册工具
3.3 工具描述生成器(ToolUtil) ToolUtil 类负责通过反射扫描工具类,自动生成供 LLM 使用的工具描述:
public class ToolUtil {
public static String getToolDescription (Class<?> clazz) {
List<String> toolNameList = new ArrayList <>();
List<String> formattedToolList = new ArrayList <>();
for (Method declaredMethod : clazz.getDeclaredMethods()) {
if (declaredMethod.isAnnotationPresent(Tool.class)) {
Tool toolAnnotation = declaredMethod.getAnnotation(Tool.class);
String toolName = declaredMethod.getName();
String toolDescription = toolAnnotation.description();
String paramDescription = declaredMethod
.getParameters()[0 ].getAnnotation(ToolParam.class).description();
String formattedTool = String.format(
"- toolName=%s, toolDescription=%s, paramDescription=%s" ,
toolName, toolDescription, paramDescription);
formattedToolList.add(formattedTool);
toolNameList.add(toolName);
}
}
return String.join("\n\n" , formattedToolList);
}
}
自动发现 :使用 Java 反射机制扫描所有带 @Tool 注解的方法
零配置 :添加新工具只需在方法上加注解,无需修改任何配置文件
LLM 友好 :生成的描述格式清晰,便于模型理解工具用途和参数要求
- toolName =writeFile, toolDescription=将指定内容写入本地文件。, paramDescription=包含 'file_path' 和 'content' 的 JSON 字符串。
3.4 实现具体工具(AgentTools) public class AgentTools {
private final ObjectMapper objectMapper = new ObjectMapper ();
@Tool(description = "将指定内容写入本地文件。")
public String writeFile (@ToolParam(description = "包含 'file_path' 和 'content' 的 JSON 字符串。") String jsonInput) {
try {
JsonNode rootNode = objectMapper.readTree(jsonInput);
String filePath = rootNode.get("file_path" ).asText();
String content = rootNode.get("content" ).asText();
try (FileWriter writer = new FileWriter (filePath)) {
writer.write(content);
return "写入成功" ;
} catch (IOException e) {
return String.format("写入文件 '%s' 时发生错误:%s" , filePath, e.getMessage());
}
} catch (Exception e) {
return String.format("解析输入或执行工具时出错:%s" , e.getMessage());
}
}
}
JSON 字符串作为输入 :这与 LLM 生成的格式一致,避免复杂的类型转换
简洁的返回值 :返回"写入成功"而非冗长的描述,让 LLM 能快速理解执行结果
完善的错误处理 :
JSON 解析失败时返回明确的错误信息
文件写入失败时返回具体的异常原因
这些信息会作为 Observation 反馈给 LLM,帮助它调整策略
3.5 Prompt 模板设计 Prompt 设计是 ReAct Agent 的灵魂。一个优秀的 Prompt 需要清晰地告诉 LLM:你是谁、你能做什么、你应该如何输出。
private static final String REACT_PROMPT_TEMPLATE = """
# 角色定义
你是一个强大的 AI 助手,通过思考和使用工具来解决用户的问题。
# 任务
你的任务是尽你所能回答以下问题。你可以使用以下工具:
{tools}
# 规则
- Action 中只需要返回工具的名字,比如 writeFile,不要返回以下格式 toolName=writeFile
- 每次只做一次 Reason/Action/ActionInput 或者 FinalAnswer 的输出过程,不要一次性都做了
- 每次返回的过程中不要自己生成 Observation 的内容
- 返回 Reason/Action/ActionInput 的时候不要生成并返回 Observation 的内容
# 输出过程参考
第一轮
Reason: 你思考的过程
Action: 你的下一步动作,你想要执行的工具是哪个,必须是{tools}中的一个
ActionInput: 你要调用的工具的输入参数是什么
第二轮
Reason: 你思考的过程
Action: 你的下一步动作
ActionInput: 你要调用的工具的输入参数
...
最后一轮
FinalAnswer: 表示最终的答案,只需要最后输出就可以了
# 用户需求
Question: {input}
# 历史聊天记录
{history}
""" ;
部分 作用 设计考量 角色定义 设定 AI 的身份和能力边界 让模型明确自己是一个"使用工具解决问题"的助手 工具清单 告知可用工具 {tools} 占位符会被替换为实际的工具描述规则约束 控制输出格式 防止模型自行编造 Observation,确保每轮只输出一次 输出示例 引导输出格式 通过多轮对话示例展示期望的输出结构 历史记录 维持上下文 {history} 保存之前的推理过程,实现"记忆"能力
一次性输出多轮的 Reason/Action/ActionInput
自己编造 Observation 内容(而不是等待真实的工具执行结果)
跳过工具调用直接给出答案(即使它无法完成任务)
通过明确的规则,我们确保了 LLM 的行为可预测、可控制。
3.6 核心 Agent 循环(ReActAgent) 现在让我们看看 ReAct Agent 的核心——主循环逻辑:
public class ReActAgent {
private OpenAIClient apiClient;
public ReActAgent (OpenAIClient apiClient) {
this .apiClient = apiClient;
}
public String run (String input) throws NoSuchMethodException {
HashMap<String, Method> tools = new HashMap <>();
tools.put("writeFile" , AgentTools.class.getMethod("writeFile" , String.class));
StringBuilder history = new StringBuilder ();
int i = 0 ;
while (i < 10 ) {
try {
String prompt = buildPrompt(input, history.toString());
ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
.addUserMessage(prompt)
.model(ModelConfig.LLM_NAME)
.build();
ChatCompletion chatCompletion = apiClient.chat().completions().create(params);
String rawLlmOutput = chatCompletion.choices().get(0 ).message().content().get();
System.out.println("大模型原始输出:" + rawLlmOutput);
ParsedOutput parsedOutput = parseLlmOutput(rawLlmOutput);
if (parsedOutput.type.equals("final_answer" )) {
return parsedOutput.answer;
}
String observation = executeTool(parsedOutput, tools);
System.out.println("工具执行结果:" + observation);
history.append("Reason: " ).append(parsedOutput.reason).append("\n" )
.append("Action: " ).append(parsedOutput.action).append("\n" )
.append("ActionInput: " ).append(parsedOutput.actionInputStr).append("\n" )
.append("Observation: " ).append(observation).append("\n" );
} catch (Exception e) {
e.printStackTrace();
i++;
}
}
return "达到了循环最大次数" ;
}
}
工具注册 :将可用工具及其方法引用存入 HashMap,便于后续通过名称查找和调用
历史记录初始化 :使用 StringBuilder 累积每轮的执行记录
循环控制 :设置最大循环次数为 10,防止 LLM 陷入无限循环
Prompt 构建 :将用户输入和历史记录填充到模板中
LLM 调用 :使用 OpenAI SDK 发送请求并获取响应
输出解析 :区分"继续行动"和"最终答案"两种情况
终止判断 :如果输出包含 FinalAnswer,则返回结果
工具执行 :根据解析结果调用对应工具
记忆更新 :将本轮的 Reason、Action、ActionInput、Observation 追加到历史记录
private String buildPrompt (String input, String history) {
String prompt = REACT_PROMPT_TEMPLATE.replace("{tools}" , ToolUtil.getToolDescription(AgentTools.class));
prompt = prompt.replace("{input}" , input);
prompt = prompt.replace("{history}" , history);
return prompt;
}
{tools} → 工具描述列表
{input} → 用户的原始问题
{history} → 之前的对话历史
3.7 输出解析器 LLM 的输出是自由文本,需要被解析为结构化数据以便程序处理:
private ParsedOutput parseLlmOutput (String llmOutput) {
if (llmOutput.contains("FinalAnswer: " )) {
return new ParsedOutput ("final_answer" , llmOutput.split("FinalAnswer: " )[1 ].strip(), null , null , null , null );
}
Pattern actionPattern = Pattern.compile("Reason:(.*?)Action:(.*?)ActionInput:(.*)" , Pattern.DOTALL);
Matcher matcher = actionPattern.matcher(llmOutput);
if (matcher.find()) {
String reason = matcher.group(1 ).trim();
String action = matcher.group(2 ).trim();
String actionInputStr = matcher.group(3 ).trim();
if (actionInputStr.startsWith("```json" )) {
actionInputStr = actionInputStr.substring(7 );
}
if (actionInputStr.endsWith("```" )) {
actionInputStr = actionInputStr.substring(0 , actionInputStr.length() - 3 );
}
actionInputStr = actionInputStr.trim();
return new ParsedOutput ("action" , null , reason, action, actionInputStr, null );
}
return new ParsedOutput ("error" , null , null , null , null , String.format("解析 LLM 输出失败:'%s'" , llmOutput));
}
private record ParsedOutput (String type, // 输出类型:final_answer / action / error
String answer, // 最终答案(当 type 为 final_answer 时)
String reason, // 推理过程
String action, // 要执行的工具名
String actionInputStr, // 工具输入参数(JSON 字符串)
String message // 错误信息(当 type 为 error 时)
) {}
优先级判断 :首先检查是否包含 FinalAnswer:,这是终止循环的信号
正则匹配 :使用 Pattern.DOTALL 模式,使 . 能匹配换行符,处理多行输出
格式兼容 :处理 LLM 可能生成的 Markdown 代码块格式(如 ```json 包裹的 JSON)
不可变数据 :使用 Java Record 定义数据结构,简洁且线程安全
3.8 工具执行器 private static String executeTool (ParsedOutput parsedOutput, HashMap<String, Method> tools)
throws IllegalAccessException, InvocationTargetException {
String toolName = parsedOutput.action;
String toolParams = parsedOutput.actionInputStr;
Method toolMethod = tools.get(toolName);
Object observation = toolMethod.invoke(new AgentTools (), toolParams);
return String.valueOf(observation);
}
动态调用 :无需硬编码 switch-case 分支,根据工具名动态查找并调用
易于扩展 :添加新工具只需注册到 HashMap,无需修改执行器代码
解耦设计 :Agent 核心逻辑与具体工具实现完全分离
四、完整执行示例
4.1 启动代码 public static void main (String[] args) throws Exception {
OpenAIClient apiClient = OpenAIOkHttpClient.builder()
.apiKey(ModelConfig.API_KEY)
.baseUrl(ModelConfig.BASE_URL)
.build();
ReActAgent reActAgent = new ReActAgent (apiClient);
String result = reActAgent.run("将 1 到 10 中间的所有整数写到文件中" );
System.out.println("最终结果:" + result);
}
4.2 执行过程输出 大模型原始输出:Reason : 用户需要将 1 到 10 之间的所有整数写入到一个文件中。 我需要使用 writeFile 工具来完成这个任务,需要指定文件路径和内容。
Action : writeFile
ActionInput :{"file_path" :"numbers.txt" ,"content" :"1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n 10" }
工具执行结果:写入成功
大模型原始输出:Reason : 文件已经成功写入,用户的任务已经完成。
FinalAnswer : 我已经成功将 1 到 10 之间的所有整数写入到了 numbers.txt 文件中。
最终结果:我已经成功将 1 到 10 之间的所有整数写入到了 numbers.txt 文件中。
4.3 执行流程分析 轮次 阶段 内容 1 Reason 分析用户需求,决定使用 writeFile 工具 1 Action writeFile 1 ActionInput 构造 JSON 参数,包含文件路径和内容 1 Observation 系统执行工具后返回"写入成功" 2 Reason 观察到成功结果,判断任务完成 2 FinalAnswer 返回最终答案给用户
这个两轮对话完美地展示了 ReAct 的核心机制:思考→行动→观察→再思考→最终答案 。
五、工程化改进建议 当前实现是一个最小可行版本,适合学习和理解原理。在生产环境中,还需要进行以下改进:
5.1 循环计数修复 当前实现中,i 只在异常时递增,可能导致正常情况下无限循环:
while (i < 10 ) {
try {
} catch (Exception e) {
i++;
}
}
while (i < 10 ) {
try {
} catch (Exception e) {
e.printStackTrace();
}
i++;
}
5.2 未知工具处理 当模型输出未知工具名时,当前实现会触发 NullPointerException:
private static String safeExecuteTool (ParsedOutput parsedOutput, HashMap<String, Method> tools) {
String toolName = parsedOutput.action;
Method toolMethod = tools.get(toolName);
if (toolMethod == null ) {
return "未知工具:" + toolName + "。请检查工具清单并重新选择。" ;
}
}
5.3 自动工具注册 将手动注册改为反射扫描,添加新工具时无需修改 Agent 代码:
private static HashMap<String, Method> registerTools (Class<?> toolClass) {
HashMap<String, Method> tools = new HashMap <>();
for (Method method : toolClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(Tool.class)) {
tools.put(method.getName(), method);
}
}
return tools;
}
HashMap<String, Method> tools = registerTools(AgentTools.class);
5.4 参数校验增强 模型可能输出不合法的 JSON,建议增加基础校验:
if (toolParams == null || !toolParams.trim().startsWith("{" ) || !toolParams.trim().endsWith("}" )) {
return "ActionInput 不是合法 JSON 对象,请输出形如 {\"key\":\"value\"} 的参数。" ;
}
5.5 更多工具示例 @Tool(description = "读取本地文件内容")
public String readFile (@ToolParam(description = "文件路径") String filePath) {
try {
return Files.readString(Path.of(filePath));
} catch (IOException e) {
return "读取文件失败:" + e.getMessage();
}
}
@Tool(description = "执行网络搜索")
public String webSearch (@ToolParam(description = "搜索关键词的 JSON") String jsonInput) {
}
@Tool(description = "执行数学计算")
public String calculate (@ToolParam(description = "包含 expression 的 JSON") String jsonInput) {
}
六、与主流框架的对比 维度 手写实现 LangChain Spring AI 学习价值 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐ 灵活性 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ 开发效率 ⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 生产就绪 ⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 社区生态 ⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
建议 :学习原理用手写实现,工程落地用成熟框架。理解了底层原理后,使用框架会更加得心应手。
七、进阶扩展方向 掌握了基础的 ReAct 实现后,可以进一步探索以下方向:
7.1 多轮对话记忆 当前实现在单次任务结束后会清空历史。可以引入持久化存储,实现跨会话记忆:
public class ChatMemory {
private final List<Message> messages = new ArrayList <>();
public void addMessage (Message message) {
messages.add(message);
}
public String getHistory () {
return messages.stream().map(Message::toString).collect(Collectors.joining("\n" ));
}
}
7.2 并行工具调用 当多个工具之间没有依赖关系时,可以并行执行以提升效率:
List<CompletableFuture<String>> futures = actions.stream()
.map(action -> CompletableFuture.supplyAsync(() -> executeTool(action)))
.toList();
List<String> results = futures.stream().map(CompletableFuture::join).toList();
7.3 流式输出 对于长时间运行的任务,可以使用流式输出提升用户体验:
apiClient.chat().completions().createStream(params).forEach(chunk -> {
String content = chunk.choices().get(0 ).delta().content().orElse("" );
System.out.print(content);
});
7.4 更高级的 Agent 架构
Plan-and-Execute :先制定完整计划,再逐步执行
Tree of Thoughts :探索多条推理路径,选择最优解
Multi-Agent :多个 Agent 协作完成复杂任务
Self-Reflection :Agent 自我反思和错误修正
八、总结 我们从零开始实现了一个完整的 ReAct Agent,涵盖以下核心知识点:
主题 内容要点 ReAct 原理 推理与行动交替进行,通过观察结果迭代优化 工具系统 基于注解的声明式定义,反射机制自动发现 Prompt 工程 角色设定、规则约束、示例引导、历史记录 输出解析 正则匹配提取结构化信息,处理多种输出格式 循环控制 最大次数限制、终止条件判断、异常处理
ReAct 模式是构建 AI Agent 的基础范式,掌握其原理对于理解更复杂的 Agent 架构至关重要。通过纯 Java 实现,我们深入理解了 Agent 的工作机制——它不是魔法,而是精心设计的 Prompt + 循环 + 工具调用的组合。
AI Agent 是一个快速发展的领域,ReAct 只是众多架构之一。建议读者在掌握本文内容后,进一步探索 LangChain、Spring AI 等成熟框架,将原理知识转化为工程实践能力。
参考资料