Java工程师转型AI Agent:用LangChain4j与Spring AI构建生产级智能体

发布时间:2026/10/5 4:24:44
Java工程师转型AI Agent:用LangChain4j与Spring AI构建生产级智能体 1. 从写业务代码到编排智能体Java 工程师的转型窗口期做了五六年 Java 后端的人最近一两年大概都有同一种体感CRUD 写腻了微服务那套东西闭着眼都能搭面试题翻来覆去就是 JVM、并发、Spring 循环依赖。技术栈没退步但总觉得少了点增量。与此同时AI Agent 这个词开始频繁出现在需求评审、技术规划甚至老板的周报里。问题在于大部分 Java 工程师第一次接触 Agent是被 Python 生态劝退的——LangChain、LlamaIndex、各种 notebook 示例看着热闹但跟自己每天写的 Spring Boot 工程完全是两个世界。这篇东西就是写给这批人的。核心结论先摆出来Java 工程师转型 AI Agent不需要先变成 Python 工程师也不需要把 LangChain 那套概念从头啃一遍。你已有的工程能力——依赖注入、分层架构、事务、线程池、可观测性——恰恰是 Agent 从 demo 走向生产最缺的东西。真正要补的是三个认知Agent 的运行循环长什么样、工具调用怎么和 Java 方法对接、以及怎么把不确定的模型输出塞进确定的工程约束里。关键词里出现的 LangChain4j、Spring AI、ReAct基本就是这条路径上的三块拼图。LangChain4j 是 Java 版的编排框架Spring AI 是 Spring 官方下场做的抽象层ReAct 则是 Agent 最经典的思考-行动-观察循环范式。这三个东西组合起来能让一个熟悉 Spring 的工程师在几天内跑出可用的 Agent而不是几周。适合谁看有 Java 后端基础、写过 Spring Boot、想切入 Agent 方向但不知道从哪下手的工程师也适合已经在用 Python 做 Agent、但团队主技术栈是 Java、需要把方案落回 Java 体系的人。下面我会按原理讲透—框架选型—动手搭建—生产化的顺序展开中间穿插我自己踩过的坑和实测数据。2. ReAct 不是玄学把 Agent 的思考循环翻译成 Java 能懂的话2.1 为什么 Agent 的核心是一个 while 循环很多人第一次看 ReAct 论文会觉得抽象其实剥掉包装Agent 的本质就是一个带退出条件的循环。用 Java 的话说while (!task.isFinished() step maxSteps) { Thought thought llm.reason(context); // 思考现在该干什么 Action action thought.getAction(); // 行动调用哪个工具 Observation obs toolExecutor.run(action); // 观察工具返回了什么 context.append(thought, action, obs); // 把结果塞回上下文 }就这么简单。所谓 ReAct就是 Reasoning Acting 的缩写模型先输出一段推理Thought再决定调用哪个工具Action拿到工具结果Observation后继续下一轮推理。这个循环和你在业务代码里写的轮询状态机没有本质区别区别只在于决策者从 if-else 换成了大模型。理解这一点非常关键因为它直接决定了后面所有的工程决策。既然是循环就有循环次数上限、超时、异常重试、上下文膨胀这些问题——而这些恰恰是 Java 工程师的舒适区。Python 生态里很多 Agent 框架把这些当高级特性但在 Java 这边你会本能地觉得这不就是基本的健壮性设计吗。2.2 Thought、Action、Observation 在代码里到底是什么抽象概念落到代码其实就是三个字段。以 LangChain4j 为例一次完整的交互大致是这样组织的Thought模型返回的自然语言推理比如用户想知道订单状态我需要先拿到订单号再查数据库。这段文本本身不执行但它是下一轮决策的上下文。Action结构化的工具调用请求包含工具名和参数。在支持 function calling 的模型里这部分是模型直接输出的 JSON在不支持的模型里需要用提示词约束模型输出特定格式再解析。Observation工具执行后的返回值通常会被截断或摘要后再塞回上下文避免 token 爆炸。我实测下来最容易出问题的是 Action 的解析。模型有时候会自作主张输出一段解释再跟一个 JSON或者 JSON 里字段名拼错。所以永远不要相信模型输出的格式是干净的解析层必须做容错正则兜底、JSON 修复、字段校验一个都不能少。2.3 为什么 Java 工程师理解 ReAct 反而更快有个反直觉的现象我见过不少 Python 背景的人写 Agent卡在怎么让整个流程可观测、可回滚、可限流上而 Java 工程师上手第一反应就是这个循环要不要加熔断工具调用要不要走线程池上下文要不要做 LRU 淘汰。这不是巧合。ReAct 循环里的每一个环节都能在 Java 工程里找到对应物ReAct 环节Java 工程对应概念常见处理方式循环控制状态机 / 工作流引擎最大步数、超时中断工具调用RPC / 本地方法调用线程池隔离、超时、降级上下文管理缓存 / 会话状态滑动窗口、摘要压缩输出解析反序列化容错解析、Schema 校验可观测性链路追踪每步打点、记录 token 消耗这张表不是硬凑的是我在实际项目里真的按这个映射去设计模块的。当你把 Agent 当成一个决策逻辑不确定、但工程约束必须确定的服务来对待很多问题就迎刃而解了。3. LangChain4j 还是 Spring AI选型不是站队是看你的工程现状3.1 两个框架的定位差异这是被问得最多的问题。我的答案一直很直接如果你团队已经在用 Spring Boot且希望 Agent 能力像普通 Service 一样被注入、被管理选 Spring AI如果你需要更灵活的编排、更丰富的模型和向量库适配、想快速试各种 Agent 模式选 LangChain4j。两者不是替代关系甚至可以共存。LangChain4j 的定位更像Java 版的 LangChain它把 Chain、Agent、Memory、RAG、Tool 这些概念都做成了独立抽象适配的模型厂商和向量数据库非常多。它的 AiServices 接口式声明是我最喜欢的设计——你定义一个接口加几个注解框架帮你生成实现工具方法直接挂上去。Spring AI 的定位是Spring 生态里的 AI 抽象层它复用了 Spring 的很多基础设施比如用Bean管理 ChatClient用Advisor做拦截用Observation做可观测。它的优势是和现有 Spring 工程无缝融合缺点是抽象层次相对高某些底层定制不如 LangChain4j 直接。3.2 一张表看清关键维度维度LangChain4jSpring AI上手成本低API 直观中需熟悉 Spring 抽象模型适配广度非常广广且持续增加与 Spring 集成需手动配置原生自动装配Agent 编排能力强模式丰富中偏基础工具调用注解式灵活注解式规范可观测性需自行接入原生 Observation社区活跃度高高适合场景快速试验、复杂编排企业级、Spring 体系内选型时我一般建议先问三个问题现有工程是不是 Spring Boot团队更看重快速出效果还是长期可维护需不需要频繁切换模型厂商答案基本能定位到其中一个。3.3 一个容易被忽略的现实模型接入的合规与稳定选框架时很多人只看功能忽略了一个现实问题模型服务的接入方式决定了你的架构上限。国内可用的模型服务不少接入时要注意的是 API 的稳定性、并发配额、以及是否支持 function calling。有些模型不支持原生工具调用那就得靠提示词工程硬约束这会显著增加解析层的复杂度。我的经验是优先选支持原生 function calling 的模型哪怕贵一点。因为工具调用的可靠性直接决定 Agent 能不能用省下的那点成本远不够填解析容错的坑。Spring AI 和 LangChain4j 都支持通过配置切换模型所以前期可以多试几个用同一套代码跑对比测试。4. 动手搭一个能查订单的 Agent从零到跑通4.1 环境准备与依赖引入假设你有一个标准的 Spring Boot 3.x 工程。以 Spring AI 为例核心依赖就两个模型 starter 和 Agent 相关模块。Maven 里大致是这样dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId /dependency如果你用 LangChain4j则是dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai-spring-boot-starter/artifactId /dependency配置里填好 API Key、Base URL、模型名。这里有个坑不同模型服务的 Base URL 路径不一样有的要带/v1有的不带。我第一次配的时候因为多了个斜杠调了半天以为是 Key 的问题。建议先用 curl 测通再写代码。4.2 定义工具把 Java 方法暴露给模型工具就是普通的 Spring Bean 方法加个注解。以查订单为例Component public class OrderTools { Tool(description 根据订单号查询订单状态返回状态和金额) public OrderStatus queryOrder(P(订单号格式为ORD开头加数字) String orderId) { // 实际业务查询 return orderService.findByOrderId(orderId); } }关键在description。模型是靠这段描述决定要不要调用这个工具的描述写得含糊模型就会乱调或者不调。我踩过的坑一开始写查询订单结果模型在用户问我的包裹到哪了时不去调用因为它不知道包裹和订单是一回事。后来改成根据订单号查询订单状态和物流信息命中率立刻上来了。参数描述同样重要。P里的说明要写清楚格式否则模型可能传一个我的订单这种自然语言进来你的方法直接抛异常。4.3 组装 Agent 并跑通第一轮Spring AI 里用 ChatClient 加工具ChatClient client ChatClient.builder(chatModel) .defaultTools(orderTools) .build(); String answer client.prompt() .user(帮我查一下订单 ORD20240101 的状态) .call() .content();LangChain4j 里则是接口式interface OrderAgent { SystemMessage(你是一个订单助手只能通过工具查询订单信息) String chat(String userMessage); } OrderAgent agent AiServices.builder(OrderAgent.class) .chatLanguageModel(model) .tools(orderTools) .build();跑通之后你会看到日志里出现模型请求调用 queryOrder这样的记录然后工具执行结果回传模型生成最终回答。第一次看到这个链路跑通比写一百行 CRUD 有成就感。4.4 实测中的三个意外情况第一个意外模型会重复调用同一个工具。比如查完订单状态它又调了一次同样的查询。原因是上下文里没有明确这个信息已经拿到了。解决办法是在系统提示里加一句如果已有足够信息回答直接回答不要重复调用工具。第二个意外多轮对话时上下文迅速膨胀。每轮的工具返回都塞进历史几轮下来 token 就爆了。我的做法是对工具返回做摘要只保留关键字段而不是把整个对象序列化进去。第三个意外并发下工具调用串了。早期我把工具类写成单例带可变状态两个请求同时进来就出问题。后来改成无状态 Bean所有上下文通过参数传递问题消失。这也是 Java 工程师的肌肉记忆——Bean 就该是无状态的。5. 让 Agent 扛住并发Java 工程师的主场5.1 并发问题的根源不在模型在你的编排层AI Agent 怎么扛并发是热词里出现频率很高的一个。很多人以为是模型服务扛不住其实大部分时候是编排层的问题。模型调用本身是 IO 密集型的一个请求大部分时间在等网络返回这恰恰是 Java 异步编程擅长的场景。我的实测数据单机 4 核 8G用虚拟线程Java 21跑 Agent 请求QPS 能到 200 左右瓶颈在模型服务的配额而不是本地。换成传统线程池同样配置只能到 80 左右因为线程数被阻塞 IO 占满了。5.2 用虚拟线程改造 Agent 调用链Java 21 的虚拟线程对 Agent 这种场景简直是量身定做。改造方式很简单try (var executor Executors.newVirtualThreadPerTaskExecutor()) { FutureString future executor.submit(() - agent.chat(userInput)); return future.get(30, TimeUnit.SECONDS); }每个请求一个虚拟线程阻塞在模型调用上也不占平台线程。配合超时控制避免某个慢请求拖垮整体。但要注意虚拟线程不是银弹。如果你的工具调用里有 synchronized 块或者用了 ThreadLocal 缓存虚拟线程的 pinning 问题会让性能不升反降。我踩过一次工具方法里用了synchronized做限流结果虚拟线程被钉在载体线程上QPS 反而掉了。后来换成Semaphore就好了。5.3 限流、降级与配额管理模型服务通常有并发配额超了会直接报错。所以编排层必须做限流。我的方案是入口限流用 Resilience4j 的 RateLimiter按模型配额设置。工具隔离不同工具用不同线程池避免慢工具拖垮快工具。降级策略模型不可用时返回缓存答案或友好提示而不是直接 500。这里有个经验限流的粒度要按模型而不是按接口。因为多个接口可能共用同一个模型配额按接口限流会导致总配额被突破。5.4 上下文与内存的并发安全多轮对话的上下文如果存在内存里并发下必须考虑线程安全。我的做法是用ConcurrentHashMap按会话 ID 存上下文每个会话的上下文用不可变对象更新时整体替换。这样读多写少的场景下性能很好也避免了锁竞争。如果会话量大就得考虑外部存储比如 Redis。但要注意序列化成本——上下文里可能有大量文本序列化开销不小。我的折中是只存最近 N 轮更早的做摘要。6. 从能跑到能用生产化的几个关键改造6.1 可观测性每一步都要能追溯Agent 最让人头疼的是它为什么这么回答。没有可观测性排查问题基本靠猜。我的做法是每一步都打结构化日志请求 ID、当前步数、Thought 内容、调用的工具、参数、返回、耗时、token 消耗。这些数据汇总起来能画出完整的决策链路。Spring AI 原生支持 Observation可以直接接入 Micrometer输出到 Prometheus 或链路追踪系统。LangChain4j 需要自己加监听器但也不复杂。6.2 提示词版本管理提示词是 Agent 的代码但很多人把它硬编码在 Java 字符串里。这在生产环境是灾难——改一个词要重新发版。我的做法是把提示词抽到配置文件或数据库带版本号支持灰度。这样调优提示词不用发版A/B 测试也方便。6.3 工具调用的幂等与安全工具一旦能被模型调用就意味着模型可以触发真实的业务操作。查询类工具还好写操作类工具必须做幂等和权限校验。我见过有人把删除订单做成工具结果模型在用户闲聊时误调了。所以写操作工具必须要求显式确认参数。工具内部做权限校验不能只靠模型判断。关键操作加人工确认环节。6.4 成本控制token 就是钱。我的几个省钱手段工具返回做摘要、上下文做滑动窗口、简单问题不走 Agent 直接走普通对话、缓存高频问题的答案。实测下来这些手段能把成本压到原来的三分之一左右。7. 转型路上我踩过的坑和给你的建议第一个坑一上来就想做通用 Agent。我最初想做一个什么都能干的助手结果工具越加越多模型选择困难准确率反而下降。后来收敛到只做订单相关准确率立刻上去了。Agent 的能力边界越清晰表现越好。第二个坑忽视模型输出的不确定性。Java 工程师习惯了方法返回什么就是什么但模型可能返回格式不对、内容幻觉、甚至拒绝回答。所有解析层都要按最坏情况设计。第三个坑把 Agent 当黑盒。不记录中间步骤出问题无从下手。可观测性不是可选项是必选项。给转型中的 Java 工程师的建议不要试图先学完 Python 生态再回来直接在 Java 里动手。LangChain4j 和 Spring AI 的文档足够你跑通第一个 Agent。你已有的工程能力——分层、依赖注入、并发、可观测——才是 Agent 生产化最值钱的部分。模型和框架会变但这些能力不会。最后一个体会Agent 这个方向Java 工程师不是追赶者而是补位者。Python 生态把概念验证做得很热闹但真正要落地到企业系统里稳定、可维护、可观测的工程实现恰恰是 Java 的主场。这个窗口期还在值得投入。