ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java工程师转型AI Agent开发:LangChain4j与Spring AI实战指南

Java工程师转型AI Agent开发:LangChain4j与Spring AI实战指南 1. 从写业务代码到指挥智能体Java 工程师的思维切换做了五六年 Java 后端的人第一次接触 AI Agent 这个概念时最容易犯的错就是把它当成一个高级一点的 HTTP 接口来用。我当初也是这么想的——不就是发个请求、拿个返回、解析一下 JSON 吗结果真动手搭第一个 Agent 的时候才发现完全不是一回事。传统 Java 后端的核心逻辑是确定性的你写一个orderService.createOrder()输入参数合法走完事务输出就是可预期的。但 AI Agent 的核心是不确定性下的决策循环——它要根据当前上下文判断下一步该调哪个工具、该不该继续、什么时候停下来。这个思维落差比从 Spring MVC 转到响应式编程还要大。这篇内容我想讲清楚三件事Java 工程师转型 AI Agent 开发底层要补哪些原理、工程上怎么用 LangChain4j 和 Spring AI 落地、ReAct 这类决策框架在 Java 里到底怎么跑起来。适合有 Java 基础、想切入 AI Agent 方向但不知道从哪下手的后端同学也适合已经在用 Python 写 Agent、想看看 Java 生态能不能扛住生产流量的架构师。先说结论Java 做 AI Agent 不是能不能的问题而是什么场景该用的问题。高并发、强事务、企业级集成这些场景Java 生态反而比 Python 更有优势。但前提是你得先把 Agent 的运行机制吃透不然写出来的东西就是个套壳聊天框。2. Agent 不是聊天框先搞懂它和普通 LLM 调用的本质区别2.1 一次 LLM 调用和一次 Agent 运行差在哪很多人对 AI Agent 的理解停留在能调用工具的 ChatGPT。这个理解不算错但太浅了。我用一个对比表把两者的差异摊开讲维度普通 LLM 调用AI Agent 运行交互轮次单轮或固定多轮动态循环轮次由模型决定控制流代码控制模型自主决策工具使用无或硬编码动态选择、动态传参终止条件代码判断模型判断 代码兜底状态管理无状态为主需要维护执行轨迹失败处理异常捕获需要重试、反思、回退策略关键差异在控制流的归属。普通调用里代码是导演模型是演员Agent 里模型是导演兼演员代码退居成舞台监督——负责提供工具、限制边界、兜底异常。这个转变带来的直接后果是你不能再假设输入 A 必然得到 B。Agent 可能调了三次工具才给出答案也可能一次都不调直接回复甚至可能陷入循环。所以 Java 工程师转型时第一件要建立的能力是为不确定性设计系统。2.2 ReAct 框架Agent 决策循环的最小内核ReActReasoning Acting是目前绝大多数 Agent 框架的底层范式LangChain4j 和 Spring AI 的实现都绕不开它。它的核心循环就三步Thought思考模型根据当前上下文推理下一步该做什么Action行动选择一个工具并生成调用参数Observation观察执行工具把结果喂回上下文然后重复直到模型认为可以给出最终答案Final Answer。用 Java 伪代码表达这个循环大概是这样while (!finished stepCount maxSteps) { // 1. 把历史轨迹 用户问题拼成 prompt String prompt buildReActPrompt(history, userQuery, tools); // 2. 调用 LLM拿到 Thought Action LlmResponse response llmClient.chat(prompt); // 3. 解析出工具名和参数 ToolCall call parseToolCall(response); if (call.isFinalAnswer()) { return call.getAnswer(); } // 4. 执行工具拿到 Observation String observation toolExecutor.execute(call); // 5. 把这一轮追加到历史 history.add(new Step(response.getThought(), call, observation)); stepCount; }看起来简单但魔鬼全在细节里。parseToolCall怎么保证模型输出的格式一定可解析maxSteps设多少合适工具执行抛异常了怎么办这些才是 Java 工程师真正要花时间的地方。2.3 为什么 Java 工程师反而有优势我见过不少 Python 转过来的 Agent 开发者写 demo 飞快但一到生产环境就抓瞎——并发上不去、状态管理混乱、工具调用没有事务边界。这些恰恰是 Java 工程师的强项。Agent 落地到企业场景本质是一个分布式决策系统要处理并发请求、要管理会话状态、要保证工具调用的幂等性、要做限流降级。这些问题的解法Java 后端已经沉淀了十几年。你缺的不是工程能力而是对 LLM 特性和 Agent 范式的理解。补上这块转型速度会比想象中快。3. LangChain4j 与 Spring AI两条技术路线的选型逻辑3.1 两个框架的定位差异Java 生态里做 AI Agent目前主流就两条路LangChain4j和Spring AI。很多人纠结选哪个其实先要看清它们的出身。LangChain4j 是 LangChain 理念在 Java 的移植设计上更贴近Agent 编排框架工具调用、记忆管理、RAG 这些能力开箱即用抽象层次高。Spring AI 则是 Spring 官方出品走的是Spring 生态原生集成路线把 AI 能力当成一个个ChatClient、EmbeddingModel这样的 Bean 来管理和 Spring Boot 的自动配置、依赖注入无缝衔接。我个人的判断标准很简单项目已经是 Spring Boot 体系团队熟悉 Spring 那套选Spring AI学习成本最低需要复杂的 Agent 编排、多工具协作、自定义决策逻辑选LangChain4j抽象更灵活两者也可以混用Spring AI 管模型接入LangChain4j 管 Agent 逻辑但要注意版本兼容3.2 用 LangChain4j 搭一个最小 ReAct Agent先看 LangChain4j 的写法。它的AiServices机制可以把一个 Java 接口自动实现成 Agentinterface CustomerServiceAgent { SystemMessage(你是一个电商客服助手可以查询订单、处理退款) String handle(String userMessage); } // 定义工具 class OrderTools { Tool(根据订单号查询订单状态) String queryOrder(P(订单号) String orderId) { return orderRepository.findById(orderId).toString(); } Tool(为用户发起退款) String refund(P(订单号) String orderId, P(金额) BigDecimal amount) { return refundService.process(orderId, amount); } } // 组装 ChatLanguageModel model OpenAiChatModel.builder() .apiKey(System.getenv(API_KEY)) .modelName(gpt-4o) .build(); CustomerServiceAgent agent AiServices.builder(CustomerServiceAgent.class) .chatLanguageModel(model) .tools(new OrderTools()) .chatMemory(MessageWindowChatMemory.withMaxMessages(20)) .build(); String result agent.handle(帮我查一下订单 12345 的状态);这段代码背后LangChain4j 自动帮你做了 ReAct 循环把工具描述塞进 system prompt、解析模型的工具调用意图、执行工具、把结果回填、再调模型。你写的是接口框架跑的是循环。3.3 Spring AI 的接入方式与百炼 Qwen 配置Spring AI 的思路更Spring 化。以接入阿里云百炼的 Qwen 模型为例配置大概是这样spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-max temperature: 0.7然后注入ChatClient使用Service public class AgentService { private final ChatClient chatClient; public AgentService(ChatClient.Builder builder, OrderTools tools) { this.chatClient builder .defaultSystem(你是一个电商客服助手) .defaultTools(tools) .build(); } public String handle(String message) { return chatClient.prompt() .user(message) .call() .content(); } }Spring AI 2.0 之后对工具调用的支持成熟了很多defaultTools会自动把Tool注解的方法注册进去。但要注意Spring AI 的 Agent 循环控制相对隐式如果你需要精细控制每一步的决策可能还是要自己写循环或者引入 LangChain4j 的编排能力。3.4 选型时容易忽略的三个坑第一个坑模型对工具调用的支持程度不一样。不是所有模型都原生支持 function calling。有些模型需要你手动在 prompt 里描述工具格式然后解析文本输出。LangChain4j 和 Spring AI 都做了适配层但适配质量参差不齐。选模型前一定要测它的工具调用稳定性。第二个坑流式输出和工具调用会打架。流式返回时模型可能边输出文字边决定调工具处理起来很麻烦。生产环境里涉及工具调用的场景我一般先关掉流式等 Agent 决策完成后再对最终答案做流式渲染。第三个坑框架版本迭代快API 不稳定。LangChain4j 和 Spring AI 都在快速演进半年前写的代码可能已经编译不过。建议锁定版本升级前先跑通测试用例。4. 让 Agent 真正干活工具调用、记忆与 RAG 的工程实现4.1 工具设计Agent 能力的天花板Agent 能干什么完全取决于你给它什么工具。工具设计有几个原则都是踩坑踩出来的工具粒度要适中。太细模型要调很多次才能完成一件事容易中途跑偏太粗参数复杂模型传参容易出错。我的经验是一个工具对应一个业务动作比如查订单退款改地址而不是执行 SQL这种底层操作。工具描述要像写给新人看的文档。模型选工具全靠描述。描述里要写清楚这个工具干什么、什么场景用、参数什么含义、返回什么格式。我见过有人工具描述就写个查询模型根本不知道查什么。参数校验必须在工具内部做。不要相信模型传的参数一定合法。金额可能是负数订单号可能是乱码。工具方法第一行就该做校验非法参数直接返回错误信息让模型重新决策。Tool(为用户发起退款金额必须大于0且不超过订单金额) public String refund(P(订单号) String orderId, P(退款金额) BigDecimal amount) { if (amount.compareTo(BigDecimal.ZERO) 0) { return 退款金额必须大于0请重新确认; } Order order orderRepository.findById(orderId); if (order null) { return 订单不存在请确认订单号; } if (amount.compareTo(order.getAmount()) 0) { return 退款金额不能超过订单金额 order.getAmount(); } return refundService.process(orderId, amount); }注意这里返回的是给模型看的自然语言不是抛异常。抛异常会中断 Agent 循环返回错误描述则让模型有机会自我纠正。4.2 记忆管理别让上下文无限膨胀Agent 的记忆分短期和长期。短期记忆就是当前会话的历史轨迹长期记忆是跨会话的用户偏好、历史事实。短期记忆最大的坑是上下文爆炸。ReAct 循环每轮都会往历史里追加 Thought、Action、Observation几轮下来 token 就爆了。LangChain4j 的MessageWindowChatMemory用滑动窗口控制条数但更精细的做法是按 token 数裁剪ChatMemory memory TokenWindowChatMemory.withMaxTokens(4000, new OpenAiTokenCountEstimator(gpt-4o));长期记忆一般用向量库存。用户说我上次买的那双鞋Agent 得能检索出历史订单。这块和 RAG 是同一套技术。4.3 RAG 在 Agent 里的正确打开方式RAG检索增强生成不是 Agent 的必需品但企业场景里几乎绕不开——你的 Agent 要回答公司内部知识模型本身不知道。LangChain4j 的 Easy RAG 把流程简化到了几行EmbeddingStoreTextSegment store new InMemoryEmbeddingStore(); EmbeddingStoreIngestor ingestor EmbeddingStoreIngestor.builder() .documentSplitter(DocumentSplitters.recursive(500, 50)) .embeddingModel(embeddingModel) .embeddingStore(store) .build(); ingestor.ingest(Document.fromFile(manual.pdf)); ContentRetriever retriever EmbeddingStoreContentRetriever.builder() .embeddingStore(store) .embeddingModel(embeddingModel) .maxResults(5) .minScore(0.7) .build();但生产环境里RAG 的坑比 Agent 本身还多。分块策略决定检索质量500 字一块配 50 字重叠是常见起点但技术文档和客服话术的最优分块完全不同。相似度阈值设太低会召回无关内容污染上下文设太高又可能漏掉关键信息。我的做法是先跑一批真实问题人工看召回结果再调阈值。还有一个容易被忽略的点RAG 检索本身也可以是一个工具。与其每次对话都强制检索不如把知识库检索做成一个工具让 Agent 自己判断什么时候需要查资料。这样既省 token又更符合 ReAct 的决策逻辑。5. 高并发场景下 Agent 的稳定性设计5.1 Agent 扛并发的瓶颈到底在哪AI Agent 怎么扛并发是最近被问得最多的问题。很多人以为是 Java 线程模型的问题其实瓶颈根本不在那。Agent 请求的耗时分布大概是LLM 调用占 80% 以上工具执行占 10%框架开销占不到 5%。也就是说一个 Agent 请求大部分时间在等模型返回。这时候你开多少线程都没用瓶颈在下游模型的 QPS 限制和响应延迟。所以扛并发的核心思路不是多线程,而是异步化 限流 缓存。5.2 用异步和虚拟线程提升吞吐Java 21 的虚拟线程在这里是神器。传统线程池处理阻塞的 LLM 调用线程会被占满虚拟线程可以在等待时释放载体线程同样硬件能扛的并发数提升一个量级。try (var executor Executors.newVirtualThreadPerTaskExecutor()) { ListFutureString futures requests.stream() .map(req - executor.submit(() - agent.handle(req))) .toList(); for (FutureString f : futures) { results.add(f.get(30, TimeUnit.SECONDS)); } }配合 Spring AI 或 LangChain4j 的异步 API返回CompletableFuture或Flux效果更好。但要注意虚拟线程不是银弹——如果你的工具调用里有synchronized块或者用了线程池隔离pin 住载体线程就没意义了。5.3 限流、降级与超时Agent 的保命三件套限流要分两层入口按用户限出口按模型限。模型侧一般有 QPS 配额超了直接报错。用 Resilience4j 的RateLimiter或 Sentinel 都能做。超时必须设。Agent 循环可能因为模型抽风陷入死循环每个 LLM 调用设 30 秒超时整个 Agent 运行设 60 秒上限超时直接返回兜底话术。降级策略要提前想好。模型服务挂了怎么办我的做法是准备一个规则引擎兜底——常见问题走关键词匹配复杂问题转人工。用户体验上宁可给个稍后再试也别让请求挂在那转圈。Bean public ChatClient chatClient(ChatClient.Builder builder) { return builder .defaultOptions(ChatOptions.builder() .timeout(Duration.ofSeconds(30)) .build()) .build(); }5.4 会话状态与幂等分布式下的 Agent 难题单机跑 Agent 很简单多实例部署就麻烦了。用户的会话历史存在哪如果存在本地内存负载均衡一转发上下文就丢了。标准解法是把会话状态外置到 Redispublic class RedisChatMemory implements ChatMemory { private final StringRedisTemplate redis; private final ObjectMapper mapper; Override public void add(String conversationId, ListChatMessage messages) { String key agent:memory: conversationId; redis.opsForList().rightPushAll(key, messages.stream().map(this::serialize).toList()); redis.expire(key, Duration.ofHours(2)); } }另一个坑是工具调用的幂等性。Agent 可能因为重试或循环把同一个退款请求发两次。所有有副作用的工具都要带幂等键——用conversationId stepIndex或者业务单号做去重。6. 从 Demo 到上线我踩过的那些坑6.1 模型输出格式不稳定解析器天天崩ReAct 循环依赖模型按格式输出 Thought/Action。但模型不是每次都听话有时候少个字段有时候多写一段解释解析器直接抛异常。我的解决方案是三层防御第一层prompt 里用 few-shot 示例强化格式第二层解析失败时用正则做宽松匹配第三层实在解析不了把原始输出当 Observation 喂回去让模型自己纠正。private ToolCall parseWithFallback(String raw) { try { return strictParse(raw); } catch (ParseException e) { ToolCall loose looseParse(raw); if (loose ! null) return loose; return ToolCall.retry(上次输出格式有误请严格按照 Thought/Action 格式重新输出); } }6.2 工具调用陷入死循环Agent 有时候会反复调同一个工具比如一直查订单查不到就一直查。maxSteps能兜底但体验很差。更好的做法是检测重复调用如果连续两轮调了同一个工具、传了同样的参数直接中断并返回提示。或者在 Observation 里加入引导语比如该订单已查询 3 次仍未找到建议告知用户订单号可能有误。6.3 成本失控token 烧得比想象中快Agent 的 token 消耗是普通对话的好几倍——每轮循环都要把完整历史重新发一遍。一个复杂任务跑 10 轮token 消耗可能是单次对话的 20 倍。控制成本的手段精简 system prompt工具描述能省则省、裁剪历史只保留最近 N 轮、缓存相同问题命中缓存直接返回。我们线上做过统计加一层语义缓存后token 成本降了将近 40%。6.4 测试怎么做Agent 的不确定性怎么测传统单元测试那套在 Agent 上基本失效——同样的输入模型可能给不同输出。我的测试策略分三层工具层正常单元测试输入输出确定解析层用录制的模型输出做回归测试保证解析逻辑稳定端到端用一批标准问题跑人工评估或用一个裁判模型打分关注通过率而非单次结果端到端测试不要追求 100% 通过Agent 场景下 85% 以上的任务完成率就算不错了。关键是建立基线每次改动后对比防止劣化。7. 转型路径Java 工程师该补什么、怎么补7.1 知识补齐的优先级如果你是有经验的 Java 后端转型 AI Agent 需要补的东西其实不多按优先级排LLM 基础认知token、上下文窗口、temperature、function calling 机制这些是常识一两天能搞明白Prompt 工程怎么写 system prompt、few-shot 示例、输出格式约束这个靠练Agent 范式ReAct、Plan-and-Execute、Reflection理解每种范式的适用场景框架实操LangChain4j 或 Spring AI 选一个深入跑通工具调用、记忆、RAG工程化并发、限流、可观测性、成本控制这块你本来就会不用去啃 Transformer 论文除非你要做模型微调。Agent 开发是应用层的事把模型当黑盒用就行。7.2 一个可落地的练手项目光看不动手等于没学。我建议从企业内部知识助手入手这个项目麻雀虽小五脏俱全用 Spring AI 接入模型百炼或本地部署的都行把公司文档灌进向量库做 RAG加两三个工具查员工信息、查制度、提交工单用 ReAct 让 Agent 自己决定查知识库还是调工具加上会话记忆、限流、日志这个项目做完Agent 开发的核心链路你就全通了。再往深走可以研究多 Agent 协作、工作流编排比如把 Dify 的工作流转成 Spring AI 代码。7.3 面试里会被问到的几个点现在 Java 岗位面试开始问 AI Agent 了高频问题集中在ReAct 循环的终止条件怎么设计工具调用失败怎么处理Agent 怎么扛并发这题考的是异步和限流不是线程池RAG 的召回质量怎么优化怎么控制 token 成本回答这类问题的关键是结合工程实践别背概念。面试官想听的是你踩过什么坑、怎么解决的而不是 ReAct 的定义。8. 一些个人体会转型这件事最大的障碍从来不是技术难度而是心态。我见过太多 Java 老手因为AI 是算法工程师的活这种想法迟迟不肯动手。实际上 Agent 开发 90% 是工程问题剩下 10% 的模型知识看几篇文档就够了。另一个体会是别追新。LangChain4j、Spring AI、各种 Agent 框架每周都在更新追是追不完的。把 ReAct 这个内核吃透把工具调用、记忆、RAG 这三件事做扎实框架换了也就是换个 API 的事。最后说个实际的Java 做 Agent 目前最大的短板是生态工具不如 Python 丰富很多新的 Agent 范式比如各种多智能体框架都是 Python 先出。但这恰恰是机会——把 Python 生态里验证过的模式用 Java 工程化地实现一遍本身就是很有价值的事。企业级场景要的从来不是最新而是最稳。
返回列表