
1. 为什么 Java 工程师转 AI Agent 有天然优势很多 Java 工程师一提到 AI Agent第一反应是“这是 Python 的天下我是不是得从头学一门语言”。我一开始也这么想直到真正把 LangChain4j 和 Spring AI 跑通之后才发现Java 工程师转型 AI Agent 不但不吃亏反而在某些环节上有明显优势。原因很简单Agent 的本质不是训练模型而是编排模型。编排这件事恰恰是 Java 工程师干了十几年的老本行。1.1 Agent 的本质是“编排”不是“炼丹”先把概念理清楚。大模型本身只是一个输入输出函数你给它一段文本它返回一段文本。它不会自己查数据库、不会自己调接口、不会自己记住上一轮对话。所谓 AI Agent就是在模型外面套一层“大脑 手脚”的结构大脑负责推理决策手脚负责执行工具中间还需要记忆和状态管理。这套结构和我们熟悉的后端架构几乎一一对应Agent 概念Java 后端对应概念说明LLM 推理业务规则引擎负责决策但输出不确定Tool / Function CallingRPC 接口调用模型决定调哪个方法MemorySession / 缓存保存上下文状态Prompt Template配置模板参数化拼装请求Agent Executor工作流引擎控制多步执行循环Guardrail参数校验 / 拦截器过滤非法输入输出你看除了“模型输出不确定”这一条是新东西其余全是我们熟悉的老朋友。Java 工程师真正需要补的是理解模型的“不确定性”以及如何用工程手段去约束它而不是重新学一套编程范式。1.2 Java 生态在 Agent 落地阶段的三个硬优势第一个优势是工程化能力。Python 写 Demo 快但真要把 Agent 部署到生产环境面对并发、超时、重试、熔断、可观测性这些问题时Java 的 Spring 生态几乎是现成的。一个 Agent 服务要扛住几百 QPSPython 那边可能要折腾半天异步和 GILJava 这边线程池加虚拟线程直接上。第二个优势是类型安全。LangChain4j 和 Spring AI 都支持把工具方法定义成强类型接口参数用 record 或 POJO 描述模型返回的 JSON 直接反序列化成对象。这在 Python 里要靠 Pydantic 手动校验Java 里编译期就能发现一堆问题。第三个优势是存量系统集成。大部分企业的核心业务系统是 Java 写的Agent 要真正“下地干活”就得调用这些系统。用 Java 写 Agent直接复用现有的 Service、Mapper、Feign Client不用跨语言再搭一层网关。提示如果你现在还在纠结“要不要转 Python”我的建议是先把 LangChain4j 或 Spring AI 跑通一个完整 Demo你会发现 80% 的工作量和你写普通 Spring Boot 服务没有区别。1.3 转型路径的现实判断我不建议一上来就啃 Transformer 原理和反向传播。对于绝大多数应用层 Java 工程师来说你需要掌握的是Prompt 工程、Function Calling 机制、ReAct 循环、RAG 检索增强、以及 Agent 的可观测性。这些内容的学习曲线远比想象中平缓一两周就能上手写可用的东西。真正难的不是技术而是思维方式的转变从“我写死逻辑”变成“我描述意图让模型决定怎么走”。这个转变需要你接受“模型可能不按你想的来”然后学会用约束、校验、重试去兜底。2. LangChain4j 与 Spring AI 的选型对比与核心抽象选框架这件事我的态度是先看你的项目底座。如果本来就是 Spring Boot 项目Spring AI 的集成成本最低如果你想要更灵活的 Agent 编排能力LangChain4j 的抽象更完整。两者不是非此即彼实际项目里混用也很常见。2.1 两个框架的定位差异LangChain4j 的定位更接近“Java 版的 LangChain”它把 Agent、Chain、Memory、Retriever、Tool 这些概念都做了独立抽象适合需要精细控制执行流程的场景。它的AiServices接口代理机制非常优雅你定义一个接口加几个注解框架自动帮你生成实现。Spring AI 的定位是“Spring 生态的 AI 抽象层”它更强调和 Spring Boot 的无缝集成ChatClient的流式 API 写起来很顺手配置走application.yml和现有 Spring 项目风格一致。它的 Tool Calling 支持也比较完善Tool注解直接标注方法即可。对比维度LangChain4jSpring AI核心抽象AiServices / Chain / AgentChatClient / Advisor工具定义Tool 接口代理Tool 方法注册记忆管理ChatMemory 多种实现ChatMemory AdvisorRAG 支持内置 EmbeddingStore 体系VectorStore 抽象配置方式代码为主yml 代码适合场景复杂 Agent 编排Spring 项目快速集成2.2 核心抽象AiServices 与 ChatClientLangChain4j 的AiServices是我最喜欢的设计。你只需要这样定义一个接口interface Assistant { SystemMessage(你是一个订单查询助手只能查询订单状态) String chat(UserMessage String userMessage); }然后AiServices.builder(Assistant.class).chatLanguageModel(model).build()就能得到一个可用的实例。框架在背后帮你处理了 Prompt 拼装、消息历史、工具调用循环。这种“声明式”的写法对 Java 工程师来说非常亲切就像当年用 MyBatis 的 Mapper 接口一样。Spring AI 的ChatClient则是流式 API 风格String answer chatClient.prompt() .system(你是一个订单查询助手) .user(userMessage) .call() .content();两种风格各有拥趸。我个人在需要复杂多步 Agent 时用 LangChain4j在写简单的对话接口时用 Spring AI因为它的流式返回和 Spring WebFlux 配合得很好。2.3 工具调用的实现细节工具调用是 Agent 的“手脚”这块两个框架都做得不错但细节有差异。LangChain4j 里你定义一个带Tool注解的方法框架会自动生成 JSON Schema 描述给模型class OrderTools { Tool(根据订单号查询订单状态) String queryOrderStatus(P(订单号) String orderId) { return orderService.getStatus(orderId); } }Spring AI 类似但需要把工具类注册到 ChatClient 上。这里有个坑我要提前说工具方法的参数描述一定要写清楚模型完全靠这段描述来决定传什么值。我见过太多人参数名写成arg1、arg2结果模型乱传参数排查半天才发现是描述没写明白。注意工具方法的返回值尽量用结构化字符串或 JSON不要返回一大坨自然语言。模型解析结构化数据的能力远强于解析散文。3. ReAct 模式在 Java 里的落地从思考到行动ReActReasoning Acting是 Agent 最核心的执行模式说白了就是“想一步、做一步、看结果、再想下一步”。这个循环听起来简单但真正落地时有大量细节要处理。3.1 ReAct 循环的完整拆解一个标准的 ReAct 循环包含四个阶段Thought思考、Action行动、Observation观察、Repeat重复。模型先输出一段思考决定调用哪个工具框架执行工具把结果作为观察喂回模型模型基于新信息继续思考直到认为可以给出最终答案。在 LangChain4j 里这个循环是框架自动处理的你不需要手写 while 循环。但理解它的内部机制很重要因为出问题时你需要知道卡在哪一步。核心逻辑大致是while (!finished steps maxSteps) { AiMessage response model.generate(messages); if (response.hasToolExecutionRequests()) { for (ToolExecutionRequest req : response.toolExecutionRequests()) { String result executeTool(req); messages.add(ToolExecutionResultMessage.from(req, result)); } } else { finished true; } steps; }这里最关键的是maxSteps这个参数。如果不设上限模型可能陷入死循环反复调用同一个工具。我一般设 5 到 10 步超过就强制中断并返回“无法完成”。3.2 工具描述怎么写模型才不跑偏工具描述是 ReAct 能否跑通的关键。我总结了三条经验第一描述要写“什么时候用”而不只是“是什么”。比如“查询订单状态”不如“当用户询问订单物流、发货、签收情况时用此工具查询订单当前状态”。模型需要的是使用场景的提示。第二参数描述要给出格式示例。订单号是纯数字还是带前缀日期格式是yyyy-MM-dd还是时间戳这些都要写清楚否则模型会猜猜错就报错。第三工具数量不要太多。一次给模型超过 10 个工具它的选择准确率会明显下降。如果业务复杂用“工具分组 路由”的方式先让模型选类别再给具体工具。3.3 处理模型“不听话”的三种兜底策略模型不按预期调用工具是常态我一般用三层兜底格式校验层工具执行前校验参数格式不合法直接返回错误信息给模型让它重新生成。重试层工具执行失败时把异常信息作为 Observation 返回模型通常会调整参数重试。降级层连续失败超过阈值直接返回预设的兜底话术避免用户等待过久。这三层配合下来实际生产环境里 Agent 的成功率能稳定在 95% 以上。剩下 5% 大多是模型对模糊意图的理解偏差需要靠 Prompt 优化和用户引导来解决。4. 并发场景下 Agent 服务的稳定性设计“AI Agent 怎么扛并发”是最近被问得最多的问题。我的答案是Agent 服务的并发瓶颈通常不在模型推理而在你自己的工具调用和状态管理。把这两块处理好扛并发并不难。4.1 模型调用的超时与限流模型 API 调用是典型的慢操作一次响应可能几秒到几十秒。如果不设超时线程会被长时间占用。我的做法是给模型调用设三层超时连接超时 5 秒、读取超时 30 秒、整体超时 60 秒。超过就中断并返回降级结果。限流方面用 Resilience4j 的RateLimiter或Bulkhead控制并发调用数。模型服务商通常有 QPS 限制超过会被拒绝与其被动挨打不如主动限流。Bean public ChatLanguageModel chatModel() { return OpenAiChatModel.builder() .timeout(Duration.ofSeconds(30)) .maxRetries(2) .build(); }4.2 会话状态的隔离与清理每个用户的对话历史必须隔离否则会出现“张三的问题被李四的上下文污染”这种严重事故。LangChain4j 的ChatMemory用memoryId区分会话Spring AI 用conversationId。关键是这个 ID 要跟用户会话绑定并且设置合理的过期时间。我一般用 Redis 存对话历史TTL 设 30 分钟。太短用户觉得“失忆”太长内存扛不住。另外要注意历史消息不能无限增长超过一定条数要做摘要压缩否则 Prompt 会越来越长成本和延迟都上去了。4.3 工具调用的幂等与熔断Agent 调用的工具往往是写操作比如下单、发消息。模型可能因为重试而重复调用同一个工具所以工具方法必须幂等。我的做法是在工具层加一个基于请求 ID 的去重表重复请求直接返回上次结果。熔断方面如果某个下游服务挂了不能让 Agent 一直重试拖垮整个链路。用 Resilience4j 的CircuitBreaker包一层失败率超过阈值就快速失败返回“服务暂时不可用”给模型模型通常会选择其他路径或告知用户。提示Agent 服务的线程池要和普通业务线程池隔离避免 Agent 的慢调用把核心业务线程占满。这是血泪教训。5. RAG 与多路召回让 Agent 用上你的私有知识Agent 光会调工具还不够很多场景下它需要“知道”一些私有知识比如公司产品文档、历史工单、内部规范。这就是 RAG检索增强生成要解决的问题。5.1 从文档到向量的完整链路RAG 的第一步是把文档切块并向量化。切块大小很关键太大检索不精准太小语义不完整。我一般用 500 到 800 字符一块重叠 100 字符。LangChain4j 提供了DocumentSplitterSpring AI 有TokenTextSplitter。向量化之后存到向量库Milvus、PgVector、Redis 都可以。选型看你的存量设施如果已经有 PostgreSQL直接上 PgVector 最省事。5.2 多路召回为什么比单路强单一向量检索有个问题它对“关键词精确匹配”不敏感。用户问“订单号 A12345 的状态”向量检索可能召回一堆泛泛的订单说明却漏掉精确匹配的那条。多路召回就是同时跑向量检索和关键词检索BM25然后合并结果。LangChain4j 支持EmbeddingStoreContentRetriever和自定义ContentRetriever组合。我的做法是向量召回 Top 10关键词召回 Top 10用 RRF倒数排名融合算法合并取 Top 5 喂给模型。实测下来召回准确率比单路提升 20% 以上。5.3 检索结果如何影响 Agent 决策检索到的内容不是直接塞给用户而是作为上下文注入 Prompt。这里要注意两点一是要标注来源让模型知道哪些是检索内容、哪些是用户问题二是要控制注入量太多会稀释关键信息太少又不够用。我一般把检索结果格式化成带编号的片段Prompt 里写“以下是相关知识片段请基于这些内容回答不要编造”。这样模型会优先使用检索内容减少幻觉。6. 从 Demo 到生产我踩过的那些坑前面讲的都是“应该怎么做”这一节讲讲“实际会怎么翻车”。这些坑都是我一个个踩过来的希望能帮你省点时间。6.1 模型返回 JSON 解析失败的排查链路最常见的坑是模型返回的 JSON 格式不对导致反序列化失败。排查链路是这样的先打印原始返回内容看是不是被 Markdown 代码块包裹了模型很喜欢加 json再看是不是有尾随逗号或注释最后看字段名是不是和你的 POJO 对不上。解决办法有三层一是 Prompt 里明确要求“只返回 JSON不要任何其他文字”二是用JsonOutputParser做容错解析三是加一个修复步骤解析失败时把错误信息发回模型让它重新生成。6.2 工具调用死循环的真实案例我遇到过一次 Agent 反复调用同一个查询工具连续调了 20 多次。原因是工具返回“未找到订单”模型理解为“可能查错了”于是换个参数再查一直查不到就一直查。解决办法是给工具加“连续失败计数”同一个工具连续失败 3 次就返回一个明确的终止信号比如“该订单确实不存在请告知用户”。模型看到这个信号就会停止重试。另外maxSteps一定要设这是最后一道防线。6.3 流式输出与工具调用的冲突流式输出SSE和工具调用天然有冲突工具调用需要模型先输出完整的工具请求而流式输出是边生成边推送。如果处理不好用户会看到“我要调用工具了”这种中间状态被推送到前端。我的做法是在工具调用阶段不推送内容只推送“正在处理”的状态提示等最终答案生成时再开启流式。LangChain4j 和 Spring AI 都支持这种分段处理需要你在StreamingChatResponseHandler里判断当前是工具调用还是最终回答。6.4 成本失控的预防措施Agent 的 Token 消耗比普通对话高得多因为每轮 ReAct 循环都要把完整历史发一遍。如果不控制成本会指数级增长。我的预防措施有三条一是限制历史消息条数超过就做摘要二是限制工具返回内容的长度超长内容截断三是设置单次会话的 Token 上限超过就强制结束。另外不同任务用不同模型。简单意图识别用小模型复杂推理用大模型这样能省不少钱。7. 一个可复现的最小 Agent 项目骨架讲了这么多原理最后给一个可以直接跑起来的最小骨架。这个骨架用 Spring Boot LangChain4j包含对话、工具调用、记忆三个核心能力。7.1 依赖与配置dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai-spring-boot-starter/artifactId version0.35.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-spring-boot-starter/artifactId version0.35.0/version /dependency配置文件里填模型地址和密钥注意密钥走环境变量不要硬编码。7.2 定义工具与助手接口Component public class OrderTools { Tool(当用户询问订单状态、物流、发货情况时调用) public String queryOrder(P(订单号纯数字) String orderId) { // 实际业务查询 return orderService.query(orderId); } } interface OrderAssistant { SystemMessage(你是订单助手只能处理订单相关问题其他问题礼貌拒绝) String chat(MemoryId String sessionId, UserMessage String message); }7.3 装配与测试Bean public OrderAssistant orderAssistant(ChatLanguageModel model, OrderTools tools) { return AiServices.builder(OrderAssistant.class) .chatLanguageModel(model) .tools(tools) .chatMemoryProvider(id - MessageWindowChatMemory.withMaxMessages(10)) .build(); }跑起来之后用 Postman 发一句“帮我查下订单 12345 的状态”观察日志里的工具调用过程。第一次跑通这个循环你就理解了 Agent 的核心机制。7.4 后续可以扩展的方向这个骨架跑通后可以逐步加上 RAG 检索、多 Agent 协作、可观测性埋点。我的建议是不要一次加太多每加一个能力就压测一次确保稳定性。Agent 系统的复杂度是叠加的一次改太多出问题很难定位。我个人在实际项目中的体会是Agent 落地最难的不是技术选型而是把“不确定的模型输出”纳入“确定的工程流程”。Java 工程师在这方面其实有天然优势因为我们习惯了用类型、校验、重试、熔断这些手段去驯服不确定性。把模型当成一个“不太靠谱但很聪明的下游服务”来对待很多问题就迎刃而解了。