Spring Boot + Milvus + LangChain4j 实现 RAG 问答:从向量入库到 DeepSeek 生成

本文基于 com.haiwei.javaai.demo1 包,梳理一套完整的 检索增强生成(RAG) 流程:应用启动时自动创建 Milvus 库表、加载文档并切块向量化入库;用户提问时检索相似片段、拼装 Prompt,再调用 DeepSeek 大模型生成答案。


一、整体架构

问答请求阶段

应用启动阶段

Spring Boot 启动

MilvusCollectionCreator

创建/重建 demo1 数据库

创建 my_collection_1 Collection

MilvusService.loadFile

读取 Smartshell.txt

DocumentSplitter 切块

EmbeddingModelUtil 向量化

写入 Milvus

GET /ai/rag/call

用户 message 向量化

RetrievalService 向量检索 TopK

buildRagPrompt 拼装 Prompt

DeepSeekChatModel 生成回答

返回 generation

核心组件职责

职责
MilvusConfig 注册 MilvusClientV2 Bean
MilvusClientService Milvus 连接配置(URI、Token)
MilvusConstant 数据库名、Collection 名常量
MilvusCollectionCreator 启动时建库建表,触发文档入库
MilvusService 文档加载、切块、向量化、插入 Milvus
EmbeddingModelUtil 本地 ONNX 嵌入模型(384 维)
RetrievalService 按查询向量在 Milvus 中检索 Top3
ChatRAGController RAG 问答入口:检索 + Prompt + LLM
SearchResultWithScore 检索结果 DTO(id、score、message)

数据流概览

  1. 离线/启动入库Smartshell.txt → 文本切块 → AllMiniLmL6V2 384 维向量 → Milvus vector 字段 + 原文 message 字段。
  2. 在线问答:用户问题 → 同样模型向量化 → Milvus L2 近邻搜索 → 取最相似 3 段原文 → 拼进 Prompt → DeepSeek 生成。

二、Maven 依赖(pom.xml)

demo1 依赖以下核心库(节选):

<properties>
    <java.version>17</java.version>
</properties>

<dependencies>
    <!-- LangChain4j:文档处理、本地嵌入模型 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
        <version>1.11.8</version>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-document-parser-apache-tika</artifactId>
        <version>1.11.8-beta19</version>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-embeddings</artifactId>
        <version>1.11.8-beta19</version>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-embeddings-all-minilm-l6-v2-q</artifactId>
        <version>1.11.8-beta19</version>
    </dependency>

    <!-- Milvus Java SDK V2 -->
    <dependency>
        <groupId>io.milvus</groupId>
        <artifactId>milvus-sdk-java</artifactId>
        <version>2.6.18</version>
    </dependency>

    <!-- Spring AI DeepSeek -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-deepseek</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>2.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

依赖说明:

依赖 用途
langchain4j-embeddings-all-minilm-l6-v2-q 本地量化嵌入模型,输出 384 维 向量,与 Milvus Collection 的 dimension(384) 一致
milvus-sdk-java Milvus V2 API:建库建表、插入、向量检索
spring-ai-starter-model-deepseek 通过 Spring AI 调用 DeepSeek Chat API
gson 构造 Milvus 插入时的 JSON 行数据

三、配置与环境变量(application.properties)

server.port=${SSHELL_PORT:8080}
server.servlet.context-path=/javaai

# DeepSeek(Spring AI)
spring.ai.deepseek.api-key=${DEEPSEEK_API_KEY:}
spring.ai.deepseek.base-url=${DEEPSEEK_API_BASE_URL:https://api.deepseek.com}

deepseek.api-base-url=${DEEPSEEK_API_BASE_URL:https://api.deepseek.com}
deepseek.api-key=${DEEPSEEK_API_KEY:}
deepseek.model=deepseek-chat

环境变量 / 启动参数

变量名 默认值 说明
SSHELL_PORT 8080 HTTP 端口
DEEPSEEK_API_KEY 必填,DeepSeek API Key
DEEPSEEK_API_BASE_URL https://api.deepseek.com DeepSeek API 地址

Milvus 连接(代码内硬编码)

当前在 MilvusClientService 中配置,application.properties

private static final String CLUSTER_ENDPOINT = "http://localhost:19530";
private static final String TOKEN = "root:Milvus";

启动前需确保本地 Milvus 已运行,且地址/凭证与上述一致。

完整问答 URL 示例

GET http://localhost:8080/javaai/ai/rag/call?message=SmartShell和传统堡垒机有什么区别?

四、Milvus 连接与 Bean 配置

MilvusConfig 将单例客户端注入 Spring 容器:

@Configuration
public class MilvusConfig {

    @Bean
    public MilvusClientV2 milvusClient() {
        return MilvusClientService.getClient();
    }
}

MilvusClientService 使用懒加载单例:

public class MilvusClientService {
    private static final String CLUSTER_ENDPOINT = "http://localhost:19530";
    private static final String TOKEN = "root:Milvus";
    private static MilvusClientV2 instance;

    public static MilvusClientV2 getClient() {
        if (instance == null) {
            ConnectConfig connectConfig = ConnectConfig.builder()
                    .uri(CLUSTER_ENDPOINT)
                    .token(TOKEN)
                    .build();
            instance = new MilvusClientV2(connectConfig);
        }
        return instance;
    }
}

常量定义:

public class MilvusConstant {
    public static final String DATA_BASE = "demo1";
    public static final String MY_COLLECTION_1 = "my_collection_1";
}

五、启动时创建 Milvus 库表与入库

MilvusCollectionCreator 实现 InitializingBean,在 所有 Bean 属性注入完成后 执行 afterPropertiesSet()

5.1 启动逻辑(注意:会清空 demo1 库)

存在

不存在

检查 demo1 库是否存在

删除所有 Collection

dropDatabase demo1

createDatabase demo1

useDatabase demo1

创建 my_collection_1

milvusService.loadFile

重要行为:demo1 数据库已存在,会先 删除该库下全部 Collection 并 drop 整个数据库,再重建。每次启动都会重新导入文档,适合 Demo,生产环境需改造。

5.2 Collection Schema

字段 类型 说明
id Int64 主键,autoID=true
vector FloatVector 维度 384,与嵌入模型一致
message VarChar(2000) 切块后的原文片段

5.3 索引配置

  • vectorIVF_FLAT,度量 L2nlist=128
  • messageAUTOINDEX(标量字段索引)

5.4 关键代码:建表

fieldSchemaList1.add(CreateCollectionReq.FieldSchema.builder()
        .name("id")
        .dataType(DataType.Int64)
        .isPrimaryKey(true)
        .autoID(true)
        .build());
fieldSchemaList1.add(CreateCollectionReq.FieldSchema.builder()
        .name("vector")
        .dataType(DataType.FloatVector)
        .dimension(384)
        .build());
fieldSchemaList1.add(CreateCollectionReq.FieldSchema.builder()
        .name("message")
        .dataType(DataType.VarChar)
        .maxLength(2000)
        .build());

indexParams1.add(IndexParam.builder()
        .indexName("vector_index")
        .fieldName("vector")
        .indexType(IndexParam.IndexType.IVF_FLAT)
        .metricType(IndexParam.MetricType.L2)
        .extraParams(Collections.singletonMap("nlist", 128))
        .build());

milvusClientV2.createCollection(createCollectionReq);
// 建表成功后
milvusService.loadFile();

六、文档加载、拆分、向量化与存储

6.1 嵌入模型

使用 LangChain4j 本地 ONNX 模型,无需额外 API

public class EmbeddingModelUtil {
    private static final EmbeddingModel embeddingModel =
            new AllMiniLmL6V2QuantizedEmbeddingModel();

    public static float[] embed(String text) {
        Response<Embedding> response = embeddingModel.embed(text);
        return response.content().vector();
    }
}

6.2 文档处理流程(MilvusService.loadFile)

ClassPathResource data/Smartshell.txt

Document.from 全文

recursive splitter 500/50

遍历 TextSegment

EmbeddingModelUtil.embed

insertData vector + message

Milvus insert

切块参数:

  • MAX_SEGMENT_SIZE = 500:单段最大字符数
  • MAX_OVERLAP_SIZE = 50:段间重叠,避免语义在边界断裂

源文档: src/main/resources/data/Smartshell.txt(SmartShell 智能运维平台产品说明)

6.3 关键代码:加载与入库

public void loadFile() {
    ClassPathResource resource = new ClassPathResource("data/Smartshell.txt");
    String text = resource.getContentAsString(StandardCharsets.UTF_8);
    Document document = Document.from(text);

    DocumentSplitter splitter = DocumentSplitters.recursive(MAX_SEGMENT_SIZE, MAX_OVERLAP_SIZE);
    List<TextSegment> segments = splitter.split(document);

    for (TextSegment segment : segments) {
        String segmentText = segment.text().trim();
        if (segmentText.isEmpty()) continue;

        float[] vector = EmbeddingModelUtil.embed(segmentText);
        insertData(vector, segmentText);
    }
}

public void insertData(float[] vector, String message) {
    JsonObject row = new JsonObject();
    row.add("vector", JsonUtil.gson.toJsonTree(vector));
    row.addProperty("message", message);

    milvusClientV2.insert(InsertReq.builder()
            .collectionName(MilvusConstant.MY_COLLECTION_1)
            .data(List.of(row))
            .build());
}

入库与检索使用 同一套嵌入模型,是保证 RAG 效果的前提。


七、问答链路:ChatRAGController

7.1 请求处理流程

DeepSeekChatModel Milvus RetrievalService EmbeddingModelUtil ChatRAGController User DeepSeekChatModel Milvus RetrievalService EmbeddingModelUtil ChatRAGController User GET /ai/rag/call?message=... embed(message) queryVector[384] retrieveDocuments(queryVector) search TopK=3, L2 id, message, score List SearchResultWithScore buildRagPrompt + sort by score Prompt(promptMessage) ChatResponse { generation: "..." }

7.2 向量检索(RetrievalService)

  • topK = 3:最多返回 3 条相关片段
  • 度量类型 L2:与建表时 vector_index 一致
  • L2 距离越小表示越相似(Controller 中按 score 升序排序)
SearchReq searchReq = SearchReq.builder()
        .collectionName(MilvusConstant.MY_COLLECTION_1)
        .data(Collections.singletonList(new FloatVec(queryVector)))
        .topK(topK)
        .metricType(IndexParam.MetricType.L2)
        .outputFields(List.of("id", "message"))
        .build();

SearchResp searchResp = milvusClient.search(searchReq);

7.3 Prompt 拼装

将检索到的文档片段编号后作为「参考文章」,再接用户原问题:

private String buildRagPrompt(List<SearchResultWithScore> results, String question) {
    StringBuilder prompt = new StringBuilder();
    prompt.append("参考文章:\n\n");
    for (int i = 0; i < results.size(); i++) {
        prompt.append("[").append(i + 1).append("] ")
                .append(results.get(i).getMessage())
                .append("\n\n");
    }
    prompt.append("请根据如上信息回答如下问题:\n")
            .append(question);
    return prompt.toString();
}

7.4 调用 DeepSeek 生成

@GetMapping("/call")
public Map<String, String> call(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
    float[] vectors = EmbeddingModelUtil.embed(message);

    List<SearchResultWithScore> searchResultWithScores = retrievalService.retrieveDocuments(vectors);
    String promptMessage = message;
    if (!searchResultWithScores.isEmpty()) {
        searchResultWithScores = searchResultWithScores.stream()
                .sorted(Comparator.comparingDouble(SearchResultWithScore::getScore))
                .toList();
        promptMessage = buildRagPrompt(searchResultWithScores, message);
    }

    DeepSeekChatOptions options = DeepSeekChatOptions.builder()
            .model(DeepSeekApi.ChatModel.DEEPSEEK_V4_PRO.getValue())
            .temperature(0.8)
            .build();
    Prompt prompt = new Prompt(promptMessage, options);
    ChatResponse response = chatModel.call(prompt);
    return Map.of("generation", response.getResult().getOutput().getText());
}

说明: 此处将整段 RAG Prompt 作为 单条用户消息 发给模型,未使用 System Prompt 或多轮 Message 结构;对 Demo 足够,生产可改为 UserMessage + SystemMessage 分离。


八、启动与验证步骤

8.1 前置条件

  1. Milvus 运行在 localhost:19530(Docker 或本地安装)
  2. 设置环境变量 DEEPSEEK_API_KEY
  3. Java 17 + Maven

8.2 启动应用

# Windows PowerShell 示例
$env:DEEPSEEK_API_KEY="your-api-key"
mvn spring-boot:run

或:

java -jar target/javaai-0.0.1-SNAPSHOT.jar

启动日志中应出现:

  • start create milvus collections
  • 文件切分为 N 个文本片段
  • 成功插入 1 条数据(每个片段一条)

8.3 调用问答接口

curl "http://localhost:8080/javaai/ai/rag/call?message=SmartShell和传统堡垒机有什么区别?"

预期响应:

{
    "generation": "SmartShell 是一款**智能化的运维与数据库管理工具**。它通过 SSH 连接主机和数据库,但不同于传统堡垒机的死板命令拦截,SmartShell 使用 AI 实时分析命令的实际风险。例如,面对类似 `rm -rf` 或 `DELETE FROM` 这类高危指令,它会结合当前目录内容或表中是否有数据等真实场景进行判断:如果目标是空目录、日志文件或空表,会放行并给予提示;如果表中有数据,则会先备份再执行,而不是像传统堡垒机那样无条件禁止。"
}

九、设计要点与可改进方向

主题 当前实现 建议
启动清库 每次启动 drop demo1 生产改为增量更新或版本化 Collection
Milvus 配置 硬编码 URI/Token 迁入 application.properties 或环境变量
入库效率 每段单独 insert 批量 insert 提升性能
检索失败 无结果时抛 RuntimeException 降级为「无上下文」直接问答
Prompt 简单字符串拼接 增加 System 角色、引用标注、长度截断
文档来源 仅 classpath 单文件 扩展 Tika 解析 PDF/Word、对象存储等

十、类与文件索引

src/main/java/com/haiwei/javaai/demo1/
├── MilvusConstant.java          # 库名、Collection 名
├── MilvusConfig.java            # MilvusClientV2 Bean
├── MilvusCollectionCreator.java # 启动建库建表 + 触发 loadFile
├── MilvusService.java           # 文档切块、向量化、写入
├── RetrievalService.java        # Milvus 向量检索
├── ChatRAGController.java       # RAG 问答 API
└── SearchResultWithScore.java   # 检索结果模型

src/main/java/com/haiwei/javaai/langchain4j/
└── EmbeddingModelUtil.java      # 384 维本地嵌入

src/main/java/com/haiwei/javaai/service/impl/
└── MilvusClientService.java     # Milvus 连接单例

src/main/resources/
├── application.properties
└── data/Smartshell.txt          # RAG 知识库文档

总结

demo1 实现了一条清晰的 RAG 闭环:启动阶段用 Milvus V2 SDK 建库建表并向量入库;请求阶段对用户问题做相同向量化、L2 检索 Top3、拼装「参考文章 + 问题」的 Prompt,再通过 Spring AI 调用 DeepSeek 生成答案。嵌入层与向量库维度(384)、度量(L2)保持一致,是整条链路能跑通的关键约束。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐