
Java 工程师转型 AI Agent 这件事我从去年下半年开始认真琢磨到现在带着团队把两个内部智能体推上了生产环境。回头看这条路最难的其实不是写代码而是思维方式的切换。做了七八年 Java习惯了 Controller 调 Service、Service 调 DAO 的确定性链路突然要面对一个模型可能返回任何东西的世界那种失控感非常真实。但好消息是Java 生态这两年补课补得很快LangChain4j、Spring AI 这些框架把大量脏活累活封装掉了Java 工程师的工程化能力反而是做 Agent 落地时最稀缺的东西。这篇就把我从原理到落地的完整思路拆开讲包括选型逻辑、核心代码骨架、并发扛压的坑、以及那些文档里不会写的经验。1. 先想清楚Java 工程师做 Agent 的独特优势在哪1.1 不是转行是能力迁移很多人把转型 AI Agent 理解成要重新学一套东西甚至有人跑去从头啃 Python 生态。我的看法是如果你已经有扎实的 Java 功底完全没必要推翻重来。Agent 的本质是什么是一个能感知输入、做决策、调用工具、拿到结果再继续决策的循环系统。这个循环里真正跟AI强相关的只有两件事一是跟大模型的通信prompt 组装、响应解析二是决策逻辑的设计ReAct 模式那套思考-行动-观察的循环。剩下的全是工程问题——状态管理、并发控制、错误重试、可观测性、权限校验、数据一致性这些恰恰是 Java 工程师天天在干的事。我举个具体的例子。一个电商客服 Agent用户问我上周买的那双鞋能退吗Agent 需要查订单状态、判断是否在退货窗口期、检查商品类目是否支持退货、生成回复。这里面判断是否在窗口期是纯业务逻辑Java 写起来又快又稳理解用户意图并决定调用哪个工具才是模型干的活。你会发现一个 Agent 项目里模型相关的代码可能只占 20%剩下 80% 都是你熟悉的那套东西。1.2 Java 生态的 Agent 框架已经能打了去年年初的时候Java 做 Agent 确实有点尴尬LangChain 是 Python 的想用只能通过 HTTP 调服务。但现在情况完全变了。LangChain4j 和 Spring AI 这两个框架成熟度上来了基本的 Chain、Tool Calling、RAG、Memory 这些能力都有而且跟 Spring 生态集成得非常顺。特别是 Spring AI如果你团队本来就在用 Spring Boot引入成本极低依赖注入、配置管理、Actuator 监控这些全都能复用。我列个简单的对比方便你判断选哪个维度LangChain4jSpring AI定位独立框架不绑定 SpringSpring 生态原生学习曲线中等概念较多较低Spring 开发者上手快模型支持非常广国内外都覆盖广国内模型适配在快速补齐RAG 能力成熟多路召回等高级特性全基础能力完善高级特性跟进中工具调用灵活注解式声明注解式跟 Spring 风格一致适合场景复杂 Agent、多模型混用Spring 体系内的企业应用我的建议是如果你是从零起一个新项目且团队是 Spring 技术栈直接上 Spring AI省心。如果你要做比较复杂的 Agent 编排需要多路召回、多模型切换、复杂的工具链LangChain4j 的灵活性更好。当然两者也不是互斥的我见过有项目用 Spring AI 做基础通信层用 LangChain4j 的某些组件做 RAG 增强。1.3 市场需求的真实变化从招聘市场看现在很多岗位 JD 里开始出现有 AI Agent 开发经验优先这样的字眼但注意它们要的不是会调 OpenAI API的人而是能把 Agent 稳定跑在生产环境的人。这两者的差距就是 Java 工程师的机会窗口。会写 prompt 的人很多但能把 Agent 的并发、超时、降级、监控、成本控制都处理好的人少。我面过几个候选人聊 ReAct 原理头头是道问他Agent 调用工具超时了怎么办多个用户同时触发同一个 Agent 实例怎么隔离状态就答不上来了。这些恰恰是工程能力是你的主场。2. ReAct 模式Agent 的思考骨架到底怎么运转2.1 用生活化的方式理解 ReActReAct 这个词拆开就是 Reasoning Acting推理加行动。你可以把它想象成一个刚入职的助理你让他帮我订一张明天去上海的高铁票。他不会直接就去订而是先想我需要知道出发地、具体时间、座位偏好。然后他可能去查你的日程表行动发现你明天上午有个会观察于是推断你应该下午出发推理再去查下午的车次行动看到有几趟观察最后选一趟合适的下单行动。这个想一步、做一步、看结果、再想下一步的循环就是 ReAct 的核心。跟传统的一次性把问题丢给模型要答案不同ReAct 允许模型在过程中调用外部工具、获取新信息、修正自己的判断。这也是 Agent 和普通 Chatbot 最本质的区别——Chatbot 只能基于训练时学到的知识回答Agent 能主动去获取实时信息并采取行动。2.2 循环的四个阶段拆解一个标准的 ReAct 循环在代码层面通常是这样运转的第一阶段Thought思考。模型根据当前的任务目标和已有的观察结果输出一段推理决定下一步该干什么。这段推理是自然语言比如用户想知道订单状态我需要调用订单查询工具参数是订单号。第二阶段Action行动。从思考结果里解析出要调用的工具名和参数。这一步在代码里通常是解析模型返回的结构化内容JSON 或者特定格式的文本。第三阶段Observation观察。执行工具调用把返回结果作为新的上下文喂回给模型。这里要注意工具返回的内容可能很长需要做截断或摘要否则上下文会爆炸。第四阶段判断是否结束。如果模型认为任务完成了输出最终答案循环结束否则带着新的观察结果回到第一阶段。用伪代码表示大概是这样public String runAgent(String userInput) { ListMessage messages new ArrayList(); messages.add(systemPrompt()); messages.add(userMessage(userInput)); for (int i 0; i MAX_ITERATIONS; i) { // 调用模型获取思考和行动 AiMessage response chatModel.generate(messages); messages.add(response); // 判断是否要调用工具 if (response.hasToolCalls()) { for (ToolCall call : response.toolCalls()) { String result executeTool(call); messages.add(toolResultMessage(call.id(), result)); } } else { // 没有工具调用说明模型给出了最终答案 return response.text(); } } return 任务超出最大迭代次数请简化问题; }这段代码看着简单但里面藏着好几个坑。MAX_ITERATIONS设多少合适设小了任务做不完设大了可能死循环烧钱。我的经验是 8 到 12 之间具体看任务复杂度。还有工具执行失败怎么办直接抛异常会让整个循环崩掉正确做法是把错误信息也作为观察结果喂回去让模型自己决定是重试还是换个思路。2.3 为什么 ReAct 比一次性问答更适合复杂任务我做过一个对比测试同样一个帮我分析这份销售数据并给出建议的任务。用普通的一次性问答模型只能基于你给的数据做表面分析如果数据里有个字段它不理解它就只能瞎猜。用 ReAct 模式模型可以先调用一个查询字段含义的工具搞清楚每个字段代表什么再调用计算同比环比的工具最后基于准确的计算结果给建议。准确率差了不止一个档次。但 ReAct 也不是银弹。它的代价是延迟高、token 消耗大。一个需要 5 轮循环的任务token 消耗可能是单次问答的 5 到 8 倍。所以我的原则是简单任务用单次问答需要多步推理或外部信息获取的才上 ReAct。别为了炫技把所有请求都走 Agent 循环成本扛不住。3. 框架选型与第一个可运行 Agent 的搭建3.1 环境准备里最容易忽略的细节搭第一个 Agent 之前有几个准备工作看着不起眼但没做好后面会反复踩坑。模型接入方式的选择。国内项目现在主流是接百炼、智谱、DeepSeek 这些平台的模型。Spring AI 和 LangChain4j 都提供了对应的 starter但要注意版本匹配。我遇到过 Spring AI 某个版本跟某个模型平台的 API 协议对不上工具调用一直返回格式错误折腾了半天才发现是版本问题。建议锁定版本后先跑通官方的最小示例再往项目里集成。API Key 的管理。千万别硬编码在代码里也别直接写在 application.yml 里提交到仓库。用环境变量或者配置中心。我见过有团队把 key 提交到公开仓库第二天就被刷爆了。另外要设置好配额告警模型调用是按 token 计费的一个死循环的 Agent 一晚上能烧掉不少钱。超时和重试配置。模型调用是网络请求超时是常态。默认超时往往太长建议连接超时设 5 秒读取超时设 30 到 60 秒取决于任务复杂度。重试策略要谨慎因为模型调用不是幂等的盲目重试可能重复扣费或产生重复的工具调用。我的做法是只对网络层面的错误重试业务层面的错误不重试。3.2 用 Spring AI 搭一个最小可用的 Agent假设你用的是 Spring Boot 3.x引入 Spring AI 的 starter 之后一个带工具调用的 Agent 大概长这样Configuration public class AgentConfig { Bean public ChatClient chatClient(ChatClient.Builder builder, OrderTools orderTools, LogisticsTools logisticsTools) { return builder .defaultSystem(你是一个电商客服助手负责处理订单和物流相关问题。 回答要简洁准确不确定的信息要主动调用工具查询。) .defaultTools(orderTools, logisticsTools) .build(); } }工具类的定义用注解声明Component public class OrderTools { Tool(description 根据订单号查询订单详情包括状态、金额、下单时间) public OrderDetail queryOrder(ToolParam(description 订单号) String orderId) { // 实际的查询逻辑 return orderService.getById(orderId); } Tool(description 判断订单是否在退货窗口期内) public boolean isInReturnWindow(ToolParam(description 订单号) String orderId) { OrderDetail order orderService.getById(orderId); return Duration.between(order.getCreateTime(), LocalDateTime.now()) .toDays() 7; } }调用的时候RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient chatClient) { this.chatClient chatClient; } PostMapping(/chat) public String chat(RequestBody ChatRequest request) { return chatClient.prompt() .user(request.getMessage()) .call() .content(); } }就这么点代码一个能查订单、能判断退货窗口的 Agent 就跑起来了。框架会自动处理工具调用的循环——模型决定调用哪个工具框架执行把结果喂回去直到模型给出最终答案。这就是我说的Java 生态已经能打了大量样板代码都被封装掉了。3.3 工具设计的三条实战原则工具是 Agent 的手脚设计得好不好直接决定 Agent 的能力上限。我总结了三条原则。原则一工具粒度要适中。太粗比如一个处理订单的工具包揽所有订单操作模型很难准确调用太细比如把查询订单拆成查询订单状态查询订单金额查询订单时间三个工具模型要调三次浪费 token 还容易出错。我的经验是按业务动作划分一个工具对应一个完整的业务操作。原则二描述要写给模型看不是写给人看。Tool的 description 是给模型判断何时调用用的要写清楚什么场景下用这个工具参数是什么含义。我见过有人写查询订单太模糊了模型不知道什么时候该用。写成根据订单号查询订单的完整信息适用于用户询问订单状态、金额、下单时间等场景模型判断准确率明显提升。原则三返回值要控制大小。工具返回的内容会进入上下文返回一个包含几十个字段的完整订单对象既浪费 token 又可能干扰模型判断。只返回模型需要的信息或者对返回内容做摘要。我一般会把返回控制在几百个 token 以内。4. 让 Agent 扛住并发那些压测时才暴露的问题4.1 有状态和无状态第一个要做的架构决策Agent 跟普通接口最大的不同是它通常是有状态的——多轮对话需要记住上下文。这就带来一个经典问题状态放哪最简单的做法是放内存用一个 Map 存 sessionId 到对话历史的映射。单机跑没问题一上多实例就完蛋用户第一次请求打到 A 机器第二次打到 B 机器上下文就丢了。我早期就踩过这个坑测试环境单机跑得好好的一上生产多副本就出现Agent 失忆。正确做法是把会话状态外置。用 Redis 存对话历史是最常见的方案key 用 sessionIdvalue 存消息列表设置合理的过期时间。这样任何一台机器都能拿到完整的上下文。但要注意序列化问题消息对象要能正确序列化和反序列化特别是包含工具调用信息的那种复杂结构。还有一种思路是无状态设计每次请求把完整上下文带上来。适合那种不需要长期记忆的场景比如一次性的任务处理。好处是水平扩展毫无压力坏处是客户端要维护上下文且请求体可能很大。4.2 模型调用的并发瓶颈与应对Agent 的响应时间主要花在模型调用上一次调用几秒到几十秒很正常。如果你的 Agent 一个请求要循环 5 次那就是几十秒的响应时间。并发一上来线程池瞬间被打满。我实测下来几个有效的应对手段第一异步化。Spring AI 和 LangChain4j 都支持流式响应和异步调用。对于不需要即时返回的场景把 Agent 调用放到异步线程池里接口立即返回一个任务 ID客户端轮询结果。这样能大幅提升吞吐量。第二连接池调优。模型调用的 HTTP 客户端连接池要单独配置不能跟业务接口共用。默认的连接数往往不够需要根据并发量调整。但也不能无限大模型平台通常有 QPS 限制连接开太多反而会触发限流。第三分级降级。当系统压力大时把复杂的 Agent 循环降级成单次问答牺牲一些能力换取响应速度。这个策略要提前设计好开关压力来了能一键切换。第四缓存。很多 Agent 请求是重复的比如退货政策是什么这种问题。对这类请求做结果缓存能省下大量模型调用。但要注意涉及实时数据的请求不能缓存。4.3 一个真实的并发事故复盘去年我们上线一个内部知识问答 Agent上线第二天就出事了。现象是响应越来越慢最后整个服务卡死。排查下来是这么回事Agent 的对话历史存在 Redis 里每次请求都要读取完整历史。有个用户开了个超长会话历史消息累积到几百条每次请求都要把这几百条消息序列化后发给模型。这个请求特别慢占着线程不放慢慢把线程池耗尽了。修复方案有三层一是给对话历史设置长度上限超过就做摘要压缩只保留最近几轮和关键信息二是给单个请求设置总超时超时就中断不能让一个请求无限期占用线程三是把模型调用和业务逻辑的线程池隔离模型调用慢不能拖垮整个服务。这个事故给我的教训是Agent 的状态是个隐形炸弹一定要在设计阶段就想清楚它的生命周期和大小边界。5. RAG 增强让 Agent 用上你的私有知识5.1 为什么 Agent 需要 RAG模型的知识有截止日期也不知道你公司的内部文档。用户问我们产品的退款政策是什么模型要么瞎编要么说不知道。RAG检索增强生成就是解决这个问题的——先从你的知识库里检索相关内容把检索结果作为上下文一起发给模型让模型基于这些真实内容回答。Agent 和 RAG 是绝配。Agent 可以在需要的时候主动触发检索而不是每次都把所有知识塞进上下文。比如用户问订单问题Agent 调用订单工具用户问政策问题Agent 调用知识检索工具。按需检索既准确又省 token。5.2 检索质量决定 RAG 的上限RAG 的效果七分靠检索三分靠生成。检索不准模型再强也白搭。我踩过的坑主要在这几个地方分块策略。文档不能整篇塞进去要切成小块。切太大检索出来的内容包含太多无关信息切太小可能把完整的意思切断了。我的经验是按语义切分而不是按固定字数。比如按段落切或者用模型辅助判断语义边界。块大小控制在 300 到 500 字比较合适块之间留一点重叠避免边界信息丢失。多路召回。单一检索方式容易漏。LangChain4j 支持多路召回可以同时用向量检索和关键词检索然后把结果融合排序。向量检索擅长语义相似关键词检索擅长精确匹配两者互补。我实测下来多路召回比单路召回的准确率能提升 20% 以上。重排序。初步召回的结果往往不够精准可以用一个重排序模型对结果重新打分排序把最相关的排到前面。这一步会增加一点延迟但对准确率提升明显值得做。5.3 把 RAG 接进 Agent 的两种方式一种是把 RAG 做成一个工具Agent 需要的时候调用。好处是灵活Agent 自己判断什么时候该检索。坏处是依赖模型的判断有时候它该检索却不检索。另一种是在 Agent 处理前先做一次检索把结果作为背景知识注入。好处是保证每次都有知识支撑坏处是可能检索出无关内容干扰模型。我的做法是两者结合常规问题走预检索复杂问题让 Agent 自己决定是否调用检索工具。同时给 Agent 的 system prompt 里明确写涉及公司政策、产品信息的问题必须先调用知识检索工具用提示词引导它的行为。6. 从 Demo 到生产上线前必须补的课6.1 可观测性看不见的 Agent 最危险Agent 是个黑盒模型为什么这么决策你很难完全搞清楚。所以可观测性特别重要。至少要记录这几样东西每次请求的完整对话历史、每次模型调用的输入输出和耗时、每次工具调用的参数和结果、整个请求的总耗时和 token 消耗。这些数据一方面用于排查问题另一方面用于成本核算。我见过有团队上线一个月才发现某个 Agent 的 token 消耗是预期的十倍就是因为没做监控。建议把 Agent 的执行链路用 traceId 串起来跟现有的链路追踪系统打通这样出问题能快速定位。6.2 成本控制别让 Agent 变成吞金兽模型调用是按 token 计费的Agent 的循环特性让它特别费 token。控制成本的手段有几个设置单次请求的 token 上限超过就中断对简单问题走轻量模型复杂问题才用大模型缓存高频问题的答案定期分析 token 消耗找出异常请求。我还会给每个 Agent 设置日消耗上限超过就告警甚至限流。这不是抠门是保证系统可持续。一个失控的 Agent 一晚上烧掉的钱可能比你一个月预算还多。6.3 安全与权限Agent 能做的事要有边界Agent 能调用工具就意味着它能执行实际操作。如果工具里有删除订单修改用户信息这种敏感操作一定要做好权限校验。不能让模型随便就能触发这些操作。我的做法是敏感工具调用前必须经过人工确认或者设置严格的白名单。另外工具的参数要做校验防止模型被诱导传入恶意参数。还有 prompt 注入的问题用户可能在输入里藏指令试图让 Agent 执行非预期操作这个要在输入层做过滤和检测。7. 我踩过的几个典型坑和应对思路7.1 工具调用死循环现象是 Agent 反复调用同一个工具停不下来。原因通常是工具返回的结果模型无法理解或者模型陷入了某种循环推理。应对方法是设置最大迭代次数同时在 prompt 里明确告诉模型如果连续两次调用同一工具得到相同结果应该停止并告知用户。7.2 上下文超长导致调用失败对话轮次多了上下文越来越长最后超过模型的上下文窗口限制调用直接报错。应对方法是做上下文压缩把早期的对话用模型总结成摘要只保留最近几轮原文。或者用滑动窗口只保留最近 N 轮。7.3 模型返回格式不稳定让模型输出 JSON它有时候会多输出一段解释文字导致解析失败。应对方法是用框架提供的结构化输出能力或者在 prompt 里严格约束格式同时代码里做容错解析解析失败时尝试提取 JSON 部分。7.4 工具执行超时拖垮整个请求某个工具调用外部接口特别慢把整个 Agent 请求卡住。应对方法是给每个工具调用设置独立超时超时后把超时信息作为观察结果返回给模型让它决定是重试还是换方案。8. 给转型路上的 Java 工程师的几句实在话如果你现在还在观望我的建议是别等了直接动手搭一个。不用追求完美先跑通一个最简单的 Agent哪怕只是查天气、算数学题。跑通之后你会有很多具体的疑问带着疑问去学比看十篇原理文章都管用。学习路径上我的建议是先理解 ReAct 的循环逻辑这是 Agent 的骨架然后熟悉一个框架Spring AI 或 LangChain4j 选一个把工具调用和 RAG 跑通最后补工程化的东西并发、监控、成本、安全。前面两块一两周能上手后面那块才是真正拉开差距的地方也是你作为 Java 工程师的优势所在。别被那些AI 要取代程序员的论调吓到。至少目前来看Agent 落地最缺的就是能把系统做稳的工程师。模型能力在快速进步但把模型能力变成可靠产品的工程能力进步得慢得多。这个时间差就是你的机会。最后分享一个我自己的习惯每做一个 Agent我都会建一个失败案例库把模型判断错误、工具调用异常、用户反馈不好的案例都记下来。定期回顾这些案例比看任何教程都能提升你对 Agent 行为的理解。这个习惯坚持了半年现在设计新 Agent 的时候很多坑在动手前就能预判到。