最近被问得最多的一个问题:Java后端到底能不能像Python那样做一套完整的RAG?这篇LangChain4j笔记就是我的回答。我用它接入了Qwen Embedding做文本向量化,把向量存进Milvus,再通过混合检索和重排把召回精度拉起来,整个过程踩了不少坑,也留下了一批可以直接抄的Java代码。如果你正打算在Java服务里做知识库问答、语义检索,或者只是想把通义千问的模型能力接进现有工程,这篇笔记应该能帮你少走弯路。
这套链路的核心路径是:文本切块 -> Qwen Embedding向量化 -> 写入Milvus -> 用户提问后做向量检索 + 关键词检索的混合召回 -> 重排模型精排 -> 交给大模型生成答案。文章会按这条路径拆开细讲,最后还会聊一下LangChain4j和Spring AI Alibaba的选型问题,因为这两个东西最近经常被放在一起问。
1. 为什么Java项目里我不直接套LangChain,而是选了LangChain4j
1.1 Java后端做LLM应用,缺的到底是什么
我所在团队的后端是纯Java/Spring Boot,没有条件为了一个AI功能单独引入Python微服务。早期我试过直接用HTTP调模型接口,简单对话还行,一旦要拆文档、维护向量库、做检索、拼Prompt,代码很快就变成一堆工具类和if-else,改起来非常痛苦。
这个阶段缺的不是能力,而是抽象。你需要一个统一的模型接口来切换不同厂商,需要一个通用的向量存储接口来屏蔽Milvus、PGVector、Redis的差异,还需要一套RAG组件来把检索、重排、Prompt填充串起来。如果这些全靠自己写,工作量远远大于业务本身。
LangChain4j就是为解决这个问题出现的。它是JVM生态里目前最接近LangChain设计思路的框架,但不是把Python的LangChain翻译成Java,而是按照Java的习惯重新设计了一套组件。核心包括:ChatLanguageModel统一大模型对话调用、EmbeddingModel统一文本向量化、EmbeddingStore统一向量库操作、ContentRetriever统一检索器接口。
1.2 几个核心抽象先记住
先说ChatLanguageModel。它管的是你和大模型之间的对话,不管是OpenAI、通义千问还是其他模型,只要能封装成同一个接口,业务代码里就不需要关心底层HTTP数据长什么样。
然后是EmbeddingModel。它接收文本,输出一个浮点数组,也就是向量。这个向量代表文本的语义,后续所有相似度检索都建立在它上面。EmbeddingStore则是向量库的抽象,比如Milvus、Redis、PGVector都实现了这个接口,换存储不用改业务代码。
最后是ContentRetriever。这是RAG系统的核心扩展点,你可以在里面实现混合检索、过滤、重排等各种自定义逻辑。配合AiServices,可以把一个普通接口变成具备RAG能力的助手,调用者只需要像调本地方法一样传入问题,框架自动完成检索和回答。
1.3 这篇笔记覆盖的路径
内容上分为三块:第一块是从Maven依赖到第一个对话Demo跑通;第二块是完整的RAG链路,包括Qwen Embedding向量化、Milvus存储、混合检索和重排;第三块是LangChain4j和Spring AI Alibaba的对比选型。如果你只关心某一环,可以直接跳到对应章节看代码和注意事项,但建议从头读一遍,因为前后环环相扣。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从零到跑通对话:Maven依赖、版本坑和最小示例
2.1 依赖引入
先看Maven依赖。我使用的是LangChain4j 1.x,如果项目里还没有锁定版本,建议用dependencyManagement统一管理,避免多个模块版本不一致。
xml复制<properties>
<langchain4j.version>1.0.0</langchain4j.version>
</properties>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-milvus</artifactId>
<version>${langchain4j.version}</version>
</dependency>
</dependencies>
有人可能会问,我用的明明是通义千问,为什么要引入langchain4j-open-ai?因为DashScope提供了OpenAI兼容接口,LangChain4j的OpenAI模块可以直接通过配置baseUrl来对接。这样既不用等专门的DashScope模块,又能用上社区维护最完善的客户端实现。
2.2 第一个对话示例
依赖加好之后,先别急着玩RAG,跑通一个对话是最高优先级。下面是最小的调用示例:
java复制import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
public class ChatDemo {
public static void main(String[] args) {
ChatLanguageModel model = OpenAiChatModel.builder()
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.modelName("qwen-plus")
.build();
String answer = model.chat("用一句话解释什么是RAG");
System.out.println(answer);
}
}
这里有一个很重要的点:apiKey不要硬编码在代码里。我一开始图省事写在配置里,结果代码被同事发到群里,第二天就把AK轮换了。更稳妥的做法是放进环境变量,或者接入公司的密钥管理服务。
跑通这个Demo后,你已经完成了50%的工程。接下来所有复杂功能,其实都是在ChatLanguageModel之外再加更多组件。
2.3 版本更新踩过的坑
LangChain4j从0.x升级到1.x,API有比较大的调整。我在老项目里用的是0.36版本,升级后最直接的变化是:AiServices.builder的包名变了,EmbeddingSearchRequest的构造方式也变了,编译报错一片。
另外要注意的是依赖冲突。langchain4j-milvus底层依赖了gRPC和protobuf,如果你的项目本身有gRPC组件,版本不一致时会出现NoSuchMethodError,而且往往只在运行时才暴露。我的处理方式是在dependencyManagement里统一gRPC版本,而不是随便exclusions掉,否则Milvus客户端可能直接起不来。
我的建议是:新项目直接用最新稳定版本,老项目升级前先看官方迁移文档,别硬莽。 版本统一之后,再开始写功能。
3. 文本向量化环节:Qwen Embedding接入与调用示例
3.1 用OpenAI兼容模式接通Qwen Embedding
对话模型跑通之后,接下来是做文本向量化。DashScope的文本向量化模型可以通过同一个OpenAI兼容端点来调用,我用的模型是text-embedding-v3,它返回的向量维度是1024。
核心代码如下:
java复制import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.baseUrl("
