
做 Java AI 应用开发绕不开 Spring AI 2.0、LangChain4j、DeepSeek、RAG、Agent 这一串关键词。很多 Java 团队在接入大模型时第一步都会纠结到底用 Spring 官方的 Spring AI还是用社区生态里更接近 Python LangChain 的 LangChain4j。这篇文章不打算替你做唯一选型而是用一条完整的实战主线把概念串起来以 LangChain4j 为基础接入 DeepSeek实现一个带知识库、带工具调用、带多轮记忆的企业助手同时对照 Spring AI 2.0 的对应能力。学完之后你能独立搭出最小可运行的 Java AI 应用也能说清楚 Tools、RAG、Agent 在真实项目里是怎么配合的。文章里的代码、配置和排查思路都面向落地不是只讲概念。1. Java 做 AI 应用先选 Spring AI 2.0 还是 LangChain4j1.1 两个框架都在解决什么问题Java 生态接入大模型的痛点很一致没有统一 API、提示词管理混乱、对话记忆要自己拼、工具调用要处理多轮函数返回、知识库检索要和模型生成集成。Spring AI 2.0 和 LangChain4j 本质上都在解决这些问题只是设计思路不同。Spring AI 是 Spring 官方推出的 AI 集成框架目标是把 AI 能力像 Spring Data、Spring Security 一样整合进 Boot 应用。LangChain4j 则是社区开源项目设计上借鉴了 Python 生态的 LangChain但在 Java 基础设施上做了很多适配。两者都支持模型接入、RAG、工具调用但在抽象层、API 风格、与 Spring Boot 的融合深度上有明显差异。实际项目里选哪个不能只看“谁更新”要看团队对 Spring 官方生态的依赖程度以及业务需要多复杂的 Agent 编排。1.2 Spring AI 2.0 的核心能力Spring AI 2.0 的核心角色是ChatClient可以理解为一个更高层的客户端抽象。模型调用、System Prompt、用户消息、工具描述都可以通过流式 API 组合起来。它提供的关键能力包括多模型接入OpenAI、DeepSeek、通义、Ollama 等都能统一到ChatModel接口。Advisors机制可以在对话上下文中统一注入额外信息比如系统提示词、历史消息、RAG 检索结果。函数调用通过Bean或手动注册Function模型可以在对话中触发外部业务方法。RAG 支持提供QuestionAnswerAdvisor配合向量库完成检索增强。对 Spring Boot 的天然集成自动配置、Starter、Actuator 语义都能直接用。在 2026 年发布的新版本里Spring AI 2.0 的模块划分和 API 命名已经比早期稳定很多。学习时对照官方文档中的版本说明会更容易落地。1.3 LangChain4j 的核心能力与设计哲学LangChain4j 的模块更离散也更接近 Python 社区的习惯。它把模型、嵌入、向量存储、文档切分、工具、记忆、Agent 拆成独立组件使用者可以按需组装。最核心的一个概念是AiServices。它允许你定义普通 Java 接口框架自动生成实现把ChatLanguageModel、ContentRetriever、ChatMemory、Tools注入进去。这让业务代码非常干净接口方法就是对话能力。例如interface Assistant { String chat(String userMessage); }通过AiServices.builder(Assistant.class)构建后业务层只需要调用assistant.chat(你的问题)框架会负责模型调用、工具调度、记忆管理和知识检索。LangChain4j 的优势在于能力边界清晰RAG、Tools、Agent 都有独立的组件可以组合。缺点是它不直接绑定 Spring Boot需要自己写一些配置类。不过因为大多模块基于普通 Java 实现集成 Spring Boot 并不复杂。1.4 选型对照表与本文路线对比维度Spring AI 2.0LangChain4jSpring Boot 集成官方深度融合自动配置完善社区集成手动配置较多核心抽象ChatClient、AdvisorsChatLanguageModel、AiServicesRAG 支持QuestionAnswerAdvisorContentRetriever EmbeddingStoreTools 支持Function Callback 注册Tool 注解式声明Agent 编排依赖部分实验模块相对轻量AiServices 组合 Tools 和记忆更直观学习路径适合已经使用 Spring 官方组件的团队适合想快速理解 LangChain 概念的 Java 团队本文的实战主线用 LangChain4j原因是它在 Tools、RAG、Agent 三个方向上都有非常具体的组件代码路径最短适合当作教程主线。Spring AI 2.0 会在关键节点做对照方便你把思路迁移过去。2. 环境与项目骨架先把 DeepSeek 连通性跑通2.1 基础环境清单开始写代码前先把环境对齐否则后续配置会花掉你非常多时间。建议使用以下基础环境JDK 17 Maven 3.9 Spring Boot 3.2 Docker 20.10Docker 后面用来启动 Milvus 向量库。如果只是验证 DeepSeek 连通性可以暂时不启动 Milvus。还需要准备一个 DeepSeek API Key。在平台申请后把 Key 配置到环境变量里不要写死在代码里。对于 LangChain4j建议在 Maven 中央仓库查看最新稳定版本不要直接复制博客里的旧版号。不同版本之间 API 可能会有调整尤其Tool、EmbeddingStoreIngestor这类核心类。2.2 Maven 依赖配置创建一个 Spring Boot 项目添加以下核心依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version${langchain4j.version}/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version${langchain4j.version}/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-milvus/artifactId version${langchain4j.version}/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-document-parser-pdf/artifactId version${langchain4j.version}/version /dependency这里使用langchain4j-open-ai是因为 DeepSeek 提供了 OpenAI 兼容接口。只要配置baseUrl就可以用 OpenAI 协议的客户端调用 DeepSeek 模型。注意langchain4j和langchain4j-open-ai的版本必须保持一致。很多项目最后出现的方法找不到、类路径冲突都是版本不一致造成的。2.3 DeepSeek 连接配置新建配置类把 DeepSeek 的ChatLanguageModel注册为 Spring BeanConfiguration public class DeepSeekConfig { Value(${deepseek.api-key}) private String apiKey; Bean public ChatLanguageModel chatLanguageModel() { return OpenAiChatModel.builder() .baseUrl(https://api.deepseek.com/v1) .apiKey(apiKey) .modelName(deepseek-chat) .temperature(0.2) .build(); } }配置放到application.ymldeepseek: api-key: ${DEEPSEEK_API_KEY}为什么要单独用Value读取因为 API Key 不应该出现在代码和配置历史里。生产环境建议把 Key 放在配置中心或环境变量。baseUrl是否要带/v1取决于 DeepSeek 当前接口文档。一般 OpenAI 兼容接口都会兼容/v1路径。如果返回 404优先检查这个地址是否拼对。2.4 最小调用 Demo 验证连通性写一个启动时测试确认模型能正常返回内容Component public class DeepSeekSmokeTest implements ApplicationRunner { private final ChatLanguageModel chatLanguageModel; public DeepSeekSmokeTest(ChatLanguageModel chatLanguageModel) { this.chatLanguageModel chatLanguageModel; } Override public void run(ApplicationArguments args) { String answer chatLanguageModel.chat(用一句话说明 Java 17 密封类的作用); System.out.println(DeepSeek 返回 answer); } }启动 Spring Boot 后如果日志打印出 DeepSeek 的返回内容说明连通性没问题。2.5 检查点与常见坑问题现象常见原因检查方式处理建议401 UnauthorizedAPI Key 错误或未配置检查环境变量是否注入确认 Key 正确重启应用404 Not FoundbaseUrl 路径错误打印完整请求地址按接口文档调整/v1返回内容为空模型名配置不对打印模型名称使用deepseek-chat或文档推荐模型名响应很慢网络环境或模型负载查看超时日志设置合理的超时时间生产建议降级这个阶段最容易忽略的是环境变量。如果 IDE 里没有设置DEEPSEEK_API_KEY代码不会报错但请求会一直返回 401。3. RAG 知识库把文档变成可检索的向量3.1 RAG 为什么能解决幻觉问题RAG 的核心思路模型回答前先从你的知识库里检索相关片段把片段拼到提示词里再让模型基于这些片段生成回答。它解决了大模型的两个关键问题一是模型不知道企业内部私有数据二是模型可能会凭记忆编造内容。只要检索到的信息足够准确生成结果就有依据。一条 RAG 链路包含文档加载、文本切分、向量化、向量存储、检索、重组提示词、模型生成。任何一环质量差最终答案都会受影响。3.2 文档加载与解析的完整流程LangChain4j 用Document表示一份文档。加载时需要根据文件类型选择解析器。Path path Paths.get(docs, 退款处理手册.md); Document document FileDocumentLoader.load(path, new TextDocumentParser());对于 PDF 文件可以使用 PDF 解析器Document pdfDocument FileDocumentLoader.load( Paths.get(docs, 操作手册.pdf), new PdfDocumentParser() );文档解析是 RAG 的上限。很多项目最终效果差不是因为模型不好而是 PDF 解析出来后内容错乱、表格丢失、页眉页脚混入正文。建议优先使用 Markdown、HTML 等结构化格式。如果只有 PDF先人工抽查几页确认解析结果。对扫描件 PDF需要先做 OCR不能直接靠普通 PDF 解析器。3.3 切块策略不是越短越好切分的目的是把长文档拆成适合检索的片段。片段太长检索精度低片段太短上下文信息不完整。LangChain4j 提供多种切分器常用的是递归切分DocumentSplitter splitter DocumentSplitters.recursive(500, 80);参数含义第一参数500是每个文本块的目标字符数。第二参数80是相邻块之间的重叠字符数。为什么需要重叠因为很多概念被切在边界上重叠部分可以降低信息断裂风险。不同材料适合不同策略文档类型推荐策略说明Markdown按标题结构切分保留章节语义条款类文章固定大小 重叠简单直接便于控制数量代码文档按代码块边界切分避免切割语法片段产品 FAQ按问题段落切分保持一个问答的完整性切块粒度会影响检索结果的召回率但没有绝对标准需要结合中文特点和真实查询实验验证。3.4 Embedding 模型与 Milvus 向量库配置DeepSeek 官方目前没有对外开放 Embedding 接口所以在 RAG 场景里需要单独选一个 Embedding 模型。可以选择 DashScope 的 OpenAI 兼容接口也可以使用本地模型。下面示例使用 DashScope 兼容接口EmbeddingModel embeddingModel OpenAiEmbeddingModel.builder() .baseUrl(https://dashscope.aliyuncs.com/compatible-mode/v1) .apiKey(dashScopeApiKey) .modelName(text-embedding-v3) .build();向量库使用 Milvus。学习环境可以用 Docker 启动单机版docker run -d --name milvus \ -p 19530:19530 \ -p 9091:9091 \ milvusdb/milvus:latest然后在代码中配置EmbeddingStoreTextSegment embeddingStore MilvusEmbeddingStore.builder() .host(localhost) .port(19530) .collectionName(java_ai_kb) .dimension(1024) .build();这里有两个容易踩的坑。第一个坑dimension必须和 Embedding 模型输出维度一致。text-embedding-v3输出 1024 维collection 就必须建 1024 维。如果维度不匹配插入向量时会报错。第二个坑Milvus 的 collection 已经存在时dimension修改不会自动生效。需要先删掉旧 collection或者设计版本化 collection 策略。3.5 将第一批文档导入向量库把加载、切分、向量化、存储串起来使用EmbeddingStoreIngestorEmbeddingStoreIngestor ingestor EmbeddingStoreIngestor.builder() .documentSplitter(splitter) .embeddingModel(embeddingModel) .embeddingStore(embeddingStore) .build(); ListDocument documents loadAllDocuments(); ingestor.ingest(documents);执行完这段代码后文档会变成向量存入 Milvus。这里要注意执行是一次性操作正式项目里要记录哪些文档已经导入过避免重复。文档内容更新后应该先删除旧版本向量再导入新版本。灌库时可以批量处理不建议一次把所有文件塞进内存。3.6 检索与生成把命中片段拼进提示词检索部分使用EmbeddingStoreContentRetrieverContentRetriever contentRetriever EmbeddingStoreContentRetriever.builder() .embeddingModel(embeddingModel) .embeddingStore(embeddingStore) .maxResults(4) .minScore(0.6) .build();maxResults控制每次检索返回的片段数量minScore过滤低相关片段。检索到片段后LangChain4j 会把这些片段注入到提示词中。模型会根据这些片段回答并标注信息来源。这一步是整个 RAG 的核心也是最容易忽略质量的环节。检索结果如果不相关模型再强也很难输出正确结果。3.7 引用溯源与 groundednessRAG 必须能验证依据RAG 系统在生产环境要解决的不只是“回答对不对”还有“你怎么知道回答有依据”。引用溯源Citation和 groundedness 是同一个问题的两面答案必须能从检索片段里找到依据。实现思路是在 Prompt 中要求模型对每个关键结论标注来源编号例如请基于以下参考资料回答用户问题。 如果某个结论来自参考片段请在句末标注 [1]、[2] 等编号。 如果参考资料中没有相关信息请直接说明“当前知识库中未找到依据”。在 LangChain4j 中你也可以自定义 Prompt 模板把检索到的Content和用户问题一起传入模型。生成结果后把模型返回的[1]和检索片段一一对应就能在界面上展示引用来源。groundedness 评估可以这样做把回答拆成若干句。检查每一句是否能在检索片段中找到支持。无支持的句子占比过高说明系统在“编造”。将评估结果纳入回归测试防止优化一项指标时破坏另一个指标。4. Tools让模型在回答前先查外部数据4.1 为什么需要 Tools大模型本身的训练数据是静态的它不知道某个退款单号的真实状态也不知道你系统里的库存数量。Tools 机制让模型可以在回答前调用外部方法获取实时数据再基于结果生成答案。举个例子用户问“我的退款单 RF123456 为什么还没到账”模型不能直接猜它需要调用订单系统的退款状态查询接口。Tools 的本质是函数调用。模型不是直接执行代码而是决定“这里需要调用某个函数”框架负责把函数参数解析出来、执行方法、再返回结果。4.2 用 Tool 声明一个工具在 LangChain4j 中工具就是一个普通 Java 方法Component public class OrderTool { Tool(根据退款单号查询退款状态) public String getRefundStatus( Parameter(退款单号例如 RF123456) String refundId) { // 实际项目里这里会调用订单服务 return 退款单 refundId 当前状态银行处理中预计 1-3 个工作日到账; } }关键点在注解描述。Tool的描述决定模型什么时候调用这个方法Parameter的描述决定模型如何从用户问题中提取参数。如果描述不清楚模型可能把参数传错或者在不需要工具时乱调工具。4.3 工具调用背后的执行流程一次完整工具调用流程是用户输入问题。框架把问题和已注册的工具描述发送给模型。模型判断需要调用getRefundStatus返回工具名和参数。框架执行方法拿到结果。框架把工具结果返回给模型。模型基于工具结果生成最终回答。这个过程中模型可能会多次调用工具也可能先调用 A 再调用 B取决于业务复杂度。LangChain4j 会把这些步骤封装在AiServices里你不需要手写循环。4.4 工具设计建议使用 Tools 时建议遵守以下规则工具返回内容尽量是结构化短文本方便模型解读。不要返回超长列表模型容易丢失重点。工具方法要有超时和异常处理不能因为外部服务超时导致对话失败。记录工具调用次数和耗时便于排查和成本控制。工具名称和参数描述要偏向“模型视角”而不是代码视角。工具设计错误推荐做法方法名queryOrder()Tool(根据退款单号查询退款状态)参数是对象拆成多个基本类型参数返回完整实体 JSON返回模型需要的结论文本不处理异常捕获异常并返回“查询失败请稍后重试”5. Agent把对话、知识库和工具串成完整业务助手5.1 Agent 与普通多轮对话的区别普通多轮对话只是把聊天历史拼接起来模型根据历史回答。Agent 更进一步模型可以主动决定调用哪些工具、检索哪些知识、什么时候输出最终答案。一个典型的 Agent 工作流程类似用户提问“我的退款为什么还没到账”Agent 先检索知识库判断是否有退款政策相关文档。Agent 再调用退款状态查询工具获取实时状态。Agent 综合两份信息生成回答。这个过程不需要用户自己分步操作Agent 负责规划。LangChain4j 做 Agent 的路线很轻量使用AiServices把ChatLanguageModel、ContentRetriever、ChatMemory、Tools组合起来接口层依然是普通方法调用。5.2 构建业务助手Assistant 接口与 AiServices先定义一个接口interface RefundAssistant { String chat(String userMessage); }然后组装RefundAssistant assistant AiServices.builder(RefundAssistant.class) .chatLanguageModel(chatModel) .contentRetriever(contentRetriever) .tools(new OrderTool()) .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) .build();这段代码非常重要。它把前面所有组件串了起来chatLanguageModel核心对话能力。contentRetrieverRAG 知识库检索。tools订单状态查询等外部能力。chatMemory最近 10 条消息的上下文记忆。MessageWindowChatMemory.withMaxMessages(10)表示最多保留最近 10 条消息。消息数量太多会增加 Token 成本太少则丢失上下文。5.3 Controller 接入 REST API在 Spring Boot 中暴露一个对话接口RestController RequestMapping(/api) public class ChatController { private final RefundAssistant assistant; public ChatController(RefundAssistant assistant) { this.assistant assistant; } PostMapping(/chat) public MapString, String chat(RequestBody ChatRequest request) { String answer assistant.chat(request.message()); return Map.of(answer, answer); } }这里的ChatRequest是一个简单 recordpublic record ChatRequest(String message) {}这样前端只需要调用 POST 接口不需要关心模型调用、工具调用、RAG 检索的复杂性。5.4 验证流程与预期输出启动项目后用 curl 测试curl -X POST http://localhost:8080/api/chat \ -H Content-Type: application/json \ -d {message:退款单 RF123456 为什么还没到账}如果一切正常返回类似{ answer: 根据订单系统记录退款单 RF123456 当前状态为【银行处理中】。按照《退款处理手册》的说明银行处理中通常需要 1-3 个工作日到账请耐心等待。\n\n[1] 退款处理手册-第2章-退款状态说明\n[2] 订单系统实时状态 }这个输出验证了三件事工具被正确调用拿到了订单状态。知识库被正确检索引用了手册内容。模型把两者组合成了对用户有用的回答。5.5 多轮记忆与上下文窗口的取舍多轮记忆不是越长越好。实战中建议按业务域划分会话不同用户问题不要共用一个会话。限制消息条数和最大 Token 数。长对话做摘要压缩而不是无限保留历史。涉及敏感数据的工具结果不要全部塞进对话历史。MessageWindowChatMemory.withMaxMessages(20)适合大多数客服场景。如果对话非常长就需要引入摘要模式或者把历史消息持久化到 Redis 中。6. 从学习到生产配置、日志、成本与评估6.1 学习环境与生产环境的差异维度学习环境生产环境MilvusDocker 单机集群多副本持久化API Key环境变量配置中心 / KMS文档更新手动重灌增量同步 版本管理日志控制台输出结构化日志 全链路追踪监控无请求量、Token 用量、耗时告警评估人工抽查自动化测试集 回归生产环境不是把学习代码直接部署它需要额外考虑稳定性、成本和可观测性。6.2 配置外置与密钥管理不要把 API Key 写在application.yml里也不要用Value硬编码。推荐做法本地开发使用环境变量或.env。测试和生产使用配置中心。使用密钥管理服务托管 API Key。定期轮换 Key轮换时要考虑正在连接的请求。LangChain4j 和 Spring AI 都支持在配置中引用环境变量这应该是底线。6.3 文档更新与向量同步RAG 最容易被忽略的问题是知识过期。文档更新后向量库里还是旧内容回答就会过时。建议文档入库时记录版本号。更新文档时删除旧的向量段再插入新向量。如果使用 Milvus可以按时间分区或使用独立 collection方便回滚。批量重灌时先建新 collection切换流量后再删旧 collection。6.4 日志、观测与成本控制每次模型请求都建议记录用户问题。模型名称。输入 Token、输出 Token、总 Token。调用工具名称和耗时。是否检索了 RAG检索到的片段来源。模型响应耗时和状态码。这些数据可以用来做成本分析。比如某类用户问题频繁触发长上下文可以针对性优化 Prompt 或限制历史消息。还可以引入缓存相同问题在短时间内直接返回缓存结果减少模型调用成本。但要注意缓存不能用于需要实时工具查询的场景。6.5 自动化评估与回归测试RAG 系统上线前准备一组人工标注的测试问题每个问题记录理想答案和应该命中的文档片段。每次修改切块策略、Prompt 或模型参数后跑一遍测试集对比结果。重点检查检索命中率是否下降。回答是否仍然基于检索片段。是否出现新增的幻觉内容。这种评估不需要一开始做得很重先准备 30 到 50 条业务问题覆盖常见场景和边界场景就能发现大部分问题。7. 高频报错排查清单排查 AI 应用问题时建议按“数据链路”顺序检查用户输入、模型参数、RAG 检索、工具调用、最终生成。不要一上来就怀疑模型能力。7.1 模型接入类问题问题现象可能原因检查方式处理建议401 UnauthorizedAPI Key 错误检查环境变量更新 Key 并确认生效404 Not FoundDeepSeek baseUrl 路径不对对照接口文档补上/v1或去掉重复路径模型返回空 content模型名不匹配或 Prompt 异常打印请求参数换成deepseek-chat检查 Prompt中文乱码控制台编码问题查看 HTTP 返回原始内容统一 UTF-8 编码7.2 向量库与 RAG 类问题问题现象可能原因检查方式处理建议Milvus 连接超时没有启动容器或端口不对docker ps检查 19530启动 Milvus确认端口维度不一致Embedding 维度与集合不匹配打印模型维度重建 collection 或调整 dimension检索结果为空文档未灌入或 minScore 太高查询 collection 条数降低 minScore确认灌库成功回答与知识库无关切块粒度不合适查看检索到的片段调整切块策略检查重叠7.3 工具与 Agent 类问题问题现象可能原因检查方式处理建议工具没有被调用描述不清晰或模型不支持 function calling查看模型请求日志完善Tool描述确认模型支持工具参数错误Parameter描述不清楚打印模型生成的参数参数描述中给出示例值Agent 循环调用工具工具返回内容让模型无法判断结束查看完整调用链限制最大调用次数优化工具返回多轮记忆失效ChatMemory 未注入检查 AiServices 配置添加chatMemory一个特别常见的现象是Agent 反复调用同一个工具直到 Token 耗尽。这个问题通常因为工具返回结果没有让模型得到足够信息或者没有限制最大迭代次数。生产环境一定要给 Agent 设置最大工具调用次数和超时控制。8. 实战建议与下一步练习方向Java AI 应用开发建议按照“聊天 - 工具 - RAG - Agent”的顺序学习。先把 DeepSeek 连通性跑通再让模型能调用一个工具然后加入知识库最后用AiServices组合起来。每一步都能独立运行排查范围也会小很多。如果只抓一个重点先把 RAG 的引用溯源做扎实。一个没有依据的回答看起来再流畅生产环境也不敢用。建立回答与检索片段之间的可验证关系才是 RAG 项目从 Demo 走向产品的分水岭。下一步可以尝试的方向包括接入 MCP 生态让 Spring AI 2.0 或 LangChain4j 统一调用更多外部系统。使用多模型路由简单问题用小模型复杂问题用大模型。把 Milvus 替换为生产级集群设计 collection 版本管理。加入人工反馈机制让用户能标记错误回答形成数据集。实际项目里不要一开始就追求复杂的 Agent 规划。先把普通对话、工具调用和 RAG 分别调好再组合成 Agent问题会少很多。把上面的代码按自己的业务场景调整重点关注切块策略、工具描述和引用溯源这套架构就能逐步稳定下来。