公司动态
LangChain4j实战:Java生态接入Qwen与Milvus构建RAG应用
先说一个很常见的场景后端是 Java 技术栈业务突然需要把内部文档接入大模型做一个知识库问答或者智能客服。翻了一圈资料大多数教程都基于 Python 的 LangChainJava 团队要么自己写 HTTP 封装要么把检索逻辑放到 Python 微服务里链路长、排错难、维护成本高。后来接触到 LangChain4j才意识到 Java 生态里也能把大模型调用、Embedding 向量化、Milvus 存储、RAG 检索完整串起来。本文就围绕 LangChain4j 零基础入门到项目实战展开完整演示从环境搭建、接入 Qwen 对话模型到 Qwen Embedding 写入 Milvus再到混合检索与重排的整个闭环。如果你正准备在 Java 项目里落地大模型应用或者已经尝试过 LangChain4j 但卡在 Milvus 集成和检索质量调优上这篇文章应该能帮到你。文章不会只贴代码也会解释每一步背后的原理包括为什么用向量数据库、为什么需要混合检索、重排到底在解决什么问题。代码示例以常见环境为准版本迭代较快的组件我会给出明确的替换思路你按实际依赖版本微调即可。1. LangChain4j 是什么为什么 Java 开发者需要它1.1 技术诞生背景LangChain4j 可以理解为 Java 生态里的 LLM 编排框架名字里的 4j 就是 for Java 的意思。它诞生的背景很直接Python 生态里已经有 LangChain、LlamaIndex 这些框架可以快速把大模型、提示词、记忆、外部工具、向量数据库组装成 AI 应用。但很多企业的核心业务系统是 Java 写的特别是金融、电商、政企类项目想在已有 Spring Boot 服务里直接引入 AI 能力不可能把所有逻辑都迁到 Python。早期 Java 开发者的做法是直接调用大模型 HTTP API自己维护对话上下文自己处理文档切分和向量化再自己写向量数据库的客户端代码。功能能做出来但每个项目都要重复造轮子而且不同大模型厂商的 API 格式还不一致切换模型成本很高。LangChain4j 就是把这一层通用能力抽象出来让 Java 开发者可以用统一 API 对接不同大模型、不同向量库、不同 RAG 组件。1.2 它解决什么问题LangChain4j 解决的问题可以拆成四类第一是模型接入统一化通过ChatLanguageModel接口屏蔽各家大模型 API 差异OpenAI、Qwen、通义千问、智谱等模型都可以通过对应模块接入第二是对话管理标准化Message、ChatMemory、AiServices这些组件让你不用自己拼历史消息数组第三是 RAG 链路组件化EmbeddingModel、EmbeddingStore、ContentRetriever分别负责向量化、向量存储和召回替换实现类就能切换底层组件第四是与 Java 生态融合它不强制你换框架Spring Boot、Quarkus、普通 Maven 项目都能用。对于团队来说使用 LangChain4j 之后AI 能力和业务代码可以放进同一个工程共用日志、监控、配置中心和权限体系。这样理解起来更简单LangChain4j 是 Java 侧的一层 AI 编排中间件你只需要负责业务数据和提示词策略底层通信交给它。1.3 典型应用场景LangChain4j 的典型场景主要有这么几类企业内部知识库问答把产品文档、技术规范、操作手册切成片段后存入向量库用户提问时先召回相关内容再交给大模型生成回答智能客服工单处理结合工具调用实现查订单、查物流、转人工代码辅助和日志分析把异常日志喂给模型让它输出定位建议内容分析和信息抽取从非结构化文本中抽取结构化字段。这些场景本质上有共同点先用检索缩小范围再用大模型做生成或决策LangChain4j 正好覆盖这条完整链路。2. 核心概念扫盲模型、消息、记忆与 RAG2.1 ChatLanguageModel 与 Message在 LangChain4j 里ChatLanguageModel是最核心的接口它表示一个能处理多轮对话的大语言模型。你不需要关心底层是调用 OpenAI 还是 Qwen只要拿到ChatLanguageModel实例调用它的generate系列方法就能得到模型回复。对话内容由Message表达常见的有SystemMessage系统消息、UserMessage用户消息、AiMessage助手消息还有ToolExecutionResultMessage工具执行结果消息。为什么要用 Message 而不是简单传一个字符串因为大模型的上下文是一系列消息组成的数组系统消息可以设定角色历史消息帮助模型理解对话脉络工具消息让模型知道工具调用结果。LangChain4j 把这些概念封装成类型开发时不会拼错 JSONIDE 也能自动提示。比如你要让模型扮演客服可以SystemMessage.from(你是 XX 产品的客服助手)然后把用户问题和历史对话一起传进去。2.2 EmbeddingModel 与 EmbeddingStoreEmbedding 就是把文本变成一组浮点数向量语义相近的文本在向量空间里距离更近。EmbeddingModel负责做向量化EmbeddingStore负责存储向量并执行相似度检索。LangChain4j 中的EmbeddingStoreTextSegment是一个泛型接口TextSegment代表一段被切分后的文本同时可以附带元数据比如文档 ID、标题、章节、来源链接等。典型流程是先把文档拆成多个TextSegment用EmbeddingModel给每段生成向量再调用EmbeddingStore.add把向量和原始文本存进去。查询时把用户问题向量化调用findRelevant方法返回最相似的 TopK 片段。当前支持多种向量库Milvus 是其中比较常用的一种适合海量向量和分布式部署场景。2.3 AiServices 与 RAGAiServices是 LangChain4j 的高级 API它的思想是把 AI 能力绑定到普通 Java 接口上。你定义一个接口比如Assistant里面声明一个方法answer(String question)然后用AiServices.builder(Assistant.class)给它配置模型、记忆、工具和检索器LangChain4j 会自动生成这个接口的实现类。调用方法时它内部自动完成消息组装、工具调用、RAG 召回、结果生成。RAGRetrieval-Augmented Generation检索增强生成在 LangChain4j 中对应的核心组件是ContentRetriever。它接收用户的查询返回相关的Content列表。框架内置了基于向量库的EmbeddingStoreContentRetriever也支持自定义实现。如果你想做混合检索或者重排自定义ContentRetriever是最灵活的方式。2.4 与 Python LangChain 的差异很多同学会问我直接学 Python LangChain 不行吗当然可以但从 Java 工程化角度看LangChain4j 有几个明显优势。第一是类型安全接口方法、消息类型、返回结果都是 Java 强类型编译期就能发现问题第二是依赖治理Maven/Gradle 管理依赖比 Python 虚拟环境在大型企业里更成熟第三是部署一致性AI 应用和业务服务可以打成同一个 Jar沿用现有 CI/CD 流程第四是回归测试JUnit 可以方便地模拟模型返回做接口级测试。当然 LangChain4j 的生态组件数量相比 Python LangChain 还有差距部分新模型和新的 RAG 技术可能需要自己封装。但核心链路已经很完整覆盖大多数生产需求。技术选型时不要只看生态数量还要看团队维护成本和系统集成复杂度。3. 环境准备与项目初始化3.1 运行环境清单本文示例以一套常见 Java 开发环境为例具体版本需要根据你本机情况调整。推荐环境如下JDK 17 或更高版本Maven 3.8 以上IDE 使用 IntelliJ IDEA需要一个可访问的通义千问 DashScope API Key用于调用对话模型和 Embedding 模型还需要一个可访问的 Milvus 服务本地开发可以用 Docker 起单机版也可以用公司测试环境。这里要特别提醒LangChain4j 版本更新非常快不同小版本的 API 可能有细微差异。我在示例代码里不写死具体版本你用 Maven 依赖时建议直接访问 Maven 中央仓库查询最新稳定版或者使用你项目里已经引入的 LangChain4j 版本。本文重点关注设计思路和核心代码API 变化后通过官方 Javadoc 很容易调整。3.2 创建 Maven 项目结构为了演示方便我建议创建一个简单的 Maven 项目不引入 Spring Boot先用main方法跑通全流程。后续接入 Spring Boot 时把核心代码抽成 Bean 即可。项目结构大致如下langchain4j-demo ├── pom.xml └── src/main/java └── com/example/demo ├── DemoApplication.java ├── QwenConfig.java ├── MilvusConfig.java ├── HybridContentRetriever.java └── Assistant.java如果你用的是 Spring Boot可以把QwenConfig和MilvusConfig改成Configuration配置类把模型和向量库实例声明为Bean这样业务代码里直接注入使用。先把直接可运行的main方法写出来更容易理解原理。3.3 添加 Maven 依赖在pom.xml中加入核心依赖。因为版本迭代快我把版本号用占位符标注请替换成你选择的版本dependencies !-- LangChain4j 核心依赖 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version${langchain4j.version}/version /dependency !-- 通过 OpenAI 兼容模式接入 Qwen 需要用到 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version${langchain4j.version}/version /dependency !-- Milvus 向量库集成 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-milvus/artifactId version${langchain4j.version}/version /dependency /dependencies在properties中定义langchain4j.version例如properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding langchain4j.version请填写你选择的最新稳定版本/langchain4j.version /properties注意上面这个占位符不能直接用于 Maven 构建实际使用时要替换成具体版本号。如果你不确定哪个版本稳定建议先看官方 GitHub 仓库的 release 说明或者看 Maven 中央仓库中dev.langchain4j的最新版本。3.4 配置文件虽然纯 Java 示例不需要配置文件但我建议把 API Key、Milvus 地址统一放到环境变量或配置中心避免硬编码。这里使用环境变量方式更安全。后面代码中会读取DASHSCOPE_API_KEY、MILVUS_URI、MILVUS_TOKEN这几个环境变量。export DASHSCOPE_API_KEY你的DashScope密钥 export MILVUS_URIhttp://localhost:19530 export MILVUS_TOKENroot:MilvusAPI Key 是敏感信息生产环境一定不要提交到 Git 仓库也不要写在配置文件中随代码发布。Spring Boot 项目可以用配置中心管理普通项目用环境变量或密钥管理服务。4. 第一个案例通过 LangChain4j 接入 Qwen 对话模型4.1 选择接入协议Qwen 系列模型可以通过 DashScope 的 OpenAI 兼容模式接入LangChain4j 的langchain4j-open-ai模块可以直接复用。这种做法的好处是无需引入额外的国产模型专用 SDK只要配置baseUrl、apiKey和modelName就能切换模型。如果你企业内部已经部署了兼容 OpenAI API 的模型网关也可以用同样的方式接入。需要说明的是DashScope 的兼容模式接口地址是https://dashscope.aliyuncs.com/compatible-mode/v1。对话模型可以填写qwen-plus、qwen-turbo等实际支持情况以 DashScope 官方文档为准。下面代码用qwen-plus做演示你可以根据业务场景选择合适模型。4.2 最小代码示例创建一个DemoApplication.java先体验最简单的对话生成// 文件路径src/main/java/com/example/demo/DemoApplication.java package com.example.demo; import dev.langchain4j.model.openai.OpenAiChatModel; public class DemoApplication { public static void main(String[] args) { OpenAiChatModel chatModel OpenAiChatModel.builder() .apiKey(System.getenv(DASHSCOPE_API_KEY)) .baseUrl(https://dashscope.aliyuncs.com/compatible-mode/v1) .modelName(qwen-plus) .build(); String answer chatModel.generate(请用一句话介绍 LangChain4j); System.out.println(answer); } }这段代码里的OpenAiChatModel来自langchain4j-open-ai它实现了ChatLanguageModel接口。generate方法接收一个用户消息字符串返回模型生成的文本。因为配置了 DashScope 兼容地址实际请求会发送到通义千问服务。运行前确认环境变量DASHSCOPE_API_KEY已设置然后执行mvn compile exec:java -Dexec.mainClasscom.example.demo.DemoApplication如果你在 IDE 里运行直接右键执行main方法即可。4.3 运行与验证正常执行后控制台会输出类似下面的内容LangChain4j 是一个面向 Java 开发者的大语言模型应用开发框架用于构建基于大模型的智能应用。由于模型输出有随机性每次结果不完全一样这是正常现象。到这里你已经完成了 LangChain4j 接入 Qwen 的第一个闭环Java 代码直接对话大模型。这个基础非常重要后面的 RAG 和工具调用都是在这个模型实例之上扩展的。4.4 引入记忆与流式输出生产场景里问答助手通常需要多轮对话记忆。LangChain4j 里最简单的做法是使用MessageWindowChatMemory并在AiServices中配置。先看依赖已经包含不需要额外引入。示例改造为带记忆的助手// 文件路径src/main/java/com/example/demo/Assistant.java package com.example.demo; import dev.langchain4j.service.AiServices; import dev.langchain4j.memory.chat.MessageWindowChatMemory; public interface Assistant { String chat(String message); } // 在 main 中构建 Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(chatModel) .chatMemory(MessageWindowChatMemory.withMaxMessages(20)) .build(); System.out.println(assistant.chat(我叫小明)); System.out.println(assistant.chat(我叫什么名字));AiServices会自动管理历史消息第二句回答会记得用户叫小明。这种方式比手动拼接历史消息可靠得多也是后续项目实战的首选方式。流式输出则使用OpenAiStreamingChatModel回调接口里接收每个 token适合打字机效果这里先不展开。5. Qwen Embedding 与 Milvus 向量存储5.1 为什么需要向量化与向量数据库问答模型本身不具备企业私有知识它只知道训练时见过的数据。要让模型回答特定文档内容常见做法是 RAG先把私有文档切分成片段离线生成向量并存入向量数据库用户提问时把问题也转成向量在向量库中查找语义相似的文档片段最后把这些片段作为上下文交给对话模型生成回答。向量数据库负责存储和检索高维向量。Milvus 是开源向量数据库支持海量向量、标量过滤、混合检索Java 集成也相对成熟。在 LangChain4j 中MilvusEmbeddingStore封装了向量集合管理、索引和查询逻辑我们不用直接操作 Milvus Java SDK 的复杂客户端。5.2 创建 EmbeddingModel使用 Qwen 的向量模型生成 Embedding。DashScope 兼容模式同样支持 Embedding 接口模型名可以填写text-embedding-v3。示例代码如下// 文件路径src/main/java/com/example/demo/QwenConfig.java package com.example.demo; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.model.openai.OpenAiEmbeddingModel; public class QwenConfig { public static EmbeddingModel createEmbeddingModel() { return OpenAiEmbeddingModel.builder() .apiKey(System.getenv(DASHSCOPE_API_KEY)) .baseUrl(https://dashscope.aliyuncs.com/compatible-mode/v1) .modelName(text-embedding-v3) .build(); } }EmbeddingModel的核心方法是embed(String text)返回一个ResponseEmbedding里面有向量数据。不同向量模型输出的维度不同比如text-embedding-v3可以配置输出维度使用时需要和 Milvus 集合的维度保持一致。5.3 文档拆分为 TextSegment向量化之前需要把文档拆成适合检索的片段。原因很简单整篇文档直接转成一个向量检索精度很低但如果切得太碎又会丢失上下文。常用的策略是按标题、段落、固定长度切分片段之间保留少量重叠。下面是最简单的按固定长度切分// 文件路径src/main/java/com/example/demo/TextSplitter.java package com.example.demo; import dev.langchain4j.data.segment.TextSegment; import java.util.ArrayList; import java.util.List; public class TextSplitter { public static ListTextSegment split(String text, int chunkSize, int overlap) { ListTextSegment segments new ArrayList(); int start 0; int index 0; while (start text.length()) { int end Math.min(start chunkSize, text.length()); segments.add(TextSegment.from(text.substring(start, end))); if (end text.length()) { break; } start end - overlap; index; } return segments; } }这个工具类只是一个演示实际生产中文档结构很复杂建议使用 LangChain4j 内置的DocumentSplitter或者按 Markdown 标题、PDF 段落先做结构化切分。切分质量直接影响 RAG 回答效果值得多花时间调优。5.4 接入 MilvusEmbeddingStore接下来把向量和原始片段写入 Milvus。用MilvusEmbeddingStore的 builder 创建实例配置 Milvus 地址、集合名和向量维度。示例假设text-embedding-v3输出维度为 1024实际以你使用的模型配置为准。// 文件路径src/main/java/com/example/demo/MilvusConfig.java package com.example.demo; import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore; public class MilvusConfig { public static MilvusEmbeddingStore createStore() { return MilvusEmbeddingStore.builder() .uri(System.getenv(MILVUS_URI)) .token(System.getenv(MILVUS_TOKEN)) .collectionName(java_rag_demo) .dimension(1024) .build(); } }如果你的 Milvus 版本不需要 token可以省略.token()方法。MilvusEmbeddingStore在首次写入时会自动创建 collection但你也可以在 Milvus 控制台提前建好集合、设置索引和距离算法这样更可控。入库示例// 文件路径src/main/java/com/example/demo/IngestExample.java package com.example.demo; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore; public class IngestExample { public static void main(String[] args) { EmbeddingModel embeddingModel QwenConfig.createEmbeddingModel(); MilvusEmbeddingStore store MilvusConfig.createStore(); String doc LangChain4j 是一个 Java 大模型编排框架。 它支持对话模型、Embedding、向量检索、工具调用。 Milvus 是一个高性能向量数据库。; for (TextSegment segment : TextSplitter.split(doc, 30, 5)) { Embedding embedding embeddingModel.embed(segment.text()).content(); store.add(embedding, segment); System.out.println(已写入片段 segment.text()); } } }执行后每个TextSegment会被向量化并写入 Milvus 的java_rag_demo集合。5.5 向量相似度检索检索时使用findRelevant方法传入问题向量和返回条数// 检索示例 Embedding queryEmbedding embeddingModel.embed(Java 的向量数据库怎么选).content(); ListEmbeddingMatchTextSegment matches store.findRelevant(queryEmbedding, 5); for (EmbeddingMatchTextSegment match : matches) { System.out.println(相似度 match.score()); System.out.println(内容 match.embedded().text()); }findRelevant返回的列表已经按相似度从高到低排序score越高越相关。如果你发现检索结果不准常见原因是文档切分不合理、向量模型维度低、Milvus 索引类型不合适或者查询表述和文档表达方式差异过大。6. 混合检索与重排提升 RAG 精度的关键6.1 什么是混合检索向量检索擅长语义相似比如用户问“怎么使用 LangChain4j 接入向量库”文档里写的是“LangChain4j Milvus 集成示例”语义上接近向量检索可以找回。但向量检索也有弱点对专有名词、缩写、代码符号、精确 ID 不敏感。比如用户搜“订单号 ORD-2026-001”向量检索很可能找不到完全匹配片段而关键词检索可以精准命中。混合检索就是把向量检索和关键词检索的结果合并起来再做去重和排序。关键词检索可以用传统数据库的LIKE、全文索引、Elasticsearch也可以先通过 Milvus 的标量过滤能力实现。混合检索的目的是让召回结果更完整再用重排模型精排最后交给大模型生成。下表对比了两种检索方式的特点检索方式优势劣势常见实现向量检索语义理解强容错性好精确关键词可能不准Milvus、FAISS关键词检索精确匹配可解释性强依赖词表无法理解语义Elasticsearch、数据库全文索引6.2 在 LangChain4j 中实现混合召回LangChain4j 提供了ContentRetriever接口我们自定义一个实现类同时执行向量检索和关键词检索。关键词检索部分需要结合你的实际存储下面使用一个简单的思路基于 Milvus 的元数据过滤模拟精准匹配更完整的生产实现可以用 Elasticsearch。// 文件路径src/main/java/com/example/demo/HybridContentRetriever.java package com.example.demo; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.rag.content.Content; import dev.langchain4j.rag.content.ContentRetriever; import dev.langchain4j.rag.content.RetrievalRequest; import dev.langchain4j.rag.content.TextContent; import dev.langchain4j.store.embedding.EmbeddingMatch; import dev.langchain4j.store.embedding.EmbeddingStore; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class HybridContentRetriever implements ContentRetriever { private final EmbeddingModel embeddingModel; private final EmbeddingStoreTextSegment embeddingStore; public HybridContentRetriever(EmbeddingModel embeddingModel, EmbeddingStoreTextSegment embeddingStore) { this.embeddingModel embeddingModel; this.embeddingStore embeddingStore; } Override public ListContent retrieve(RetrievalRequest retrievalRequest) { String query retrievalRequest.query().text(); // 1. 向量召回 Embedding queryEmbedding embeddingModel.embed(query).content(); ListEmbeddingMatchTextSegment vectorMatches embeddingStore.findRelevant(queryEmbedding, 10); ListContent vectorContents vectorMatches.stream() .map(EmbeddingMatch::embedded) .map(TextSegment::text) .map(TextContent::from) .collect(Collectors.toList()); // 2. 关键词召回这里简化处理按含有查询关键词过滤 ListContent keywordContents keywordSearch(query); // 3. 合并去重 MapString, Content mergedMap new LinkedHashMap(); for (Content content : vectorContents) { mergedMap.putIfAbsent(content.textSegment().text(), content); } for (Content content : keywordContents) { mergedMap.putIfAbsent(content.textSegment().text(), content); } return new ArrayList(mergedMap.values()); } private ListContent keywordSearch(String query) { // 生产环境建议接入 Elasticsearch 或数据库全文索引 // 这里仅演示接口扩展点 return List.of(); } }这个类把向量召回和关键词召回合并后续还可以继续接入重排。实际开发中keywordSearch可以注入一个 Elasticsearch Client也可以调用数据库查询返回结果统一包装成TextContent。6.3 重排原理与接入方式混合召回得到的结果可能包含几十条直接全部塞给大模型会浪费 token而且相关性差的片段会干扰回答。重排的目的是用更精细的模型对“查询-文档”对重新打分只保留最相关的 TopK。常见重排模型有 Cohere Rerank、BGE Rerank 等企业内部也可以自己训练排序模型。LangChain4j 中接入重排的思路是在ContentRetriever.retrieve返回之前调用重排服务。我们可以封装一个简单的接口// 文件路径src/main/java/com/example/demo/ReRanker.java package com.example.demo; import dev.langchain4j.rag.content.Content; import java.util.List; public interface ReRanker { ListContent rerank(String query, ListContent contents); }实现类可以调用内部 HTTP 重排服务。下面是一个基于 Java HttpClient 的示例思路// 文件路径src/main/java/com/example/demo/HttpReRanker.java package com.example.demo; import dev.langchain4j.rag.content.Content; import dev.langchain4j.rag.content.TextContent; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.ArrayList; import java.util.List; public class HttpReRanker implements ReRanker { private final String endpoint; public HttpReRanker(String endpoint) { this.endpoint endpoint; } Override public ListContent rerank(String query, ListContent contents) { // 实际需要把 query 和 documents 序列化为 JSON发送到重排服务 // 下面仅展示结构需要根据你的重排服务协议调整 ListContent result new ArrayList(); result.addAll(contents); // TODO: 调用 endpoint得到排序后的结果并返回 return result; } }在HybridContentRetriever中增加ReRanker成员合并去重后调用rerank再截取前 5 条返回。重排会带来额外的网络开销和成本属于质量与性能的权衡。如果业务场景对延时敏感可以只对向量和关键词合并后的 Top 30 做重排而不是全量。6.4 组装完整的 RAG 问答助手现在把对话模型、混合检索器、Milvus 存储组装成一个具备 RAG 能力的助手。首先定义一个业务接口// 文件路径src/main/java/com/example/demo/Assistant.java package com.example.demo; import dev.langchain4j.service.SystemMessage; public interface Assistant { SystemMessage(你是企业知识库助手请基于提供的上下文回答问题不要编造。) String answer(String question); }然后在 main 方法中完成组装// 文件路径src/main/java/com/example/demo/RagApplication.java package com.example.demo; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.rag.content.retriever.ContentRetriever; import dev.langchain4j.service.AiServices; import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore; public class RagApplication { public static void main(String[] args) { ChatLanguageModel chatModel OpenAiChatModel.builder() .apiKey(System.getenv(DASHSCOPE_API_KEY)) .baseUrl(https://dashscope.aliyuncs.com/compatible-mode/v1) .modelName(qwen-plus) .build(); MilvusEmbeddingStore store MilvusConfig.createStore(); ContentRetriever retriever new HybridContentRetriever( QwenConfig.createEmbeddingModel(), store ); Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(chatModel) .contentRetriever(retriever) .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) .build(); String answer assistant.answer(LangChain4j 如何接入 Milvus); System.out.println(answer); } }执行后助手会先从 Milvus 中召回相关文档片段再由 Qwen 模型基于这些片段生成回答。这个结构已经非常接近生产项目后续只需要把数据源、切分策略、检索器替换成你的业务实现。7. 常见问题与排查思路7.1 Milvus 连接异常问题现象常见原因解决思路连接超时Milvus 服务未启动或网络不通用telnet localhost 19530检查端口认证失败token 错误确认 Milvus 用户密码默认 root:Milvus集合不存在collectionName 配置错误检查集合名让程序首次写入自动创建端口冲突本地多个 Milvus 实例检查 docker ps确保端口唯一排查流程建议从底向上先确认 Milvus 服务本身可用再用 LangChain4j 日志观察是否发出请求。Milvus 相关版本升级后MilvusEmbeddingStore.Builder的方法名可能有变化注意看依赖源码。7.2 Embedding 维度不匹配写入向量时出现类似dimension mismatch的报错通常是因为EmbeddingModel输出的维度和 Milvus collection 的dimension配置不一致。text-embedding-v3可配置输出维度而不同配置下模型返回维度不同如果你换用了其他向量模型维度需要同步调整。解决方法先打印向量长度比如embedding.content().dimension()再在MilvusEmbeddingStore.builder().dimension(...)中填写相同数值。如果集合已经创建修改维度需要删除原有集合重建所以生产环境要提前固化向量模型。7.3 Qwen API 调用报错调用模型时返回 401 或InvalidApiKey一般是DASHSCOPE_API_KEY设置不正确。排查步骤确认环境变量已加载确认 Key 没有多余空格确认账号已开通 DashScope 服务。如果返回 404要检查baseUrl是否为https://dashscope.aliyuncs.com/compatible-mode/v1以及模型名是否正确。如果返回限流错误需要增加重试和退避策略或升级模型服务配额。7.4 检索结果与预期不符混合检索上线后回答质量仍然不好先不要急着换大模型而是检查召回链路。打印ContentRetriever返回的所有片段看问题是被错误召回还是相关片段没有召回。如果相关片段没有召回可能是切分粒度太大也可能是向量模型表达能力不够。如果召回相关但回答不好可以优化 Prompt 或增加系统提示词约束。重排之后还要对比重排前后的检索命中率用评估集量化判断。8. 最佳实践与工程建议8.1 文档切分与元数据文档切分是 RAG 项目里最容易被低估的环节。不要对所有文档使用同一套切分参数而是按文档类型设计Markdown 按标题层级切分PDF 按章节切分纯文本按段落和固定长度切分。每个TextSegment都建议带上元数据包括文档标题、URL、更新时间、所属部门、权限级别。这样检索时可以通过元数据过滤缩小范围也能在回答中标注来源提升可信度。切分时保留片段重叠可以缓解标题和正文被切断的问题。重叠长度一般控制在 50 到 100 个字符左右具体需要实验。比如技术文档中一个代码块如果被切到两个片段单独检索可能都不完整重叠能降低这种风险。8.2 权限控制与数据安全企业知识库通常涉及敏感数据做 RAG 时必须有权限控制意识。最简单的方式是为不同知识库创建不同 Milvus 分区或者给TextSegment元数据添加department、level字段在ContentRetriever中根据当前用户权限过滤元数据。更复杂的场景可以引入独立的权限服务在召回之前把用户权限转换成