大模型开发 - 零手写 AI Agent:深入理解 ReAct 模式与 Java 实现

大模型开发 - 零手写 AI Agent:深入理解 ReAct 模式与 Java 实现

文章目录

在这里插入图片描述
面向读者:Java 后端开发者 / AI 工程师 / 技术爱好者
阅读收获:从零搭建一个"能思考、能调用工具、能闭环"的 ReAct Agent,深入理解 AI Agent 的核心工作原理

引言

在大语言模型(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 的工作流程可以概括为一个迭代循环:

  1. Reason(推理):分析当前状态,思考下一步应该做什么
  2. Action(行动):决定调用哪个工具
  3. Action Input(行动输入):生成工具调用参数
  4. Observation(观察):系统执行工具并返回结果
  5. 重复上述过程,直到模型输出最终答案(Final Answer)

用流程图表示:

┌─────────────────────────────────────────────────────────────┐ │ ReAct 执行循环 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 用户问题 ──→ 构建 Prompt ──→ 调用 LLM │ │ ↑ │ │ │ │ ↓ │ │ 历史记录更新 解析 LLM 输出 │ │ │ │ │ │ │ ↓ │ │ ┌─────┴─────┐ 是 FinalAnswer? │ │ │ │ │ │ │ │ Observation ├── 是 ──→ 返回最终答案 │ │ │ │ │ │ │ └─────┬─────┘ ↓ 否 │ │ │ 执行工具 │ │ │ │ │ │ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘ 

1.4 一个具体的执行示例

让我们通过一个具体例子来理解 ReAct 的执行流程。假设用户提出需求:“将 1 到 10 之间的所有整数写入文件”。

第一轮

Reason: 用户需要将 1 到 10 的整数写入文件,我需要使用 writeFile 工具来完成这个任务。 我需要指定文件路径和要写入的内容。 Action: writeFile ActionInput: {"file_path": "numbers.txt", "content": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"} 

系统执行工具后返回

Observation: 写入成功 

第二轮

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
工具工具类通过反射生成工具描述ToolUtil
Agent 核心实现 ReAct 循环逻辑ReActAgent

2.3 项目依赖

项目基于 Java 21,只依赖两个核心库:

<dependencies><!-- OpenAI 官方 Java SDK --><dependency><groupId>com.openai</groupId><artifactId>openai-java</artifactId><version>0.32.0</version></dependency><!-- JSON 处理(工具实现中使用 Jackson,来自 SDK 传递依赖) --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.56</version></dependency></dependencies>

为什么选择纯 Java 实现?

选择 OpenAI 官方 SDK 而非 Spring AI 等框架,是因为我们要能够理解 Agent 的底层实现原理,而非仅仅学会调用封装好的 API。这种方式的优势包括:

  1. 原理透明:每一行代码都可追溯,便于理解 Agent 工作机制
  2. 依赖精简:仅需 OpenAI SDK 和 JSON 库,启动快、包体小
  3. 灵活可控:可根据业务需求自由定制 Prompt、解析逻辑
  4. 学习价值:为后续使用框架打下坚实基础

三、核心代码实现详解

3.1 模型配置(ModelConfig)

首先,我们需要配置与大模型的连接。这里我们使用阿里云的 DashScope 服务(兼容 OpenAI API 格式):

publicclassModelConfig{// 从环境变量获取 API Key,安全且便于管理publicstaticfinalString API_KEY =System.getenv("DASHSCOPE_API_KEY");// 使用 DashScope 的 OpenAI 兼容接口publicstaticfinalString BASE_URL ="https://dashscope.aliyuncs.com/compatible-mode/v1";// 使用通义千问 Max 模型publicstaticfinalString LLM_NAME ="qwen-max";}

设计要点

  • 环境变量读取 API Key:避免将敏感信息硬编码到代码中,这是安全最佳实践
  • OpenAI 兼容接口:DashScope 提供了与 OpenAI API 兼容的接口,这意味着只需修改 BASE_URLAPI_KEY,即可无缝切换到 OpenAI、DeepSeek 等其他服务商
  • 模型选择qwen-max 是目前通义千问系列中推理能力最强的模型,适合需要复杂推理的 Agent 场景

3.2 工具注解系统(Tool / ToolParam)

为了让 Agent 能够识别和调用工具,我们设计了一套基于注解的工具系统。这套设计借鉴了 Spring 框架的声明式编程理念。

@Tool 注解:标记一个方法为可被 Agent 调用的工具

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceTool{Stringdescription();// 工具功能描述,供 LLM 理解工具用途}

@ToolParam 注解:描述工具参数

@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)public@interfaceToolParam{Stringdescription();// 参数描述,帮助 LLM 理解如何构造输入}

注解设计的核心价值

  1. 声明式定义:工具的元信息直接标注在方法上,代码即文档
  2. 运行时可见@Retention(RetentionPolicy.RUNTIME) 确保注解在运行时可被反射读取
  3. 自动化集成:无需手动维护工具列表,系统自动发现和注册工具

3.3 工具描述生成器(ToolUtil)

ToolUtil 类负责通过反射扫描工具类,自动生成供 LLM 使用的工具描述:

publicclassToolUtil{publicstaticStringgetToolDescription(Class<?> clazz){List<String> toolNameList =newArrayList<>();List<String> formattedToolList =newArrayList<>();// 遍历类中的所有方法for(Method declaredMethod : clazz.getDeclaredMethods()){// 检查是否有 @Tool 注解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);}}returnString.join("\n\n", formattedToolList);}}
  1. 自动发现:使用 Java 反射机制扫描所有带 @Tool 注解的方法
  2. 零配置:添加新工具只需在方法上加注解,无需修改任何配置文件
  3. LLM 友好:生成的描述格式清晰,便于模型理解工具用途和参数要求

生成的工具描述示例:

- toolName=writeFile, toolDescription=将指定内容写入本地文件。, paramDescription=包含 'file_path' 和 'content' 的 JSON 字符串。 

3.4 实现具体工具(AgentTools)

现在我们来实现一个具体的工具——文件写入工具:

publicclassAgentTools{privatefinalObjectMapper objectMapper =newObjectMapper();/** * 将指定内容写入本地文件。 * @param jsonInput 一个包含 'file_path' 和 'content' 的 JSON 字符串。 * @return 执行结果的描述字符串。 */@Tool(description ="将指定内容写入本地文件。")publicStringwriteFile(@ToolParam(description ="包含 'file_path' 和 'content' 的 JSON 字符串。")String jsonInput ){try{// 解析 JSON 输入JsonNode rootNode = objectMapper.readTree(jsonInput);String filePath = rootNode.get("file_path").asText();String content = rootNode.get("content").asText();// 执行文件写入try(FileWriter writer =newFileWriter(filePath)){ writer.write(content);return"写入成功";}catch(IOException e){returnString.format("写入文件 '%s' 时发生错误: %s", filePath, e.getMessage());}}catch(Exception e){returnString.format("解析输入或执行工具时出错: %s", e.getMessage());}}}

设计细节解析

  1. JSON 字符串作为输入:这与 LLM 生成的格式一致,避免复杂的类型转换
  2. 简洁的返回值:返回"写入成功"而非冗长的描述,让 LLM 能快速理解执行结果
  3. 完善的错误处理
    • JSON 解析失败时返回明确的错误信息
    • 文件写入失败时返回具体的异常原因
    • 这些信息会作为 Observation 反馈给 LLM,帮助它调整策略

3.5 Prompt 模板设计

Prompt 设计是 ReAct Agent 的灵魂。一个优秀的 Prompt 需要清晰地告诉 LLM:你是谁、你能做什么、你应该如何输出。

privatestaticfinalString 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} """;

Prompt 设计要点详解

部分作用设计考量
角色定义设定 AI 的身份和能力边界让模型明确自己是一个"使用工具解决问题"的助手
工具清单告知可用工具{tools} 占位符会被替换为实际的工具描述
规则约束控制输出格式防止模型自行编造 Observation,确保每轮只输出一次
输出示例引导输出格式通过多轮对话示例展示期望的输出结构
历史记录维持上下文{history} 保存之前的推理过程,实现"记忆"能力

为什么规则约束如此重要?

没有严格的规则约束,LLM 可能会:

  • 一次性输出多轮的 Reason/Action/ActionInput
  • 自己编造 Observation 内容(而不是等待真实的工具执行结果)
  • 跳过工具调用直接给出答案(即使它无法完成任务)

通过明确的规则,我们确保了 LLM 的行为可预测、可控制。

3.6 核心 Agent 循环(ReActAgent)

现在让我们看看 ReAct Agent 的核心——主循环逻辑:

publicclassReActAgent{privateOpenAIClient apiClient;publicReActAgent(OpenAIClient apiClient){this.apiClient = apiClient;}publicStringrun(String input)throwsNoSuchMethodException{// 1. 注册可用工具HashMap<String,Method> tools =newHashMap<>(); tools.put("writeFile",AgentTools.class.getMethod("writeFile",String.class));// 2. 初始化历史记录,用于维护推理上下文StringBuilder history =newStringBuilder();// 3. 最大循环次数,防止无限循环int i =0;while(i <10){try{// 4. 构建完整 PromptString prompt =buildPrompt(input, history.toString());// 5. 调用大模型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);// 6. 解析 LLM 输出ParsedOutput parsedOutput =parseLlmOutput(rawLlmOutput);// 7. 如果是最终答案,直接返回if(parsedOutput.type.equals("final_answer")){return parsedOutput.answer;}// 8. 执行工具并获取结果String observation =executeTool(parsedOutput, tools);System.out.println("工具执行结果:"+ observation);// 9. 更新历史记录 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"达到了循环最大次数";}}

核心流程逐步解析

  1. 工具注册:将可用工具及其方法引用存入 HashMap,便于后续通过名称查找和调用
  2. 历史记录初始化:使用 StringBuilder 累积每轮的执行记录
  3. 循环控制:设置最大循环次数为 10,防止 LLM 陷入无限循环
  4. Prompt 构建:将用户输入和历史记录填充到模板中
  5. LLM 调用:使用 OpenAI SDK 发送请求并获取响应
  6. 输出解析:区分"继续行动"和"最终答案"两种情况
  7. 终止判断:如果输出包含 FinalAnswer,则返回结果
  8. 工具执行:根据解析结果调用对应工具
  9. 记忆更新:将本轮的 Reason、Action、ActionInput、Observation 追加到历史记录

Prompt 构建方法

privateStringbuildPrompt(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 的输出是自由文本,需要被解析为结构化数据以便程序处理:

privateParsedOutputparseLlmOutput(String llmOutput){// 1. 检查是否为最终答案if(llmOutput.contains("FinalAnswer: ")){returnnewParsedOutput("final_answer", llmOutput.split("FinalAnswer: ")[1].strip(),null,null,null,null);}// 2. 使用正则表达式提取 Reason、Action、ActionInputPattern 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();// 3. 处理可能的 Markdown 代码块格式if(actionInputStr.startsWith("```json")){ actionInputStr = actionInputStr.substring(7);}if(actionInputStr.endsWith("```")){ actionInputStr = actionInputStr.substring(0, actionInputStr.length()-3);} actionInputStr = actionInputStr.trim();returnnewParsedOutput("action",null, reason, action, actionInputStr,null);}// 4. 解析失败returnnewParsedOutput("error",null,null,null,null,String.format("解析LLM输出失败: '%s'", llmOutput));}// 使用 Java Record 定义解析结果数据结构privaterecordParsedOutput(String type,// 输出类型:final_answer / action / errorString answer,// 最终答案(当 type 为 final_answer 时)String reason,// 推理过程String action,// 要执行的工具名String actionInputStr,// 工具输入参数(JSON 字符串)String message // 错误信息(当 type 为 error 时)){}

解析器设计要点

  1. 优先级判断:首先检查是否包含 FinalAnswer:,这是终止循环的信号
  2. 正则匹配:使用 Pattern.DOTALL 模式,使 . 能匹配换行符,处理多行输出
  3. 格式兼容:处理 LLM 可能生成的 Markdown 代码块格式(如 ```json 包裹的 JSON)
  4. 不可变数据:使用 Java Record 定义数据结构,简洁且线程安全

3.8 工具执行器

工具执行器通过反射调用实际的工具方法:

privatestaticStringexecuteTool(ParsedOutput parsedOutput,HashMap<String,Method> tools )throwsIllegalAccessException,InvocationTargetException{String toolName = parsedOutput.action;String toolParams = parsedOutput.actionInputStr;// 根据工具名查找方法Method toolMethod = tools.get(toolName);// 通过反射调用工具方法Object observation = toolMethod.invoke(newAgentTools(), toolParams);returnString.valueOf(observation);}

反射调用的优势

  1. 动态调用:无需硬编码 switch-case 分支,根据工具名动态查找并调用
  2. 易于扩展:添加新工具只需注册到 HashMap,无需修改执行器代码
  3. 解耦设计:Agent 核心逻辑与具体工具实现完全分离

四、完整执行示例

4.1 启动代码

publicstaticvoidmain(String[] args)throwsException{// 1. 创建 OpenAI 客户端OpenAIClient apiClient =OpenAIOkHttpClient.builder().apiKey(ModelConfig.API_KEY).baseUrl(ModelConfig.BASE_URL).build();// 2. 创建 ReAct AgentReActAgent reActAgent =newReActAgent(apiClient);// 3. 执行任务String result = reActAgent.run("将1到10中间的所有整数写到文件中");// 4. 输出结果System.out.println("最终结果:"+ result);}

4.2 执行过程输出

大模型原始输出:Reason: 用户需要将1到10之间的所有整数写入到一个文件中。 我需要使用writeFile工具来完成这个任务,需要指定文件路径和内容。 Action: writeFile ActionInput:{"file_path":"numbers.txt","content":"1\n2\n3\n4\n5\n6\n7\n8\n9\n10"} 工具执行结果:写入成功 大模型原始输出:Reason: 文件已经成功写入,用户的任务已经完成。 FinalAnswer: 我已经成功将1到10之间的所有整数写入到了numbers.txt文件中。 最终结果:我已经成功将1到10之间的所有整数写入到了numbers.txt文件中。 

4.3 执行流程分析

从输出可以清晰看到 ReAct 的工作过程:

轮次阶段内容
1Reason分析用户需求,决定使用 writeFile 工具
1ActionwriteFile
1ActionInput构造 JSON 参数,包含文件路径和内容
1Observation系统执行工具后返回"写入成功"
2Reason观察到成功结果,判断任务完成
2FinalAnswer返回最终答案给用户

这个两轮对话完美地展示了 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

privatestaticStringsafeExecuteTool(ParsedOutput parsedOutput,HashMap<String,Method> tools){String toolName = parsedOutput.action;// 检查工具是否存在Method toolMethod = tools.get(toolName);if(toolMethod ==null){return"未知工具: "+ toolName +"。请检查工具清单并重新选择。";}// ... 执行工具逻辑}

5.3 自动工具注册

将手动注册改为反射扫描,添加新工具时无需修改 Agent 代码:

privatestaticHashMap<String,Method>registerTools(Class<?> toolClass){HashMap<String,Method> tools =newHashMap<>();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,建议增加基础校验:

// 最小 JSON 校验if(toolParams ==null||!toolParams.trim().startsWith("{")||!toolParams.trim().endsWith("}")){return"ActionInput 不是合法 JSON 对象,请输出形如 {\"key\":\"value\"} 的参数。";}

5.5 更多工具示例

扩展工具集以支持更多场景:

@Tool(description ="读取本地文件内容")publicStringreadFile(@ToolParam(description ="文件路径")String filePath){try{returnFiles.readString(Path.of(filePath));}catch(IOException e){return"读取文件失败: "+ e.getMessage();}}@Tool(description ="执行网络搜索")publicStringwebSearch(@ToolParam(description ="搜索关键词的 JSON")String jsonInput){// 调用搜索 API 实现}@Tool(description ="执行数学计算")publicStringcalculate(@ToolParam(description ="包含 expression 的 JSON")String jsonInput){// 使用表达式求值库实现}

六、与主流框架的对比

维度手写实现LangChainSpring AI
学习价值⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
灵活性⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
开发效率⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
生产就绪⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
社区生态⭐⭐⭐⭐⭐⭐⭐⭐⭐

建议:学习原理用手写实现,工程落地用成熟框架。理解了底层原理后,使用框架会更加得心应手。


七、进阶扩展方向

掌握了基础的 ReAct 实现后,可以进一步探索以下方向:

7.1 多轮对话记忆

当前实现在单次任务结束后会清空历史。可以引入持久化存储,实现跨会话记忆:

publicclassChatMemory{privatefinalList<Message> messages =newArrayList<>();publicvoidaddMessage(Message message){ messages.add(message);// 可选:持久化到数据库}publicStringgetHistory(){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 等成熟框架,将原理知识转化为工程实践能力。


参考资料


在这里插入图片描述

Read more

Python操作国产金仓数据库(KingbaseES)全流程:搭建自己的网页数据管理(增删改查)

Python操作国产金仓数据库(KingbaseES)全流程:搭建自己的网页数据管理(增删改查)

Python操作国产金仓数据库(KingbaseES)全流程:搭建自己的网页数据管理(增删改查) Python操作国产金仓数据库(KingbaseES)全流程:搭建自己的网页数据管理(增删改查),现在国产化替代是大趋势,国产数据库的应用越来越广,金仓数据库(KingbaseES)作为其中的佼佼者,在政务、金融这些领域用得特别多。今天我就带大家从0到1,一步步实现用Python操作KingbaseES数据库,还会基于Flask框架搭一个可视化的网页管理系统,数据的增删改查全流程都能搞定,不管你是Python开发者还是数据库管理员,跟着学都能用得上。 前言     中电科金仓(北京)科技股份有限公司(以下简称“电科金仓”)成立于1999年,是成立最早的拥有自主知识产权的国产数据库企业,也是中国电子科技集团(CETC)成员企业。电科金仓以“提供卓越的数据库产品助力企业级应用高质量发展”为使命,致力于“成为世界卓越的数据库产品与服务提供商”。     电科金仓自成立起始终坚持自主创新,专注数据库领域二十余载,具备出色的数据库产品研发及服务能力,核心产品金仓数据库管理系统Kingbas

By Ne0inhk
【python】全流程图文安装教程

【python】全流程图文安装教程

目录 一、前言 二、下载安装 2.1 官网下载 2.2 安装python 2.3 验证python是否可用 三、如何编写python文件代码 3.1 打开IDLE 3.2 新建脚本文件 3.3 编写脚本 3.4 运行脚本 一、前言 在数字化浪潮中,编程已成为一项“新通用技能”,而Python因其近乎零门槛的入门体验,成为无数人打开代码世界的第一把钥匙。无论你是想自动化办公、分析数据,还是探索人工智能,只需一行 print("Hello World") ,就能见证计算机对你的首次回应。 二、下载安装 2.1 官网下载

By Ne0inhk
GraphQL在Python中的完整实现:从基础到企业级实战

GraphQL在Python中的完整实现:从基础到企业级实战

目录 摘要 1 引言:为什么GraphQL是API设计的未来 1.1 GraphQL的核心价值定位 1.2 GraphQL技术演进路线图 2 GraphQL核心技术原理深度解析 2.1 Schema定义语言与类型系统 2.1.1 Schema定义原则 2.1.2 类型系统架构 2.2 Resolver解析机制深度解析 2.2.1 Resolver执行模型 2.2.2 Resolver执行流程 2.3 Strawberry vs Graphene框架深度对比 2.3.1 架构设计哲学对比 2.3.2 框架选择决策树 3 实战部分:

By Ne0inhk
Python保姆级下载安装教程-->Windows版本

Python保姆级下载安装教程-->Windows版本

Windows版本保姆级下载安装 一、下载Python  1、点击下载官网地址 Python官方网站地址https://www.python.org/downloads/ 2、官网页面如下: 3、点击下载界面: 上面最新的版本是3.14.2版本,一般来说新版较之老版优化了一些内容且版本向下兼容,但是不建议下载最新版本,因为python在很多地方使用时没有更新到最新版本,向下兼容性并不好,但也不要太低版本的,很多不适用。 点击Downloads,选择适合自己电脑系统的版本,我的电脑是Windows系统,就选择了Windows,点击后会跳转到另一个页面 【Stable Releases】:稳定发布版本,是官方完成全面测试、修复已知 Bug 的成熟版本,运行稳定、风险低,无论入门学习还是机器视觉项目开发,都优先选这个版本; 【Pre-releases】:预发布版本,属于测试阶段的 “体验版”,可能包含新功能但存在未修复的 Bug,稳定性差,小白或做实际项目(如机器视觉开发)千万别选,易出现代码报错、

By Ne0inhk