Java大模型开发:Spring AI与Langchain4j实战对比与选型指南

Java大模型开发:Spring AI与Langchain4j实战对比与选型指南 如果你是一名 Java 后端工程师最近一定感受到了一股隐隐的焦虑。团队里突然开始讨论 AI 应用但翻了一圈资料满屏都是 Python 脚本、FastAPI 接口和 electron 客户端。你第一反应可能是“我是不是也得去学 Python”但冷静下来想一想你手里那套稳健的 Spring 生态真的就没法和 LLM 协作吗答案当然不是。真正的问题不是“该不该换语言”而是“用哪一套框架把大模型接入 Java 业务里更稳”。这几个月我一直在折腾 Spring AI 和 Langchain4j 这两套 Java 生态里的 AI 框架。结合 DeepSeek 的 API、Tools 函数调用、RAG 知识库和 Agent 流程我在一个内部项目里完成了从“单条 prompt 调用”到“多步骤智能体”的演进。这篇文章想把我踩过的一些坑、梳理出来的判断标准以及一套能直接复用的最小流程分享出来。我不会去吹某个框架“全网最强”只会老实说在什么场景下你该优先选哪条路。先说一个核心判断Spring AI 2.0 和 Langchain4j 并不是互斥的竞争品它们更像是两套互补的“乐高积木”。Spring AI 继承了 Spring 家族一贯的配置化、统一抽象和自动装配思路适合已经扎根 Spring Boot 项目的团队快速接入而 Langchain4j 则更贴近 Python 生态里 LangChain 的设计哲学在 Agent、Memory、Chain 等概念上更完整。对大多数 Java 开发者来说早期不必纠结“到底选谁”更应该先想清楚你要做的 AI 功能是简单的单次问答还是需要工具调用、知识库检索和自主决策的复杂流程。1. 先搞清楚 Spring AI 2.0 和 Langchain4j 到底解决了什么问题很多人第一次接触 Spring AI 时会误以为它只是把 OpenAI SDK 封装了一遍类似RestTemplate包了个ChatClient。实际上Spring AI 从 1.0 到 2.0 的演进核心在于把“大模型对话”这件事从“发请求拿文本”升级成了“一套可组合的 AI 应用骨架”。1.1 Spring AI 2.0 的关键变化从单次调用到流程编排Spring AI 2.0 在底层引入了一个更清晰的抽象层ChatClient成为主入口同时统一了 Embedding、VectorStore、Function Calling 等接口。换句话说你不再需要为不同的模型各自写一套 adapter而是通过一个统一的 API 去访问 DeepSeek、通义千问、OpenAI 等后端模型。这就带来一个实际好处你的业务代码里不再到处是 HTTP 调用的硬编码而是变成了类似“先构建 Message再配置模型参数然后调用chatClient.chat()”的流程。如果之后要换模型供应商你只需要改配置文件和少量参数业务层几乎不动。不过Spring AI 2.0 的定位不只是“多个模型的统一接口”。它更重要的能力是把工具调用和 RAG 纳入到“会话”的整体流程中。举个例子你可以直接给ChatClient挂一个Tool函数让模型在对话过程中自动决定是否调用你的 Java 方法来查数据库而不需要你自己写一个if-else来解析用户意图。这种能力在 2.0 里变得非常顺滑。1.2 Langchain4j更像 Python 圈的那套思想搬到 JavaLangchain4j 和 Spring AI 最大的差异在于“设计哲学”。Langchain4j 几乎全盘继承了 Python 生态里 LangChain 的核心概念AiService、Chain、Memory、Tool、Retriever。如果你之前看过 LangChain 的代码再看 Langchain4j 会觉得很亲切。Langchain4j 的优势是它的模块拆分非常细致。比如你想做 RAG它可以单独引入langchain4j-easy-rag模块你想用 Milvus 做向量库也有langchain4j-milvus模块。这种“按需引入”的思路对 Maven 用户来说很友好依赖也不会过分臃肿。但这里有一个容易被忽略的点Langchain4j 并不是 Spring 家族的官方项目它本身是独立社区维护的。这意味着如果你项目里已经用了 Spring Boot最好通过它的langchain4j-spring-boot-starter来整合否则你要自己管 Bean 生命周期和配置注入。换个角度说如果你的项目本来就是纯 Spring BootSpring AI 的集成会更“原生”如果你更喜欢 LangChain 式的灵活组合Langchain4j 会更顺手。我在实际项目里的做法是把 Spring AI 作为“主路线”因为它能和 Spring Security、Spring Data 等现有组件自然融合同时用 Langchain4j 来做一些偏实验性的 Agent 编排因为它对复杂 Chain 的支持更直观。这样既保证了生产环境的稳定又保留了探索空间。2. 用 DeepSeek 跑通最小闭环环境准备和第一个调用不管你最后选哪套框架第一步永远是先把“模型连接”跑通。这个阶段最容易犯的错误是一上来就想把 RAG 和 Agent 全搭好结果问题出在 API 密钥、网络代理或模型名称拼写上导致排查半天。2.1 Maven 依赖和配置的最小集合以 Spring AI 2.0 为例你要做的第一件事是在pom.xml里引入 Spring AI 的 BOM然后添加对具体模型供应商的依赖。DeepSeek 目前提供了 OpenAI 兼容的 API 接口所以你可以直接使用 Spring AI 的 OpenAI starter然后修改 base-url 指向 DeepSeek 的端点。一个常见的基础配置dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version2.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId /dependency /dependencies然后application.yml里像这样配置spring: ai: openai: api-key: ${DEEPSEEK_API_KEY} base-url: https://api.deepseek.com chat: options: model: deepseek-chat temperature: 0.7这样你就能用 Spring AI 的ChatClient调用 DeepSeek 了。这里要特别注意一旦你改了base-url很多默认参数都会变化比如 DeepSeek 支持的上下文长度、响应格式、是否支持函数调用都要以 DeepSeek 的官方文档为准。我一般会先在代码里打印一次响应确认模型名称和返回结构都正常再进入下一步。2.2 用 Langchain4j 也叫一次 DeepSeek如果你更想先用 Langchain4j 快速体验配置方式也类似。Langchain4j 有一个langchain4j-open-ai模块但注意它可能需要指定OpenAiChatModel。常见的写法OpenAiChatModel model OpenAiChatModel.builder() .apiKey(System.getenv(DEEPSEEK_API_KEY)) .baseUrl(https://api.deepseek.com) .modelName(deepseek-chat) .logRequests(true) .logResponses(true) .build();然后调用String response model.chat(你好请用三句话介绍你自己); System.out.println(response);这种最小调用虽然简单但它是后续所有复杂功能的地基。在这个阶段我最想提醒的是不要跳过“日志”配置。开发时一定要开logRequests和logResponses否则你根本看不到模型到底收到了什么、返回了什么。很多“Agent 不干活”的问题最后都出在用户输入被截断或者模型参数被错误注入。3. Tools把 Java 方法变成大模型的“双手”当你能正常拿到 DeepSeek 的回复后下一步最值得做的事不是立刻建知识库而是先实践 Tools 函数调用。因为 Tools 是 Agent 和 RAG 的共同基础Agent 靠工具决策下一步RAG 里的检索器也可以被暴露成一个工具。3.1 Spring AI 2.0 里的 Tool 注解Spring AI 2.0 对函数调用的支持非常友好。你只需要定义普通 Java Bean 方法并加上Tool注解然后通过ChatClient的.tools(myService)方法把 Bean 注册进去。框架会自动生成模型的 tool schema并在模型决定调用时帮你触发对应方法。一个简单示例Component public class OrderService { Tool(查询指定用户的最近订单输入 userId) public String getRecentOrder(String userId) { // 实际逻辑 return 用户 userId 的最近订单是 ...; } }然后ChatClient chatClient /* 构建好的 client */; String result chatClient.prompter() .system(你是订单助手。当用户询问订单时使用工具获取实时数据。) .user(我的用户ID是 1001请告诉我最近的订单状态) .tools(orderService) .call() .content();这里的关键在于Tool注解里的description要写清楚参数含义和触发条件。因为 LLM 是通过自然语言描述来决定“要不要调用”的如果描述太模糊模型可能会漏调用或错调用。3.2 Langchain4j 的 Tool 定义差异Langchain4j 使用Tool注解和ToolServices类似但它更强调“工具集合”的概念。你可以用ToolSpecification手动定义参数结构也可以直接用注解自动生成。如果你的工具里有复杂对象参数建议先定义成简单的String或基本类型这样模型更容易正确填充。在实际开发中我用 Tools 踩过最大的坑是“并发调用”。当你的 Agent 同时调用多个工具时如果某个工具内部有阻塞操作比如调外部 HTTP 接口整个线程池可能会被占满。所以设计工具时最好让每个工具都具备超时控制和降级逻辑。不要指望模型自己会考虑性能问题。4. RAG 实战把文档知识变成可检索的数据库RAG 是很多人入局 AI 应用时最想做的功能你有一批内部文档或业务知识希望模型能基于这些内容准确回答而不是胡编乱造。但 RAG 的难点不在“调用一个 Embedding 接口”上而是整个链路里每一个环节都有可能让最终效果崩盘。4.1 文档加载和切块策略在 Java 里RAG 的第一步是加载文档。Spring AI 提供DocumentReader接口你可以用它读取 PDF、TXT、Markdown 等格式。Langchain4j 也有DocumentLoader和TextSplitter。这里最容易踩坑的是“切块策略”。切块不是越大越好也不是越小越好。太大会导致检索时引入无关内容模型可能被误导太小会损失上下文模型无法理解局部含义。我的基线策略是固定大小 400 到 800 字符重叠 10% 到 20%再根据文档结构手动调整。对于带标题的 Markdown 或 HTML 文档最好先按标题分块再用固定切片补充。一个通用切块代码示例Langchain4jDocumentByTextSplitter splitter new DocumentByTextSplitter(500, 50); ListDocument chunks splitter.split(document);切完块后还要考虑一件事是否要给每一块加上元数据比如来源文件、章节路径、时间戳。这些元数据在后续“引用溯源”里非常关键否则模型给出回答后你无法定位到具体出处。4.2 Embedding 和向量库选型切好块的下一件事是 embedding。你可以用 DeepSeek 的 embedding 接口也可以用通义千问qwen的 embedding因为 embedding 模型和对话模型并不要强绑定。我自己的选择是先用轻量的 qwen embedding 做验证因为它的维度适中兼容性也比较好。向量库方面标题和热词里提到了 Milvus。Milvus 在 Java 生态里可以直接通过官方 SDK 或者 Spring AI 的MilvusVectorStore集成。但要注意Milvus 是一个独立的服务你需要提前部署好如果只是为了学习也可以先用内存向量库比如 Spring AI 自带的 SimpleVectorStore验证流程。存入向量库的过程通常三步// 1. 构建 EmbeddingModel // 2. 初始化 VectorStore // 3. 把切好的文本转成 Document 并写入 vectorStore.add(documents);这里的核心参数是“相似度检索的 TopK”和“最小相关分数”。如果 TopK 太大会引入噪声如果反过来太小可能遗漏关键信息。我会先跑几个测试问题打印出每一条检索结果的 score再根据业务场景调整。4.3 混合检索与重排别再只靠向量相似度真正到生产级 RAG 时单靠向量检索往往不够。原因很简单向量相似度擅长语义匹配但对关键词、精确数字、代码符号非常不敏感。所以现在业界更推荐“混合检索”BM25 关键词检索 向量检索然后通过一个 Rerank 模型重新排序。热词里提到的“qwen embedding、并存储 milvus 调用示例”以及“java langchain4j milvus java混合检索跟重排”正好对应这个方向。在 Java 里你可以先用 Milvus 做向量检索再用 Elasticsearch 或 Lucene 做关键词检索最后把两堆结果合并去打乱。这里的重排不一定需要调用模型 API你也可以自己定义打分规则比如“关键词命中加权 向量相似度加权”。我当时实现了一个简单版本// 伪代码展示混合检索思路 ListDocument vectorResults vectorStore.similaritySearch(query, topK, minScore); ListDocument keywordResults keywordRetriever.search(query, topK); MapString, Double mergedScores new HashMap(); for (Document doc : vectorResults) mergedScores.merge(doc.id(), doc.score()*0.7, Double::sum); for (Document doc : keywordResults) mergedScores.merge(doc.id(), doc.score()*0.3, Double::sum);这个思路虽然是简化的但它揭示了一个关键RAG 的效果不是单靠某个“神奇模型”决定的而是检索策略、切块方式、分数权重等多因素共同作用的结果。如果你只关心“调用一次 API”那很难做出稳定的产品。5. Agent把 Tools 和 RAG 放进一个会循环的流程里当你有了 Tools 和 RAG下一步自然就是想着让流程自动化Agent 能根据用户提问自主决定是查数据库、查知识库还是直接对话。这个想法很美好但实践起来有一个常见的误区把 Agent 当成一个“永远聪明”的万能盒子结果一跑就超时或者答非所问。5.1 Agent 的最小架构一个带工具循环的 ChatClient在 Spring AI 2.0 里最简单的 Agent 实现就是允许模型在对话中多次调用工具。你不需要自己写while循环框架的ChatClient会处理工具调用的来回。比如String result chatClient.prompter() .system(你是一个智能助手可以调用工具完成复杂任务。) .user(请帮我查一下订单和相关的知识库文档) .tools(orderService, ragService) .call() .content();这里的“Agent”其实就是一个带工具的对话轮次。模型如果觉得需要调用工具它会返回一个 tool call然后框架帮你执行工具再把结果传回模型生成最终回答。如果你需要多轮规划可以使用Agent或AiService的概念。但要注意这种方式和自主 Agent 的区别是它没有长期记忆和任务规划器。如果你需要“先拆解任务再执行”那就要考虑引入专门的 Agent 编排流程。Langchain4j 在这块更灵活你可以自己定义Chain在每次执行后判断是否继续。但灵活带来的代价是你需要更严格地控制循环次数和 token 消耗。我建议至少设置一个最大迭代次数避免模型陷入死循环。5.2 用 Agentic RAG 替代全自动 Agent现在热词里出现的“agentic rag”其实是另一种思路不是让模型一直自主决策而是让 RAG 的每一步变得更智能。比如检索时先判断是否需要检索如果不需要就直接回答如果需要再决定用哪种检索策略。这种“可控的智能”比“全自动 Agent”更容易稳定落地。我后来在项目里改成先用一个轻量分类模型判断意图如果是事实性问题就走 RAG如果是操作性问题就走 Tools如果两者都涉及就先 RAG 后 Tools。这个做法虽然没有“全自动 Agent”那么酷但它在生产环境里异常稳定也更容易定位问题。如果你想做真正的 Agent建议按这个优先级来先把 Tool 调用和 RAG 分别跑通。在单轮里让 Agent 调用一个工具。再尝试多工具组合。最后才考虑任务拆解和长期记忆。很多人一上来就想做“能自主规划”的 Agent结果败在工具调用不稳定、上下文管理混乱或 prompt 冲突上。这不是框架不行而是你对框架和模型边界还没摸清楚。6. 高频故障排查和工程化建议热词里出现了一条具体的报错信息the agent execution provider did not respond in time。这其实是一个通用问题Agent 在执行过程中某个外部依赖或模型响应耗时超过了你设置的超时时间。这种情况在 Java 里尤其常见因为线程池、HTTP 连接和模型 API 的超时设置往往是分开的。6.1 排查顺序先看日志再看配置遇到这种超时报错不要急着调大超时时间。建议按照这个顺序排查先看日志是模型 API 超时还是工具方法阻塞还是整体链路的超时被某个中间层吃掉。再看输入这次提问是否特别长上下文是否已经超过模型限制。再看环境当前机器的网络是否稳定是否通过某个代理访问模型 API代理会不会有额外延迟。再看参数你的并发数、批量大小、maxIterations、timeout是否合理。最后看框架边界Spring AI 和 Langchain4j 对某个功能的支持是否成熟比如多轮 Tool 调用时是否有 bug。我收到这类问题后最常做的一步是关闭所有异步延迟改成同步串行执行一次最小复现。只有在单线程同步模式下跑通了才能说明框架逻辑没问题之后再去调并发。6.2 生产落地必须补齐的几块拼图如果你只是学习默认配置基本够用。但如果你要把 Agent 或 RAG 放到真实业务里还需要补上这些能力日志和追踪必须记录每一次 prompt、tool call、retrieval 结果否则你没法复盘“为什么模型这么答”。超时和重试给每个模型调用和工具调用设置明确的超时时间并做好失败重试。注意重试时不要无限循环最多 2 到 3 次。权限和审计当 Agent 能调用写操作工具时一定要做权限校验。不能让模型随意删除数据。成本控制设定单次请求的 token 上限和总预算。Agent 自带循环机制很容易消耗大量 token。还有一个我经常强调的点不要把 RAG 的结果直接扔给模型而不做校验。最好在最终回复里插一段引用来源让用户能追溯到具体文档。这不仅是体验问题更是责任问题。否则一旦模型答错你连“错了哪句话”都说不清楚。7. 留给你的一个最小可行性路线图我不会说“你照着这个栈做就一定能上线”但如果你现在处于“听了很多概念还没跑通一个完整项目”的状态我很建议按下面这个路线推进每一步都有明确的验收标准。7.1 路线图Step 1模型调用。用 Spring AI 或 Langchain4j 调用 DeepSeek 的聊天接口能打印出回复。验收标准1 天。Step 2Tools。定义一个查询订单的Tool让模型在对话中主动调用。验收标准1 到 2 天。Step 3RAG 单机版。加载几个 Markdown 文档用 qwen embedding 写入内存向量库能检索并让模型回答。验收标准2 到 3 天。Step 4RAG Milvus。迁移到 Milvus 向量库测试切块策略对效果的影响。验收标准3 到 5 天。Step 5Agent 雏形。把 Tools 和 RAG 结合进一个 ChatClient处理“查文档 查订单”这类复合任务。验收标准2 到 3 天。Step 6混合检索和重排。引入 BM25 关键词检索做一个简单的打分规则提升准确率。验收标准3 到 5 天。这个路线图的总时长大概 1 到 2 周具体取决于你每天能投入的时间。别急着把每一步都做“完美”先跑通再优化这是所有 AI 工程实践里最大的坑。7.2 适用边界这套方案适合谁、不适合谁这套“Spring AI/Langchain4j DeepSeek Tools RAG Agent”的组合最适合以下情况你已经有 Java/Spring Boot 的存量项目希望在现有架构里加入 AI 能力。团队对 Java 很熟对 Python 不熟不打算引入一套新的技术栈。你的 AI 功能需要和现有数据库、权限体系、监控系统深度集成。不太适合的情况你需要研究最前沿的推理模型或复杂强化学习那时 Python 生态的试验性更强。你的项目几乎没有 Java 代码完全从零搭建那直接用 Python 快速原型可能更快。你只是想做一个小 demo不关心生产稳定性那用 Python 会更灵活。另外要提醒一句这套东西的长期维护成本不低。模型 API 会变向量库版本会变框架也还在快速演进。你不能只依赖某个视频教程里的“配置”要养成读官方文档和看 release notes 的习惯。我知道很多人看完这篇后第一反应依然是“那到底该选 Spring AI 还是 Langchain4j”。说实话这个问题没有标准答案。如果让我现在给团队建议我会说主线用 Spring AI实验用 Langchain4j。因为 Spring AI 让你更快接入 Spring 生态而 Langchain4j 让你理解 LangChain 的思维。两者都值得你花时间去碰。但比框架选择更重要的是你对这条链路本身的理解模型调用只是入口工具是你的手知识库是你的记忆Agent 是你的大脑。你真正要调试的不是“框架有没有 bug”而是信息在每一层之间传递时有没有失真或丢失。当你开始像排查分布式系统那样去排查 AI 链路你就已经走在正确的路上了。下一步先别急着写 Agent。打开你的 Maven 项目把第一步的模型调用跑通打印一次响应然后从那儿开始。