
在实际 Java 项目中接入大模型2026 年已经不是一个实验性需求而是很多业务系统里真实存在的功能模块。Spring AI 2.0 是 Spring 官方统一 Java 侧 LLM 集成方式的框架DeepSeek 是目前 Java 开发者接入成本最低的国产对话模型之一Langchain4j 则提供了另一套偏轻量、偏链路的 Java AI 框架思路。与此同时Spring AI Alibaba 在国内环境下提供了一系列更贴近云上实践的组件。只要同时关注这几条技术路线就能在项目选型时把“用哪个框架”和“接哪个模型”拆开考虑。这篇教程不打算做成纯概念罗列而是按一条完整的落地链路来走先理解 Spring AI 2.0 是什么再准备好 JDK、Maven、API Key接着用 DeepSeek 跑通最小对话然后做结构化输出再进入向量检索场景最后对比 Langchain4j 和 Spring AI Alibaba并梳理生产环境常见问题。读完以后你应该能独立完成一个“DeepSeek Spring AI”的最小可运行项目也知道后续该往哪个方向扩展。1. Spring AI 2.0 到底改了哪些东西Java 接入大模型为什么值得关注1.1 从 Spring AI 1.x 到 2.0 的定位变化Spring AI 在 1.x 时代解决的核心问题是“让 Java 开发者能用类似 Spring 的风格调用大模型”。到了 2.0它不再只是把 OpenAI 客户端包装成一个 Bean而是把模型调用、提示词管理、结构化输出、向量检索、Agent 编排等能力做成了统一的抽象层。对业务开发者来说最直观的变化有三个ChatClient 成为和 RestTemplate、JdbcTemplate 一样的基础组件写模型调用越来越像写普通数据访问代码。模型接入通过 spring-ai-starter-model-* 这样的模块隔离切换模型供应商时业务代码可以保持稳定。AI 相关的自动配置被大幅精简application.yml 里配好 Key 和模型名就能直接注入 ChatClient。但要注意Spring AI 2.0 的 API 仍然在迭代不同小版本之间可能存在方法签名调整。实际项目落地时建议先锁定一个具体版本再查看该版本的官方参考文档不要直接照抄最新博客里的代码。1.2 三条技术路线的定位差异在搜资料时会发现同一个 DeepSeek 接入需求至少有三套写法Spring AISpring 官方方案适合已经使用 Spring Boot 的项目能和 Spring 的配置、监控、测试体系天然整合。Langchain4j更偏 LLM 应用的通用 Java 库不强制依赖 Spring适合想自己控制链路、或者非 Spring 技术栈的项目。Spring AI Alibaba面向国内云环境和阿里系中间件的派生方向提供了一些本地化组件和更细的 AI 应用编排能力。这三者不是互斥关系。最简单的项目可以只依赖 Spring AI如果业务里需要高度自定义 Prompt 链Langchain4j 的 AiServices 模型会更顺手如果整套基础设施都在阿里云上Spring AI Alibaba 可能更合适。1.3 本文的最小目标与学习路径这篇教程的最小目标是在一个 Spring Boot 工程里通过 Spring AI 调用 DeepSeek完成对话、结构化输出、向量检索三个场景。再往后文章会给出 Langchain4j 和 Spring AI Alibaba 的选型参考方便你决定下一步用哪套体系。学习路径可以这样安排先跑通最小对话理解 ChatClient 和模型配置。再做结构化输出理解模型返回如何映射成 Java 对象。然后加入向量库理解检索增强生成的基本链路。最后再看框架选型避免一开始就陷入多框架比较。2. 开发环境准备JDK、Maven、模型 API Key 三者缺一不可2.1 环境清单与版本确认在写代码之前先把环境检查一遍。Spring AI 2.0 整体对 Spring Boot 3.x / 4.x 的兼容情况在不同版本有差异下面是常见开发环境的参考清单。项目推荐参考值说明JDK17 或 21Spring Boot 3.x 要求 JDK 17 起步建议直接用 21Maven3.8仓库拉取 Spring AI BOM 需要可用的 Maven 仓库Spring Boot3.3.x / 3.4.x 或更高以 Spring AI 版本对应的兼容矩阵为准Spring AI2.0.x文中示例基于 2.x API 思路DeepSeek API Key从开放平台申请用于访问对话模型需要单独配置Embedding 模型qwen embedding 或 OpenAI 兼容接口用于向量检索场景与对话模型分开配置注意原始材料没有给出固定的 Spring Boot 版本因此落地前要确认依赖版本和兼容矩阵。不要在 pom.xml 里随意写一个高版本 Spring Boot 去搭配低版本 Spring AI否则很容易出现自动配置不生效的问题。2.2 申请 DeepSeek API Key 并核对模型名DeepSeek 的接入方式和 OpenAI 兼容 API 非常接近。在 DeepSeek 开放平台完成注册后可以创建 API Key。创建之后需要注意两点第一API Key 只显示一次离开页面后无法再次查看需要自己保存好。建议使用环境变量或本地配置中心管理不要把 Key 硬编码提交到 Git 仓库。第二模型名要以官方文档为准。常见的对话模型有 deepseek-chat、deepseek-reasoner 等不同时期开放平台可能增加或调整模型。写配置时不要把模型名写死成博客里的示例值先到开放平台确认当前模型名。export DEEPSEEK_API_KEYsk-你的key在本地开发时这个环境变量会被 Spring 的配置占位符读取避免 Key 出现在代码中。2.3 新建一个干净的 Spring Boot 工程创建一个最小工程建议先不引入复杂依赖。在 start.spring.io 或者 IDE 初始化工具中选择 Spring Web 和 Spring AI 相关依赖即可。如果 IDE 里还没有 Spring AI 选项可以在 pom.xml 中手动引入 BOM。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.4.x/version relativePath/ /parent properties java.version21/java.version spring-ai.version2.0.x/spring-ai.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement核心依赖只需要一个 Web Starter 和一个模型 Starter。dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency /dependenciesDeepSeek 提供 OpenAI 兼容接口所以这里复用 OpenAI Starter然后把 base-url 指向 DeepSeek 即可。这个方式是当前 Java 生态接 DeepSeek 最常用的做法。2.4 确认依赖能拉到再进行配置引入依赖后先执行一次 Maven 编译确认依赖能正常拉取。mvn -DskipTests package这一步可以提前发现仓库源、版本号错误、BOM 冲突等问题。不要直接启动应用否则一旦依赖缺失报错会混在一起排查效率很低。这里有第一个常见坑如果只引入了 spring-ai-starter-model-openai但 pom.xml 里没有导入 spring-ai-bom会导致 Spring AI 的多个子模块版本不一致运行时经常出现 NoSuchMethodError。java.lang.NoSuchMethodError: org.springframework.ai.chat.client.ChatClient.create(Lorg/springframework/ai/chat/client/ChatClient$Builder;) ...这类错误在第一眼看起来像代码写错实际是依赖版本没有对齐。先检查 dependencyManagement 是否生效再检查是否同时引入了多个 Spring AI 版本的传递依赖。3. 最小对话案例用 DeepSeek 跑通第一个 ChatModel 调用3.1 为什么先跑最小对话接入大模型时最容易踩的坑不是业务代码写不出来而是配置项、模型名、网络访问、序列化方式这些基础问题没解决。先跑一个没有业务逻辑的最小对话可以快速确认配置链路是否正确。最小对话的验证目标是Spring AI 的自动配置是否生效。DeepSeek API Key 是否有效。模型名是否配置正确。ChatClient 能否正常注入和调用。在这个阶段不要关心 Prompt 设计也不要关心上下文管理。只发一句话收一句话确认链路通。3.2 application.yml 中的核心配置在 application.yml 中做如下配置spring: application: name: spring-ai-deepseek-demo ai: openai: base-url: https://api.deepseek.com api-key: ${DEEPSEEK_API_KEY} chat: options: model: deepseek-chat temperature: 0.7这里要注意几个参数的含义base-urlDeepSeek 的 OpenAI 兼容网关地址。具体是 https://api.deepseek.com 还是 https://api.deepseek.com/v1以官方文档为准部分 SDK 会拼接 v1 路径配错会返回 404。api-key从环境变量读取避免把 Key 提交到代码库。model模型的名称必须和开放平台当前提供的一致。temperature控制生成随机性0 到 1 之间值越高回答越发散。知识库问答场景建议调低创意文案场景可以调高。如果项目里同时接多个模型可以给不同模型增加不同的配置前缀。实际项目中推荐把 base-url、model 这些基础项放到配置中心不要散落在多台服务器上。3.3 用 ChatClient 实现对话接口先定义一个简单的 ServiceService public class DeepSeekChatService { private final ChatClient chatClient; public DeepSeekChatService(ChatClient.Builder builder) { this.chatClient builder .defaultSystem(你是一个乐于助人的 Java 技术助手回答尽量简洁。) .build(); } public String chat(String message) { return chatClient.prompt() .user(message) .call() .content(); } }然后提供一个 HTTP 接口方便验证RestController RequestMapping(/api/chat) public class ChatController { private final DeepSeekChatService chatService; public ChatController(DeepSeekChatService chatService) { this.chatService chatService; } GetMapping(/{message}) public String chat(PathVariable String message) { return chatService.chat(message); } }这段代码的核心是 ChatClient.Builder。通过 builder.defaultSystem 设置系统提示词之后每次调用 prompt().user(...) 时系统提示词会自动带上。3.4 运行验证与日志观察启动应用后访问接口curl http://localhost:8080/api/chat/什么是向量数据库正常返回的是一段文本。第一次调用时可能比较慢这是因为模型推理本身需要时间并不是程序卡死。如果配置正确日志里会看到 Spring AI 自动装配相关信息。如果配置不正确最常见的是以下几类错误。3.5 常见坑模型名配错、401、429现象常见原因检查方式处理建议返回 401API Key 无效或未读取到环境变量检查启动日志确认 api-key 是否被替换成 ${DEEPSEEK_API_KEY} 字面量确认环境变量已设置重启应用返回 404base-url 路径拼接错误看异常中的请求 URL核对官方文档检查是否需要 v1 后缀返回 400提示 model not found模型名称不存在到开放平台查看当前模型列表修改 model 配置返回 429请求超限或余额不足查看开放平台余量增加限流和重试设置合理 timeout长时间无响应网络不通或超时时间过短看 Http 客户端日志设置更长的 read timeout检查网络链路这里有一个关键点Spring AI 在调用 OpenAI 兼容接口时如果 base-url 配错异常信息往往很晚才抛出且可能被包装成通用的远程调用异常。先看最底层的 cause 类型再判断是网络问题还是认证问题。注意不要一上来就写复杂的重试逻辑。先确认手工调用一次能成功再考虑超时、重试、并发限流这些非功能性增强。4. 结构化输出让模型返回 Java 实体而不是一段难以解析的文本4.1 为什么需要结构化输出在大模型应用里最常见的需求不是让模型写一段话而是让模型填一个表单、抽取一条记录、生成一个对象。如果模型返回的是普通文本业务代码还需要做字符串切割、正则匹配、JSON 解析这个过程非常脆弱。例如从一段商品评论里抽取“商品名称、评分、问题标签”如果让模型自由发挥它可能返回带 Markdown 格式的 JSON也可能漏字段也可能把数字返回成字符串。结构化输出的作用就是约束模型按预定义的 JSON Schema 返回再由框架反序列化成 Java 对象。Spring AI 2.0 中ChatClient 提供了 entity() 方法来完成这件事。实体类可以用普通 class也可以用 Java record。public record ReviewInfo( String productName, int rating, ListString issueTags, String summary ) { }4.2 定义实体类与调用方式定义一个 ServiceService public class ReviewAnalysisService { private final ChatClient chatClient; public ReviewAnalysisService(ChatClient.Builder builder) { this.chatClient builder.build(); } public ReviewInfo analyze(String reviewText) { return chatClient.prompt() .system(你是一个商品评论分析助手。请只返回 JSON不要输出任何解释或 Markdown 代码块。) .user(请分析下面这条评论 reviewText) .call() .entity(ReviewInfo.class); } }调用的结果不再是字符串而是一个 ReviewInfo 对象业务代码可以直接读取 productName、rating、issueTags。这段代码背后Spring AI 会把实体类结构转换成 JSON Schema 描述发送给模型时要求严格按 Schema 输出。模型中如果本身支持 JSON mode 或结构化输出效果会更稳定。4.3 不同结构化输出方式的取舍实际项目中结构化输出有几种实现思路方式优点缺点适用场景纯提示词要求返回 JSON实现简单不依赖框架模型偶尔会附带解释文字解析不稳定内部工具、非关键链路ChatClient.entity()由框架完成 Schema 生成和反序列化复杂嵌套类需要测试不同模型支持度对外接口、业务核心链路自定义 JSON Schema 二次校验可控性最强成本高需要写校验逻辑对字段精度要求很高的场景推荐做法是先用 ChatClient.entity() 跑通然后在代码里对关键字段做非空校验和类型校验。不要完全信任模型的输出模型再稳定也有概率返回 null 字段或非法枚举值。4.4 验证解析结果与异常分支验证方式很简单{ productName: 无线机械键盘, rating: 4, issueTags: [按键响应慢, 包装破损], summary: 整体性能不错但到货时包装破损键盘有个按键响应偶尔变慢。 }如果模型返回的内容无法解析Spring AI 会抛出类似下面的异常Failed to convert AI response to target type ...这时先看原始输出是什么。常见原因是模型返回了带注释的 JSON或者把 JSON 包在 json 代码块中导致反序列化失败。第二个常见坑是枚举类型解析失败。比如实体类里定义了一个 RatingLevel 枚举模型返回的数字或中文字符串无法精确匹配枚举值。建议在实体类中使用 String 接收原始值再通过自定义转换器处理避免反序列化直接抛异常。第三个常见坑是嵌套对象太深。Spring AI 的实体类转换依赖 Jackson如果类里有复杂循环引用会出现转换失败。尽量使用扁平结构或 record 类型减少循环引用。5. 从对话到检索把 DeepSeek 和 Milvus 组合成知识库问答5.1 为什么需要向量库Embedding 模型怎么选单纯用大模型回答问题时模型只能依据训练数据。如果希望模型回答“公司内部某文档里写了什么”就必须先把文档切成片段、转成向量、存入向量库需要时把相似片段检索出来拼进 Prompt。这个过程就是最常见的检索增强生成。向量库选择时Milvus 是很多团队在中等规模数据量下的选择。Spring AI 对 Milvus 提供了 VectorStore 集成调用方式和 Elasticsearch、Redis 等向量存储类似。选 Embedding 模型时要注意DeepSeek 开放平台主要提供对话模型向量化通常采用其他兼容 OpenAI 接口的嵌入模型例如 qwen embedding。业务代码里Embedding 模型和对话模型是分开配置的。5.2 引入依赖和基础配置在 pom.xml 中加入 Milvus 相关依赖dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-milvus-store/artifactId /dependency具体模块名在不同版本可能有调整以当前发布版本为准。接下来需要配置 Milvus 连接信息和向量维度spring: ai: vectorstore: milvus: client: host: ${MILVUS_HOST:localhost} port: 19530 username: ${MILVUS_USERNAME:} password: ${MILVUS_PASSWORD:} database-name: default collection-name: java_rag_demo embedding-dimension: 1024embedding-dimension 必须和 Embedding 模型输出的维度一致。qwen embedding 的不同版本可能输出不同维度一般以模型文档为准。如果配错写入向量时会直接报维度校验错误。5.3 文档写入向量库覆盖旧数据的方案写入文档的常规思路是读取文档内容。将文档切分成多个片段。每个片段转成向量。将向量和原文一起存入 Milvus。Spring AI 中可以使用 Document 对象描述一个文档ListDocument documents List.of( new Document(Spring AI 2.0 支持 ChatClient、结构化输出和向量存储。, Map.of(source, manual.md)), new Document(Milvus 是面向大规模向量的开源向量数据库。, Map.of(source, manual.md)) ); vectorStore.add(documents);这里有一个非常常见的坑如果每次重新导入同一份文档直接调用 add向量库中会多出重复文档。原因在于 add 方法默认按文档对象的 id 写如果没有指定稳定 id框架会生成随机 id。推荐做法包括两种写入前生成稳定文档 id例如使用文档路径 分片序号作为 id。写入前先按业务标识删除旧数据再添加新数据。Spring AI 的 VectorStore 接口提供了 delete 方法支持按 id 删除vectorStore.delete(List.of(manual.md-0, manual.md-1));如果使用 Elasticsearch 作为向量存储现象是相同的。每次重新导入都新增一份文档本质都是没有按稳定 id 或没有先 delete 再 add。public void upsertDocuments(String bizId, ListDocument documents) { ListString oldIds documents.stream() .map(doc - bizId - doc.getMetadata().get(chunkIndex)) .toList(); vectorStore.delete(oldIds); vectorStore.add(documents); }5.4 检索调用示例检索时先把用户问题转成向量再从向量库中找最相似的片段最后把片段拼入 Prompt。Service public class KnowledgeBaseService { private final VectorStore vectorStore; private final ChatClient chatClient; public KnowledgeBaseService(VectorStore vectorStore, ChatClient.Builder builder) { this.vectorStore vectorStore; this.chatClient builder.build(); } public String answer(String question) { ListDocument docs vectorStore.similaritySearch( SearchRequest.builder() .query(question) .topK(4) .build() ); String context docs.stream() .map(Document::getText) .reduce((a, b) - a \n b) .orElse(); return chatClient.prompt() .system(请严格基于提供的资料回答。如果资料中没有答案请直接说明。) .user(资料\n context \n\n问题 question) .call() .content(); } }topK 表示返回最相似的片段数量。在知识库问答中topK 通常取 3 到 5太小容易漏掉关键信息太大会超过模型的上下文限制且无关片段会干扰回答质量。5.5 常见坑维度不一致、批量写入失败、ES 覆盖问题问题现象常见原因处理建议写入向量库报维度错误embedding-dimension 与模型输出维度不一致先打印模型返回的向量长度再修改配置检索结果为空集合里没有数据或 collection-name 配置错误先检查集合是否存在再查写入是否成功每次导入都重复新增没有使用稳定文档 id删除旧 id 后再 add或用业务 id 拼接 chunkIndex中文检索效果差分段粒度过大或 Embedding 模型不适合中文按段落或句子切分测试不同 embedding 模型批量写入很慢每次写入都单独提交使用批量接口控制每批数据量模型回答不基于资料Prompt 约束不够强或检索到的片段被窗口截断强化系统提示词增加来源限制检查上下文拼接长度这里有一个容易被忽略的点向量检索只能找出“语义相似”的片段不能保证片段里包含正确答案。生产环境需要对检索结果做二次过滤例如按来源、时间、业务分类过滤减少无关片段进入 Prompt。6. Langchain4j 与 Spring AI Alibaba什么时候选它6.1 Langchain4j 的定位和适用场景Langchain4j 的目标是给 Java 开发者提供一套完整的 LLM 应用开发库。它不绑定 Spring也可以配合 Spring 使用。与 Spring AI 相比Langchain4j 在几个方面有明显差异更强调 AiServices 这种服务代理模式可以通过接口定义 AI 能力。更早提供了 MessageWindowChatMemory、TokenWindowChatMemory 这类内存管理抽象。在 RAG 领域提供了更多分段策略和检索组件的灵活组合。如果项目不是 Spring Boot 体系或者团队希望完全控制 Prompt 组装、消息窗口、工具调用链路Langchain4j 会更合适。它对 Spring 依赖更小单元测试也更容易写。6.2 Spring AI Alibaba 的定位和适用场景Spring AI Alibaba 可以理解为面向更完整 AI 应用编排的集成体系。在搜索材料中经常能看到 Spring AI Alibaba Graph 这类内容它解决的是多个模型调用、条件分支、循环执行等复杂流程编排问题。如果业务场景类似于先决定是否需要查数据库。然后决定调用哪个工具。再根据工具结果决定是否追问。最后汇总答案。这类多步骤流程用 Graph 编排比在业务代码里写 if-else 更清晰。但选型时要先评估团队是否真的需要这套编排能力。如果只是简单的“查资料、拼接 Prompt、调用模型”直接用 Spring AI 就够不要引入额外的图编排模块增加学习成本和维护成本。6.3 选型对比表维度Spring AILangchain4jSpring AI Alibaba核心模型Spring 官方统一抽象独立 Java LLM 库面向 AI 应用编排的国内生态Spring 依赖深度整合 Spring Boot可选不强制基于 Spring AI 思路扩展学习曲线中中偏高涉及更多组件典型场景常规模型接入、RAG、结构化输出需要灵活控制链路和内存的项目多步骤流程、图编排、国内云场景社区资料官方文档较全文档较全示例多国内资料逐渐增加生产风险版本迭代较快自定义代码多需要自己维护链路依赖版本和云厂商绑定需评估实际项目选型建议如果只是接入 DeepSeek 做对话和简单 RAG选 Spring AI。如果对消息窗口、 Tool Calling、AI Service 代理有强需求且希望脱离 Spring 也能测试考虑 Langchain4j。如果整个后端运行在阿里云且业务中确实需要流程编排再考虑 Spring AI Alibaba。6.4 一个语言模型多个接入层不要把“模型”和“框架”混在一起。DeepSeek 是模型供应商Spring AI、Langchain4j、Spring AI Alibaba 都是接入层。同一个 DeepSeek 模型可以同时被多套框架调用也可以在必要时更换框架而保留模型。这种解耦的价值很明显今天用 Spring AI 调 DeepSeek明天想换 qwen 或者别的 OpenAI 兼容模型只需要修改配置和少量代码不需要重写业务逻辑。7. 生产环境必须处理的连接、上下文和监控问题7.1 连接中断与重连策略本地跑通对话之后不能直接部署到生产环境。生产环境最常见的问题是大模型接口偶尔超时、连接中断、上游暂时不可用。Spring AI 底层使用 HTTP 客户端发起同步调用。生产环境需要考虑三件事设置合理的连接超时和读取超时避免线程长时间阻塞。配置超时重试但必须控制重试次数避免雪崩。对调用结果做兜底例如返回友好提示或降级文案。可以参考下面的配置思路spring: ai: openai: client: connect-timeout: 10s read-timeout: 60s如果框架未提供超时配置项可以直接在 HTTP 客户端层配置。重试建议使用 Spring Retry 或自定义拦截器只对可重试异常进行重试。注意不要对 400、401 这类确定错误进行重试重试只会浪费请求次数和时间。只有超时、429、5xx 这类瞬时错误才值得重试。7.2 上下文数量限制如何控制对话类应用一旦进入多轮交互历史消息会不断累积最终超过模型的上下文窗口。控制上下文通常有两种思路一是控制会话消息数量。例如只保留最近 10 轮用户和助手消息更早的丢弃。二是控制 Token 数量。根据模型支持的最大上下文窗口计算当前 Token 估算值接近上限时裁剪最旧的消息。在 Spring AI 中可以通过 ChatMemory 实现消息窗口管理ChatMemory chatMemory MessageWindowChatMemory.builder() .maxMessages(20) .build();这个组件可以让 ChatClient 在维护多轮对话时自动丢弃超出窗口范围的消息。实际项目里可以把这个参数做成配置项允许不同业务场景使用不同窗口大小。7.3 日志、超时与成本控制生产环境至少需要记录以下信息日志项记录内容用途请求日志调用模型前的问题、参数、耗时排查业务问题响应日志模型返回摘要、结构化输出结果审计和验证异常日志异常类型、状态码、底层 cause定位故障成本日志Token 用量、模型名、调用时间费用归因和优化注意不要把完整 Prompt 和模型输出原样全量写入日志。Prompt 中可能包含用户隐私或业务敏感信息建议先脱敏再记录。Token 用量可以从模型返回的 usage 字段读取。7.4 上线前检查清单上线前可以按这个清单逐项确认API Key 是否通过环境变量或配置中心注入Git 仓库中是否残留 Key。模型名和 Embedding 模型维度是否与当前环境一致。超时、重试、限流参数是否已经配置。向量库集合是否已创建维度是否匹配。文档导入是否使用稳定 ID重复导入能否覆盖。多轮对话消息窗口是否有上限。是否存在敏感信息泄露风险日志是否脱敏。模型调用失败时业务接口是否有降级文案。Token 用量是否埋点能否在监控面板上看到。是否预留了模型切换开关方便紧急情况下切换备用模型。这十条看起来琐碎但每一条都在真实生产事故里出现过。8. 常见问题排查从现象到根因8.1 排查顺序当 Spring AI 接入 DeepSeek 出现问题时不要先怀疑框架。按下面的顺序排查效率最高输入是否正确Prompt、参数、实体类字段是否写错。配置是否生效环境变量是否读取base-url、model 是否拼错。网络链路是否正常能否直接访问 API是否有超时。认证是否通过Token 是否有效余额是否充足。依赖版本是否对齐BOM 是否导入是否有传递依赖冲突。日志中的底层异常是什么是 JSON 解析失败还是 HTTP 状态码错误。模型本身是否支持该能力结构化输出、JSON 模式、工具调用是否有版本限制。8.2 thinking mode 下的 reasoning_content 报错有搜索材料中提到了一个非常典型的 400 错误现象类似upstream_status: http 400 cause: the reasoning_content in the thinking mode must be passed back to the api这个问题发生在模型开启思考模式、且调用方使用 OpenAI 兼容接口传递多轮对话历史时。思考类模型在返回时除了 content还会返回 reasoning_content 字段进行下一轮对话时OpenAI 兼容协议要求把上一轮的 reasoning_content 原样传回否则 API 返回 400。排查方式看请求体里是否保留了上一轮 assistant 消息的 reasoning_content。看中间层是否对消息做了清洗例如把带 reasoning 标记的消息删除。看是否使用了不支持思考模式回传的 SDK。解决方式有两种使用支持该字段回传的 SDK 或客户端保证 assistant 消息完整保留。普通对话场景改用非思考模型避免 reasoning_content 参与多轮回传。如果项目里封装了一层统一的 HTTP 调用客户端需要特别检查消息序列化时是否丢字段。不要简单地把 reasoning_content 从消息里剥离。8.3 向量库写入重复与覆盖问题前面已经提到向向量库导入文档时如果每次都新增就会出现大量重复内容。排查顺序先查集合中是否存在同一文档的多个副本。再确认 Document 对象是否设置了稳定 ID。再检查导入代码中是否执行了 delete 或 upsert。推荐方案是使用业务 ID 拼接分片序号作为文档 ID并在导入时先删除再写入。如果是全量重建索引可以直接删除整个集合并重建效率更高。8.4 配置修改不生效、Key 泄露、模型名称不匹配问题现象常见原因处理建议api-key 变成 ${DEEPSEEK_API_KEY} 字符串服务启动时环境变量不存在在当前进程导出环境变量后重启base-url 修改后仍请求旧地址应用缓存了旧配置或加载了其他 Profile检查 spring.profiles.active 和配置来源Key 泄露到 Gitapplication.yml 中硬编码使用 git-secrets 或泄露扫描工具清理模型名包含空格或多余字符从网页复制时带入了换行符检查模型名前后是否有不可见字符结构化输出偶尔解析失败模型返回了 Markdown 或附加文本加强提示词约束增加二次解析8.5 结构化输出频繁解析失败如果 entity() 转换经常失败不要反复调模型先找规律收集原始返回内容看是 JSON 格式问题还是字段类型问题。如果是枚举字段解析失败把字段类型改成 String。如果是嵌套对象解析失败检查是否有循环引用。如果是 JSON 外层包了 Markdown 代码块在解析前做预处理或提高系统提示词约束力度。也可以在调用 entity() 之前先调用 content() 拿到原始字符串然后手动用 Jackson 解析。这样能更清楚看到模型实际返回的内容。String raw chatClient.prompt() .system(请只返回 JSON) .user(分析 text) .call() .content(); ObjectMapper mapper new ObjectMapper(); ReviewInfo info mapper.readValue(raw, ReviewInfo.class);手动解析比 entity() 多写几行代码但排查问题时很有效。生产链路可以用一个公共的 JSON 清洗工具统一处理代码块包裹、注释、尾部逗号这类常见问题。9. 从最小项目到业务落地的建议Spring AI 2.0 的最大价值不是“多了一个调用大模型的客户端”而是把模型接入、结构化输出、向量检索这些能力放进了 Spring 的编程模型里。对 Java 开发团队来说这意味着可以少写很多胶水代码也能直接用 Spring 现有的配置、测试、监控体系去管理 AI 调用。这篇教程从环境准备走到向量检索再走到框架选型最终希望达成的效果是你能用最小代码把 DeepSeek 接入 Spring Boot并且知道上线前要处理哪些非功能性风险。如果继续深入建议按下面的顺序练习把最小对话改成多轮对话测试 ChatMemory 消息窗口。把结构化输出接入真实业务表加入字段校验和兜底逻辑。用一个真实的内部文档库测试不同分段策略对检索效果的影响。尝试在 Langchain4j 中实现同样的功能对比两套框架的开发和排查成本。如果业务出现复杂流程再研究 Spring AI Alibaba 的图编排能力。每一步都要写清楚验证方式和日志不要只满足于“能运行”。在生产项目里能稳定运行、能快速排错、能低成本维护才是接入大模型后真正重要的能力。