1. 为什么我要做这个对比测试
做Java后端的人最近一两年应该都有同感:AI能力集成这件事,从“要不要做”变成了“怎么做”。尤其是大模型调用、RAG检索增强、工具函数编排这几块,摆在面前的选择其实不多——要么用Spring AI,要么用LangChain4j,偶尔也有人自己裸写HTTP客户端调API。
我所在的项目组之前一直用自己封装的HTTP工具类调模型接口,简单场景够用,但一旦涉及多轮对话记忆、向量检索、函数调用编排,代码就开始失控。于是团队决定引入一个成熟的AI框架。问题来了:选哪个?
网上关于这两个框架的对比文章不少,但大多数停留在“Spring AI有什么特性”“LangChain4j支持哪些模型”这种罗列层面。真正让我下决心做这个测试的,是一个很实际的问题:如果我用同一套业务逻辑,分别用两个框架实现,代码差异到底有多大?迁移成本有多高?运行时的表现有没有本质区别?
这个测试我前前后后花了大概两周的业余时间,中间踩了不少坑,也推翻了自己一开始的一些判断。下面把整个过程和结论完整记录下来,包括代码结构、配置细节、性能数据、以及那些文档里不会写的坑。
注意:本文涉及的所有模型服务均通过标准API接口调用,测试环境为本地开发机,不涉及任何特定网络环境配置。
2. 测试方案设计与核心思路拆解
2.1 为什么选“同一套业务代码”作为测试基准
很多对比文章喜欢用“Hello World”级别的示例来展示两个框架的用法差异,比如调一次模型返回一句话。这种测试有意义,但意义有限——它只能告诉你API长什么样,不能告诉你真实项目里哪个更顺手。
我设计的测试基准是一个模拟的智能客服工单处理系统,核心业务逻辑包括:
- 接收用户自然语言描述的问题
- 调用大模型进行意图分类(比如“退款”“咨询”“投诉”)
- 根据意图决定是否需要查询知识库(RAG)
- 如果需要,从向量库检索相关文档片段
- 将检索结果和用户问题一起交给模型生成最终回复
- 记录对话历史,支持多轮追问
这套逻辑覆盖了AI应用开发中最常见的几个能力点:对话管理、结构化输出、向量检索、提示词模板、多轮上下文。用同一套业务需求分别用两个框架实现,才能看出真正的差异。
2.2 两个框架的定位差异(以及为什么这很重要)
在动手之前,有必要先理清两个框架的设计哲学,因为这直接决定了后续代码的组织方式。
Spring AI的定位很明确:它是Spring生态的一部分。如果你用过Spring Data、Spring Security,你会发现Spring AI的API设计风格一脉相承——大量使用注解、自动配置、依赖注入。它的核心抽象是ChatClient,围绕它构建了Advisor、VectorStore、FunctionCallback等组件。优势是跟Spring Boot项目集成极其顺滑,劣势是如果你不用Spring,或者项目结构比较特殊,会感觉被框架“绑架”。
LangChain4j的定位是“Java版的LangChain”。它的设计更偏向于链式调用和显式编排。核心抽象是AiServices接口和ChatLanguageModel,通过建造者模式组装各种组件。优势是灵活,不依赖特定容器,可以按需组合;劣势是配置相对繁琐,跟Spring Boot集成需要额外写配置类。
我一开始的判断是:如果项目本来就是Spring Boot,Spring AI应该更省事;如果项目结构复杂或者需要精细控制,LangChain4j更合适。但实际跑下来,这个判断需要修正。
2.3 测试环境与依赖版本说明
为了让测试结果有参考价值,我把环境信息列清楚:
| 项目 | 版本/配置 |
|---|---|
| JDK | 21 (LTS) |
| Spring Boot | 3.3.x |
| Spring AI | 1.0.0-M6 |
| LangChain4j | 0.35.0 |
| 模型服务 | 兼容OpenAI接口的通用模型服务 |
| 向量库 | 内存向量库(测试用) |
| 构建工具 | Maven 3.9.x |
| 操作系统 | Linux (开发机) |
提示:Spring AI在1.0.0-M6版本中API变动较大,如果你用的是更早的版本,部分代码可能需要调整。LangChain4j 0.35.0的API相对稳定,但不同小版本之间也有细微差异。
两个框架都通过标准的模型服务接口调用,配置项包括API地址、密钥、模型名称等,这些在两个框架中都是通过配置文件或建造者参数传入的。
3. 核心细节解析与实操要点
3.1 对话客户端初始化:注解驱动 vs 建造者模式
先看最基础的模型调用初始化。
Spring AI的做法:
在application.yml中配置:
spring: ai: openai: api-key: ${API_KEY} base-url: ${BASE_URL} chat: options: model: gpt-4o-mini temperature: 0.7然后直接注入ChatClient:
@Service public class IntentService { private final ChatClient chatClient; public IntentService(ChatClient.Builder builder) { this.chatClient = builder.build(); } }LangChain4j的做法:
需要手动构建模型对象:
@Configuration public class LangChainConfig { @Bean public ChatLanguageModel chatModel() { return OpenAiChatModel.builder() .apiKey(System.getenv("API_KEY")) .baseUrl(System.getenv("BASE_URL")) .modelName("gpt-4o-mini") .temperature(0.7) .build(); } }然后注入使用:
@Service public class IntentService { private final ChatLanguageModel model; public IntentService(ChatLanguageModel model) { this.model = model; } }差异分析:Spring AI的自动配置确实省事,配置文件一写就能用。LangChain4j需要显式构建,但好处是你可以精确控制每个参数,而且不依赖Spring的自动配置机制。如果你的项目有多个不同配置的模型实例(比如一个用于意图分类用低温度,一个用于生成回复用高温度),LangChain4j的建造者模式反而更清晰。
实操心得:Spring AI也可以通过
ChatClient.Builder的defaultOptions()方法覆盖配置,但多个实例的管理不如LangChain4j直观。我在测试中需要两个不同温度的模型实例,Spring AI那边折腾了一会儿才搞定。
3.2 提示词模板:字符串拼接 vs 结构化模板
意图分类需要把用户输入嵌入到提示词中。
Spring AI提供了PromptTemplate:
PromptTemplate template = new PromptTemplate(""" 你是一个客服意图分类器。请将以下用户问题分类为: 退款、咨询、投诉、其他。 只返回分类结果,不要解释。 用户问题:{question} """); Prompt prompt = template.create(Map.of("question", userInput)); String result = chatClient.call(prompt).getResult().getOutput().getContent();LangChain4j的做法类似但更灵活:
PromptTemplate template = PromptTemplate.from(""" 你是一个客服意图分类器。请将以下用户问题分类为: 退款、咨询、投诉、其他。 只返回分类结果,不要解释。 用户问题:{{question}} """); String result = model.generate(template.apply(Map.of("question", userInput)).text());差异分析:两者都支持模板变量替换,但LangChain4j的模板语法更接近Mustache风格,Spring AI用的是{}占位符。功能上差别不大,但LangChain4j的PromptTemplate可以更方便地跟其他组件组合。
注意:Spring AI的
PromptTemplate在M6版本中对特殊字符的处理有一些边界问题,比如用户输入中包含{}时可能导致模板解析异常。LangChain4j在这方面更稳健一些。我在测试中特意用包含花括号的用户输入做了验证。
3.3 结构化输出:类型安全转换的两种路径
意图分类的结果需要映射为Java枚举。这是两个框架差异比较大的地方。
Spring AI提供了.entity()方法:
public enum Intent { REFUND, CONSULT, COMPLAINT, OTHER } Intent intent = chatClient.call(prompt) .entity(Intent.class);看起来很简单,但实际用的时候发现,模型返回的文本如果跟枚举值不完全匹配(比如返回“退款”而不是“REFUND”),转换就会失败。需要额外配置BeanOutputConverter或者自定义转换器。
LangChain4j的做法是通过AiServices接口:
interface IntentClassifier { @UserMessage(""" 你是一个客服意图分类器。请将以下用户问题分类为: 退款、咨询、投诉、其他。 用户问题:{{it}} """) Intent classify(String userInput); } IntentClassifier classifier = AiServices.create(IntentClassifier.class, model); Intent intent = classifier.classify(userInput);差异分析:LangChain4j的AiServices方式更声明式,接口定义即契约,代码更干净。Spring AI的.entity()方式更直接,但需要处理转换失败的边界情况。实测下来,LangChain4j在结构化输出这块的体验明显更好,尤其是当输出结构比较复杂(比如嵌套对象)时。
实操心得:无论用哪个框架,都建议在提示词中明确要求模型返回JSON格式,并且在代码中做好异常兜底。模型不按格式返回是常态,不是异常。
3.4 向量检索与RAG集成
RAG是这次测试的重点,也是两个框架差异最大的地方。
Spring AI的RAG集成围绕Advisor构建:
@Bean public ChatClient chatClient(ChatClient.Builder builder, VectorStore vectorStore) { return builder .defaultAdvisors(new QuestionAnswerAdvisor(vectorStore)) .build(); }然后调用时自动触发检索:
String response = chatClient.prompt() .user(question) .call() .content();LangChain4j需要显式编排检索链:
Retriever retriever = EmbeddingStoreRetriever.from(embeddingStore, embeddingModel, 5); String response = ConversationalRetrievalChain.builder() .chatLanguageModel(model) .retriever(retriever) .build() .execute(question);差异分析:Spring AI的Advisor机制确实优雅,检索逻辑对业务代码透明。但问题在于,当检索策略需要动态调整时(比如根据意图决定是否检索、检索多少个文档),Advisor的灵活性就不够了。LangChain4j的显式编排虽然代码多一些,但控制力更强。
我在测试中需要实现“只有咨询类问题才触发检索”的逻辑。Spring AI那边需要自定义Advisor,写了不少代码;LangChain4j那边就是一个简单的if判断。
提示:Spring AI也支持通过
QuestionAnswerAdvisor的参数动态控制检索行为,但文档不够详细,我是翻源码才找到的。
4. 实操过程与核心环节实现
4.1 项目结构搭建与依赖引入
先看两个框架在Maven项目中的依赖引入方式。
Spring AI:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>1.0.0-M6</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-core</artifactId> <version>1.0.0-M6</version> </dependency>LangChain4j:
<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>0.35.0</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>0.35.0</version> </dependency>Spring AI的starter会自动配置大量Bean,LangChain4j需要手动配置。这一点在项目启动阶段就能感受到差异:Spring AI的项目启动后,日志里会多出一堆自动配置相关的输出;LangChain4j则干净很多。
4.2 完整业务逻辑实现对比
我把智能客服的核心流程完整实现了一遍,下面是关键代码的对比。
意图分类环节:
Spring AI版本:
public Intent classifyIntent(String userInput) { PromptTemplate template = new PromptTemplate(INTENT_PROMPT); Prompt prompt = template.create(Map.of("question", userInput)); try { return chatClient.call(prompt).entity(Intent.class); } catch (Exception e) { log.warn("意图分类失败,降级为OTHER", e); return Intent.OTHER; } }LangChain4j版本:
public Intent classifyIntent(String userInput) { try { return intentClassifier.classify(userInput); } catch (Exception e) { log.warn("意图分类失败,降级为OTHER", e); return Intent.OTHER; } }RAG检索环节:
Spring AI版本需要自定义Advisor:
public class ConditionalRagAdvisor implements CallAroundAdvisor { private final VectorStore vectorStore; private final Intent currentIntent; @Override public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) { if (currentIntent == Intent.CONSULT) { List<Document> docs = vectorStore.similaritySearch( SearchRequest.query(request.userText()).withTopK(5)); String context = docs.stream() .map(Document::getContent) .collect(Collectors.joining("\n")); request = AdvisedRequest.from(request) .withUserText(context + "\n\n" + request.userText()) .build(); } return chain.nextAroundCall(request); } }LangChain4j版本:
public String generateResponse(String userInput, Intent intent) { String context = ""; if (intent == Intent.CONSULT) { List<TextSegment> segments = retriever.retrieve(userInput); context = segments.stream() .map(TextSegment::text) .collect(Collectors.joining("\n")); } String prompt = buildPrompt(userInput, context); return model.generate(prompt); }差异分析:Spring AI的Advisor机制在简单场景下很优雅,但一旦需要条件逻辑,代码复杂度急剧上升。LangChain4j的显式编排虽然看起来“笨”,但逻辑清晰,调试也方便。
4.3 多轮对话记忆的实现
多轮对话是客服系统的刚需。
Spring AI通过ChatMemory实现:
@Bean public ChatMemory chatMemory() { return new InMemoryChatMemory(); } @Bean public ChatClient chatClient(ChatClient.Builder builder, ChatMemory memory) { return builder .defaultAdvisors(new MessageChatMemoryAdvisor(memory)) .build(); }LangChain4j通过ChatMemory和AiServices结合:
interface Assistant { String chat(@MemoryId String sessionId, @UserMessage String message); } Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemoryProvider(sessionId -> MessageWindowChatMemory.withMaxMessages(20)) .build();差异分析:两者都提供了开箱即用的内存记忆实现。Spring AI的记忆管理跟Advisor机制绑定,LangChain4j的记忆管理跟AiServices绑定。实测下来,LangChain4j的@MemoryId注解方式在多会话场景下更直观。
实操心得:内存记忆在生产环境不可靠,重启就丢。两个框架都支持接入外部存储(比如Redis),但LangChain4j的扩展点更清晰。我后来把记忆存储换成了基于文件的实现,LangChain4j那边只改了一个Provider,Spring AI那边需要自定义
ChatMemory实现类。
4.4 性能实测数据
我在本地环境跑了100次完整的客服对话流程(意图分类+条件检索+生成回复),记录平均耗时:
| 环节 | Spring AI | LangChain4j |
|---|---|---|
| 意图分类 | 820ms | 810ms |
| 向量检索 | 45ms | 42ms |
| 回复生成 | 1500ms | 1480ms |
| 框架开销 | 约120ms | 约35ms |
| 总耗时 | 约2485ms | 约2367ms |
框架开销的差异主要来自Spring AI的Advisor链式调用和自动配置带来的额外代理层。LangChain4j的调用链更短,开销更小。不过这个差异在模型调用耗时面前占比不大,实际体感差异不明显。
注意:这个数据是在本地开发环境测的,没有做JVM预热和GC调优。生产环境的表现可能不同,建议在自己的环境里实测。
5. 常见问题与排查技巧实录
5.1 依赖冲突与版本兼容问题
Spring AI因为跟Spring Boot深度集成,版本兼容性需要特别注意。我遇到的一个典型问题是:Spring AI 1.0.0-M6要求Spring Boot 3.3.x以上,但项目里其他依赖锁定了3.2.x,导致启动时报NoSuchMethodError。
排查思路:先看mvn dependency:tree,找到冲突的Spring核心包,用<exclusions>排除旧版本。LangChain4j的依赖相对独立,跟Spring Boot版本耦合度低,这方面问题少一些。
5.2 模型返回格式不符合预期
这是最常见的坑。无论用哪个框架,模型都可能返回意料之外的格式。比如要求返回JSON,结果返回了带Markdown代码块的JSON。
解决方案:
- 在提示词中明确要求“只返回JSON,不要包含任何其他文字”
- 代码中做容错处理,比如用正则提取JSON部分
- 设置合理的重试机制
Spring AI的.entity()方法在格式不匹配时会抛异常,需要捕获处理。LangChain4j的AiServices同样会抛异常,但异常信息更详细,便于定位问题。
5.3 向量检索结果不相关
RAG效果不好,很多时候不是框架的问题,而是检索策略的问题。
排查清单:
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| 检索结果完全不相关 | 嵌入模型不匹配 | 检查嵌入模型和向量库的维度是否一致 |
| 检索结果部分相关 | 分块策略不合理 | 调整文档分块大小和重叠度 |
| 检索结果重复 | 相似度阈值过低 | 提高相似度阈值或减少TopK |
| 检索不到任何结果 | 向量库为空 | 确认文档已正确写入向量库 |
实操心得:Spring AI的
VectorStore默认相似度阈值比较宽松,容易召回不相关文档。LangChain4j的EmbeddingStoreRetriever可以更精细地控制检索参数。建议在测试阶段把TopK设大一些,观察召回情况后再调整。
5.4 多轮对话上下文丢失
多轮对话中,模型“忘记”之前说过的话,通常是因为记忆窗口设置太小,或者记忆没有正确关联到会话ID。
Spring AI的MessageChatMemoryAdvisor默认使用ChatMemory.DEFAULT_CONVERSATION_ID,如果多会话场景下没有显式传递会话ID,所有对话会混在一起。LangChain4j的@MemoryId注解强制要求指定会话ID,反而避免了这个问题。
5.5 常见问题速查表
| 问题 | Spring AI排查方向 | LangChain4j排查方向 |
|---|---|---|
| 启动报Bean冲突 | 检查自动配置排除项 | 检查手动配置的Bean |
| 模型调用超时 | 检查spring.ai.openai配置 | 检查OpenAiChatModel超时参数 |
| 结构化输出失败 | 检查BeanOutputConverter配置 | 检查接口方法的返回类型 |
| 检索无结果 | 检查VectorStore写入 | 检查EmbeddingStore内容 |
| 记忆不生效 | 检查Advisor顺序 | 检查ChatMemoryProvider |
6. 我的最终选择与迁移建议
两周测试跑下来,我最初的判断被推翻了。原本以为Spring AI会因为Spring Boot的加持而全面胜出,但实际结果是:在业务逻辑复杂度较高的场景下,LangChain4j的显式编排反而更省心。
Spring AI的优势在于简单场景的快速集成——如果你只是调个模型、做个简单的RAG,Spring AI的自动配置确实快。但一旦业务逻辑涉及条件分支、动态检索策略、多模型实例管理,Spring AI的Advisor机制就开始显得笨重。
LangChain4j的优势在于控制力和灵活性。它的API设计更接近“工具库”而非“框架”,你可以按需组合组件,不被框架的约定绑架。代价是需要写更多的配置代码,但换来的是更清晰的调用链和更低的调试成本。
如果你正在做技术选型,我的建议是:
- 项目是标准Spring Boot应用,AI功能只是辅助(比如智能客服的意图分类),选Spring AI
- 项目以AI能力为核心,业务逻辑复杂,需要精细控制检索和编排,选LangChain4j
- 团队对Spring生态非常熟悉,选Spring AI;团队更倾向于显式编程风格,选LangChain4j
迁移成本方面,如果从Spring AI迁移到LangChain4j,核心业务逻辑(提示词、检索策略、记忆管理)基本可以复用,主要改动在框架API的适配层。反过来迁移也类似。所以选型时不用太纠结“选错了怎么办”,两者的核心概念是相通的。
最后分享一个我在测试中总结的小技巧:无论用哪个框架,都建议把模型调用、检索、记忆管理封装成独立的Service层,框架相关的代码只出现在这一层。这样即使将来换框架,业务代码几乎不用动。我在测试中就是这么做的,两套实现共用同一套业务逻辑接口,切换框架只需要换实现类。