ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java后端接入大模型实战:Spring AI 2.0 + DeepSeek + LangChain4j 保姆级教程

Java后端接入大模型实战:Spring AI 2.0 + DeepSeek + LangChain4j 保姆级教程 如果你是一个 Java 后端开发2026 年无论如何都绕不开这三个名字Spring AI、LangChain4j、DeepSeek。Spring AI 2.0 把 LLM 接入做成了经典的 Spring 风格LangChain4j 在 Java 生态里提供了类似 LangChain 的编排能力DeepSeek 则把大模型 API 的成本和效果拉到了非常有竞争力的位置。这篇文章不是概念科普而是一套可以照着敲的保姆级流程从创建 Spring Boot 项目、接入 DeepSeek API到结构化输出、RAG 向量检索、批量任务和接口化全部跑通。默认你的环境是 JDK 17、Spring Boot 3.x用 Maven 管理依赖。DeepSeek 使用云端 API不需要本地显卡所以显存、CUDA 这些在这个教程里不是门槛如果你要在本地跑 Qwen 或 DeepSeek 的蒸馏模型那才需要关注 Ollama 和显存占用。文章会把云端 API 和本地模型两条路径都说清楚你按自己的场景选。另外Spring AI Alibaba 值得单独拿出来看。它对 Qwen 系模型、DashScope、以及 Graph 图编排提供了更完整的支持很多企业项目在 Spring AI 基础上直接叠加它来做私有化 AI 应用。下面按照实际开发中最常用的接入方式展开每一步都可以直接复制到你的项目里验证。1. 核心能力速览能力项说明项目类型Java 生态大模型应用开发框架核心功能对话、流式输出、结构化输出、RAG、向量存储、Tool Calling、Agent 编排模型接入DeepSeek API、OpenAI 兼容接口、Ollama 本地模型、Qwen/DashScope主要组件Spring AI 2.0、LangChain4j、Spring AI Alibaba推荐环境JDK 17、Spring Boot 3.x、Maven 或 Gradle启动方式Spring Boot 标准启动内嵌 Tomcat接口能力支持将 AI 能力封装为 REST API供前端、移动端或外部系统调用批量任务支持异步批处理、任务队列、失败重试向量库支持 Milvus、Elasticsearch、Redis、PGVector 等是否支持本地部署支持可通过 Ollama 部署 Qwen 或 DeepSeek 蒸馏模型适合场景Java 后端接入大模型、私有知识库、智能客服、AI 应用服务化从这张表格能看出来这套组合解决的不是“怎么调一个模型接口”的问题而是“怎么把大模型能力工程化地放进 Java 后端系统”的问题。如果你之前只用 Python 写过 AI 脚本Spring AI 2.0 会给你一套更贴近企业项目习惯的写法。2. 技术栈分工Spring AI、LangChain4j、DeepSeek、Spring AI Alibaba 各管什么很多初学者容易把这四个概念混在一起。先理清分工后面写代码才不会乱。Spring AI 是 Spring 官方推出的 AI 框架目标是让 Java 开发者用最小的成本接入大模型。它的核心抽象是ChatClient、EmbeddingModel、VectorStore、ToolCallback等只要配置好模型提供方业务代码基本不用改。Spring AI 2.0 相比 1.x 更强调模块化模型接入、向量数据库、Agent 编排被拆分得更清楚同时兼容了大量主流模型厂商。LangChain4j 是 Java 生态里的 LLM 编排框架设计灵感来自 Python 的 LangChain。它擅长做对话记忆管理、结构化输出、RAG、Tool Calling 和 Agent 流程编排。LangChain4j 和 Spring AI 不是对立关系两者在功能上有重叠但在工程集成上各有优势。Spring AI 更“Spring 原生”适合深度使用 Spring Boot 的项目LangChain4j 更灵活RAG 和 Agent 示例也更丰富。实际项目里有人只用其中一个也有人在一个系统里同时引入两者分别承担不同模块。DeepSeek 在这套组合里是模型提供方。DeepSeek 的 API 兼容 OpenAI 协议这意味着 Spring AI 和 LangChain4j 里现成的 OpenAI 客户端稍作配置就能对接。常用的模型名是deepseek-chat和deepseek-reasoner前者适合通用对话后者支持思考模式但调用时要注意处理reasoning_content字段。Spring AI Alibaba 是阿里开源的项目基于 Spring AI 做了大量扩展。它的价值主要有三点第一对 Qwen 通义千问系列模型的接入做了封装包括文本生成、Embedding、语音等第二提供 DashScope 平台的适配企业如果已经用阿里云百炼可以直接对接第三提供了 Graph 图编排模块可以用节点和边的方式设计 AI 工作流相当于 Java 版的轻量 LangGraph。一句话总结分工Spring AI 2.0 是主框架LangChain4j 是增强型工具集DeepSeek 是背后的模型引擎Spring AI Alibaba 负责把阿里系能力补齐。四个组件可以组合使用也可以按需取舍。3. 环境准备与前置条件在动手前先把环境检查一遍。以下是这套教程的最小环境清单每一项如果不满足后面跑起来会出现各种奇怪问题。检查项要求说明JDK17 及以上Spring Boot 3.x 强制要求 JDK 17Spring Boot3.2 及以上更高版本兼容性更好推荐 3.3构建工具Maven 3.6 或 Gradle 7.5本文示例使用 MavenDeepSeek API Key必选在 DeepSeek 开放平台创建充值和开通模型服务网络能访问 DeepSeek API国内网络可以直接访问无需额外手段可选组件Milvus、Elasticsearch、Ollama只有做 RAG 或本地模型时才需要磁盘空间2GB 左右主要是 Maven 依赖和日志若本地跑 Ollama 模型需额外预留 10GBDeepSeek API Key 的申请路径很简单打开 DeepSeek 开放平台完成注册进入控制台创建 API Key然后把 Key 保存到本地环境变量或配置文件中。注意 API Key 只在创建时完整展示一次之后无法再次查看只能重新创建。如果你打算在本地跑 Ollama 模型还要提前安装 Ollama 客户端并下载对应的模型比如qwen2.5或 DeepSeek 蒸馏版本。显存占用取决于模型大小7B 级别模型通常需要 6GB 左右显存小参数模型可以用 CPU 跑但速度会明显慢。这里不展开具体数字以你本机实际测试为准。如果你的目标是做 RAG需要准备向量数据库。Milvus 是比较流行的选择本地可以用 Docker 快速起一个单机版Elasticsearch 适合已经在用 ES 做搜索的公司可以直接把向量索引和业务索引统一管理。两种方案后面我都会给出接入思路。4. 搭建项目并接入 DeepSeek4.1 创建 Spring Boot 项目最简单的方式是去 Spring Initializr 生成一个基础项目也可以直接用 IDE 创建。关键点是勾选 web 依赖语言选择 JavaBoot 版本选择 3.x。4.2 引入 Spring AI 相关依赖DeepSeek 兼容 OpenAI 协议所以 Spring AI 侧不需要单独的 DeepSeek starter直接使用 OpenAI 模块把 base-url 指向 DeepSeek 即可。先引入 BOM 管理版本再引入具体模块。properties java.version17/java.version spring-ai.version2.0.0/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 dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency /dependencies上面的spring-ai.version是我给的一个示例值实际使用时请去 Maven 中央仓库查一下当前最新稳定版本替换成真实版本号。不要把 2.0.0 当成固定结论Spring AI 迭代很快版本之间可能存在 API 差异。如果要用 LangChain4j可以额外引入它的核心包和 OpenAI 模块。这里建议先跑通 Spring AI再叠加 LangChain4j避免一开始两个框架的配置互相干扰。dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version${langchain4j.version}/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version${langchain4j.version}/version /dependencylangchain4j.version同样需要替换为实际最新版本。4.3 配置 DeepSeek 连接在application.yml中加入以下配置spring: application: name: spring-ai-demo ai: openai: base-url: https://api.deepseek.com api-key: ${DEEPSEEK_API_KEY} chat: options: model: deepseek-chat temperature: 0.7DEEPSEEK_API_KEY建议通过环境变量注入不要硬编码在配置文件中。如果你的 DeepSeek 账号支持/v1路径也可以把 base-url 写成https://api.deepseek.com/v1两种写法对 OpenAI 兼容客户端来说通常都能生效。4.4 写第一个对话接口Spring AI 2.0 的核心对象是ChatClient。在配置类里注入ChatClient.Builder然后构建一个全局的ChatClient实例。package com.example.demo; import org.springframework.ai.chat.client.ChatClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class AIConfig { Bean public ChatClient chatClient(ChatClient.Builder builder) { return builder.build(); } }接着写一个 REST 接口把对话能力暴露出去。package com.example.demo; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient chatClient) { this.chatClient chatClient; } GetMapping(/chat) public String chat(RequestParam(defaultValue 你好请介绍一下你自己) String message) { return chatClient.prompt(message) .call() .content(); } }启动项目后访问http://127.0.0.1:8080/chat?message你好如果返回一段正常的中文回复说明 Spring AI 2.0 到 DeepSeek 的链路已经通了。这是整个教程的“地基”后面所有功能都在这个基础上扩展。5. 功能测试对话、流式输出与结构化输出5.1 流式输出普通接口一次返回全部内容适合内部工具但做智能客服或前端对话框时流式输出体验更好。Spring AI 的流式输出返回FluxString前端可以用 SSE 方式接收。import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; RestController public class StreamChatController { private final ChatClient chatClient; public StreamChatController(ChatClient chatClient) { this.chatClient chatClient; } GetMapping(/chat/stream) public FluxString streamChat(RequestParam String message) { return chatClient.prompt(message) .stream() .content(); } }Vue 前端做对话页面时可以用EventSource或fetch配合ReadableStream接收流式数据。这里给一个简单的 fetch 思路具体封装方式看你的前端框架。const response await fetch(/chat/stream?message encodeURIComponent(text)); const reader response.body.getReader(); const decoder new TextDecoder(utf-8); let result ; while (true) { const { done, value } await reader.read(); if (done) break; result decoder.decode(value, { stream: true }); }5.2 多轮对话与上下文数量限制大模型本身是无状态的多轮对话需要手动把历史消息传给模型。Spring AI 里有两个方案一是自己维护消息列表二是使用内置的ChatMemory。用内置方案时可以限制上下文数量避免历史消息无限增长导致 token 费用过高。import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class ChatMemoryConfig { Bean public MessageWindowChatMemory chatMemory() { return MessageWindowChatMemory.builder() .maxMessages(20) .build(); } }这里的maxMessages(20)就是控制上下文窗口条数。20 条是一个保守值具体要看你用的模型上下文长度。如果业务场景需要更长的记忆可以调大这个值但要注意 token 成本会随之上升。5.3 结构化输出默认情况下模型返回的是纯文本但我们经常需要把输出绑定到实体类上比如解析一本书的信息、抽取一篇文章的标题和作者。Spring AI 支持把回复直接映射到 Java 对象。先定义一个实体类package com.example.demo; public record BookInfo( String title, String author, String category, String summary ) { }然后在调用时指定目标类型import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; RestController public class BookController { private final ChatClient chatClient; public BookController(ChatClient chatClient) { this.chatClient chatClient; } GetMapping(/parse-book) public BookInfo parseBook(RequestParam String text) { return chatClient.prompt(请从下面的文本中抽取书籍信息返回 JSON 格式 text) .call() .entity(BookInfo.class); } }结构化输出的重点在于提示词要给出明确的格式约束实体类字段名最好用英文并且加说明性注释这样模型的召回率更高。如果返回结果经常解析失败检查两个方向一是实体类字段是否过于复杂且语义模糊二是在提示词中补充“只返回 JSON不要解释”之类的约束。LangChain4j 同样支持结构化输出并且对复杂嵌套对象的容错性更好后面章节会提到。5.4 DeepSeek 思考模式的坑reasoning_content 必须原样回传如果你使用deepseek-reasoner模型会在响应里多出一个reasoning_content字段代表模型内部的思考过程。这是 DeepSeek 的一个特色但也容易踩坑。在多轮对话时如果直接把content拼到历史消息里把reasoning_content丢了下一次请求可能收到 400 错误提示思考模式下的reasoning_content必须传回 API。解决方式是把reasoning_content保存到 assistant 消息中下一轮原样带回。在 Spring AI 中可以构造一个通用的消息转换工具import java.util.HashMap; import java.util.Map; public class DeepSeekMessageBuilder { public static MapString, Object assistantMessageWithReasoning(String content, String reasoningContent) { MapString, Object message new HashMap(); message.put(role, assistant); message.put(content, content); if (reasoningContent ! null) { message.put(reasoning_content, reasoningContent); } return message; } }核心原则是reasoning_content和content必须绑定在同一条 assistant 消息里回传不能拆开也不能省略。这属于 DeepSeek 协议层的行为不管用 Spring AI、LangChain4j 还是直接用 HTTP 客户端调用都要遵守。6. RAG 实战向量存储、ES 覆盖策略、Milvus 混合检索与重排6.1 为什么需要 RAG大模型的知识截止时间有限企业内部资料也无法靠训练塞进模型。RAG检索增强生成的思路是先把文档切块、向量化存进向量数据库用户提问时先检索最相关的片段和问题一起送给模型回答。这样既控制成本又能保证最新文档被回答到。6.2 Qwen Embedding 接入向量化这一步通常用专门的 Embedding 模型。这里以阿里云百炼 DashScope 的text-embedding-v3为例在 Spring AI Alibaba 体系里可以像配置 Chat 模型一样配置 Embedding 模型。dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter/artifactId version${spring-ai-alibaba.version}/version /dependencyspring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} embedding: options: model: text-embedding-v3从材料看这是实际项目里比较常见的接入路径。如果你没有阿里云百炼的 Key也可以使用本地 Ollama 的 embedding 模型例如nomic-embed-text只是召回效果和延迟会有差异。6.3 文档写入 Milvus混合检索加 RerankLangChain4j 提供了 Milvus 向量存储实现基础用法如下import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore; MilvusEmbeddingStore embeddingStore MilvusEmbeddingStore.builder() .host(127.0.0.1) .port(19530) .collectionName(java_knowledge) .dimension(1024) .build();dimension必须和 Embedding 模型输出维度一致不同模型维度不同写错了写入时就会报错。写入文档后查询时可以做混合检索同时用关键词和向量相似度召回再用 Rerank 模型对结果重排把最相关的片段排到前面这能明显提升 RAG 答案质量。重排阶段如果使用 DashScope 服务注意在查询链路里增加重排 API 调用并把 TopK 结果截断后再送进 Prompt。混合检索加重的流程本身不复杂但每一步的参数都需要观察实际返回结果来调整不是配好就能永远最优。6.4 ES 向量存储重复文档怎么覆盖用 Spring AI 把文档向量化后写入 Elasticsearch 时很容易遇到一个现象同一份 PDF 重复执行导入任务ES 里会出现多条重复记录。原因在于 Spring AI 默认写入文档时如果没有指定稳定 id每次都会生成一个新的 UUID重复导入自然产生新记录。解决办法是在写入前给文档设置稳定的业务 id比如用文件路径、文档编号或内容哈希。Spring AI 的Document构造器允许传入 idimport org.springframework.ai.document.Document; import java.util.List; public class DocumentService { public ListDocument buildDocs(ListString chunks) { return chunks.stream() .map(chunk - new Document(doc- Integer.toHexString(chunk.hashCode()), chunk)) .toList(); } }当同一个 id 再次写入时ES 向量库会按 id 执行 upsert 语义覆盖旧文档而不是追加新文档。如果某个 id 对应的内容已经不存在还需要主动删除旧向量避免脏数据残留。另一种更稳妥的方案是在批量任务开头先按业务标记删除本批次对应的旧文档再执行写入。比如每个文档写入时 metadata 里保存batchId清理时按batchId批量删除。这个策略适合定时重建知识库的场景。7. 接口 API 化与批量任务7.1 封装 REST API前面已经写了一个/chat接口生产环境通常还会加上对话记录持久化、用户维度上下文隔离、token 用量日志。这里给出一个相对完整的接口示例查询参数和返回结构可以按你公司规范调整。RestController public class ChatApiController { private final ChatClient chatClient; public ChatApiController(ChatClient chatClient) { this.chatClient chatClient; } PostMapping(/api/chat) public ChatResponse chat(RequestBody ChatRequest request) { String answer chatClient.prompt(request.messages()) .call() .content(); return new ChatResponse(answer, request.sessionId()); } public record ChatRequest(String sessionId, String message) {} public record ChatResponse(String answer, String sessionId) {} }前端 Vue 项目可以对接这个接口也可以对接前面的流式接口。建议流式接口给终端用户非流式接口给内部系统做异步处理。7.2 批量任务示例批量任务常见于文档解析、商品文案生成、评论分类等场景。原则是不要在主线程里同步循环调用模型那样既慢又容易触发 API 限流。正确做法是异步提交 任务队列 失败重试。import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; Service public class BatchAIService { private final ChatClient chatClient; public BatchAIService(ChatClient chatClient) { this.chatClient chatClient; } Async(aiTaskExecutor) public CompletableFutureString processOne(String prompt) { try { String result chatClient.prompt(prompt).call().content(); return CompletableFuture.completedFuture(result); } catch (Exception e) { return CompletableFuture.failedFuture(e); } } public ListCompletableFutureString processBatch(ListString prompts) { ListCompletableFutureString futures new ArrayList(); for (String prompt : prompts) { futures.add(processOne(prompt)); } return futures; } }线程池建议单独配置不要把模型调用塞进 Tomcat 的工作线程。线程池大小可以根据模型 API 的并发限制来调整如果 DeepSeek 并发额度不高线程数过大只会增加堆积和超时。8. 资源占用与性能观察这套组合的性能观察点和 Python 本地模型不同重点不是显存而是网络、超时、并发、token 用量。如果你只用 DeepSeek 云端 API本机不跑任何模型那么资源占用主要是 JVM 内存和少量网络 IO普通开发机完全扛得住。需要在监控面板里重点观察的是API 响应延迟DeepSeek 首次 token 时间是否稳定高峰期是否明显变慢。超时配置默认 HTTP 超时在慢网络下容易触发 SocketTimeoutException建议把连接超时设置为 10 秒到 30 秒之间。并发控制同一账号并发过高会触发限流返回 429 状态码需要在代码里做重试和退避。token 用量每次请求的输入 token 和输出 token 都建议记录到日志或数据库月底对账和成本评估都靠它。如果你在本地用 Ollama 跑 Qwen 或 DeepSeek 蒸馏模型那么资源和显存占用才是重点。模型加载后显存会持续占用7B 模型大约需要 6GB 左右显存量化版本会低一些。降低显存占用的常见手段包括使用量化模型、关闭不用的模型、降低上下文长度、限制并发数。在 Ollama 里可以通过环境变量控制模型常驻策略具体以 Ollama 文档为准。下面是两个常用指标采集点指标采集方式用途调用耗时在 ChatClient 调用前后记时判断模型响应是否稳定token 消耗从响应对象中读取 usage 信息成本统计和限流策略429 重试次数在重试拦截器中累积计数判断并发额度是否充足JVM 内存Spring Boot Actuator Prometheus防止内存泄漏9. 常见问题与排查方法问题现象可能原因排查方式解决方案调用 DeepSeek 返回 400提示思考模式 reasoning_content 未回传多轮对话丢弃了 thinking 内容查看请求日志中 assistant 消息结构把 reasoning_content 拼回 assistant 消息后重试启动后接口一直超时base-url 配置错误或网络不通用 curl 直接测试 DeepSeek API确认 base-url 和 api-key 是否正确对话结果不稳定偶尔返回空内容模型参数配置或提示词约束不够查看完整响应日志调整 temperature增强提示词约束重复导入文档后 ES 记录越来越多文档未设置稳定 id检查 Document id 生成逻辑使用业务 id 并配合删除旧批次RAG 检索结果相关度差向量维度不匹配或缺少重排检查 embedding 维度打印召回结果校准维度加入 rerank 环节批量任务跑到一半卡住并发过高触发 API 限流查看 429 响应统计降低线程池并发增加退避重试JDK 版本过低导致依赖冲突JDK 8 无法运行 Spring Boot 3执行 java -version 查看版本升级到 JDK 17LangChain4j 和 Spring AI 同时使用时 Bean 冲突两个框架都扫描了 OpenAI 客户端查看启动日志的 Bean 冲突提示配置不同的包扫描路径或排除自动配置10. 最佳实践与合规建议第一API Key 全部走环境变量或配置中心不要提交到 Git 仓库。一旦泄露立刻去平台删除重建。DeepSeek 开放平台的后台可以查看用量建议设置额度告警防止异常调用导致费用飞涨。第二批量任务一定要有日志和任务表。每次任务的输入、输出、耗时、token 数记录清楚失败任务要有重试机制。批量跑文档解析时建议先跑 5 条样本验证效果再放开全部任务避免大批量失败后回滚困难。第三涉及 RAG 的文档导入必须考虑数据版本管理。文档更新后旧版本向量要及时清理或覆盖。不要长期堆积无主数据否则检索结果会越来越差最终影响回答准确性。第四合规边界要重视。如果你的业务涉及用户上传的文档、图片、录音或者要处理他人的人脸、声音、版权内容必须确认有合法授权。企业内部知识库接入 AI 时要评估数据是否适合发送到第三方模型 API敏感数据建议本地部署模型或做脱敏处理。部署测试环境时先用脱敏数据验证不要直接把生产数据导进去试。第五任何一个 AI 功能上线前准备一套固定的验收用例。包括普通问答、多轮连续性、长文本、异常输入、空输入、重复提交等场景。模型输出有随机性不能依赖一次测试通过就认定稳定至少跑三到五次观察结果波动。11. 总结与后续方向这套组合最值得尝试的点是把大模型能力变成 Java 项目里的普通依赖从配置 DeepSeek 到跑通对话接口只需要几十分钟。接下来优先验证这几个功能流式对话是否能稳定推送、结构化输出解析是否准确、RAG 检索到你自己的文档时回答是否靠谱。最容易踩的坑有三个DeepSeek 思考模式下忘记回传reasoning_content、ES 写入时没设置文档 id 导致重复数据、两个 AI 框架同时引入后出现 Bean 冲突。后两者通过配置隔离和稳定 id 就能解决。后续想继续深入可以按这个顺序扩展先做 Tool Calling让模型能调用你内部的查询接口再用 Spring AI Alibaba Graph 编排复杂的多步骤任务最后把批量任务和定时任务结合起来做成一个完整的知识库自动更新系统。这篇文章建议直接收藏配置代码都是可以复制改的真正用时拿起来就能跑。
返回列表