Spring AI 2.0实战:Java团队零Python接入大模型

发布时间:2026/9/8 6:52:27
Spring AI 2.0实战:Java团队零Python接入大模型 最近一次代码评审我终于把维护了一整年的Python大模型中转服务下线了。上一轮项目刚开始接大模型的时候公司技术栈全是Java团队里没有任何人有Python实战经验最后只能临时找外包搭了一个Flask服务专门负责把群里的模型API请求包装成Java能调的REST接口。这个服务在线上苟延残喘了将近一年期间经历了conda环境崩溃、pip依赖冲突、SSE流式连接被网关掐断、模型升级后参数不兼容等一堆问题。每次一出事Java这边看不懂Python日志外包又离职了简直成了整个项目组的梦魇。直到我把Spring AI 2.0真正用进项目才彻底从这个泥潭里爬出来。这篇文章就围绕Spring AI 2.0的实战接入过程把从依赖引入到企业级能力落地的完整路径拆给你看重点聊聊Java程序员如何不碰一行Python就完成大模型集成。1. 先回答一个问题为什么Spring AI 2.0值得Java程序员专门学一次1.1 过去的日子接一个大模型需要“三层套娃”如果你的项目在Spring AI出现之前就接入过大模型大概率经历过下面三种方案之一。第一种是纯Java手写HTTP调用。用RestTemplate或者OkHttp去请求模型API自己拼Prompt、自己解析JSON、自己处理鉴权。这种做法在模型只有一个、接口只用一两个的时候还能抗住一旦涉及流式输出、多轮记忆、结构化返回代码复杂度会飞速膨胀。我记得当时光一个SSE流式解析器就写了两百多行还得自己处理连接中断重连维护成本相当高。第二种是当前最常见的“套娃Z”架构Java业务服务调Python中转服务Python再去调模型API。这种做法的好处是能用上Python生态的各种成熟库坏处是团队必须同时养两套技术栈。部署上的问题尤其致命Java服务能用现成的镜像和K8s编排一键发布Python服务却往往要从装conda、配pip源开始每次发版都像拆盲盒。更麻烦的是两边数据结构经常对不上Java这边定义了一个OrderVOPython那边返回的是dict联调阶段出现JSON key大小写不一致这种低级问题的频率高得让人崩溃。第三种是硬着头皮直接用LangChain4j这类Java侧的开源库。这个方向本身没问题但早期版本的东西要么API不够稳定要么和Spring Boot的自动装配体系结合得不好总有一种拾人牙慧的感觉。很多Java团队的方案是“等一等观望一下”。这些问题的根源其实不在这三种方案本身而在于整个Java生态缺少一个真正由Spring官方背书、能与Spring Boot深度集成的AI开发框架。模型API是标准的但工程化的接入路径一直是散装的。1.2 从“能连”到“可复用”Spring AI 2.0的核心设计Spring AI 2.0做的事情如果只记住一句话就是把大模型集成变成了Spring Boot里一个普通的自动配置模块。我习惯用一个类比来理解它Spring AI在AI开发中的角色类似于JDBC在数据库开发中的角色。在没有JDBC的年代Java连MySQL要写一堆厂商相关的代码有了JDBC换数据库只需要换Driver和连接串。Spring AI也是同样的思路它把“对话模型”“嵌入模型”“向量存储”“结构化输出”“函数调用”这些AI开发中的常用能力全部抽象成统一接口再通过starter机制完成自动配置。具体到2.0这个版本和更早期的AI框架雏形相比最大变化是抽象层已经非常清晰。你引入一个starter之后Spring容器里会自动装配好ChatModel、EmbeddingModel、VectorStore等核心Bean业务代码里直接注入使用即可。不再需要自己写累赘的工厂类或者动态代理去适配不同厂商的模型API。另一个核心设计是它对业界标准的拥抱。2.0的模型调用统一走OpenAI兼容协议这也是目前几乎所有主流模型服务商都在用的标准对MCP协议的原生支持更是让模型可以无缝调用外部工具服务。Spring AI Alibaba这类分支项目也把国内模型的对接链路做了完整封装。这些标准化的结果就是你的业务代码只依赖Spring AI的抽象接口模型厂商是谁、部署在哪里、本地还是云端全都从代码层面解耦。1.3 和LangChain4j相比我为什么最终选了Spring AILangChain4j作为Java生态里最早的LangChain移植项目确实也做得不错。它在Agent编排、记忆管理等方面的API设计很成熟人群中评价也很高。但我在对比后还是选了Spring AI原因主要是三个。第一是血缘关系。Spring AI由Spring官方团队维护天然和Spring Boot的配置体系、Actuator监控体系、Bean生命周期管理是一套东西。你在Spring AI里写的代码未来能无缝吃到Spring生态的升级红利。比如Spring Boot升级后AI模块的自动配置也能跟着调整这让我这种老Spring用户很安心。第二是标准协议的支持深度。Spring AI 2.0在MCP和结构化输出方面做得非常彻底。MCP客户端的自动发现与注册机制很轻量结构化输出对Java泛型类型的处理也很顺滑后面的实战章节我会展开讲。第三是模型接入的覆盖面。Spring AI官方提供了OpenAI、Ollama、Azure、Bedrock等多个接入模块社区又有Spring AI Alibaba补全了国内模型阵容你基本不会遇到“某个模型供应商没有适配”的情况。LangChain4j当然也支持很多模型但遇到冷门供应商时常常需要自己写适配器这不是不行但多一事不如少一事。当然这不是捧一踩一工程选型本来就是结合团队情况的权衡。但如果你本身就是Spring技术栈我倾向于认为Spring AI是投入产出比更高的选择。2. 从零到能对话一个纯Java项目只需要动三个文件2.1 环境检查清单先确认版本再动手Spring AI 2.0对Java版本有明确要求这一点很多人会忽略。我建议至少使用JDK 17如果条件允许直接上JDK 21。JDK 8在这个框架面前是彻底无能为力的原因是Spring AI底层依赖了Spring Boot 3.x而Spring Boot 3.x从Java 17起步。如果你所在的公司还在用JDK 8先把基础版本升级这件事当成前置条件否则后续每一步都会遇到编译错误。Spring Boot的版本最好和Spring AI官方兼容矩阵保持一致。以我手头的项目为例用的是Spring Boot 3.4.x搭配Spring AI 2.0.x。你在创建新项目时最好去Spring Initializr上直接勾选Spring AI相关依赖这样它会自动帮你匹配兼容版本比自己对着文档查省心太多。构建工具方面Maven 3.6或Gradle 7.5都可以。国内团队用Maven居多下面的示例默认用Maven。另外建议在IDE里安装Lombok插件并且确认注解处理已经开启因为后面写实体类的时候大概率要用的而Lombok和新版JDK之间时不时的兼容性小坑也是真实存在的提前装好能省一道麻烦。2.2 依赖与配置文件这一份可以直接抄我们用一个最简单的Spring Boot Web项目来演示。这个项目要做的事就一件接收HTTP请求调用大模型返回回答。pom.xml里需要引入Spring AI的starter。这里用OpenAI兼容协议接入因为国内很多模型服务商比如DeepSeek、通义百炼的兼容模式等都提供OpenAI风格的API端点用一个starter就能覆盖多家。dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId version2.0.0/version /dependency /dependencies注意这个starter的名字是“model-openai”它表示的是协议兼容不是只允许连OpenAI官方。只要目标服务的API风格是OpenAI兼容的都能通过配置接入。application.yml里最核心的配置就四个字段模型服务地址、API Key、模型名称、可选温度参数。spring: application: name: spring-ai-demo ai: openai: base-url: ${AI_BASE_URL:https://api.deepseek.com} api-key: ${AI_API_KEY:sk-xxxxxxxx} chat: options: model: ${AI_MODEL_NAME:deepseek-chat} temperature: 0.7环境变量AI_BASE_URL、AI_API_KEY、AI_MODEL_NAME的存在是为了避免把密钥硬编码进配置文件。这点很重要尤其是项目代码要提交到Git仓库的场景。之前见过不少人把api-key直接写死在yml里结果代码一泄露key马上被人盗刷教训很深刻。2.3 模型API选型开发和线上怎么搭配更省钱模型API的选择影响开发调试效率和线上成本我的建议是分环境用不同模型。开发阶段可以用本地Ollama部署的小参数模型比如qwen2.5:7b、llama3.1:8b这类。好处是零成本、不限流、不依赖外网改Prompt和调参数可以快速迭代。缺点是生成质量和速度不如云端商用模型但这对于功能联调来说完全够用。线上环境再切换到云端商用模型。国内可选范围很大DeepSeek的API、阿里云百炼上的通义千问系列都是OpenAI兼容协议配置方式几乎不用改。前文示例里用的就是这种方案。还有一个折中做法是开发环境直接接云端模型的免费试用额度比如新用户赠送的token额度或者一些开放平台的开发者免费档位。如果只是写Demo、做技术验证这会比本地模型效果更好。唯一的风险是免费额度有效期短不适合长期开发。下面这张表是我实际对比过的选型思路供参考。使用阶段首选方案优点缺点本地开发调试Ollama qwen2.5:7b免费、离线、稳定小参数模型效果有限功能验证阶段云端模型免费额度效果接近线上额度有限过期后需付费生产环境DeepSeek/通义等商用API效果好、SLA有保障按token计费配置层面本地Ollama只需要把base-url改成http://localhost:11434模型名改成本地模型名其他代码完全不用动。这就是Spring AI抽象层带来的实际好处。3. 10分钟跑通第一个对话用ChatClient写一个可用的AI接口3.1 最简可运行代码Controller ChatClient现在进入正题。假设你已经建好了一个Spring Boot Web项目并且把前面的依赖和配置都配好了接下来的代码量少得会让你惊讶。首先写一个ControllerRestController RequestMapping(/api/ai) public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient builder.build(); } GetMapping(/chat) public String chat(RequestParam(defaultValue 介绍一下你自己) String message) { return this.chatClient.prompt() .user(message) .call() .content(); } }就这么点代码。ChatClient由Spring AI自动配置的Builder建造构造器注入即可用。prompt()开启一次对话user(message)设定用户消息call()发起同步调用并等待模型返回content()取出文本内容。启动应用浏览器打开http://localhost:8080/api/ai/chat?message你好几秒种后就能看到模型回话。这个接口已经能完成最基本的单轮对话。如果你只想验证“Spring AI是否能跑通”到这里就够了。从创建项目到跑通花费的时间确实不会超过10分钟这也是我为什么在标题里敢用“10分钟集成”。但要注意一个小细节ChatClient实例本身不是无状态的工具类它在内部可以携带一些默认配置比如系统提示词、默认模型参数、可能注册的函数工具。所以同一个Spring Boot应用里可以同时创建多个不同配置的ChatClient Bean分别服务不同的业务场景。这种设计我们在后面会用到。3.2 理解ChatModel、ChatClient和Prompt三者之间的关系如果你刚接触Spring AI可能会被ChatModel、ChatClient、Prompt这几个概念搞晕。我用自己的理解给你捋一下。ChatModel是最底层的模型抽象负责直接对接具体的模型API。OpenAI模型、Ollama模型、通义模型都能用对应的ChatModel实现来替换。它有点像DataSource定义了数据库访问的底层能力但你平时不会直接拿DataSource去拼SQL执行。ChatClient则是面向业务开发者的高层API。它把Prompt构建、模型调用、返回解析、工具注册这些繁琐细节都封装好了类似JdbcTemplate对DataSource的那一层封装。你的业务代码里只需要和ChatClient打交道。Prompt是每一次请求的输入封装。它不仅包含用户说的话还包含系统提示词、消息历史、模型参数温度、最大Token数等等完整上下文。ChatClient的prompt().user(...).call()就是一个构建Prompt并执行的过程。用数据库开发来类比ChatModel是JDBC Driver层面的东西ChatClient是Spring JDBCPrompt则是你传入的一条SQL加参数列表。这个类比可能不是百分百精确但足够帮助你建立初始的理解框架。用顺手之后你会发现最频繁打交道的其实是ChatClient。3.3 流式输出让AI回答一行一行“长”出来接手过真实项目后你会意识到同步返回对用户体验并不友好。尤其模型的生成时间往往有数秒甚至十几秒让用户一直干等页面转圈非常煎熬。流式输出Streaming能把模型的生成过程实时推给前端呈现“打字机”效果体验会好一大截。Spring AI对SSE流式输出的支持同样很简洁GetMapping(value /chat/stream, produces text/event-stream) public FluxString chatStream(RequestParam String message) { return this.chatClient.prompt() .user(message) .stream() .content(); }区别就两个地方返回值类型变成了FluxString终止操作从call()换成了stream()。call()是同步阻塞等待全部结果stream()则返回一个响应式流内容会分块推送。WebFlux的Flux类型天然支持SSE协议前端用EventSource或者fetch流式读取都能对接。这里要提醒一个常见的坑把响应式重返值写到Controller时如果你的项目只引入了spring-boot-starter-webSpring MVC而没有引入spring-boot-starter-webfluxFlux可能没法直接使用。原因在于spring-webmvc本身不包含Reactive Streams的类型。解决方案有两个简单一点在pom里额外引入webflux依赖虽然有点重讲究一点直接用WebFlux构建整个服务。根据我自己的经验对于一个纯AI后端服务直接上WebFlux往往更契合流式输出的场景。4. 企业级标配结构化输出与多模型切换4.1 让大模型直接返回Java对象而不是手工解析JSONPython中转服务时代最折磨人的一件事就是JSON对账。模型返回一段JSONPython那边先解析成dict再转换成Java团队约定的DTO结构一旦key大小写不一致或者嵌套层级变了两边就要反复联调。Spring AI 2.0的结构化输出能力直接把这个痛点从根上解决了你让模型直接返回一个Java对象。看这个例子。我希望模型帮我分析一段用户评论提取出摘要、关键词和情感倾向。public record ReviewAnalysis( String summary, ListString keywords, boolean positive) {} GetMapping(/analyze) public ReviewAnalysis analyze(RequestParam String content) { return chatClient.prompt() .user(请分析下面这条用户评论: content) .call() .entity(ReviewAnalysis.class); }核心是末尾的entity(ReviewAnalysis.class)。Spring AI会为这个Java类型生成一个JSON Schema描述把它放进Prompt里要求模型严格按照该结构返回最后再把模型输出的JSON自动反序列化成ReviewAnalysis对象。整个过程你不需要手写一行JSON解析代码。如果返回的是泛型类型比如ListReviewAnalysis就用ParameterizedTypeReferenceListReviewAnalysis list chatClient.prompt() .user(请分析这批评论: contents) .call() .entity(new ParameterizedTypeReferenceListReviewAnalysis() {});这种写法在处理批量数据、报表统计、信息抽取等场景里非常实用。我之前用一个接口从几百份合同文档里提取“合同编号、甲方、乙方、金额、有效期”返回一个ListContractInfo然后直接批量写入数据库。整个过程只用了不到50行Java代码这在以前至少需要一整个Python服务才能做到。顺带说一句结构化输出对模型的JSON生成能力有一定要求。经过实测主流的商用大模型问题都不大但一些本地的小参数模型偶尔会格式翻车。遇到这种情况可以在Prompt里再强调一下“只输出JSON对象”或者考虑把模型换成能力更强的版本。4.2 一套代码接入多家模型配置决定一切上面4.1的代码示例里你从头到尾没有见过“DeepSeek”“通义”“Ollama”任何一家厂商的专属API。这是因为Spring AI把所有模型厂商都收编到了同一个ChatModel接口后面。所以“多模型切换”在代码层面基本是零改动变的只有配置。在实际项目里我是这样利用这个特性的。项目里定义一个ChatClient然后定义三个Spring Profiledev、test、prod。dev环境的配置指向本地Ollamatest环境指向云端模型免费测试Keyprod环境指向商用API。同一个Java进程的心理上没区别部署到不同环境时通过环境变量或配置中心切换Profile整个AI能力就从本地小模型平滑切换到了云端大模型。应用代码中关于AI的这个部分不用动任何一个字节。具体配置分两种情况接Ollama本地模型引入dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-ollama/artifactId /dependency配置spring: ai: ollama: base-url: http://localhost:11434 chat: options: model: qwen2.5:7b接国内云端模型以OpenAI兼容协议为例这也是最常见的形态绝大多数服务商都支持配置几乎和章节2.2里一样。如果你想用阿里云Spring AI Alibaba官方也提供了封装更好的starter用法异曲同工。这种“代码不变、只改配置”的多模型切换能力对运维和架构的友好程度怎么高估都不过分。设想一下当某个模型服务商涨价或者出故障时你只需要改配置切换而不是改Java代码重新发版这对一个生产服务来说意味着什么做过多模型接入的人心里都清楚。4.3 MCPSpring AI 2.0最容易被低估的扩展机制MCP全称Model Context Protocol是这两年大模型应用领域最热门的协议之一。一句话描述它的作用把“模型调用外部工具”这件事标准化。你可以把MCP理解成AI领域的USB接口。USB让不同类型的设备通过统一接口连接电脑MCP让不同模型可以通过统一接口连接外部数据和工具服务。Spring AI 2.0从很早起就原生支持MCP客户端。这意味着你只需要在配置里声明某个MCP Server的地址Spring AI就能自动发现它暴露的工具并把这些工具注册给ChatClient让模型在回答时自动决定是否需要调用它们。举个实际例子。假设公司内部已经有人搭建了一个订单查询MCP Server暴露了一个queryOrder工具。你的业务服务只要在配置里加上MCP服务地址然后在ChatClient构建时开启MCP工具支持就能让模型具备“查询订单状态”的能力。spring: ai: mcp: client: enabled: true connections: order-service: type: sse url: http://order-mcp-server:8080/sseJava代码里大致这样使用Bean ChatClient chatClient(ChatClient.Builder builder) { return builder .defaultTools(mcp:queryOrder) .build(); }当用户问“我的订单OD20241201现在什么状态”模型会把这条问题转成一次对queryOrder的调用拿到返回结果后再组织成自然语言回答给用户。这个过程中你没有写一行“调用订单服务的HTTP代码”模型却自动完成了工具调用。需要提醒的是MCP不是银弹。它带来便利的同时也增加了链路复杂度特别是MCP Server的可用性和权限管理是必须关注的。我的经验是优先把成熟稳定的内部系统封装成MCP服务让团队内多个AI应用复用但对于一次性使用的小功能直接用函数调用第5章会讲反而更轻量。5. 实战进阶RAG和函数调用让大模型真正进入业务系统5.1 二选一之前先搞清楚RAG和微调的边界把大模型接进业务系统时几乎所有人都会遇到同一个问题模型不懂我们公司的内部知识比如员工手册、产品文档、私有业务数据。解决方案主要有两个微调和RAG。微调是拿一批标注数据去训练模型权重让模型“学会”某种特定知识或表现风格。它的成本比较高需要整理数据、准备微调环境、甚至可能需要GPU资源而且模型每次升级后微调结果可能还要重新做。普通业务团队不建议一上来就微调。RAG即检索增强生成是完全不同的思路。它不改变模型本身而是在模型回答问题之前先从你的私有知识库中检索相关内容把相关内容作为参考上下文塞进Prompt里让模型基于这些资料组织回答。这就像给模型发了一张“开卷考试的小抄”模型不需要背下你的全部知识只需要会读小抄。对大多数企业知识问答、制度查询、产品咨询类场景RAG是性价比更高的选择。Spring AI 2.0对RAG提供了从文档解析、切分、向量化、存储到检索增强的完整链路支持接下来用一个最小可运行的示例带你走通这条链路。5.2 从文档到问答一个你能跑通的RAG最小实例一个完整的RAG流程其实就分两大步第一步把知识文档“灌”进向量库第二步在提问时做相似度检索并交给模型回答。第一步的建设代码示例如下Service public class KnowledgeIndexService { private final VectorStore vectorStore; public KnowledgeIndexService(VectorStore vectorStore) { this.vectorStore vectorStore; } public void indexDocument(String filePath) { var reader new TikaDocumentReader(new FileSystemResource(filePath)); var splitter new TokenTextSplitter(); var documents splitter.apply(reader.get()); vectorStore.add(documents); } }TikaDocumentReader负责从docx、pdf等格式的文档中抽取文本TokenTextSplitter会把大段文本拆成固定大小的chunkVectorStore负责把每个chunk向量化并存储。三步走下来你的私有知识就进向量库了。第二步的问答接口GetMapping(/ask) public String ask(RequestParam String question) { var searchRequest SearchRequest.builder() .query(question) .topK(4) .build(); var similarDocs vectorStore.similaritySearch(searchRequest); String context similarDocs.stream() .map(Document::getText) .collect(Collectors.joining(\n---\n)); return chatClient.prompt() .system(你是一个企业内部知识助手请基于提供的参考资料回答问题。) .user(参考资料:\n context \n问题: question) .call() .content(); }这个接口做的事情很直观先用question在向量库里检索最相关的4个文档片段把它们拼成上下文再交给模型生成回答。你可以直接把员工手册、产品说明书这类文档塞进知识库然后问“年假怎么休”“退款流程是什么”这种内部问题效果比直接裸问模型好得多。我在测试环境用一套公司自己的制度文档跑这个流程回答的准确率从裸模型的30%左右直接升到80%以上差距非常明显。需要特别注意的是向量库选型。Spring AI支持Redis、PGVector、Milvus等主流向量存储。如果你的公司运维能力强PGVector可以直接复用现有PostgreSQL不用额外引入新组件如果数据量很大、并发要求高Milvus更合适。先在本地用Redis和docker跑通Demo是最快的路径。5.3 函数调用把自己写的Java Service暴露给大模型RAG解决的是“模型不知道”的问题函数调用解决的是“模型做不了”的问题。两者结合才能让AI应用从“聊天机器”进化成“数字员工”。Spring AI 2.0的函数调用非常方便核心就是Tool注解。看下面这个例子Service public class OrderTools { private final OrderMapper orderMapper; public OrderTools(OrderMapper orderMapper) { this.orderMapper orderMapper; } Tool(name queryOrderStatus, description 根据订单号查询订单当前状态) public String queryOrderStatus(ToolParam(description 订单号) String orderId) { OrderDO order orderMapper.selectByOrderId(orderId); if (order null) { return 订单不存在; } return 订单状态: order.getStatus(); } }然后构建ChatClient时把这个工具注册进去Bean ChatClient chatClient(ChatClient.Builder builder, OrderTools orderTools) { return builder .defaultTools(orderTools) .build(); }之后用户问“订单OD20241201到哪一步了”模型会自动决定调用queryOrderStatus方法拿到返回结果再组织语言回答。没有写任何调度逻辑模型自己在做工具选择。我在实战中最大的体会是函数调用是把大模型从“玩具”变成“生产力工具”的关键一步。让它能查数据库、能调内部API、能发工单它才能真正承担业务动作。RAG负责“知识补给”函数调用负责“动手做事”两者配合起来AI应用的能力边界一下子就被打开了。6. 避坑实录Spring AI 2.0实战中我踩过的7个坑6.1 JDK与Lombok的经典冲突踩过最直接的一个坑是开发环境用JDK 21但Lombok版本太旧一编译就报java: you arent using a compiler supported by lombok, so lombok will not work。这个报错看着吓人实际原因就是Lombok版本和JDK版本不匹配。解决方案是升级Lombok到1.18.30及以上版本同时在IDE的编译配置里确认注解处理器是开启状态。如果项目里还有老版本的mapstruct等注解处理库得一起升级否则会连锁报错。遇到java.lang.NoClassDefFoundError: java/applet/Applet这种异常时先检查依赖树里是不是有某个老库引用已被新版JDK移除的类。这个报错通常是第三方依赖和JDK版本不兼容导致的常见解法是升级那个老库或者排除掉无用的旧依赖。6.2 Spring Boot版本与自动配置不匹配Spring AI对Spring Boot版本要求非常严格。我一度在旧项目上直接把Spring AI 2.0的依赖塞进去结果启动时ChatModel的Bean根本不生成后面所有注入ChatClient的地方都直接抛NoSuchBeanDefinitionException。排查方向很明确去Spring Initializr生成一个正确的项目模板对照依赖版本号和父POM版本修自己的pom。最稳妥的做法是别在自己老项目里硬改版本新建一个配置正确的项目骨架然后把业务代码迁移过来。这比纠结一整天版本的性价比高很多。6.3 BaseUrl和API Key填错时的经典表现这类配置错误通常不会直接报错而是表现为“请求超时”或者“HTTP 404/401”。其中一个印象深刻的场景是我把base-url配成了平台的主站地址而不是API端点地址模型接口请求一直404。排查时先打印配置属性确认base-url和api-key是否正确加载再比对服务商文档里的API Path。还有一个比较隐蔽的坑是API Key带上了多余的前缀。比如某些平台要求Key以特定前缀开头配置时又重复加了一遍结果一直鉴权失败。这类问题在看日志时容易发现对你所在的服务商文档逐字节比对即可。6.4 超时与长任务别让对话接口半路卡死同步调用大模型接口最怕的就是默认超时时间过短。模型响应本来就要几秒遇到高峰期甚至几十秒连接超时一旦触发客户端就会拿到504。Spring AI底层走的是RestClient超时配置可以在application.yml里用spring.ai.openai.http-client配置或者自定义一个ClientHttpRequestFactory。RAG场景尤其要注意因为一次回答涉及向量检索模型生成总耗时可能比纯对话多出一倍不给足超时时间肯定不行。流式输出也有自己的坑当客户端断开连接后服务端如果响应流没有正确取消会出现线程堆积和内存泄漏。项目里我的处理是给Flux加超时与取消订阅机制避免异常断开时无限生成。6.5 结构化输出在老旧模型上翻车章节4.1的结构化输出很美好但它依赖模型对JSON Schema的理解和执行能力。本地部署的小参数模型、比较老旧的API版本可能返回的内容里有额外说明文字导致JSON解析失败甚至得到null。遇到这种情况先去模型侧确认版本是否支持结构化输出再考虑让模型“只输出合法JSON”作为兜底提示。要是上了生产还经常翻车建议彻底切换到结构化输出兼容性更强的商用模型。这类问题在本地用小模型测试时最容易放水因为测试数据少偶尔解析失败不会引起重视上线后量一旦上来就会暴露。6.6 依赖冲突Jackson和WebFlux的“跟班问题”Spring AI 2.0的依赖里包含Jackson和Reactor相关组件如果你的项目里这两个库有老版本定制极其容易产生NoSuchMethodError这类运行时错误。排查经验是把核心依赖的版本和Spring Boot BOM对齐用mvn dependency:tree查看冲突点。如果是老项目从Spring Boot 2.x升级到3.x这里可能会花不少时间。涉及到JsonMapper的定制、ObjectMapper的全局配置覆盖都会影响结构化输出的反序列化行为。建议在项目里统一通过Spring Boot的Jackson自动配置来定制ObjectMapper而不是到处new ObjectMapper。6.7 Observation和指标监控可观测性链路别忽略Spring AI 2.0集成了Micrometer Observation机制可以在模型调用、MCP工具调用时自动产生Metrics和Trace。这是个很好的能力但也是一个坑位如果你引入相关依赖却配置不当在某些场景下可能影响调用链路。我之前排查过一次MCP调用偶尔挂起的问题最后定位到是观测处理器的订阅逻辑在异常断开后没有释放连接。遇到AI调用偶发卡住的情况记得顺手看一眼Actuator暴露的Metrics如果模型调用计数器正常但工具调用耗时异常优先排查MCP和观测链路而不是盲目怀疑模型API本身。写在最后哪些场景我现在敢真正去掉Python哪些我还会留一手经过这一轮实战我个人的判断是企业内部知识问答、客服助手、结构化数据抽取、NL2SQL这类偏业务集成的大模型场景Java团队现在完全可以自己用Spring AI承接不用再依赖Python中转服务。RAG和函数调用这两个能力覆盖了绝大多数内部工具类需求开发效率也不输Python生态。但有两类场景我依然不会把Python甩开。一类是大规模模型微调和训练数据清洗、训练脚本、GPU调度这套东西Python生态的积累是碾压性的用Java硬写属于自讨没趣。另一类是重度依赖Python科学计算库的复杂数据处理比如音视频分析、复杂的统计模型、深度调优的RAG整体稳定性评测这些场景用Python工具链快速验证把结果用接口暴露给Java侧反而更合理。我觉得更务实的策略是全公司的AI基础设施让Python团队去优化模型侧的东西而业务侧的AI应用由Java团队用Spring AI直接写。两边通过标准API解耦而不是靠一堆不稳定的中间服务耦合。最后一个建议是开发顺序上先把最简单的ChatClient对话接口跑通再逐步加结构化输出、加RAG、加函数调用每走一步都做一次小验证。不要一上来就想搭一个大而全的AI平台步子迈太大最后只会卡在踩坑和调错的泥潭里。