Spring AI实战指南:从ChatClient到RAG构建Java AI应用 从第一次翻 Spring AI 官方文档算起我大概花了三天时间才从一个“只会调 OpenAI API 的 Java 程序员”变成“敢把 AI 能力写进 Spring Boot 生产项目”的人。这中间踩了不少坑也绕了不少远路。这个项目标题叫《Spring AI 入门构建第一个 Java AI 应用》听起来很基础但真正做下来你会发现入门不难难的是搞清楚 Spring AI 到底在帮你做什么、不帮你做什么以及你现有的 Java 技术栈里哪些东西可以直接复用。这篇文章就把我从环境搭建、第一个 ChatClient 调用到 RAG 知识库、Agent 扩展的完整过程捋一遍该给的代码给全该说的坑一个不落。1. Spring AI 到底是什么又能解决什么问题1.1 从传统 Java 开发到 AI 应用开发的“最后一公里”先聊一个比较实在的问题作为一个 Java 开发者你以前想做个 AI 聊天功能是怎么做的无非是引入一个 HTTP client自己封装请求参数、处理流式响应、管理 API Key、写超时重试逻辑然后再为不同的模型厂商写一套适配层。问题不在于某个具体功能的实现有多难而在于你每接一家模型就要重复一遍这些脏活累活代码里全是HttpClient和ObjectMapper的暴力组合。Spring AI 的本质就是用 Spring 的设计哲学去做 AI 应用的集成层。它把“调大模型”这个动作抽象成你熟悉的Template模式——就像RestTemplate、JdbcTemplate一样让你不再关心 HTTP 连接怎么建、请求体怎么拼、鉴权头怎么加而是专注于业务逻辑给模型什么指令、拿到的响应怎么处理。如果你之前用过 Spring Boot 里的任何*Template类上手 Spring AI 的ChatClient会非常自然。从定位上说Spring AI 解决的是两个问题一个是多模型厂商适配一个是 AI 能力和现有 Java 生态的整合。模型厂商方面OpenAI、Azure、通义千问、Ollama 本地模型等都能通过统一的接口访问生态整合方面你可以像操作数据库一样操作向量存储可以像写 Controller 一样暴露 AI 接口可以把 AI 调用纳入 Spring 的事务管理和监控体系。这对于已经有大量 Java 系统的团队来说迁移成本比想象中低很多。1.2 Spring AI 与 LangChain4j 的定位差异很多人会拿 Spring AI 和 LangChain4j 做对比。LangChain4j 出现得更早社区也很活跃功能覆盖面确实广。但我在实际项目里选择 Spring AI 的原因很朴素它毕竟出身于 Spring 官方体系和 Spring Boot 的配置机制、自动装配、依赖管理配合得天衣无缝。你用 Spring Boot 的application.yml就能完成模型配置用Service注解概念就能组织 AI 业务代码监控指标和链路追踪也都能复用现有的 Spring 基础设施。而 LangChain4j 虽然也能集成 Spring Boot但总有一种“外人”的感觉需要多一些胶水代码。当然这并不意味着 Spring AI 没有缺点。它的版本更新速度极快API 变化也比较频繁从 0.8.x 到 1.0.0再到现在的 2.0我至少遇到过两次因为升级导致的破坏性变更。如果你正在生产环境使用建议锁定版本不要盲目追新。后面我会具体说版本选择的细节。1.3 核心概念全景Model、Prompt、ChatClient、Document、VectorStore在开始写代码之前我建议你先花十分钟记住几个核心概念因为后面所有代码都会围绕它们展开Model指具体的模型比如 ChatModel、EmbeddingModel、ImageModel。Spring AI 对不同厂商做了统一抽象但底层能力差异仍然存在。Prompt即你发给模型的指令文本但在 Spring AI 里它不只是字符串而是包含一系列 ChatMessage 的请求对象。ChatClient这是 Spring AI 1.0 之后主推的流式接口负责完成“用户输入 → 组装 Prompt → 调用模型 → 解析输出”的整个链路。DocumentSpring AI 对文本内容的统一封装是 RAG 场景里做分割、向量化、检索的基本单元。VectorStore向量存储抽象类似 JPA 对数据库的抽象让你可以统一操作内存向量库、Redis、Milvus、PGVector 等。这几个概念对应到生活场景里你可以把 Spring AI 想象成一个“智能接口适配器”Model 就像是不同的电源插头标准Prompt 是你发给电器的工作指令ChatClient 是那个智能插座VectorStore 则是存储了历史说明书和维修记录的资料柜。资料柜里的内容能不能被找到、能不能被理解直接决定了你的电器应用是不是一个“懂行”的帮手。2. 环境准备与项目初始化这步真没你想的那么简单2.1 JDK、Spring Boot 与 Spring AI 的版本匹配先声明一下我下面给的版本信息是基于我的实际使用经验Spring AI 迭代很快你动手时建议查一下最新的官方文档。但版本选择和匹配的思路是通用的。我用的是JDK 17这是目前 Spring Boot 3.x 官方推荐的 LTS 版本。如果你还在用 JDK 8那么抱歉Spring Boot 3.x 和 Spring AI 基本与你无缘了除非你愿意使用早期的试验性版本但我不建议。Spring Boot 3.2是目前兼容性最稳妥的选择如果你准备使用 Spring AI 2.0那 Spring Boot 需要至少 3.3 甚至更高的版本。创建项目的方式有两种。一种是直接去Spring Initializr网站勾选依赖生成项目这是最省事的方式另一种是在已有的 Spring Boot 项目里手工添加依赖。无论哪种方式你最终都需要在pom.xml里加入 Spring AI 的 BOM 和模块依赖。BOM 的作用是统一管理依赖版本避免各个模块版本不一致导致的兼容问题。dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement2.2 依赖引入的隐藏细节如果你在 Initializr 上直接生成项目依赖会自动配好。但如果你是手工添加需要特别注意Spring AI 1.0 之后的命名和早期版本不一样了。早期你用的是spring-ai-openai-spring-boot-starter这样的独立 starter现在则是统一的spring-ai-starter-model-openai命名格式。这不仅是名字变了内部包结构也变了。我推荐你至少引入两个模块一个是模型相关的 starter一个是向量存储相关的 starter即使你的第一个应用暂时不用 RAG后面也会用到。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-vector-store-redis/artifactId /dependency注意一个最常见的坑在引入模型 starter 之后Spring Boot 启动时可能会因为找不到 API Key 而直接报错。如果你只是想先跑通本地环境可以用spring.ai.model.chatollama切换到本地模型或者用spring.ai.openai.api-key配置一个测试 Key。生产环境务必用环境变量注入不要把 Key 硬编码在配置文件里。2.3 配置文件里的几个关键参数你的application.yml大体会长这样spring: application: name: spring-ai-demo ai: openai: api-key: ${OPENAI_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.7 max-tokens: 1024这里有几个参数值得解释一下。temperature控制的是模型的随机性取值范围一般是 0 到 10 表示尽量确定、适合分类和抽取任务0.7 以上表示更有创造性、适合文案生成。max-tokens控制的是输出长度上限但注意这只是“生成”的上限不会限制你输入的长度。如果你传了一篇很长的文档进去超长部分会被模型自动截断这是新手最容易迷惑的地方。如果你追求低延迟streaming模式是必选项这需要在代码层面实现响应式流。Spring AI 对 SSEServer-Sent Events的支持非常友好后面我会给出一段流式调用的示例。2.4 第一个最小可运行程序等你把配置配好写一个真正能跑的“Hello World”其实只需要几行代码。Spring AI 1.0 之后推荐的入口是ChatClient它是构建在底层ChatModel之上的门面接口。我建议你用构造器注入的方式拿到它而不是用Autowired字段注入这样单元测试会舒服很多。Service public class ChatService { private final ChatClient chatClient; public ChatService(ChatClient.Builder builder) { this.chatClient builder.build(); } public String ask(String question) { return chatClient.prompt(question) .call() .content(); } }然后随便写一个 Controller 暴露出来RestController RequestMapping(/api/chat) public class ChatController { private final ChatService chatService; public ChatController(ChatService chatService) { this.chatService chatService; } PostMapping public String chat(RequestBody String message) { return chatService.ask(message); } }启动项目用 curl 测一下curl -X POST http://localhost:8080/api/chat \ -H Content-Type: text/plain \ -d 用一句话介绍你自己如果一切正常你会收到模型生成的一句话。到这里你的第一个 Java AI 应用就算真正跑起来了。接下来才是真正有意思的部分。3. ChatClient 实战从基础调用到结构化输出3.1 流式调用怎么实现为什么重要在真实业务场景里“用户提问后等几秒拿到完整回答”的体验往往不可接受。尤其当你把 AI 能力嵌到聊天页面里时用户期望的是“打字机式”的输出效果这背后靠的就是 SSE 流式传输。Spring AI 的ChatClient提供了.stream()方法返回的是一个FluxString也就是响应式流。你可以在 Controller 里直接返回SseEmitter或者配合 WebFlux 返回Flux。PostMapping(value /stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString chatStream(RequestBody String message) { return chatClient.prompt(message) .stream() .content(); }这里有个经验要分享如果你是在传统的 Spring MVCTomcat下使用流式响应需要确认spring.mvc.async.request-timeout配置足够长否则连接可能在生成过程中被掐断。默认超时时间往往只有几十秒遇到模型推理时间稍长就会出问题。如果你是在 WebFlux 下使用则要注意背压控制和线程模型的变化不能简单把阻塞代码混进去。3.2 Prompt 不只是字符串理解 ChatMessage 与 Prompt 体系刚开始接触 Spring AI 时我很自然地把prompt(你好)理解成一个字符串后来发现这个想法限制了我的思维。Spring AI 里的Prompt是一个包含消息列表、模型选项等信息的对象。每条消息有角色system、user、assistant其中 system 消息用于设定模型的“人设”和行为边界user 消息才是用户的实际输入。举一个我常用的例子在构建一个客服机器人时String systemPrompt 你是一位专业、耐心的Java技术客服回答用户问题时尽量给出代码示例语气简洁明确。; chatClient.prompt() .system(systemPrompt) .user(Spring Boot 3.0 和 2.7 有什么主要区别) .call() .content();你可能觉得这不就是把字符串分开写吗实际上区分 system 和 user 消息的意义在于你可以把企业内部的规范、约束、知识背景放在 system 消息里把用户的不确定输入放在 user 消息里二者隔离后模型的上下文控制会更加稳定也方便你做日志审计。3.3 让 AI 返回 JSON结构化输出的正确姿势很多 Java 开发者第一次用大模型都会踩同一个坑让模型返回 JSON它却给你一段夹杂着 Markdown 注释的文本直接JSON.parse就崩了。Spring AI 提供了一套结构化输出机制核心思路就是给模型一个明确的输出格式指令再把输出内容自动转为 Java 对象。在 Spring AI 中你可以使用ChatClient的.entity()方法来实现。这背后涉及一个 bean 的注册但核心用法很直观public record PersonInfo(String name, int age, String city) {} public PersonInfo extractPerson(String text) { return chatClient.prompt() .user(从以下文本中提取人物信息: text) .call() .entity(PersonInfo.class); }你可能会想模型怎么知道要输出成PersonInfo的字段呢这就要靠 Spring AI 内部帮你生成的 JSON Schema 了。它会根据你的 class 推断字段类型和结构并作为系统提示发给模型。这里有几个需要注意的地方Record 类型支持得很好Java Bean 也可以用但字段命名要规范避免特殊字符。模型能力差异会导致输出格式不稳定如果你是接通义千问或开源模型实测下来可能比 GPT-4 系列差一些需要用更明确的字段描述来辅助。如果下游解析失败建议在entity()外增加 try-catch 和重试逻辑不要假设每次都会成功。3.4 函数调用让你的 AI 能“动起来”函数调用Function Calling是 AI 应用从“聊天机器人”进化为“智能体”的关键一步。简单说你给模型声明了一组函数模型在回答时会根据用户意图决定是否调用这些函数并把函数参数交给你的业务代码去执行最后把执行结果再交回模型生成总结。Spring AI 中通过Tool注解就能轻松注册一个函数。这是 1.0 之后比较推荐的轻量方式Service public class WeatherService { Tool(description 查询指定城市的当前天气) public String getWeather(String city) { return 城市: city , 天气: 晴, 温度: 24°C; } }然后构建ChatClient时带上这个方法所在的类实例并在提示里明确说“你可以使用工具查天气”this.chatClient builder .defaultTools(new WeatherService()) .build();当你问“北京天气怎么样”时整个调用链路就会变成模型识别到需要调用函数 → 生成调用参数city北京→ Spring AI 帮你执行 Java 方法 → 结果返回模型 → 模型生成最终回答。这就是一个最基础的工具调用链路也是后面做 AI Agent 的底层能力之一。4. RAG 知识库问答让 AI 真正“懂”你的业务数据4.1 为什么不能用“把文档全塞进提示词”的土办法我见过很多初学 AI 的 Java 开发者遇到的第一个业务需求都是“把这个制度文档做成问答机器人”。最粗糙的方案是把几万字塞进 prompt 里让模型“理解”后回答。这在文档短、用户少的情况下确实能跑但一旦文档超过模型上下文窗口、或者用户提问密集问题就来了一是费用呈现线性增长二是上下文一长模型的有效注意力会下降回答开始丢失细节。RAGRetrieval-Augmented Generation检索增强生成解决这个问题的思路是不把所有文档都塞给模型而是先检索最相关的内容片段再把这些片段作为参考资料带着问题一起交给模型生成答案。简单类比的话你不再是让一个老师把整本教材背下来再回答而是让他在考试时翻开索引只找到跟考题相关的几页再照着回答。这不仅省费用回答的准确性也更高。4.2 RAG 核心流程拆解从文档切片到语义检索一个完整的 RAG 应用由两条流水线组成离线流程基本算是一次性的加载文档并分割成小块Document TextSplitter对每块文本做向量化得到 Embedding 向量把向量和原文存入 VectorStore在线流程每次问答都要走的链路用户输入问题同样做向量化在 VectorStore 里做相似度检索拿到 top-k 个相关片段把问题和片段组装成 prompt发给模型模型基于检索结果生成最终回答这里最反直觉的一点是向量检索关心的不是文本关键词匹配而是语义相似度。所以哪怕用户的提问里没有出现文档里的原词只要语义接近也能命中正确的片段。这是传统数据库 LIKE 查询做不到的。Spring AI 为这套流程提供了相当完整的抽象。首先要准备一个VectorStore最轻量的入门方案是用内置的SimpleVectorStore内存实现它不需要额外启动任何中间件非常适合学习和开发调试验证。等真要上生产再切换到 Redis、Milvus 或 PGVector 都能平滑迁移因为接口是一样的。Configuration public class RAGConfig { Bean public VectorStore vectorStore(EmbeddingModel embeddingModel) { return SimpleVectorStore.builder(embeddingModel).build(); } }然后做文档加载和写入。Spring AI 内置了读取 PDF、TXT、Markdown 等格式的加载器Service public class KnowledgeService { private final VectorStore vectorStore; public KnowledgeService(VectorStore vectorStore) { this.vectorStore vectorStore; } public void loadDocuments(String path) { var reader new TxtResourceReader(new FileSystemResource(path)); var documents reader.get(); vectorStore.add(documents); } }这里我强烈建议你在写入之前做文本分割。官方提供了TokenTextSplitter它会按 token 数量分割避免把语义完整的段落切碎var splitter new TokenTextSplitter(); var chunks splitter.apply(documents); vectorStore.add(chunks);块的大小是一个需要反复试验的参数。我用过的经验范围是 500 到 1000 token。块太大检索出来的片段不够精准块太小上下文信息断裂模型无法理解完整语义。没有绝对标准建议你用实际业务文档边调边看。4.3 问答接口实现把检索结果注入 Prompt写入向量库只是第一步真正要用的还是问答接口。Spring AI 早期需要你手动完成“检索→组织 prompt→调用模型”这一串动作后来提供了更简洁的 API。不过手动方式反而更适合理解原理我建议先按手动流程写一遍public String ask(String question) { // 1. 向量化用户问题检索相关片段 var searchRequest SearchRequest.builder(question) .topK(3) .similarityThreshold(0.5) .build(); var results vectorStore.similaritySearch(searchRequest); // 2. 拼接上下文 String context results.stream() .map(doc - doc.getContent()) .collect(Collectors.joining(\n---\n)); // 3. 组织最终 prompt return chatClient.prompt() .system(你是企业知识库助手只根据以下参考资料回答问题如果资料中没有相关信息明确说资料库中未找到相关信息不要编造。) .user(参考资料\n context \n\n用户问题 question) .call() .content(); }这里面有三个关键参数要解释清楚topK表示返回几条最相关的片段这个值不是越大越好太大了会引入无关噪声更会拉高 token 消耗similarityThreshold表示相似度阈值过滤掉得分太低的片段0.5 只是一个经验起点实际要根据你的文本和向量模型调整最后的 system 提示词是 RAG 应用中决定回答质量上限的隐藏变量务必加上“无信息不要编造”这类约束否则模型会在资料不足时强行给出看似合理但错误的答案。4.4 向量化模型的选择与切换做 RAG 时EmbeddingModel和ChatModel是可以分开配置的。也就是说你对话可以用 GPT-4o但向量化不一定非要用同厂家的模型。我目前的配置是对话走通义千问或者 OpenAI向量化用文本嵌入模型。价格上嵌入模型一般比对话模型便宜很多效果差异在中文场景下也不小建议你实际测试几个后再定。一个常见误区是认为“反正都是同一家的模型嵌入和对话一起配就行了”实际上如果你对中文场景有要求嵌入模型的选择直接影响你检索的准头。如果你是在本地开发可以用 Ollama 跑一个开源的嵌入模型比如nomic-embed-text完全免费不需要联网。配置方式也很简单把相关 spring AI 配置指向 Ollama 即可。唯一要提醒的是本地嵌入模型的质量和云厂商的还是有差距用于生产前务必先在业务数据上做评测不要只看相似度的绝对值。4.5 RAG 效果的直观调试方法很多同学做完 RAG 后发现回答结果不理想第一反应是“换个更强的对话模型”。这个思路不一定是错的但我每次调试 RAG都会先单独查看检索质量而不是急着调生成环节。方法很简单单独写一个调试接口只返回检索到的片段和得分先不看模型的最终回答。拿我刚才的ask方法改造一下加一个 debug 参数public ListString searchOnly(String question) { var results vectorStore.similaritySearch( SearchRequest.builder(question).topK(5).similarityThreshold(0.3).build() ); return results.stream() .map(doc - doc.getScore() | doc.getContent()) .toList(); }如果检索结果里压根没有相关片段那再怎么换对话模型也是白搭问题出在文档加载、切片、向量模型或相似度阈值上。如果检索结果里有相关内容但最终回答还是不行那再往 prompt 生成阶段去排查。这种分层排查思路能让你的调试效率翻倍这是我的血泪经验。5. 进阶扩展Spring AI Alibaba、Agent 与 Spring Cloud 集成5.1 为什么关注 Spring AI Alibaba单纯从入门讲Spring AI 官方支持的 OpenAI、Ollama 已经够用了。但如果你身在电商、金融、政企这类大量使用阿里系中间件的团队你多半会听到“Spring AI Alibaba”这个词。它本质上是为了让 Spring AI 更好地适配阿里云的通义千问系列模型以及其他阿里云中间件比如数据访问、RAG 相关组件而做的集成和扩展。我实际体验下来Spring AI Alibaba 比较有价值的地方在于它让通义千问接入方式与模型本身的生态比如百炼平台的参数体系、NL2SQL 场景等贴合度更高。换句话说如果你的业务确实长期绑定阿里云体系那就值得引入如果只是单纯想用通义千问模型直接使用官方 OpenAI 兼容配置其实也可以跑通不一定非要上同一套阿里的封装。这一点想清楚再动手能避免不必要的依赖复杂度。5.2 NL2SQL 场景让 Java 应用“自然语言查数据库”在我做的几个真实项目里NL2SQL 是老板们最容易理解、也最容易拍板付费的场景用户输入一句“查一下上个月卖得最好的五个商品”系统自动生成 SQL、执行查询、再把结果用自然语言总结出来。实现这个场景你不需要引入特别重的东西甚至可以基于函数调用来完成。核心设计是几步定义一组工具方法比如queryProductSales、queryOrdersByDate每个方法内部执行特定 SQL。让模型通过函数调用选择合适的工具并把天然语言条件“上个月”“卖得最好”解析成 SQL 查询参数。方法执行后把查询结果交给模型做最终的自然语言生成。这种做法的好处是你不需要让模型真的“直接从文本生成任意 SQL”而是让它在限定的工具集内做参数填充和路由决策出错概率大幅降低。如果你期望更接近“自由 SQL 生成”有两种路一种是让模型生成 SQL、你再用 JDBC 执行风险是 SQL 注入和不可控建议只在内部工具、只读场景使用另一种是使用 Spring AI Alibaba 生态里现成的 NL2SQL 能力它将模型与 schema 信息、SQL 生成规则做了更好的整合但仍然要保证执行环境可控。5.3 从工具调用到 Agent关键一步是“让模型自己做决策”单轮工具调用本质上还是单次“识别 → 调用 → 返回”。而 Agent智能体的核心差异在于模型可以在多轮循环中自行决定“要调用什么工具、调用几次、什么时候结束”。比如你问“帮我分析这份销售报告并汇总亮点”Agent 需要先读取文档再调用数据查询工具可能还要调用一个绘图工具最后把结果汇总给你。Spring AI 对 Agent 的支持不是一个“一键开启”的功能而是提供了一些构建 Agent 环节的基础组件。我个人的建议是在刚开始接触 Agent 时不要一上来就上复杂的多智能体框架而是先用手写循环的方式理解核心逻辑1. 把 user 消息和 system 指令发给模型声明可用工具 2. 模型返回要么是最终回答要么是一个调用工具的请求 3. 如果是要调用工具执行工具方法把结果作为新消息返回给模型 4. 重复第二步直到模型给出最终回答或达到最大迭代次数你完全可以自己用 Spring AI 的ChatModel原语实现这个循环。第一次跑通这个循环时你会对“Agent 是怎么做决策的”这个问题有种豁然开朗的感觉——它没有你想的那么神秘本质上是函数调用加循环控制。之后再去用社区里的多智能体编排框架心里就有底了。5.4 和 Spring Cloud 集成不是选修是必修在大多数企业级 Java 团队里Spring Boot 应用几乎都会接入 Spring Cloud 体系。这也意味着你的 AI 应用并不是一个孤岛需要考虑服务注册、配置中心、网关、链路追踪等。Spring AI 在这一点上继承了 Spring Boot 的优良表现它天然作为一个标准 Bean 存在能融入这个生态。我在实际接入中重点做了三件事一是把 AI 服务的接口走统一网关方便做权限控制和限流二是把 API Key 放入配置中心这样不用改代码就能切换模型或升级版本三是把 AI 调用产生的耗时、token 消耗等指标接入监控系统。尤其最后一点我在项目上线一周内发现了多次因模型超时导致的接口慢调用就是因为监控看得足够细。如果你的团队已经用了 MicrometerSpring AI 的相关自动配置会帮你省很多事。6. 项目实战中的常见问题与调优经验6.1 版本升级的“惊喜”API 变化比预期快写这篇文章时Spring AI 已经进入 2.0 时代。我在 0.8.x 阶段写的老代码升级到 1.0.0 时几乎是重写了一遍原来调用的ChatClient构造方式、Prompt类的包路径、甚至部分 starter 名称全变了。因此我的第一个忠告是如果不是必须不要让 Spring AI 版本频繁跳动。如果你正在学习优先跟随当前官方文档的版本如果你在写生产代码把版本写死并建立好升级测试用例。但版本升级这件事又绕不开因为旧版本可能包含安全补丁或新模型支持。我的建议是每次升级前先看官方RELEASE_NOTES把破坏性变更列出来再跑一遍你本来的核心示例代码。千万别按习惯“升级依赖后项目能编译就算成功”很多 API 行为是在运行时才暴露差异的。6.2 成本控制token 消耗不是小事AI 项目上线后你一定会被老板问一个问题“这个月调模型花了多少钱” 这时候你会发现token 消耗的几个大头其实都在你想不到的地方。比如system 消息每次请求都会重复发送如果里面塞了大量固定说明费用会随请求量线性放大又比如RAG 会话时把一堆用不到的历史消息全塞进上下文每次调用都在累加成本。我总结出三条铁律精简 system 提示词把高频不变的内容尽量用“最少必要字符”表达。合理控制对话历史长度不要让整个 session 的上下文无限增长用一个滑动窗口只保留最近的几轮对话。为不同业务场景配置不同的模型简单的分类任务就别用最强模型了选择便宜的低端型号性价比会高很多。6.3 安全与数据合规不要忽视最基本的边界这句话听起来像泛泛而谈但它在我接触的客户项目里几乎都是血泪教训。给外部用户开放 AI 功能时至少要做到API Key 只能存在服务端任何情况下都不应该通过接口或前端页面暴露到浏览器。在网关层做限流否则有人刷接口额度几分钟烧完。用户输入不要直接拼接进 system 提示词防止提示注入。提示注入就是你让模型“忽略之前所有指令”结果被用户利用来套取内部信息。对外输出的内容如果涉及隐私或敏感数据要有额外的过滤和脱敏机制。Spring AI 本身不会替你做这些事情它只是提供了一套集成框架。安全边界需要你自己在应用层构建这和做传统 Web 应用时“框架不替你防 SQL 注入”是一模一样的道理。6.4 一套适合团队落地的测试方法很多团队把 AI 功能测试当成“玄学”觉得没法做自动化。我的经验是确实不能像传统断言那样指望 AI 输出完全一样但可以有策略地做三层测试第一层构造固定输入写宽松断言比如断言响应里包含关键名称、数字或短语这是最基础的 smoke test。 第二层用人工标注的评测集跑一批固定问题给回答质量打分好/一般/差定期回归看版本升级前后分数是否有波动。 第三层把 RAG 检索和最终回答分开评测检索看命中是否准确回答看生成是否忠实于检索片段这样一旦效果下降能迅速定位环节。这套方法论虽然比传统自动化测试“软”一些但能让你在领导问“效果到底变没变好”时拿出数据来。AI 应用不是不能测而是测试标准和传统软件不同。7. 写在最后给初学者的几条建议如果你打算照着这篇文章把一个 Spring AI 应用从零搭起来有几点我想再叮嘱一下。第一别一上来就追求复杂架构。先把一个最简单的 ChatClient 跑通再逐步往里面加 RAG、加函数调用每加一层都做一次可运行、可验证的里程碑。我见过太多人一上来就搭出一个“多智能体 向量数据库 消息队列”的庞然大物结果连最基础的模型调用都调不通。第二多看官方文档和 Release Notes。Spring AI 的生态还在快速演变网上的很多教程包括我这篇都存在时效性问题。以官方文档为准把示例代码复制下来跑一遍比看一百次别人的经验分享都有效。当然看别人的踩坑记录能帮你少走很多弯路这两者不矛盾。第三动手做一个真实业务场景而不是只跑官方 demo。官方 demo 设计的点太顺滑了真实的业务数据、真实的用户提问往往脏、乱、缺上下文全都是坑。拿自己手头文档做一个 RAG 问答拿一个真实业务需求做一个 Agent你会遇到远比这里多的问题但解决问题的过程就是真正学会 Spring AI 的过程。我在做第一个 Spring AI 项目时最大的收获并不是学会了某个 API 的用法而是意识到 AI 应用开发和传统 Java 应用开发最大的区别在于传统应用的逻辑是确定性的你能控制每一个分支而 AI 应用的核心逻辑是概率性的你只能在效果、成本、体验和风险之间做平衡。这个认知转变可能比任何代码片段都重要。希望这篇分享能帮你在转变的路上少踩几个坑。