milvus结合RAG知识库问答

作者:青云 发布时间: 2026-05-31 阅读量:2 评论数:0

说明:本章节实操milvus结合RAG知识库完成只能问答小项目。

一、导入依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>
    <groupId>com.zhan</groupId>
    <artifactId>ai-blog</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ai-blog</name>
    <description>ai-blog</description>

    <properties>
        <java.version>17</java.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <knife4j.version>4.4.0</knife4j.version>
        <jjwt.version>0.12.5</jjwt.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud.ai</groupId>
                <artifactId>spring-ai-alibaba-bom</artifactId>
                <version>1.1.2.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>1.1.2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud.ai</groupId>
                <artifactId>spring-ai-alibaba-extensions-bom</artifactId>
                <version>1.1.2.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

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

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
            <version>${knife4j.version}</version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>${jjwt.version}</version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>${jjwt.version}</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>${jjwt.version}</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.17.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-agent-framework</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.milvus</groupId>
            <artifactId>milvus-sdk-java</artifactId>
            <version>2.6.18</version>
        </dependency>


<!--        <dependency>-->
<!--            <groupId>org.springframework.ai</groupId>-->
<!--            <artifactId>spring-ai-starter-model-ollama</artifactId>-->
<!--            <version>1.1.2</version>-->
<!--        </dependency>-->

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-rag</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-vector-store-milvus</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-tika-document-reader</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

二、添加测试问题文档:

1. 我国机动车行驶靠道路哪一侧?
右侧
2. 遇到交警手势指挥,优先看信号灯还是交警?
交警指挥
3. 红灯亮时,车辆能不能越过停止线?
不能
4. 绿灯亮起,车辆可以直接起步通行吗?
确认安全后通行
5. 黄灯亮时,已过停止线车辆能否继续行驶?
可以
6. 道路中心白色实线能不能跨越超车?
不可以
7. 道路中心虚线允许临时变道超车吗?
允许
8. 人行横道线作用是什么?
行人专用过街通道
9. 机动车行经人行道,遇到行人怎么做?
停车礼让行人
10. 没有划分车道的城市道路,机动车走哪边?
道路中间通行
11. 非机动车、行人应当在道路哪侧通行?
道路右侧
12. 在道路上临时停车,能否压住黄色实线?
不可以
13. 交叉路口50米以内可以停车吗?
禁止停车
14. 公交站台30米内允许社会车辆停车吗?
不允许
15. 高速路应急车道正常行驶可以占用吗?
禁止占用,仅故障应急使用
16. 通过无信号灯十字路口,谁优先通行?
右方来车先行
17. 转弯车辆和直行车辆相遇,谁先行?
直行车辆先行
18. 右转弯车和左转弯车相遇,谁先行?
左转弯车辆先行
19. 下坡车辆与上坡车辆会车,谁先行?
上坡车辆先行
20. 夜间会车需要关闭远光灯吗?
需要,切换近光灯
21. 没有中心线的城市道路最高时速多少?
30公里每小时
22. 没有中心线的公路最高车速?
40公里每小时
23. 同方向只有一条机动车道城市道路限速?
50公里每小时
24. 同方向单车道普通公路限速?
70公里每小时
25. 高速公路最低行驶速度?
60公里每小时
26. 高速公路最高行驶速度?
120公里每小时
27. 雨天行驶车辆,是否需要降低车速?
必须减速慢行
28. 雾天能见度低,应该提速还是减速?
减速慢行
29. 经过学校路段需要减速吗?
需要低速通行
30. 通过隧道时,能否超速行驶?
禁止超速
31. 牵引故障机动车时,车速不得超过多少?
30公里/小时
32. 进出非机动车道最高时速?
30公里/小时
33. 冰雪道路行驶,要降低车速吗?
是的,低速行驶
34. 高速路上车速低于60会被处罚吗?
会,属于低速违法
35. 高速最左侧车道是什么车道?
超车道
36. 高速中间车道为正常行车道对吗?
正确
37. 能见度小于200米高速限速多少?
60公里每小时
38. 能见度小于100米高速限速?
40公里每小时
39. 能见度50米以内高速最高车速?
20公里每小时
40. 通过铁路道口,是否减速观察?
减速停车观察确认安全
41. 夜间城市道路正常行驶用远光还是近光?
近光灯
42. 夜间超车时如何提醒前车?
交替远近光灯示意
43. 大雾天气行驶开启什么灯光?
雾灯、危险报警闪光灯
44. 车辆发生故障路边停放,要开双闪吗?
必须开启危险报警闪光灯
45. 隧道内行驶能否关闭车灯?
不可以,全程开灯
46. 夜间路边临时停车,开什么灯?
示廓灯、后位灯
47. 高速雾天能不能使用远光灯?
不能,远光会反光看不清
48. 超车前除看后视镜还要鸣喇叭示意吗?
可以短促鸣喇叭提醒
49. 医院、小区区域禁止长时间鸣喇叭吗?
正确,禁止鸣笛扰民
50. 转弯、变道前是否要打转向灯?
必须提前开启转向灯
51. 左转弯开启左转向灯对吗?
正确
52. 靠边停车开启右转向灯?
正确
53. 驶入高速需要开启左转向灯吗?
需要
54. 驶离高速出口开启右转向灯?
正确
55. 雨天行车,灯光使用近光灯,不使用双闪对吗?
正确,暴雨大雾才开双闪
56. 我国机动车驾驶证有效期分几种?
6年、10年、长期
57. 初次申领驾驶证实习期多久?
12个月
58. 实习期车辆上路是否要贴实习标志?
必须张贴
59. 驾驶证记分周期是多久?
12个月
60. 一个记分周期满分多少分?
12分
61. 记分周期扣满12分需要重考科目一吗?
需要
62. 酒后驾驶机动车一次记多少分?
12分
63. 故意遮挡车牌一次性记几分?
12分
64. 超速50%以上一次记几分?
12分
65. 不按规定安装号牌记12分吗?
是的
66. 开车不系安全带扣几分?
1分
67. 开车接打电话扣几分?
3分
68. 闯红灯一次记多少分?
6分
69. 高速倒车、逆行一次性记几分?
12分
70. 无证驾驶机动车会被拘留吗?
会,可处以行政拘留
71. 伪造驾驶证属于严重违法吗?
属于,会追究法律责任
72. 驾驶证过期还能开车上路吗?
不可以,属于无证驾驶
73. 车辆年检过期可以上路行驶吗?
禁止上路
74. 交强险到期没续保能开车吗?
不可以
75. 行驶证需要随车携带吗?
必须随车携带
76. 开车前第一件事是什么?
系好安全带
77. 儿童乘车建议坐车辆哪个位置?
后排安全座椅
78. 能不能怀抱婴儿坐在前排?
不可以,极度危险
79. 车辆爆胎第一时间猛踩刹车?
错误,轻踩刹车缓慢减速
80. 刹车失灵如何处置?
抢挂低速挡,利用手刹减速
81. 车辆起火首先要做什么?
迅速撤离车内人员
82. 高速故障停车,三角警示牌放多远?
车后150米以外
83. 普通道路故障,警示牌放置距离?
50-100米
84. 发生轻微剐蹭事故,先挪车还是先报警?
拍照固定证据后快速挪车
85. 伤人交通事故第一时间拨打什么电话?
120急救+122交警
86. 交通事故逃逸会加重处罚吗?
会,处罚升级
87. 疲劳驾驶容易引发车祸,连续开车多久需要休息?
4小时,休息至少20分钟
88. 能不能酒后驾驶机动车?
绝对不可以
89. 行驶中突然有人横穿马路,先踩刹车还是急打方向?
优先刹车制动
90. 涉水行车熄火后,能不能二次点火?
不能,会损坏发动机
91. 红色圆圈标志属于禁令标志吗?
正确,禁止类标志
92. 蓝色方形标志是什么标志?
指示标志
93. 黄色三角形标志是什么类型?
警告标志
94. 白色虚线可以临时越线超车?
可以
95. 黄色双实线允许压线转弯吗?
不允许
96. 停车让行标志要求车辆完全停下观察吗?
必须停车瞭望确认安全
97. 减速让行标志只需减速不用停车?
正确,减速避让主干道车辆
98. 机动车上路必须悬挂号牌吗?
必须前后悬挂正式号牌
99. 方向盘操控车辆转向,正确吗?
正确
100. 刹车踏板控制车辆减速停车,对吗?
正确

三、配置文件:

spring:
  ai:
    # Spring AI Retry 配置 - 启用重试机制
    retry:
      enabled: true
      max-attempts: 2
      backoff:
        initial-interval: 1000ms
        max-interval: 5000ms
    dashscope:
      api-key: sk-09c7b571687b46d5a2e25a03fbddxxxx
#      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
      chat:
        options:
          model: qwen-plus
        timeout: 60s
      embedding:
        options:
          model: text-embedding-v4
        timeout: 30s
    vectorstore:
      milvus:
        client:
          host: 127.0.0.1
          port: 19530
          token: root:Milvus
        database-name: default
        collection-name: chat_collection
        initialize-schema: true
        embedding-dimension: 1024
#    ollama:
#      base-url: http://127.0.0.1:11434
#      chat:
#        model: deepseek-r1:8b
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/ai_blog?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
    username: root
    password: 123456
  data:
    redis:
      host: 120.79.12.xxx
      port: xxx
      password: xxxxxxxxx

package com.zhan.aiblog.config;

import io.milvus.client.MilvusServiceClient;
import io.milvus.grpc.GetCollectionStatisticsResponse;
import io.milvus.param.R;
import io.milvus.param.collection.FlushParam;
import io.milvus.param.collection.GetCollectionStatisticsParam;
import io.milvus.response.GetCollStatResponseWrapper;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.apache.tika.Tika;
import org.apache.tika.exception.TikaException;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.document.Document;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.util.List;


@Configuration
@Slf4j
public class LLMConfig {

    @Autowired
    private MilvusVectorStore vectorStore;

    @Value("${spring.ai.vectorstore.milvus.collection-name}")
    private String collectionName;

    @Bean
    public ChatClient chatClient(@Qualifier("dashScopeChatModel") ChatModel dashscopeChatModel) {
        return ChatClient.builder(dashscopeChatModel).build();
    }


    @Bean
    public RetrievalAugmentationAdvisor retrievalAugmentationAdvisor() {
        VectorStoreDocumentRetriever retriver = VectorStoreDocumentRetriever.builder()
                .vectorStore(vectorStore)
                .similarityThreshold(0.2)
                .topK(6)
                .build();
        ContextualQueryAugmenter cqa = ContextualQueryAugmenter.builder()
                .allowEmptyContext(true)
                .build();
        return RetrievalAugmentationAdvisor.builder()
                .documentRetriever(retriver)
                .queryAugmenter(cqa)
                .build();
    }


    @PostConstruct
    public void initVectorData() throws TikaException, IOException {
        log.info("初始化向量数据,写入数据...");
        //获取 Milvus 客户端
        MilvusServiceClient client = (MilvusServiceClient) vectorStore.getNativeClient().get();
        R<GetCollectionStatisticsResponse> resp = client.getCollectionStatistics(
                GetCollectionStatisticsParam.newBuilder()
                        .withCollectionName(collectionName)
                        .build());
        long rowCount = new GetCollStatResponseWrapper(resp.getData()).getRowCount();
        System.out.println("milvus vector store 中的数量:" + rowCount);
        if (rowCount == 0) {
            log.info("开始写入数据...");
            //加载文档数据到向量数据库
            loadAndStoreDocumentData();
            client.flush(FlushParam.newBuilder()
                    .withCollectionNames(List.of(collectionName))
                    .build());

        }
    }

    private void loadAndStoreDocumentData() throws IOException, TikaException {
        ClassPathResource resource = new ClassPathResource("questions.txt");
        Tika tika = new Tika();
        String text = tika.parseToString(resource.getFile());
        //拆分文档,写入向量数据库
        //使用TokenTextSplitter 拆分文本
        TokenTextSplitter splitter = TokenTextSplitter.builder()
                .withChunkSize(800)
                .withMinChunkSizeChars(400)
                .withKeepSeparator(true)
                .build();
        List<Document> chunks = splitter.apply(List.of(new Document(text)));
        //写入向量数据库
        vectorStore.add(chunks);
    }
}

package com.zhan.aiblog.config;

import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * 本项目有多个EmbeddingModel,这里是为了设置默认的EmbeddingModel
 */
@Configuration
public class EmbeddingModelConfig {

    @Bean
    @Primary
    public EmbeddingModel primaryEmbeddingModel(@Qualifier("dashscopeEmbeddingModel") EmbeddingModel embeddingModel) {
        return embeddingModel;
    }
}

package com.zhan.aiblog.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.net.http.HttpClient;
import java.time.Duration;

/**
 * DashScope HTTP 客户端超时配置
 * 解决 API 调用超时问题
 * 使用 JDK 11+ 原生 HttpClient(非废弃 API)
 */
@Configuration
public class DashScopeHttpClientConfig {

    @Bean
    public HttpClient dashScopeHttpClient() {
        return HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(30))
                .build();
    }

    @Bean
    public ClientHttpRequestFactory dashScopeRequestFactory() {
        JdkClientHttpRequestFactory factory = new JdkClientHttpRequestFactory(dashScopeHttpClient());
        factory.setReadTimeout(Duration.ofSeconds(60));
        return factory;
    }

    @Bean
    public RestTemplate dashScopeRestTemplate() {
        return new RestTemplate(dashScopeRequestFactory());
    }
}

四、控制器:

package com.zhan.aiblog.controller;

import com.zhan.aiblog.common.api.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/api/rag")
@Slf4j
public class RagController {
    @Autowired
    private ChatClient chatClient;

    @Autowired
    private RetrievalAugmentationAdvisor retrievalAugmentationAdvisor;

    @Autowired
    @Qualifier("dashScopeChatModel")
    private ChatModel dashScopeChatModel;

    @GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatStream(@RequestParam(value = "message") String message) {
        ChatClient streamingClient = ChatClient.builder(dashScopeChatModel).build();
        return streamingClient.prompt()
                .user(message)
                .advisors(retrievalAugmentationAdvisor)
                .stream()
                .content()
                .doOnComplete(() -> log.info("流式回答完成"))
                .doOnError(error -> {
                    log.error("流式回答失败: {}", error.getMessage());
                });
    }
}

五、效果展示:

评论