1. 从Java开发者到AI Agent工程师:这条路到底该怎么走
这两年跟不少做Java的朋友聊过,大家普遍有个焦虑:AI这波浪潮来了,Python阵营的人好像天然占优势,写Java的是不是要被落下了?我一开始也有这个担心,但实际动手把Spring AI跑通、做了几个Agent项目之后,我的判断是——Java开发者的机会其实很大,关键在于你能不能把已有的工程能力迁移到AI应用层。
Spring AI这个框架,本质上解决的就是一件事:让Java开发者用自己熟悉的方式(依赖注入、自动配置、声明式客户端)去调用大模型、构建RAG、编排Agent。你不需要从头学Python生态,不需要把LangChain那一套全部重来一遍,用Spring Boot的思维就能把AI能力集成到现有系统里。这篇文章我想把从Java基础到Spring AI再到AI Agent的完整进阶路线拆开讲,包括每一步该学什么、为什么这么学、实际踩过哪些坑,以及15个可以拿来就练的实战方向。适合有Java基础想转AI应用开发的,也适合已经在做Spring Boot项目想集成AI能力的。
先说一个很多人搞混的概念:AI Agent、LLM、AI模型到底什么关系?大模型(比如DeepSeek、GPT、通义千问)是底层能力,负责理解和生成;LLM是这类模型的统称;Agent则是在LLM之上加了一层“手脚”和“记忆”——它能调用工具、能规划步骤、能根据结果决定下一步做什么。打个比方,LLM是一个很聪明但只能动嘴的顾问,Agent是给这个顾问配了电脑、电话和笔记本,让他能真正帮你把事情办了。Spring AI要做的,就是让你用Java代码把这个“配手脚”的过程标准化。
2. 进阶路线整体设计:为什么这么排
2.1 四个阶段的划分逻辑
我把这条路线分成四段:Java基础巩固、Spring Boot工程能力、Spring AI核心能力、AI Agent实战。这个顺序不是拍脑袋定的,背后有明确的依赖关系。
Java基础不牢的人,直接上Spring AI会非常痛苦。Spring AI大量使用了函数式接口、Lambda表达式、Optional、CompletableFuture、Reactor的Mono/Flux,这些都需要你对Java 8以上的特性有扎实理解。我见过有同学连Function<Request, Response>这种泛型嵌套都看不明白,就去调ChatClient,结果报错完全不知道从哪查。
Spring Boot工程能力是第二层地基。Spring AI本身就是一个Spring Boot Starter,它的自动配置机制、条件装配、属性绑定,全部建立在Spring Boot的约定之上。你不理解@ConditionalOnMissingBean、不理解application.yml的配置优先级、不理解Bean的生命周期,用Spring AI就是照猫画虎。
第三层才是Spring AI本身。ChatClient、EmbeddingClient、VectorStore、Advisor、ToolCallback这些核心抽象,每一个都值得花时间吃透。第四层是Agent,这是把前面所有能力组合起来解决实际问题的阶段。
2.2 为什么选择Spring AI而不是其他方案
市面上Java调大模型的方案不少,比如直接用HTTP客户端调API、用LangChain4j、用Spring AI。我选Spring AI作为主线,理由有三个。
第一,和Spring生态无缝集成。你现有的Spring Boot项目,加一个依赖、写几行配置就能用,不需要引入新的编程范式。第二,抽象层次合理。它没有过度封装,你仍然能看到底层的HTTP请求和响应,调试起来不抓瞎。第三,官方持续迭代。Spring AI从0.8到1.0再到2.0,API逐渐稳定,社区也在快速壮大,Spring AI Alibaba这样的项目让国内模型的接入变得非常方便。
注意:Spring AI 1.0之后API有较大调整,网上很多0.x时代的教程已经跑不通了。建议直接看官方最新文档,或者确认教程对应的版本号。
2.3 学习节奏建议
我给的建议是:如果你每天能投入2小时,Java基础部分花1周快速过一遍重点,Spring Boot花1周,Spring AI核心花2周,Agent实战花2到3周。总共6到7周可以有一个比较扎实的掌握。不要试图一周速成,AI应用开发涉及的面很广,提示词工程、向量检索、工具调用、上下文管理,每一项都需要动手试错才能有感觉。
3. Java基础:哪些才是真正用得上的
3.1 必须吃透的Java 8+特性
很多Java面试八股文里背的HashMap原理、JVM内存模型,在Spring AI开发中其实用得不多。真正高频使用的是下面这些:
- Lambda表达式和函数式接口:Spring AI的
ChatClient大量使用函数式风格,比如.defaultFunctions("getWeather", "getTime"),底层就是函数注册。你需要熟练使用Function、Supplier、Consumer、BiFunction这些接口。 - Stream API:处理向量检索结果、过滤文档、转换DTO时非常常用。
.stream().filter().map().collect()这套要能随手写出来。 - Optional:Spring AI的很多返回值是Optional包装的,避免空指针。
- CompletableFuture:流式调用和异步处理的基础。
- Record类(Java 16+):定义请求和响应DTO时非常简洁,Spring AI的很多示例都用Record。
我建议你花半天时间,把这几个特性各写20行以上的练习代码。不要只看不写,看和写之间的差距比你想象的大。
3.2 Reactor基础:流式响应的必修课
Spring AI的流式输出返回的是Flux<String>,这是Reactor的核心类型。如果你不理解Flux和Mono,看到.map()、.flatMap()、.subscribe()这些操作符会一头雾水。
Reactor的核心概念其实不复杂:Mono代表0或1个元素的异步序列,Flux代表0到N个元素的异步序列。你可以把它们理解成“异步版的Optional和List”。流式对话的场景下,大模型是一个token一个token返回的,Flux正好适合表达这种“陆续到达”的数据流。
// 流式调用示例 Flux<String> stream = chatClient.prompt() .user("用一句话解释什么是AI Agent") .stream() .content(); stream.subscribe(chunk -> System.out.print(chunk));这段代码里,stream()方法返回的就是Flux,每个chunk是一个token片段。subscribe是触发整个流的执行。注意,Reactor是懒执行的,你不subscribe,什么都不会发生。这个点坑过很多人。
3.3 面向对象与设计模式的实际应用
Spring AI的架构里能看到很多设计模式的影子。Advisor是责任链模式,ChatModel是策略模式,VectorStore是模板方法模式。你不需要刻意去背设计模式,但在读Spring AI源码的时候,能识别出这些模式会让理解快很多。
举个实际例子:Spring AI的CallAdvisor和StreamAdvisor接口,允许你在请求前后插入自定义逻辑,比如记录日志、修改提示词、做敏感词过滤。这就是典型的责任链。你写一个Advisor,实现adviseCall方法,就能在每次调用大模型时自动执行你的逻辑。
public class LoggingAdvisor implements CallAdvisor { @Override public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) { log.info("请求: {}", request.prompt().getContents()); ChatClientResponse response = chain.nextCall(request); log.info("响应: {}", response.chatResponse().getResult().getOutput().getContent()); return response; } }这段代码的价值在于:你不需要在每个业务方法里手动加日志,Advisor会自动帮你做。这就是Spring AOP思想在AI调用上的延伸。
4. Spring Boot工程能力:AI集成的地基
4.1 自动配置机制必须搞懂
Spring AI的Starter之所以能做到“加依赖就能用”,靠的就是Spring Boot的自动配置。你需要理解@EnableAutoConfiguration、@ConditionalOnClass、@ConditionalOnMissingBean、@ConfigurationProperties这几个注解的配合。
以Spring AI的OpenAI Starter为例,它的自动配置类大致做了这几件事:检查classpath下有没有相关类,检查有没有用户自定义的Bean,读取spring.ai.openai.*配置,然后创建OpenAiChatModel这个Bean。你如果自己定义了一个ChatModelBean,自动配置就会退让,这就是@ConditionalOnMissingBean的作用。
理解这一点之后,你就能明白为什么有时候配置不生效——可能是你的配置前缀写错了,可能是Bean被覆盖了,可能是条件不满足。排查问题的思路会清晰很多。
4.2 配置管理:多环境多模型的优雅切换
实际项目里,你往往需要在开发环境用便宜的模型,生产环境用效果好的模型,或者同时接入多个模型做对比。Spring Boot的Profile机制加上Spring AI的配置绑定,可以很优雅地做到这一点。
# application-dev.yml spring: ai: openai: api-key: ${DEV_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.7 # application-prod.yml spring: ai: openai: api-key: ${PROD_API_KEY} chat: options: model: gpt-4o temperature: 0.3启动时加--spring.profiles.active=prod就能切换。这里有个经验:temperature参数在开发环境可以调高一点方便测试多样性,生产环境要调低保证输出稳定。这个参数控制的是模型输出的随机性,0到2之间,越低越确定。
4.3 日志与可观测性
Spring Boot的日志体系在AI应用里特别重要,因为大模型调用是黑盒,出问题了只能靠日志定位。我建议至少记录这几类信息:请求的提示词、响应的token数、耗时、模型名称、是否命中缓存。
Spring AI提供了ChatModel的call和stream两种调用方式,你可以在Advisor里统一记录,也可以在业务层手动记录。我个人的做法是写一个全局Advisor,把每次调用的关键信息打到单独的logger里,方便后续做成本分析和效果追踪。
提示:大模型调用是有成本的,按token计费。生产环境一定要记录token消耗,否则月底账单会让你吃惊。
4.4 第一个Spring Boot + Spring AI程序
说了这么多理论,动手跑一个最小可运行的程序是最重要的。步骤很简单:
- 用Spring Initializr创建项目,选Spring Boot 3.2+,Java 17+,依赖选Spring Web和Spring AI OpenAI。
- 在
application.yml里配置API Key和模型名称。 - 写一个Controller,注入
ChatClient,调用.prompt().user("你好").call().content()。 - 启动,用curl或浏览器访问。
@RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @GetMapping("/chat") public String chat(@RequestParam String message) { return chatClient.prompt() .user(message) .call() .content(); } }这个程序虽然简单,但它包含了Spring AI最核心的调用链路:Builder构建ChatClient,prompt组装请求,call执行同步调用,content提取文本结果。把这四步理解透,后面的复杂功能都是在这个基础上叠加。
5. Spring AI核心能力:从对话到RAG再到工具调用
5.1 ChatClient的进阶用法
基础的对话调用只是起点。ChatClient真正强大的地方在于它的链式API和可扩展性。你可以设置系统提示词(System Prompt)来定义AI的角色,可以传入历史消息实现多轮对话,可以设置结构化输出让AI返回JSON。
系统提示词的写法很关键。我试过很多版本,总结下来有效的结构是:角色定义 + 能力边界 + 输出格式要求 + 示例。比如你要做一个客服助手,系统提示词可以这样写:
你是一个电商平台的客服助手,只回答与订单、退换货、物流相关的问题。 对于其他问题,礼貌地告知用户你无法回答。 回答要简洁,不超过三句话。 如果用户情绪激动,先安抚再解决问题。结构化输出是另一个高频需求。Spring AI支持通过.entity(Class)方法让AI直接返回Java对象,底层是让模型输出JSON然后反序列化。这个功能在做信息抽取、分类任务时特别好用。
record SentimentResult(String sentiment, double confidence) {} SentimentResult result = chatClient.prompt() .user("分析这句话的情感:这个产品太棒了,我非常满意") .call() .entity(SentimentResult.class);5.2 向量检索与RAG实战
RAG(检索增强生成)是当前企业级AI应用最主流的架构。核心思路是:把企业私有文档转成向量存起来,用户提问时先检索相关片段,再把片段和问题一起发给大模型,让模型基于这些片段回答。这样既解决了大模型不知道私有数据的问题,又避免了微调的高成本。
Spring AI的RAG支持主要围绕VectorStore接口。你需要做三件事:文档读取(DocumentReader)、文本切分(TextSplitter)、向量存储(VectorStore)。文本切分是最容易被低估的环节,切得太碎会丢失上下文,切得太大会引入噪声。我的经验是中文文档按500到800字切分,保留100字左右的重叠,效果比较均衡。
// 文档入库 List<Document> documents = new TokenTextSplitter(800, 100, 5, 10000, true) .apply(new TextReader(resource).get()); vectorStore.add(documents); // 检索增强 String answer = chatClient.prompt() .user(question) .advisors(new QuestionAnswerAdvisor(vectorStore)) .call() .content();QuestionAnswerAdvisor是Spring AI内置的RAG Advisor,它会自动完成“检索-拼接-调用”的流程。你只需要把VectorStore传进去,剩下的它帮你做。这个设计非常Spring风格——约定优于配置。
5.3 工具调用:让AI真正能干活
工具调用(Tool Calling / Function Calling)是Agent的基础。原理是:你告诉大模型有哪些工具可用,模型在需要时返回一个“调用请求”,你的代码执行这个工具,把结果再发给模型,模型基于结果生成最终回答。
Spring AI的工具调用写法很简洁,用@Tool注解标记方法即可:
public class WeatherTools { @Tool(description = "查询指定城市的天气") public String getWeather(@ToolParam(description = "城市名称") String city) { // 实际调用天气API return city + "今天晴,25度"; } } String response = chatClient.prompt() .user("北京今天天气怎么样?") .tools(new WeatherTools()) .call() .content();这里的关键是description要写清楚,模型靠这个描述来判断什么时候该调用哪个工具。描述写得太模糊,模型可能该调的时候不调,不该调的时候乱调。我踩过的坑是:工具描述里没写清楚参数格式,模型传了个空参数进来,导致工具执行失败。
5.4 多模型接入与Spring AI Alibaba
国内开发者最关心的可能是怎么接入国产模型。Spring AI Alibaba项目提供了对通义千问、智谱AI等模型的适配,用法和OpenAI Starter几乎一样,换个依赖和配置就行。
<dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> <version>1.0.0-M2</version> </dependency>配置里把spring.ai.openai换成对应的前缀,API Key换成国内平台的Key。实测下来,通义千问在中文场景下的表现很稳,响应速度也快。智谱AI的GLM系列在代码生成方面有优势。你可以根据业务场景选择合适的模型,甚至同时接入多个做A/B测试。
注意:不同模型的API参数有差异,比如有些模型不支持
temperature,有些对max_tokens有限制。接入前先看对应平台的文档。
6. AI Agent实战:从0到1搭建智能体
6.1 Agent的核心组成
一个完整的Agent通常包含四个部分:大脑(LLM)、记忆(短期对话历史+长期向量存储)、工具(外部API和函数)、规划(任务分解和步骤编排)。Spring AI提供了构建这四部分的原语,但如何组合取决于你的业务场景。
最简单的Agent就是一个带工具的ChatClient,能根据用户问题决定调用哪个工具。复杂一点的Agent需要多步推理,比如“帮我订一张明天去上海的机票,要下午的,靠窗”——这需要先查航班,再筛选时间,再选座位,最后下单。每一步的结果都影响下一步的决策。
6.2 用Spring AI实现一个ReAct Agent
ReAct(Reasoning + Acting)是Agent的经典范式:模型先思考(Reasoning),决定做什么,然后执行动作(Acting),观察结果,再思考,循环直到任务完成。Spring AI虽然没有内置ReAct循环,但用ChatClient加工具调用可以自己实现。
核心逻辑是一个循环:调用模型 -> 检查是否有工具调用请求 -> 执行工具 -> 把结果追加到消息历史 -> 再次调用模型 -> 直到模型不再请求工具,返回最终答案。
List<Message> messages = new ArrayList<>(); messages.add(new SystemMessage("你是一个助手,可以使用工具来回答问题")); messages.add(new UserMessage(userInput)); while (true) { ChatResponse response = chatModel.call(new Prompt(messages, ToolCallingChatOptions.builder().toolCallbacks(tools).build())); AssistantMessage assistantMessage = response.getResult().getOutput(); messages.add(assistantMessage); if (assistantMessage.getToolCalls().isEmpty()) { return assistantMessage.getContent(); } for (ToolCall toolCall : assistantMessage.getToolCalls()) { String result = executeTool(toolCall); messages.add(new ToolResponseMessage(result, toolCall.id())); } }这个循环就是Agent的“心脏”。理解了这个,你就能根据自己的需求定制各种Agent。比如加上最大循环次数防止死循环,加上超时控制防止卡死,加上人工确认环节处理敏感操作。
6.3 记忆管理:让Agent记住上下文
短期记忆就是对话历史,直接放在消息列表里。但消息列表不能无限增长,否则会超出模型的上下文窗口,而且成本会越来越高。常见的做法是保留最近N轮对话,或者用模型对历史做摘要压缩。
长期记忆需要向量存储。把重要的对话内容、用户偏好、业务知识存成向量,需要时检索出来。Spring AI的ChatMemory接口提供了对话记忆的抽象,VectorStore提供了长期存储的能力。
// 配置对话记忆 ChatMemory chatMemory = MessageWindowChatMemory.builder() .maxMessages(20) .build(); ChatClient chatClient = ChatClient.builder(chatModel) .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) .build();MessageWindowChatMemory保留最近20条消息,超出就丢弃最旧的。这个策略简单有效,适合大多数场景。如果需要更精细的控制,可以实现自己的ChatMemory。
6.4 15个实战方向清单
下面这15个方向,是我认为从易到难、覆盖Spring AI核心能力的练习项目。每个都值得动手做一遍:
- 基础对话接口:用ChatClient实现一个REST接口,支持多轮对话。
- 流式输出:用Flux实现打字机效果的流式响应。
- 结构化输出:让模型返回JSON,映射到Java对象。
- 系统提示词工程:为不同角色设计系统提示词,对比效果。
- 文档问答RAG:把PDF文档入库,实现基于文档的问答。
- 多格式文档处理:支持Word、Markdown、HTML的读取和切分。
- 工具调用入门:实现天气查询、时间查询等简单工具。
- 多工具编排:让模型在多个工具间自主选择。
- 对话记忆管理:实现滑动窗口和摘要压缩两种记忆策略。
- 多模型切换:同一套代码支持OpenAI和通义千问。
- Agent循环:实现ReAct循环,支持多步任务。
- RAG + 工具混合:检索和工具调用结合使用。
- 敏感词过滤Advisor:在请求前后做内容安全检查。
- Token成本统计:记录每次调用的token消耗和费用。
- 完整Agent项目:做一个能查资料、能算数、能调API的助手。
每个项目不需要做得多复杂,关键是跑通链路、理解原理、记录问题。做完这15个,你对Spring AI的掌握会超过市面上大多数教程的水平。
7. 常见问题与排查技巧实录
7.1 依赖冲突与版本对应
Spring AI对Spring Boot版本有要求,1.0需要Spring Boot 3.2+,2.0需要3.3+。版本不匹配会报各种奇怪的错,比如NoSuchMethodError、ClassNotFoundException。我的建议是新建项目时直接用Spring Initializr,它会帮你选好兼容的版本。
如果是在现有项目里集成,先检查Spring Boot版本,再查Spring AI的版本兼容表。Maven的dependency:tree命令可以看依赖树,排查冲突很有用。
7.2 API Key配置不生效
最常见的原因是配置前缀写错,或者环境变量没读到。Spring AI的配置前缀是spring.ai.openai,不是spring.ai.open-ai。API Key建议用环境变量注入,不要硬编码在代码里。
spring: ai: openai: api-key: ${OPENAI_API_KEY}如果启动报“API key must be set”,检查环境变量是否真的传进去了。在IDE里运行和命令行运行的环境变量可能不一样。
7.3 流式输出乱码或截断
流式输出涉及编码问题。确保响应头设置了Content-Type: text/event-stream;charset=UTF-8。如果用Spring MVC的SseEmitter,注意设置超时时间。用WebFlux的Flux<ServerSentEvent>会更自然。
另一个常见问题是代理层缓冲了流式响应。如果你前面有Nginx,需要配置proxy_buffering off,否则流式效果会变成一次性返回。
7.4 向量检索效果差
RAG效果不好,八成是切分或检索的问题。排查顺序:先看切分后的文本块是否语义完整,再看检索返回的片段是否真的相关,最后看提示词是否把检索结果用好了。
我常用的调试方法是:把检索到的片段打印出来,人工判断相关性。如果检索结果本身就不对,调提示词没用。这时候要调整切分策略、换Embedding模型、或者加元数据过滤。
7.5 工具调用不触发
模型不调用工具,通常是描述写得不够清楚。@Tool的description要明确说明“什么时候用这个工具”,而不只是“这个工具是什么”。比如“查询天气”不如“当用户询问某个城市的天气情况时,调用此工具获取实时天气数据”。
另一个原因是模型本身的能力。小模型在工具调用上表现不稳定,建议用GPT-4o、通义千问Max这类能力较强的模型做Agent。
| 问题现象 | 可能原因 | 排查方向 |
|---|---|---|
| 启动报API Key错误 | 配置前缀错/环境变量未注入 | 检查yml前缀和环境变量 |
| 流式输出无效果 | 代理缓冲/编码问题 | 检查Nginx配置和响应头 |
| 检索结果不相关 | 切分粒度/Embedding模型 | 打印检索片段人工验证 |
| 工具不触发 | 描述不清/模型能力不足 | 优化description换更强模型 |
| 响应超时 | 模型慢/网络问题 | 设置超时和重试机制 |
| Token消耗过高 | 历史消息过长/重复调用 | 加记忆窗口和缓存 |
7.6 实操心得
最后分享几个我踩坑总结的经验。第一,开发阶段一定要把请求和响应完整打日志,大模型调用出问题时,日志是唯一的线索。第二,提示词要版本化管理,每次修改都记录改了什么、效果变化,否则调着调着就乱了。第三,成本控制要从第一天就做,设置每日限额和告警,避免测试时不小心跑出高额账单。第四,不要迷信大模型,该用规则的地方用规则,该用传统代码的地方用传统代码,AI是补充不是替代。
这个方向后续还可以往多Agent协作、Agent与工作流引擎结合、Agent的可观测性等方向深入。我自己还在折腾的是怎么把Agent的决策过程可视化,让非技术人员也能看懂Agent在干什么,这个在落地时对建立信任很有帮助。