Ohhnews

分类导航

$ cd ..
foojay原文

Spring AI 还是 LangChain4j?同一应用、三种技术栈、一个测试

#spring ai#langchain4j#java#框架对比#quarkus

Java 已经悄然成为构建 AI 应用的可靠选择。这给刚入门的人带来一个实际问题:应该选择 Spring AI 还是 LangChain4j,以及把它运行在 Spring Boot 还是 Quarkus 上是否有影响?

回答这个问题的可靠方式不是列一张功能表,而是在每个技术栈上构建同样的东西,再解读其中的差异。本文正是这样处理一个最小可用的 AI 应用:一个 HTTP 端点,将问题转发给模型并返回其回答。我们构建三次,分别使用 Spring AI、Spring Boot 上的 LangChain4j 和 Quarkus 上的 LangChain4j,然后用同一个共享测试检验这三者。

一个接口,三种实现

一切都从一个与框架无关的契约开始:一个不依赖任何 AI 库的普通 Java 接口:

$ java
public interface QuestionAnsweringService {
  Answer askQuestion(Question question);
}

Question 和 Answer 是简单的 record。每个技术栈只提供该接口自己的实现,共享代码中不会泄漏任何框架特定的内容。因此,三者之间唯一变化的就是各自与模型通信的方式。

Spring AI:流畅的 ChatClient

$ java
@Service
public class SpringAiQuestionAnsweringService implements QuestionAnsweringService {

  private final ChatClient chatClient;

  public SpringAiQuestionAnsweringService(ChatClient.Builder chatClientBuilder) {
    this.chatClient = chatClientBuilder.build();
  }

  @Override
  public Answer askQuestion(Question question) {
    var answerText = chatClient.prompt()
      .user(question.question())
      .call()
      .content();
    return new Answer(answerText);
  }
}

Spring AI 会交给你一个由配置属性自动配置的 ChatClient.Builder,以及一套流畅 API:prompt()、user()、call()、content()。模型、其 base URL 及其选项全都是配置,而不是代码。

LangChain4j:一个普通的 ChatModel,以及两个运行时上的相同代码

在 Spring Boot 上:

$ java
@Service
public class LangChain4jQuestionAnsweringService implements QuestionAnsweringService {

  private final ChatModel chatModel;

  public LangChain4jQuestionAnsweringService(ChatModel chatModel) {
    this.chatModel = chatModel;
  }

  @Override
  public Answer askQuestion(Question question) {
    return new Answer(chatModel.chat(question.question()));
  }
}

LangChain4j 会交给你一个 ChatModel 和一个方法:chat()。没有需要调用的 builder:注入的模型可以直接使用。

下面是 Quarkus 版本:

$ java
@ApplicationScoped
public class LangChain4jQuestionAnsweringService implements QuestionAnsweringService {

  private final ChatModel chatModel;

  public LangChain4jQuestionAnsweringService(ChatModel chatModel) {
    this.chatModel = chatModel;
  }

  @Override
  public Answer askQuestion(Question question) {
    return new Answer(chatModel.chat(question.question()));
  }
}

仔细看。方法体与 Spring Boot 版本完全相同。唯一区别是注解:用 @ApplicationScoped(CDI)替代了 @Service(Spring)。同样的库、同样的 ChatModel、同样的 chat() 调用。运行时变了,代码没变。这正是值得在两个运行时上都运行 LangChain4j 的原因:它把框架成本与运行时成本隔离开来。

一个测试比较全部

只有用同一标准检验,三种实现才具有可比性。每个技术栈的集成测试都继承同一个抽象测试,该测试通过 HTTP 与运行中的应用通信:

$ java
public abstract class AbstractQuestionAnsweringIT {

  protected abstract URI baseUri();

  @Test
  @DisplayName("answers a factual question")
  protected void answersAFactualQuestion() {
    ask()
      .body(new Question("What is the capital of France? Answer with the city name only."))
      .when()
      .post("/ask")
      .then()
      .statusCode(200)
      .body("answer", not(blankOrNullString()))
      .body("answer", containsStringIgnoringCase("paris"));
  }

  // ... plus: rejects a blank question (400), rejects a malformed payload (400)

  private RequestSpecification ask() {
    return given().baseUri(baseUri().toString()).contentType(ContentType.JSON);
  }
}

这里有两点很重要。第一,测试套件使用 REST Assured 通过 HTTP 通信,从不使用进程内 API,因此同一个测试类可以用同一标准检验 Spring Boot 应用和 Quarkus 应用:子类只需提供自己的 base URI。第二,断言是语义化的,而不是精确字符串匹配。它检查回答是否包含 “paris”(不区分大小写),因为即使在 temperature 为 0 时,LLM 输出也并非确定性的。对字面补全结果做断言会让测试说谎。

测试针对 Ollama 容器中的本地模型运行,该容器由 Testcontainers 启动,因此不需要 API key,也没有每次运行的成本,整个比较过程可在 CI 中复现。

比较揭示了什么

三个技术栈的规模并不相同,启动速度也不一样。仅统计生产 Java 代码中的非空、非注释行:

Spring AILangChain4j / BootLangChain4j / Quarkus
生产 Java 代码(LOC)484739
src/main/java 中的文件数332
已解析的运行时 jar 数10649142
JVM 模式启动时间1.74 秒2.69 秒0.66 秒

启动数据是在单台机器上进行少量运行后取中位数得到的,且 Ollama 容器已经启动,因此计时窗口内只有应用启动过程:这些数据仅供参考,并非基准测试。jar 数量也不意味着 Quarkus 比 Boot 上的 LangChain4j 重三倍;它只是把运行时拆成了许多小型构件。公平比较显示,在同一个运行时上,Spring AI 带来 106 个 jar,而 LangChain4j 只带来 49 个。这是因为 Spring AI 还引入了 JSON schema 生成器、分词器和 Reactor,而本用例都没有用到。

即使这样谨慎表述,轮廓也很清楚:Quarkus 生成最小的模块并且启动最快,Spring AI 代码紧凑但依赖最重,而 Boot 上的 LangChain4j 接近 Spring AI——同样三个文件,只是其 starter 多带了几个 jar。

从定性角度看,差异不大但值得关注:

  • Spring AI 以流畅的、基于 builder 的 ChatClient 为主导,模型由配置驱动。
  • LangChain4j 以单次 ChatModel.chat() 调用为主导,注入的模型就是全部对外接口。
  • 在 Spring Boot 和 Quarkus 之间,LangChain4j 代码除了依赖注入注解外完全相同,这说明变量是运行时,而不是库。

这些都不是最终裁决。这样的比较真正有意思的输出不是赢家,而是差异,而差异才能告诉你哪个框架契合你团队已有的思维方式。

接下来走向哪里

这是最小的用例。同样的方法——每个技术栈各构建一次,并用同一个共享测试来评判——可以一路延伸到:检索增强生成、对话记忆、工具调用、模型上下文协议(Model Context Protocol)、可观测性、安全性和智能体。每增加一项能力,都会显示三个技术栈在哪些地方趋同、在哪些地方分化。

如果你想要完整论述,那正是我的书 Java AI Frameworks 的主题,该书在 Leanpub 上有售,并提供前两章的免费样章。## 加入讨论

Java [LOADING...]
[LOADING...]Somil Garg 2026年11月3日 · 66 次浏览

无引用,不论断:让 LLM 在 Java 中展示其来源

JavaAISpring

AI [LOADING...]
[LOADING...]Ricardo Mello 2026年9月15日 · 587 次浏览

我如何用 Java、Spring AI 和 MongoDB 构建职业 AI 助手

AI数据库Java

Jakarta EE [LOADING...]
[LOADING...]Luqman Saeed 2026年10月7日 · 418 次浏览

仅用 JDK 和 Jakarta 规范为 Jakarta EE 11 应用添加 RAG

Jakarta EEAIJava

Mongo [LOADING...]
[LOADING...]Otavio Santana 2026年8月10日 · 635 次浏览

Java 和 MongoDB 检索增强生成(RAG)入门

Mongo