☰
Java工程师转AI Agent实战:从ReAct原理到Spring AI生产级落地
2026/10/4 13:49:07 网站建设 项目流程

1. 为什么 Java 工程师转 AI Agent 有天然优势

1.1 从 CRUD 到智能体,差的不是智商是视角

这两年身边不少 Java 老哥都在焦虑同一件事:干了五六年业务系统,天天写 Controller、Service、Mapper,突然满世界都在聊 AI Agent,感觉自己积累的那点东西一夜之间要归零了。我一开始也这么想,直到真正动手做了几个 Agent 项目之后才发现,事情完全不是这样。

Java 工程师转 AI Agent,其实比很多纯算法背景的人更容易落地。原因很直接:Agent 这东西本质上不是一个模型问题,而是一个工程问题。它要处理工具调用、状态管理、异常重试、并发控制、上下文裁剪、日志追踪、权限校验——这些词你听着是不是特别耳熟?对,这就是我们写了这么多年的后端工程。模型只是 Agent 的“大脑”,而让这个大脑真正能干活的那套骨架,恰恰是 Java 工程师最擅长的部分。

我见过太多 Python 写得飞起、模型调得贼溜的人,一到要把 Agent 部署成稳定服务、要扛住几十上百并发、要做链路追踪和降级,就抓瞎了。反过来,一个熟悉 Spring 生态的 Java 工程师,只要把 Agent 的核心原理搞明白,落地速度往往快得惊人。所以这篇我不打算跟你讲什么高深的强化学习,就讲一个 Java 工程师怎么从原理到落地,把 AI Agent 真正跑起来。

1.2 先搞清楚:AI Agent 到底比普通调用强在哪

很多人对 Agent 的理解还停留在“调个大模型 API 返回一段文本”。那不叫 Agent,那叫聊天接口。真正的 Agent 有三个核心特征:能思考、能行动、能根据结果调整。

举个生活化的例子。你让普通大模型“帮我查一下明天北京的天气并提醒我带伞”,它只能凭训练数据瞎编一个答案。但一个 Agent 会这么做:先判断需要调用天气工具,然后真的去调天气 API,拿到真实数据,再判断温度、降水概率,最后决定要不要提醒你带伞,甚至还能顺手帮你设个闹钟。这个“判断—调用—观察—再判断”的循环,就是 Agent 的灵魂。

在技术圈,这套循环最经典的范式叫ReAct,也就是 Reasoning + Acting。模型先输出一段思考(Reasoning),决定要调用哪个工具(Action),工具返回结果(Observation),模型再基于结果继续思考,直到任务完成。你把它理解成一个 while 循环就对了:只要任务没结束,就继续“想一步、做一步、看结果”。Java 工程师看到这个结构应该会心一笑,这不就是我们写状态机、写工作流的思路吗?

1.3 技术选型:LangChain4j 还是 Spring AI

落到 Java 生态,绕不开两个框架:LangChain4j和Spring AI。这俩经常被拿来对比,我的建议是别纠结,先看你团队的技术栈。

LangChain4j 更像是一个“全家桶”,它把 Agent、RAG、工具调用、记忆管理、多路召回这些能力都封装好了,API 设计也比较贴近 Python 版 LangChain 的思路。如果你之前看过 LangChain 的教程,上手 LangChain4j 会非常快。它的AiServices可以把一个 Java 接口直接变成 Agent,你只要定义好方法签名和注解,框架帮你把提示词、工具调用、结果解析全串起来。

Spring AI 则是 Spring 官方亲儿子,最大的优势是和 Spring Boot 生态无缝集成。你熟悉的@Bean、依赖注入、配置管理、Actuator 监控,全都能直接用上。对于已经在用 Spring Boot 的团队,引入 Spring AI 的迁移成本几乎为零。而且 Spring AI 对国内模型的支持也越来越好,像阿里百炼、通义千问这些都能通过配置接进来。

我的实际选择是这样的:如果是快速验证想法、做原型,我用 LangChain4j,因为它抽象层次高,代码量少;如果是要做成生产级服务、要长期维护,我用 Spring AI,因为它的工程化程度和生态整合更让人放心。当然,两者也不是非此即彼,有些项目里我甚至混着用,用 LangChain4j 做 RAG 检索,用 Spring AI 做服务编排。

2. 核心原理拆解:Agent 的四大件

2.1 大脑:模型选型与提示词工程

Agent 的大脑就是大模型。选模型这件事,我的经验是别一上来就追求最强最贵。做 Agent 和做聊天不一样,Agent 会在一轮任务里调用模型很多次,成本是成倍放大的。所以要根据任务复杂度分层选型。

简单任务,比如意图识别、参数抽取、结果格式化,用轻量模型就够了,速度快、成本低。复杂推理任务,比如多步规划、代码生成、复杂工具编排,才需要上更强的模型。我一般会在配置里把这两类模型分开,让框架根据场景自动路由。

提示词工程这块,Java 工程师容易犯的错是把它当成“写文档”,写一大堆背景介绍。其实 Agent 的提示词核心就三件事:角色定义、工具说明、输出格式约束。角色定义告诉模型它是谁、要干什么;工具说明告诉它有哪些工具可用、什么时候用;输出格式约束最关键,必须明确要求模型按固定结构输出,比如先输出 Thought,再输出 Action,这样你的 Java 代码才能稳定解析。

提示:输出格式约束一定要用强约束语言,比如“必须严格按照以下 JSON 格式输出,不要添加任何额外文字”。我踩过的坑就是模型偶尔会“自由发挥”,加一句“好的,我来帮你”,结果解析直接崩掉。

2.2 手脚:工具调用的设计与实现

工具(Tool)是 Agent 能“下地干活”的关键。在 Java 里,一个工具本质上就是一个方法,加上描述和参数说明。LangChain4j 用@Tool注解,Spring AI 用@Tool或者函数式注册,思路都一样。

设计工具时有几个原则我反复验证过。第一,工具粒度要适中。太细了,模型要调很多次,容易乱;太粗了,模型不知道怎么用。比如“查询订单”和“取消订单”应该分开,但“查询订单列表”和“查询订单详情”可以合并成一个带参数的查询工具。第二,工具描述要写清楚边界。模型判断用不用某个工具,全靠描述。你要明确写“这个工具用于查询实时数据,不适用于历史统计”,否则模型会乱调。第三,参数校验不能省。模型给的参数不一定合法,你的工具方法里必须做校验,该抛异常抛异常,让 Agent 有机会根据错误信息重试。

@Tool("根据城市名称查询当前天气,仅支持中国大陆城市") public String getWeather(@P("城市名称,如:北京") String city) { if (city == null || city.isBlank()) { throw new IllegalArgumentException("城市名称不能为空"); } // 调用真实天气 API return weatherApi.query(city); }

2.3 记忆:上下文管理与多轮对话

Agent 的记忆分两种:短期记忆和长期记忆。短期记忆就是当前对话的上下文,长期记忆则是跨会话的知识,通常用向量数据库存。

短期记忆最大的坑是上下文爆炸。Agent 一轮任务可能产生十几条消息,多轮下来 token 轻松超限。我的做法是分层处理:最近几轮完整保留,更早的做摘要压缩,再早的直接丢弃。LangChain4j 提供了MessageWindowChatMemory,可以设置最大保留条数,超出就自动淘汰最老的。但光淘汰不够,关键信息会丢,所以我还会在任务关键节点手动插入一条“进度摘要”消息,把已完成的事项固化下来。

长期记忆这块,本质就是 RAG。把历史对话、业务文档、知识库切片存进向量库,需要时做相似度检索召回。这里有个细节:召回不是越多越好。我一般召回 top 3 到 top 5,太多会稀释关键信息,还会增加 token 消耗。LangChain4j 的多路召回能力可以同时从多个数据源检索再融合排序,适合知识来源比较杂的场景。

2.4 循环:ReAct 执行引擎

把大脑、手脚、记忆串起来的就是 ReAct 循环。用伪代码表示大概是这样:

while (!task.isDone() && step < maxSteps) { String thought = model.reason(context); if (thought.needTool()) { Object result = toolExecutor.execute(thought.action()); context.addObservation(result); } else { task.complete(thought.answer()); } step++; }

看起来简单,但工程上有几个必须处理的点。最大步数限制是必须的,否则模型可能陷入死循环,一直调工具停不下来。我一般设 10 到 15 步,超过就强制终止并返回当前结果。异常处理也要设计好,工具调用失败不能让整个 Agent 崩掉,要把错误信息作为 Observation 喂回给模型,让它自己决定是重试还是换方案。超时控制同样重要,每一步都要有超时,避免某个工具卡死拖垮整个请求。

3. 从零搭建一个可落地的 Agent 服务

3.1 环境准备与依赖引入

我以 Spring AI 为例,走一遍完整的搭建流程。首先建一个标准的 Spring Boot 项目,JDK 17 起步,推荐 21。然后在pom.xml里引入 Spring AI 的 starter。

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency>

如果你用的是国内模型,比如阿里百炼的通义千问,把 starter 换成对应的实现,然后在application.yml里配置 base-url 和 api-key 就行。这里要注意,不同模型的接口协议可能有差异,配置前先确认框架版本是否支持。

spring: ai: openai: base-url: https://dashscope.aliyuncs.com/compatible-mode api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus temperature: 0.7

注意:api-key 千万别硬编码在代码里,用环境变量或者配置中心。我见过有人把 key 提交到 Git 仓库,结果被人刷了几千块,血的教训。

3.2 定义工具与 Agent 配置

接下来定义工具。我做一个简单的订单查询 Agent,包含查询订单和取消订单两个工具。

@Component public class OrderTools { @Tool("根据订单号查询订单详情") public Order queryOrder(@P("订单号") String orderId) { return orderService.getById(orderId); } @Tool("根据订单号取消订单,仅未发货订单可取消") public String cancelOrder(@P("订单号") String orderId) { return orderService.cancel(orderId); } }

然后配置 ChatClient 和工具注册。Spring AI 的写法很 Spring,用 builder 模式把模型、工具、记忆组装起来。

@Configuration public class AgentConfig { @Bean public ChatClient chatClient(ChatClient.Builder builder, OrderTools orderTools) { return builder .defaultSystem("你是一个订单助手,帮助用户查询和取消订单。回答要简洁准确。") .defaultTools(orderTools) .build(); } }

3.3 实现 ReAct 循环与并发控制

Spring AI 内部已经帮你实现了工具调用的循环,你调用chatClient.prompt().user(input).call()时,框架会自动处理“模型要调工具—执行工具—把结果喂回模型”这个过程。但如果你想自己控制循环、加最大步数限制,可以用更底层的 API 手动编排。

并发控制是 Java 工程师的强项,但 Agent 场景有它的特殊性。模型调用是 IO 密集型,所以线程池要按 IO 密集型配置,核心线程数可以设大一些。但要注意,同一个会话的请求必须串行,否则上下文会乱。我的做法是按 sessionId 做分片,同一个 session 的请求路由到同一个队列串行处理,不同 session 之间并行。

private final Map<String, ExecutorService> sessionExecutors = new ConcurrentHashMap<>(); public String handle(String sessionId, String input) { ExecutorService executor = sessionExecutors.computeIfAbsent( sessionId, k -> Executors.newSingleThreadExecutor() ); return executor.submit(() -> agent.chat(input)).get(); }

3.4 接入 RAG 做知识增强

很多 Agent 场景需要基于私有知识回答,这就得上 RAG。流程是:文档切片、向量化、存库、检索、拼进提示词。LangChain4j 的 Easy RAG 把这一套封装得很简单,几行代码就能跑通。

EmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>(); EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder() .documentSplitter(DocumentSplitters.recursive(500, 50)) .embeddingModel(embeddingModel) .embeddingStore(store) .build(); ingestor.ingest(document);

切片大小我一般设 500 字符,重叠 50 字符。重叠是为了避免关键信息被切断。检索时召回 top 5,再用一个重排序模型精排,效果会明显提升。如果知识来源多,可以用多路召回,从不同数据源分别检索再融合。

4. 生产环境踩坑与排查实录

4.1 常见问题速查表

问题现象可能原因排查方向解决方案
模型不调用工具工具描述不清或提示词未强调检查工具描述和 system prompt补充工具使用场景说明
工具调用参数错误参数描述不明确查看模型输出的参数细化 @P 描述,加示例
响应超时工具执行慢或模型响应慢打点统计各阶段耗时加超时、异步化、换轻量模型
上下文超限历史消息过多统计 token 数加记忆窗口、做摘要压缩
死循环调工具缺少终止条件看调用步数设最大步数、优化提示词
并发下上下文串了会话未隔离检查 session 管理按 session 分片串行

4.2 几个我踩过的深坑

第一个坑是工具返回结果太大。有次我写了个查询工具,返回了一整个列表的 JSON,几千个字符,直接塞进上下文,token 瞬间爆掉,而且模型被无关信息干扰,回答质量暴跌。后来我改成工具内部先做筛选和摘要,只返回关键字段,问题就解决了。工具返回给模型的内容,一定要精简,只给模型做决策需要的信息。

第二个坑是模型幻觉调用不存在的工具。提示词里明明只注册了两个工具,模型偶尔会编一个“sendEmail”出来。这种情况框架一般会报错,但错误信息如果不处理,整个请求就挂了。我的做法是在工具执行层做一层拦截,遇到未知工具就返回一条“该工具不存在,可用工具为:xxx”的 Observation,让模型自己纠正。

第三个坑是并发下的资源竞争。多个请求同时调用同一个有状态的工具,结果数据串了。Agent 的工具方法尽量设计成无状态的,如果必须有状态,一定要做隔离。我一般用 ThreadLocal 或者按请求传上下文对象,避免共享可变状态。

4.3 性能优化的几个实操技巧

模型调用是最大的耗时点,优化空间也最大。流式输出能显著提升用户感知速度,虽然总耗时没变,但首字返回快了很多。缓存也很关键,相同或相似的请求可以缓存模型结果,尤其是那些确定性的工具调用结果。批处理适合离线场景,把多个请求合并成一次模型调用。

还有一个容易被忽略的点是提示词长度。提示词越长,模型处理越慢,成本越高。我定期会 review 提示词,删掉那些模型根本不看的冗余描述。实测下来,精简提示词能省 20% 到 30% 的 token。

提示:上线前一定要做压测,重点看并发下的 P99 延迟和错误率。Agent 服务的延迟波动比普通接口大得多,因为模型响应时间本身就不稳定。

5. 学习路线与进阶方向

5.1 给 Java 工程师的三个月学习计划

第一个月,把 ReAct 原理搞透,用 LangChain4j 或 Spring AI 跑通一个带工具调用的 Demo。别贪多,就做一个天气查询或者计算器 Agent,把循环、工具、记忆这三件事弄明白。

第二个月,加上 RAG。找一个你熟悉的领域文档,做切片、向量化、检索,让 Agent 能基于文档回答。这个阶段重点理解召回质量怎么评估、切片策略怎么调。

第三个月,做工程化。把 Agent 包装成 REST 服务,加上并发控制、超时、重试、日志追踪、监控告警。这一步是 Java 工程师的舒适区,做完你就有一个能拿得出手的生产级 Agent 项目了。

5.2 后续可以扩展的方向

Agent 做熟了之后,可以往多 Agent 协作方向走。多个各司其职的 Agent 互相配合,一个负责规划,一个负责执行,一个负责审核,能处理更复杂的任务。这个思路在 Java 里可以用工作流引擎来编排,把每个 Agent 当成一个节点。

另一个方向是可观测性。Agent 的决策过程是个黑盒,出了问题很难排查。可以引入链路追踪,把每一步的 Thought、Action、Observation 都记录下来,做成可视化的执行轨迹。这对调试和优化帮助极大,也是生产环境必备的能力。

最后再分享一个小技巧:调试 Agent 时,把每一步的中间结果都打日志,包括完整的提示词和模型原始输出。我一开始嫌日志太多没打,结果出问题完全不知道模型当时看到了什么、想了什么。后来加上详细日志,排查效率提升了不止一个档次。这个习惯,建议你从第一个 Demo 就养成。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询