先说明白一件事:做 AI Agent 从来不是 Python 的专属赛道。我写了 13 年 Java,从 Servlet 时代一路干到微服务,中间还带过几个中型团队,自认不是什么天才,但工程上的坑基本都踩过一轮。最近两年团队陆续接了多个 AI 相关的需求,我也顺手把内部几个 Agent 项目用纯 Java 栈跑通了。我见过太多 Java 背景的同事,一提到 AI 第一反应就是“完了,得学 Python”,然后跑去刷语法、配环境、调库,折腾两三个月,回来发现自己连原本的 Spring Boot 工程都快忘光了。这不是技术问题,是思路问题。
这个坑的根源在于:大家把“AI 开发”和“Python 开发”画了等号。实际情况是,AI Agent 的落地大头在工程侧——接入模型、管理上下文、设计工具调用、处理并发和状态、对接业务系统,这些恰恰是 Java 工程师最擅长的部分。你真要写 Agent,Java 生态里能用的东西比你想象的多得多,LangChain4j、Spring AI、MCP 的 Java SDK,随便挑一个都能上手做生产级应用。接下来这篇复盘,我会把它拆成三块:为什么你不需要从零学 Python,Java 做 Agent 的选型思路,以及一套可以直接复制的完整实战流程。文章偏长,但每段都是实操记录,看完了你至少能少走三个月弯路。
1. 认知纠偏:90%的人不是被技术拦住的,是被“学Python”拦住的
1.1 先聊聊那个“挂掉90%的人”的坑到底在哪
我在各种技术社群里观察到的现象高度一致:Java 背景的开发者决定进军 AI 后,第一笔时间投资几乎都花在“Python 基础语法”上。这不是坏事,但问题出在“以 Python 为核心”的学习路径。很多人报了某个“AI 训练营”,结果半个月课表全是 numpy、pandas、matplotlib,真正和 Agent 相关的 Prompt 设计、工具编排、上下文管理反倒排到很后面。等你终于可以磕磕绊绊写一个 Python 脚本调用大模型接口时,你发现你又开始纠结“这个库怎么装”、“这个环境怎么配”,而同期用 Java 直接开搞的同事,服务已经上线了。
我这不是在贬低 Python。Python 在算法训练、数据处理方向有绝对优势,一个做微调、跑实验的工程师,Python 是必须的。但 Agent 开发完全不是同一个工种。Agent 本质上是“基于大模型的业务系统”,它需要的是并发处理、可靠的接口调用、事务性状态管理、日志与可观测性——这些东西 Java 的工程化积累比你想象中扎实得多。换句话说,Python 是把模型能力包装成 Demo 最快的语言,Java 是把模型能力沉淀成系统最稳的语言。你不是做研究,你是做产品,工具链选型当然要按系统工程的标准来。
另一个“坑中坑”是很多人把“不会 Python”当成“不懂 AI”的挡箭牌。其实大模型对外暴露的能力边界非常统一,就是 HTTP 接口、结构化输出、上下文窗口、工具调用协议。你只要理解了一次接口协议,用 Java 还是用 Python 写,只差一个轮子的形状。换个表达方式:你不会因为会开自动挡就拒绝开手动挡,同理,你没必要因为没学某门小众语言就认为自己上不了 AI 这辆车。
1.2 13年Java经验在Agent开发里到底算什么家底
我可以负责任地说,Java 老兵做 Agent 不但不吃亏,反而在好几个维度是降维打击。先说并发与性能。Agent 服务要同时服务几十上百个用户,每个用户可能与模型进行多轮交互、同时触发多个子任务。Java 的线程模型、虚拟线程、响应式流,加上各类成熟的连接池和熔断组件,这套东西是经过了超大规模业务验证的。而多数 Python Agent 项目在并发处理上要么靠异步框架硬顶,要么直接把请求排队,工程化程度差不少。
再说类型系统。Agent 最难缠的问题之一就是模型的非结构化输出对业务逻辑的冲击。模型返回的字串可能要解析成 JSON、转成枚举、映射到实体类。在 Java 里,编译期的类型检查能帮你提前发现字段写错、类型不匹配、枚举值不存在的问题。这种静态类型的约束力在复杂的多步 Agent 流程中极其宝贵。
然后是生态,这个不用多说了。Spring Boot 自带依赖注入、AOP、Actuator、配置管理,再加上内部多年的微服务治理经验,你要给 Agent 接入鉴权、限流、灰度、链路追踪,全是现成的组件。用一句话总结:你手里的技能树不是过时了,而是还没找到正确的组合方式。
1.3 Java工程师进入AI Agent的四条路线对比
我盘点过身边同事和朋友进入 AI 领域的方式,基本能归纳成四条路线:
- 路线 A:彻底转 Python,从语法开始学,然后学 LangChain 或者 LlamaIndex,最后回头对接业务。优点是生态资料多,缺点是周期长,前三个月几乎没有任何可交付产出,很多人在这个阶段就放弃了。
- 路线 B:用 Java 直接调大模型 HTTP API,自己封装 Prompt、上下文、解析逻辑。优点是轻量、无框架依赖,适合快速验证想法,缺点是功能做复杂后需要手写很多胶水代码。
- 路线 C:基于 LangChain4j 或 Spring AI 这类 Java 原生框架开发。优点是框架已经把模型接入、向量检索、工具调用等抽象好了,生产效率高,适合做完整业务系统,缺点是框架迭代快,你需要保持关注版本变化。
- 路线 D:拥抱 MCP 协议,用标准化的 Tool 接入方式让 Agent 连接业务系统。优点是非常接近业界标准,未来兼容性好,缺点是生态还在快速成熟阶段,部分 SDK 不够稳定。
我的建议很直接:如果你只是做概念验证、快速出个 Demo,走 B;如果要做一个要上线、要长期迭代的业务系统,走 C 为主、D 为辅。至于 A,除非你真的对算法研究有浓厚兴趣,否则没必要把它当作进入 Agent 领域的前置条件。
2. 选型解析:Java生态里做AI Agent到底选什么框架
2.1 LangChain4j 与 Spring AI 怎么选
先说结论:两个都可以用,但适用场景略有不同。LangChain4j 是社区驱动的 Java 移植版,设计思路和 Python 的 LangChain 高度相似,模型接入、ChatMemory、向量存储、工具调用都内置了,而且它对各种本地模型、嵌入模型的支持非常丰富,文档也很活跃。Spring AI 则是 Spring 官方生态的一部分,继承了大量 Spring 的设计哲学,配置规范、自动装配、Starter 机制都是熟悉的配方,对已经重度依赖 Spring Boot 的项目来说,侵入感更低。
选型标准我一般看两点。第一,你的项目团队是否已经统一在 Spring Boot 体系内,如果是,Spring AI 的接入成本几乎为零;第二,你是否需要非常细颗粒度地控制 Agent 内部流程,比如自定义各种 Chain、自定义切换对话模型、精确控制向量切分策略,LangChain4j 的自由度会略高一点。
这两个框架我都实际用在项目里,坦白讲,生产可用性都够。比较推荐的做法是:先用 LangChain4j 快速搭建原型,因为它的工具调用写法更接近 Python 社区的思路,调试可以参考大量现有案例;等到你们对 Agent 的交互流程、状态管理、上下文策略都打磨清楚之后,如果项目整体是 Spring Boot 系的,再迁移到 Spring AI,让配置和运维更统一。当然,也有人觉得一开始就直接用 Spring AI,免得二次迁移,这个也没问题。核心在于,不要在框架选型上无限纠结,先跑起来比什么都重要。
2.2 不写Python也能调大模型,讲讲本质
很多 Java 开发者对“调用大模型”这件事有莫名的神秘感。揭开来看,它就是一次带鉴权头的 HTTP POST 请求,请求体是对话消息列表,响应体里是模型生成的内容,最多再加上流式传输和工具调用协议。你在 Java 里用 RestTemplate 或 WebClient 也能写,但框架会帮你把这些细节全部封装掉。
Spring AI 的做法是提供一个ChatClient,你只需要构造消息内容、设置参数,就能拿到模型回复。LangChain4j 的写法也类似,通过ChatLanguageModel接口统一屏蔽不同厂商的差异。我在代码里常用的方式是这样:
ChatClient chatClient = ChatClient.builder(chatModel).build(); String answer = chatClient.prompt() .system("你是某电商平台的售后客服,回答风格要简洁、专业、有耐心。") .user("我的订单超过48小时未发货,怎么处理?") .call() .content();实际生产里不会写这么简单,但核心心智模型就是这样。你把大模型当成一个“能力很强的远程函数”,输入一串文字,输出一段文字,仅此而已。至于模型选择,OpenAI、Anthropic、通义、文心、DeepSeek、各种开源模型,只要它们暴露兼容 OpenAI 格式的接口,Spring AI 和 LangChain4j 基本都能接。这就意味着你不需要为了某个特定模型去学特定的 Python 库,一份代码切换多家模型就是改配置的事。
2.3 向量库、Embedding、记忆管理的Java解法
Agent 要能处理业务知识,RAG 几乎绕不开。RAG 的本质是把你自己的文档切块、向量化、存储,然后在每次提问时检索出最相关的片段,拼进 Prompt,让模型基于这些内容回答。这套流程在 Java 里完全能闭环。
以 Spring AI 为例,它提供了VectorStore抽象,实现类可以对接多种向量库。我自己项目里用过 PGVector 和 Elasticsearch,前者适合中小业务、部署简单;后者适合检索体量大、已有 ES 基础设施的场景。Embedding 模型的调用也可以用 Java 完成,大厂的 Embedding 接口基本都是标准 HTTP 服务,框架内置了各种实现。你只需要在配置里声明模型和 end point,Service 层里注入 VectorStore,然后调用similaritySearch()就能拿到相关片段。
记忆管理这个点也很关键。Agent 的多轮对话必须保留历史,但模型上下文窗口有限,你不能把所有历史无限塞进去。Java 侧的常见做法是:使用框架自带的MessageWindowChatMemory,设置窗口大小,比如保留最近 10 轮消息,超出的按策略丢弃或压缩。再进阶一点的做法是把历史总结后仅保留摘要,配合向量库存储关键信息。这些都是纯工程逻辑,跟语言无关,Java 写起来反而比 Python 更顺手,因为内存管理、对象序列化、线程安全这些基础能力都是现成的。
3. 手把手实战:用Spring Boot把一个Java Agent完整跑起来
3.1 先搭骨架:项目结构、依赖与配置
我拿一个内部做过的“售后工单处理 Agent”为例。这个 Agent 要接收用户问题,通过大模型判断问题类型,必要时调用内部订单系统查询数据,最后生成处理建议并写入工单。整个工程是一个标准的 Spring Boot 项目,没有引入任何 Python 组件。
pom.xml 里加的核心依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-vector-store-pgvector</artifactId> </dependency>配置文件里要声明模型相关参数:
spring.ai.openai.base-url=https://your-model-api.example.com spring.ai.openai.api-key=${MODEL_API_KEY} spring.ai.openai.chat.options.model=gpt-4o-mini spring.ai.openai.chat.options.temperature=0.3如果你的模型接口是兼容 OpenAI 格式的自建服务,只需要把 base-url 指向自己的网关,key 换成自己的服务密钥。这块完全不需要了解 Python,只需要懂得配外部化配置。
启动类之后,我建议把 Agent 相关的代码按controller、service、tool分包。controller 负责收请求,service 负责编排 Agent 流程,tool 包放各个业务工具函数。这样分层的好处是,即使后面你换了 AI 框架或者接了新的模型,业务代码的改动面可以控制到最小。
3.2 让Agent“会用工具”:从方法定义到Tool API
单纯聊天不是 Agent,能调用外部系统才是。在 Spring AI 里,让 Agent 使用工具非常简单:定义一个普通 Bean 方法,加上@Tool注解,框架就会把方法描述和参数 Schema 发送给模型。模型在需要时会生成一个工具调用请求,框架自动帮你执行对应方法,再把执行结果返回给模型。
我这里给一个实际例子,模拟查询订单的 Tool:
@Component public class OrderTools { private final OrderService orderService; public OrderTools(OrderService orderService) { this.orderService = orderService; } @Tool(description = "根据订单号查询订单的当前状态和物流信息") public String getOrderStatus(String orderId) { OrderInfo info = orderService.queryByOrderId(orderId); if (info == null) { return "未查到该订单"; } return "订单状态:" + info.getStatus() + ";发货时间:" + info.getShipTime() + ";物流公司:" + info.getLogisticsCompany(); } }这段代码背后的原理值得说清楚。框架在启动时会扫描所有带@Tool注解的方法,解析方法名、方法参数、描述,生成 JSON Schema。这个 Schema 会随每次对话请求一起发给模型。模型判断“用户想查订单”时,不是自己去查,而是返回一个特定结构的消息:它想调用getOrderStatus方法,并传入"orderId": "..."这样的参数。框架收到后调起方法,拿到字符串结果,再把结果作为新的消息继续发给模型,模型基于这个结果生成最终用户可读的答复。
这整套流程你和 Python 的@tool装饰器对比一下,逻辑完全一样,但写起来更符合 Java 工程师的习惯。因为有类型检查的存在,如果你把参数类型写错,编译期直接就报错了,而不是等到运行时才发现解析异常。
3.3 上下文管理、多轮对话与流式输出
生产级 Agent 必须处理多轮对话。Spring AI 里可以用ChatMemory来管理历史,比较典型的是MessageWindowChatMemory。做法是在服务层维护一个以conversationId为 key 的会话记录,请求进来时先把它最近的消息取出来,再叠加本轮用户输入发给模型。这样用户在 Web 界面连续提问时,Agent 才能记住上一轮说了什么。
我的实现结构大致如下:
@RestController @RequestMapping("/chat") public class ChatController { private final ChatClient chatClient; private final ChatMemory chatMemory; @PostMapping("/ask") public ResponseEntity<String> ask(@RequestBody AskRequest request) { String conversationId = request.getConversationId(); String userMessage = request.getMessage(); ChatClientResponse response = chatClient.useChatMemory(chatMemory) .withConversationId(conversationId) .prompt() .user(userMessage) .call(); return ResponseEntity.ok(response.content()); } }这里useChatMemory的机制就是自动把历史消息注入 Prompt。默认窗口长度可以配置,比如MaximumChatMemoryWindowSize设置为 20,超过 20 条自动丢弃最老的。更复杂的场景可以考虑“摘要记忆”,也就是把长期对话压缩成摘要,再结合向量检索存取,但这种方案需要额外设计离线任务,一般业务用窗口记忆就够。
流式输出也值得准备一下。聊天界面常见的“打字机”效果,后端其实是 SSE 流式返回。框架里把.call()换成.stream()即可,拿到的是一个 Flux 流,逐字推给前端。这里有个经验教训:生产环境上流式输出要注意链路超时配置、网关的读超时时间、WebFlux 的线程池参数,否则前 30 秒好好的,后面一长段就断开。我一开始就没调网关超时,导致长回答总是被截断,客户端一直报 503,排查了半天才发现问题不在应用层。
4. 踩坑实录:Java做Agent的常见问题与排查手册
4.1 一图读懂那些“诡异”的运行时报错
我整理了一下这段时间遇到的最高频问题,做成了一张速查表,每个问题都是真实发生过的,对应的解法也是验证过的。
| 现象 | 根因 | 解决思路 |
|---|---|---|
| 调用模型超时,偶发 408/504 | 默认 HTTP 连接池太小 | 配置spring.ai.openai.chat.options相关超时参数,同时调大 WebClient 连接池 |
| 模型总是乱答业务问题 | Prompt 缺少系统指令约束 | 加强 system 消息,明确角色、业务边界、回复格式 |
| 工具调用时参数解析失败 | 方法参数类型与模型生成 JSON 不匹配 | 参数尽量用 String/Long/Integer,避免复杂嵌套对象 |
| 多轮对话后效果越来越差 | 历史消息无限累积,上下文窗口超限 | 设置窗口大小,加入历史摘要机制 |
| 高并发下部分请求 503 | 线程阻塞或连接池耗尽 | 评估是否切到虚拟线程,或改用 WebFlux 响应式链路 |
| Token 消耗突然暴涨 | 每次请求都塞入了全量历史 | 按滑动窗口裁剪历史,只保留最近 N 轮 |
| JSON 响应解析失败 | 模型输出了 Markdown 代码块或格式破损 | 在 Prompt 中强制要求纯 JSON,并在代码层做异常重试 |
排查的思路也有一个优先级顺序:先看日志里模型返回的原始内容,确认模型真的“理解”了你的工具;再查工具方法执行后返回的数据是不是符合预期;最后再看整个 Agent 流程里的上下文拼接有没有把无关内容带进去。大多数“模型变笨了”的问题,最后定位都是 Prompt 拼错了。
4.2 生产环境的几个隐形杀手
第一,模型 API 的 Key 管理。我见过有人把 Key 直接写在配置文件里提交到仓库,结果被扫描工具抓到,一天内被刷了几百美元额度。建议所有 Key 走环境变量或密钥管理服务,同时开启提供商后台的限额控制。
第二,上下文隐私问题。一些业务场景下,用户输入会包含敏感信息,直接发给公网模型有合规风险。我的处理方案是敏感信息识别前置,先用本地规则过滤姓名、身份证、手机号等,再在发给模型前做脱敏,脱敏后再送进去。还要在 System Prompt 里明确要求模型不要输出未授权信息。
第三,成本预估。Agent 多轮对话加工具调用,单次用户问题可能产生比普通闲聊高数倍的 Token 费用。我建议在链路里记录每一次请求的 Prompt 字符数和模型回复字符数,累加到用户账单里。开源模型可以自己算 token,商用模型一般响应头会带 usage 信息,框架里可以直接读取。
第四,降级策略。模型服务总会有不稳定的时候,Agent 必须接受“模型不可用”的事实。我在项目里加了简单的熔断逻辑:连续三次调用失败就切换到备用模型或降级为“记录工单,稍后人工处理”的话术。这个在 Java 里用 Spring Cloud CircuitBreaker 就能实现,不需要额外写。
4.3 一套可复制的性能调优路线
先把业务跑通,再谈性能。上线初期,单机单线程池模型就能应付内部测试。压测之后,我走的是三条优化路线。
第一是升级到虚拟线程。如果你用的是 JDK 21+,spring.threads.virtual.enabled=true一行配置就能让 Tomcat 处理线程变成虚拟线程,高 I/O 等待场景下吞吐提升很明显。二是引入异步事件总线。Agent 流程里的每一步其实都是独立事件,比如“查询订单完成”、“检索文档完成”,可以用 Spring 的ApplicationEventPublisher把步骤解耦,避免线性阻塞。三是做响应式网关。如果前端交互量大,整条链路切到 WebFlux 未必划算,但至少把模型调用这一段改成异步并批量聚合,压测效果好很多。
需要注意的是,性能调优不要一上来就上重武器。先用 JFR 或者 Arthas 看看耗时主要在哪个环节,多数情况下你会发如今最大的瓶颈不是代码,而是模型 API 的响应时间,这时候你再怎么优化服务端,收益都很有限。更聪明的做法是优化 Prompt、减少工具调用次数、把可以并行的工具调用改造成并行。
5. 写在最后:一些个人体会和下一步建议
这半年多我最大的体会是:技术转型能不能成,从来不取决于你会几门语言,而取决于你有没有用已有优势去承接新问题。Java 老兵做 AI Agent,真正的优势就是工程化意识和系统性解决问题的能力。模型是别人的,但 Agent 是你搭的,你把它接到业务里,解决好可靠性、可维护性和成本问题,这就是百分之八十的价值。
如果你按本文的思路走一遍,我建议你给自己布置一个小任务:不要贪多,先做一个单工具 Agent,比如“查天气 + 查机票”的二合一助手,在不写 Python 的前提下把它部署上线。跑通之后,再逐步加入 RAG、多轮记忆、工具编排。每加一个模块,都顺手记录一下当时的设计思路和踩坑过程,三个月后回头看,这些记录就是你转型最值钱的资产。
用 Java 做 Agent 不是妥协,而是一种务实的选择。希望这篇复盘能让你少踩几个坑,也欢迎你在实践中遇到什么有意思的问题,回来一起交流。