第五章 RAG 向量检索服务实战|Milvus 知识库搭建,根治大模型行业幻觉

第五章 RAG 向量检索服务实战|Milvus 知识库搭建,根治大模型行业幻觉

5.1 本章导读

前面四章我们已经完整搭建起整套自然资源 AI-GIS 系统的底层云原生底座:第二章基于 Nacos3.2.2 完成全项目配置统一托管与服务注册发现,实现所有业务参数云端动态管控;第三章落地 Higress 云原生网关,完成统一流量入口、AI 长连接适配、差异化限流、全局跨域等流量治理能力;第四章搭建 PostGIS 空间微服务,实现地块框选、面积统计、行政区筛选等国土核心空间计算能力。目前系统已经具备完整的服务治理、流量转发、空间数据算力三大基础能力,但距离真正可用的行业 AI 系统,还存在一个核心致命短板:通用大模型缺乏自然资源垂直行业知识,问答输出存在严重幻觉、规则错误、解读不规范问题。

在自然资源业务场景中,AI 问答、地块合规研判、国土标准解读是高频需求,直接使用 vLLM、Ollama 通用大模型会出现大量业务事故。例如询问耕地保护红线管控规则、土地利用分类国标、用地预审审批流程时,通用模型会凭空编造不存在的政策条款、混淆不同地类管控要求、输出与国土行业规范相悖的结论,这类错误直接会误导国土核查、规划审批业务,完全无法投入实际工作使用。想要让 AI 输出专业、合规、有据可查的内容,必须引入 RAG 检索增强生成架构,搭建专属自然资源行业向量知识库。

RAG 的核心逻辑是将海量行业文档、政策标准、分类规范拆解、向量化存入向量数据库,用户发起提问时,先根据问题语义检索出匹配度最高的行业原文,将原文作为参考上下文送入大模型,强制 AI 依托真实行业资料作答,从根源杜绝凭空编造、规则混淆等幻觉问题。本项目选用 Milvus 作为向量数据库,它是目前开源生态最成熟、海量文本检索性能最优、完美适配微服务云原生架构的向量引擎,支持百万级知识库高速相似度检索,适配自然资源海量政策、标准、文档存储需求。

本章全程实操落地完整的 RAG 向量检索微服务,覆盖 Milvus 容器化部署、知识库文本切片与向量化、向量集合创建、相似度检索逻辑封装、Python 微服务对接 Nacos 配置、服务注册至注册中心、Higress 网关路由转发、全链路问答联调完整流程。所有代码、配置、调优参数完全贴合自然资源垂直行业场景,无通用 Demo 玩具代码,全部为企业级可商用落地方案。读完本章,你将彻底掌握垂直行业 RAG 全流程工程化落地能力,补齐 AI 项目最核心的行业知识库模块,让整套 GIS 系统真正具备专业、无幻觉的智能问答能力。

5.2 本章核心收获

本章所有操作围绕 Milvus 向量库 + Python RAG 微服务完整落地,实操密度拉满,学完掌握以下稀缺全栈工程化能力:

  1. 清晰区分通用大模型幻觉产生根源,透彻理解 RAG 检索增强生成的完整运行链路,明白自然资源 GIS 项目必须搭建行业向量知识库的核心业务价值;

  2. 熟练使用 Docker Compose 一键部署 Milvus 稳定版向量数据库,解决容器端口冲突、连接超时、持久化丢失、权限认证失败等部署高频问题;

  3. 掌握自然资源行业文档标准化处理流程:政策文档清洗、文本分段切片、重叠度优化、过滤无效噪声文本,适配国土规范、分类标准、审批文件等非结构化数据;

  4. 精通向量集合创建、索引构建、相似度距离算法选型,掌握 L2、内积、余弦距离适用场景,针对国土长文本知识库做检索性能专项调优;

  5. 完整搭建 Python FastAPI RAG 微服务,实现文档入库接口、语义检索接口、知识库问答聚合接口三层核心业务能力;

  6. 实现 Python 微服务无缝对接 Nacos3.2.2 配置中心,Milvus 连接参数、检索阈值、切片长度、TopK 返回条数全部云端托管,支持配置热更新动态调参;

  7. 掌握 Python 服务注册至 Nacos 注册中心完整配置,实现与 GIS 服务、AI 模型服务、中台调度服务统一服务发现体系;

  8. 打通 Higress 网关路由转发,前端通过统一网关端口调用 RAG 检索接口,完成从用户提问→向量检索→大模型生成的全链路闭环;

  9. 掌握 RAG 效果调优全套实操:相似度阈值调整、切片大小迭代、重排开关控制、过滤低匹配噪声文本,解决问答答非所问、参考资料不匹配、检索冗余等常见问题;

  10. 具备独立排查 Milvus 连接失败、向量检索无结果、Nacos 配置拉取失败、Python 服务注册异常、SSE 问答知识库拼接错误等全链路故障的运维能力。

5.3 前置理论:RAG 如何解决自然资源大模型幻觉问题

5.3.1 通用大模型幻觉的行业危害

通用多模态大模型训练数据以全网通用文本为主,极少收录完整、细分的自然资源专业政策、国标分类、地方管控细则。在国土业务使用场景下,幻觉带来的负面影响不可忽视:第一,混淆土地利用类型编码标准,将林地、耕地、园地管控规则混为一谈,导致地块合规研判结论完全错误;第二,编造不存在的审批年限、红线管控范围、处罚标准,业务人员依据 AI 结论开展核查工作会产生工作失误;第三,无法区分新旧国土政策,混用作废标准与现行规范,输出过时、失效的政策解读;第四,面对区域专属国土管控文件,无任何参考资料支撑,只能凭空猜测作答。

对于普通互联网问答产品,幻觉仅影响用户体验,但自然资源系统服务国土、规划、执法业务,错误输出会直接带来业务风险,因此单纯调用大模型 API 的模式完全无法满足项目上线标准,RAG 架构是行业落地的硬性刚需。

5.3.2 本项目 RAG 完整执行链路

整套检索增强流程分为五大固定步骤,所有环节代码、配置均在本章完整落地:

  1. 文档预处理:导入自然资源国标、土地管控政策、用地预审规范、变更调查细则等文档,清洗空格、换行、无效注释、重复段落,按固定字符长度切片,设置段落重叠度保证上下文连贯;

  2. 文本向量化:调用嵌入模型将每一段行业文本转换为固定维度浮点向量,将文字语义转化为可数值计算的向量数据;

  3. 向量入库:将生成的向量与对应原文段落、文档来源、文档类型存入 Milvus 向量集合,构建向量索引加速检索;

  4. 语义检索:用户输入国土相关提问,将问题向量化后在 Milvus 中执行相似度匹配,筛选高于阈值的高匹配行业原文;

  5. 增强生成:将检索得到的多条行业规范原文拼接为参考上下文,连同用户问题一起送入 vLLM 大模型,限定模型仅依据提供的行业资料作答,禁止凭空编造内容,最终输出合规、专业、可溯源的问答结果。

5.3.3 为什么本项目选用 Milvus 作为向量数据库

市面上可选向量数据库包含 FAISS、Chroma、PGVector、Milvus 等,结合本项目 GIS 微服务云原生架构、海量国土文档存储、高并发检索需求,Milvus 具备不可替代的适配优势:

  1. 云原生原生适配:官方提供完整 Docker 镜像,支持容器化部署,兼容 Nacos、Higress 整套微服务生态,可独立部署为单独服务,不与 PostGIS 数据库资源争抢;

  2. 海量文本高性能检索:支持百万、千万级向量数据存储,内置多种索引算法,针对国土数千份政策文档毫秒级返回检索结果,不会出现接口超时;

  3. 灵活的检索参数调优:可自由配置相似度阈值、返回条数、距离计算方式,适配不同国土业务问答精度需求;

  4. 多语言完整 SDK 支持:提供成熟 Python SDK,完美适配本章 RAG 微服务 FastAPI 开发技术栈,调用逻辑简洁稳定;

  5. 持久化与高可用:支持数据持久化磁盘存储,重启容器向量数据不丢失,开发单机版、生产集群版无缝切换,适配项目分阶段迭代。

5.4 实操一:Milvus 向量数据库 Docker 完整部署

本章使用 Milvus 2.5 稳定版本,采用 Docker Compose 单机部署方案,适配本地开发调试,生产环境可直接升级集群分布式部署。部署配置内置数据持久化、端口映射、时区统一、基础账号密码安全配置,避免开发过程中数据丢失、时区错乱、匿名访问等问题。

5.4.1 部署目录与配置文件编写

新建项目目录milvus-local-dev,在目录内创建docker-compose.yml部署配置文件,完整内容如下,无需删减修改,直接复制使用:

yaml 复制代码
name: milvus
services:
  etcd:
    container_name: milvus-etcd
    image: quay.io/coreos/etcd:v3.5.5
    command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls=http://0.0.0.0:2379 --data-dir /etcd
    environment:
      - TZ=Asia/Shanghai
      - ETCD_AUTO_COMPACTION_MODE=revision
      - ETCD_AUTO_COMPACTION_RETENTION=1000
      - ETCD_QUOTA_BACKEND_BYTES=4294967296
      - ETCD_SNAPSHOT_COUNT=50000
    healthcheck:
      test: ["CMD", "etcdctl", "endpoint", "health"]
      interval: 30s
      timeout: 20s
      retries: 3
    networks:
      milvus-net:
    volumes:
      - etcd-data:/etcd

  minio:
    container_name: milvus-minio
    image: minio/minio:RELEASE.2023-03-20T20-16-18Z
    command: minio server /minio_data --console-address :9001
    environment:
      - TZ=Asia/Shanghai
      - MINIO_ROOT_USER=minioadmin
      - MINIO_ROOT_PASSWORD=minioadmin
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3
    networks:
      milvus-net:
    ports:
      - "9001:9001"
      - "9000:9000"
    volumes:
      - minio-data:/minio_data

  standalone:
    container_name: milvus-standalone
    image: milvusdb/milvus:v2.4.17
    command:
      - milvus
      - run
      - standalone
    depends_on:
      etcd:
        condition: service_healthy
      minio:
        condition: service_healthy
    environment:
      - TZ=Asia/Shanghai
      - ETCD_ENDPOINTS=etcd:2379
      - MINIO_ADDRESS=minio:9000
      - MINIO_ACCESS_KEY=minioadmin
      - MINIO_SECRET_KEY=minioadmin
      - MILVUS_AUTHORIZATION_ENABLE=true
      - MILVUS_ROOT_USER=milvus-admin
      - MILVUS_ROOT_PASSWORD=Milvus@2026
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
      interval: 30s
      timeout: 20s
      start_period: 2m
      retries: 3
    security_opt:
      - seccomp:unconfined
    networks:
      milvus-net:
    ports:
      - "19530:19530"
      - "9091:9091"
    volumes:
      - milvus-data:/var/lib/milvus

networks:
  milvus-net:
    name: milvus_milvus-net
    driver: bridge

volumes:
  etcd-data:
  minio-data:
  milvus-data:

配置说明:内置 etcd 元数据存储、minio 对象存储、milvus 主服务三层组件,开启账号密码认证避免匿名访问,统一时区为东八区,所有向量数据持久化映射到本地目录,容器删除数据不会丢失。

5.4.2 启动 Milvus 容器集群

进入milvus-local-dev目录,打开终端执行后台启动命令:

bash 复制代码
docker-compose up -d

执行完成后输入容器查看命令校验运行状态:

bash 复制代码
docker ps | grep milvus

etcd、minio、milvus-standalone 三个容器状态均为 Up 即代表启动成功。首次启动耗时约 1-2 分钟,等待组件完全初始化完成再执行连接测试。

访问 minio成功 , http://127.0.0.1:9001/browser

5.4.3 Milvus 连接验证

使用 Spring Boot 项目集成 Milvus Java SDK 执行连接测试,确认服务可正常连通。首先在项目的 pom.xml 中添加 Milvus Java SDK 依赖:

xml 复制代码
```xml
<!-- Milvus Java SDK -->
<dependency>
    <groupId>io.milvus</groupId>
    <artifactId>milvus-sdk-java</artifactId>
    <version>2.5.0</version>
</dependency>

创建连接测试类 MilvusConnectionTest.java

java 复制代码
package com.hdsoft.aimcp;

import io.milvus.client.MilvusClient;
import io.milvus.client.MilvusServiceClient;
import io.milvus.param.ConnectParam;
import io.milvus.param.collection.HasCollectionParam;

public class MilvusConnectionTest {
    public static void main(String[] args) {
        ConnectParam connectParam = ConnectParam.newBuilder()
                .withHost("127.0.0.1")
                .withPort(19530)
                .withAuthorization("minioadmin", "minioadmin")
                .build();

        MilvusClient milvusClient = null;
        try {
            milvusClient = new MilvusServiceClient(connectParam);
            // 构造查询参数对象
            HasCollectionParam param = HasCollectionParam.newBuilder()
                    .withCollectionName("test_connection")
                    .build();
            // 传入参数对象
            milvusClient.hasCollection(param);
            System.out.println("Milvus向量数据库连接成功!");
        } catch (Exception e) {
            System.err.println("Milvus连接失败: " + e.getMessage());
            e.printStackTrace();
        } finally {
            if (milvusClient != null) {
                milvusClient.close();
            }
        }
    }
}

执行结果如下图

或者,在 Spring Boot 项目中创建配置类进行连接管理(推荐,之后要用得到):

java 复制代码
package com.hdsoft.aimcp.config;

import io.milvus.client.MilvusClient;
import io.milvus.client.MilvusServiceClient;
import io.milvus.param.ConnectParam;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MilvusConfig {

    @Value("${milvus.host:127.0.0.1}")
    private String host;

    @Value("${milvus.port:19530}")
    private Integer port;

    @Value("${milvus.username:minioadmin}")
    private String username;

    @Value("${milvus.password:minioadmin}")
    private String password;

    /**
     * Milvus客户端Bean,destroyMethod = close 容器关闭自动释放连接
     */
    @Bean(destroyMethod = "close")
    public MilvusClient milvusClient() {
        ConnectParam connectParam = ConnectParam.newBuilder()
                .withHost(host)
                .withPort(port)
                .withAuthorization(username, password)
                .build();
        return new MilvusServiceClient(connectParam);
    }
}

nacos的配置文件增加

yaml 复制代码
# Milvus向量库配置
milvus:
  host: 127.0.0.1
  port: 19530
  username: minioadmin
  password: minioadmin

通用工具类

java 复制代码
package com.hdsoft.aimcp.util;

import io.milvus.client.MilvusClient;
import io.milvus.param.collection.HasCollectionParam;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;

@Component
public class MilvusUtil {

    @Resource
    private MilvusClient milvusClient;

    /**
     * 测试连通性
     */
    public boolean testConnect() {
        HasCollectionParam param = HasCollectionParam.newBuilder()
                .withCollectionName("test_connection")
                .build();
        return milvusClient.hasCollection(param).getData();
    }

    /**
     * 判断集合是否存在
     */
    public boolean existCollection(String collName) {
        HasCollectionParam param = HasCollectionParam.newBuilder()
                .withCollectionName(collName)
                .build();
        return milvusClient.hasCollection(param).getData();
    }
}

测试 Controller

java 复制代码
package com.hdsoft.aimcp.controller;

import com.hdsoft.aimcp.util.MilvusUtil;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/milvus")
public class MilvusTestController {

    @Resource
    private MilvusUtil milvusUtil;

    @GetMapping("/test")
    public String testConn() {
        try {
            boolean ok = milvusUtil.testConnect();
            return "Milvus连接成功,检测集合结果:" + ok;
        } catch (Exception e) {
            return "Milvus连接失败:" + e.getMessage();
        }
    }
}

运行测试代码无报错、打印连接成功提示,代表 Milvus 环境部署完整就绪,可开展后续集合创建、向量入库实操。

通过网关调用测试一下返回如下图正常

5.5 实操二:自然资源知识库文本预处理标准化流程

本项目知识库数据源分为四大类:《土地利用现状分类》国家标准、国土空间规划管控政策、用地预审与规划许可审批规范、国土变更调查实施细则,全部为长文本非结构化文档,直接送入嵌入模型会出现语义碎片化、上下文断裂、无效噪声干扰检索精度问题,因此必须执行标准化清洗与切片操作。

相关文档自行下载即可

如:

整体思路

将国标文档拆分为结构化分段文本块,通过 Embedding 模型转为稠密向量存入 Milvus,支持地类编码检索、含义模糊匹配、三大类 / 湿地分类关联查询,完整覆盖标准前言、范围、术语、总则、分类编码、附录 A/B 全部内容。

核心流程

文本清洗标准化 → 文档切片→ Embedding 向量化 → Milvus 建库建表 → 向量入库 → 向量检索(语义 / 编码混合)

本章主要了解向量化流程,后面专门写一个文章详细讲解。也可以自己研究。

5.5.1 文档清洗规则

编写统一清洗工具函数,过滤文档内无效内容,减少向量库冗余数据,提升检索匹配精准度:

  1. 清除连续换行、多余空格、制表符、特殊分隔符;
  2. 删除文档页眉、页脚、页码、文件编号等无业务语义内容;
  3. 过滤纯数字、纯符号、空白段落、重复复制的政策条款;
  4. 统一中文标点符号,替换全角半角混乱字符;
  5. 剔除作废、过期政策条文,仅保留现行有效国土规范文本。

5.5.2 文本切片与重叠度配置

国土政策单条条款关联性强,若直接按固定长度粗暴切割,会出现一条完整管控规则被拆分至两个切片,检索时仅匹配部分片段,上下文缺失导致 AI 解读不完整。本项目统一切片参数(参数后续全部存入 Nacos 可动态调整):

  • 单切片最大字符长度:480
  • 切片重叠字符长度:80
    重叠区域保证条款首尾关联文字保留在相邻切片,检索时可完整召回整套关联政策内容。
Python 实现清洗工具

完整预处理工具代码:

python 复制代码
import re

#清洗
def clean_document_text(raw_text: str) -> str:
    # 清除多余换行与空格
    text = re.sub(r"\n+", "\n", raw_text)
    text = re.sub(r"\s+", " ", text)
    # 过滤页码、纯数字无效行
    text = re.sub(r"^\d+$", "", text)
    # 统一标点
    text = text.replace(",", ",").replace("。", ".")
    return text.strip()

#分片
def split_text_chunk(clean_text: str, chunk_size: int = 480, overlap: int = 80):
    chunks = []
    start = 0
    text_len = len(clean_text)
    while start < text_len:
        end = start + chunk_size
        chunk = clean_text[start:end]
        chunks.append(chunk)
        start = end - overlap
    return chunks
Java Spring Boot 实现清洗切片

完整预处理工具类(Spring Boot 3.2 实现):

java 复制代码
package com.hdsoft.aimcp.util;

import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

@Component
public class TextChunkUtil {

    /**
     * 文档清洗:清除多余换行、空格,过滤无效行,统一标点
     */
    public String cleanDocumentText(String rawText) {
        if (rawText == null || rawText.isEmpty()) {
            return "";
        }
        // 清除多余换行(连续多个换行符替换为单个)
        String text = rawText.replaceAll("\\n+", "\n");
        // 清除多余空格(连续多个空格替换为单个)
        text = text.replaceAll("\\s+", " ");
        // 过滤纯数字行(整行只有数字)
        text = Pattern.compile("^\\d+$", Pattern.MULTILINE).matcher(text).replaceAll("");
        // 统一中文标点(可选,根据实际需求调整)
        text = text.replace(",", ",").replace("。", ".");
        return text.trim();
    }

    /**
     * 文本切片(带重叠度)
     * @param cleanText 清洗后的文本
     * @param chunkSize 单切片最大字符长度,默认480
     * @param overlap 切片重叠字符长度,默认80
     * @return 切片后的文本列表
     */
    public List<String> splitTextChunk(String cleanText, int chunkSize, int overlap) {
        List<String> chunks = new ArrayList<>();
        if (cleanText == null || cleanText.isEmpty()) {
            return chunks;
        }
        // 参数校验
        if (chunkSize <= 0) {
            chunkSize = 480;
        }
        if (overlap < 0 || overlap >= chunkSize) {
            overlap = 80;
        }
        int textLen = cleanText.length();
        int start = 0;
        while (start < textLen) {
            int end = Math.min(start + chunkSize, textLen);
            String chunk = cleanText.substring(start, end);
            chunks.add(chunk);
            // 移动起始位置,减去重叠部分
            start = end - overlap;
            // 防止重叠度过大导致死循环
            if (start <= 0) {
                start = end;
            }
        }
        return chunks;
    }

    /**
     * 重载方法:使用默认参数(chunkSize=480, overlap=80)
     */
    public List<String> splitTextChunk(String cleanText) {
        return splitTextChunk(cleanText, 480, 80);
    }
}

使用示例(在 Service 中注入调用):

java 复制代码
@Service
public class RagPreprocessService {

    @Resource
    private TextChunkUtil textChunkUtil;

    public void processDocument(String rawDocument, String docType) {
        // 1. 清洗文档
        String cleanText = textChunkUtil.cleanDocumentText(rawDocument);
        // 2. 切片(使用默认参数)
        List<String> chunks = textChunkUtil.splitTextChunk(cleanText);
        // 3. 后续处理:向量化、入库等
        // ...
    }
}

配置参数从 Nacos 动态读取(在配置类中):

yaml 复制代码
rag-search:
  chunk-max-length: 480
  chunk-overlap: 80
java 复制代码
@Component
public class RagConfig {

    @Value("${rag-search.chunk-max-length:480}")
    private int chunkMaxLength;

    @Value("${rag-search.chunk-overlap:80}")
    private int chunkOverlap;

    // 可通过getter方法供其他组件使用
    public int getChunkMaxLength() {
        return chunkMaxLength;
    }

    public int getChunkOverlap() {
        return chunkOverlap;
    }
}
技术栈选择建议
  • Python 实现:适合快速原型验证、数据处理脚本、Python 微服务项目,代码简洁,适合数据科学家和算法工程师使用。
  • Java Spring Boot 实现:适合企业级 Java 微服务架构,与项目其他 Java 服务(如 GIS 服务、AI 中台)技术栈统一,便于统一配置管理、依赖注入和集成测试。

后期我们写成知识库管理模块,通过web页面管理知识内容。

5.5.3 文本向量化嵌入配置

选用通用中文嵌入模型bge-small-zh-v1.5,输出 384 维向量,平衡推理速度与语义匹配精度,本地轻量化部署,无需依赖第三方在线接口,向量生成逻辑封装为公共工具类,后续入库、提问检索统一复用。

5.6 实操三:Milvus 向量集合创建与索引优化

5.6.1 知识库集合字段设计

针对自然资源行业文档设计专用向量集合land_standard_knowledge,字段兼顾向量存储、原文存储、文档分类筛选,完整字段结构:

业务向量集合名:land_standard_knowledge

向量维度:384(匹配 bge-small-zh-v1.5)

度量方式:COSINE 余弦相似度(文本语义检索最优)

索引类型:IVF_FLAT(百万级向量开发环境首选)

字段固定结构:

字段名 类型 说明
id INT64、主键、自增 向量唯一主键
text_vector FLOAT_VECTOR(384) 文本语义向量
content VARCHAR(2000) 切片原文
doc_type VARCHAR(64) 文档分类:国标 / 规划 / 审批 / 变更调查
create_time INT64 入库时间戳

一、创建集合完整 Python

python 复制代码
from pymilvus import connections, utility, FieldSchema, DataType, Collection
from config_nacos import MILVUS_HOST, MILVUS_PORT, MILVUS_USER, MILVUS_PWD

# 1. 建立Milvus连接
ALIAS = "gis_rag_conn"
connections.connect(
    alias=ALIAS,
    host=MILVUS_HOST,
    port=MILVUS_PORT,
    user=MILVUS_USER,
    password=MILVUS_PWD
)

# 2. 定义字段结构
def create_land_knowledge_collection():
    coll_name = "land_standard_knowledge"
    # 判断集合存在则删除重建(生产环境禁止直接删,单独写更新逻辑)
    if utility.has_collection(coll_name, using=ALIAS):
        coll = Collection(coll_name, using=ALIAS)
        coll.drop()
        print(f"旧集合 {coll_name} 已删除")

    fields = [
        FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
        FieldSchema(name="text_vector", dtype=DataType.FLOAT_VECTOR, dim=384),
        FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=2000),
        FieldSchema(name="doc_type", dtype=DataType.VARCHAR, max_length=64),
        FieldSchema(name="create_time", dtype=DataType.INT64)
    ]

    # 创建集合
    coll = Collection(name=coll_name, fields=fields, using=ALIAS)

    # 3. 构建向量索引 IVF_FLAT + COSINE
    index_params = {
        "index_type": "IVF_FLAT",
        "metric_type": "COSINE",
        "params": {"nlist": 128}
    }
    coll.create_index(
        field_name="text_vector",
        index_params=index_params
    )
    # 加载索引到内存
    coll.load()
    print(f"集合 {coll_name} 创建完成,索引构建并加载成功")
    return coll

# 4. 批量入库封装(复用原有逻辑,整合到集合工具类)
def batch_insert_knowledge(vectors, text_list, doc_type):
    import time
    coll = Collection("land_standard_knowledge", using=ALIAS)
    now_ts = int(time.time())
    data = [
        vectors,
        text_list,
        [doc_type] * len(text_list),
        [now_ts] * len(text_list)
    ]
    coll.insert(data)
    coll.flush()
    print(f"批量入库 {len(text_list)} 条知识库文本完成")

# 程序入口执行初始化
if __name__ == "__main__":
    create_land_knowledge_collection()

二、Java SpringBoot Milvus SDK 完整实现(对应同等功能)

前置依赖(pom.xml)

xml 复制代码
<!-- Milvus Java SDK -->
<dependency>
    <groupId>io.milvus</groupId>
    <artifactId>milvus-sdk-java</artifactId>
    <version>2.5.0</version>
</dependency>

常量统一配置(Nacos yml)

yaml 复制代码
milvus:
  host: 127.0.0.1
  port: 19530
  username: milvus-admin
  password: Milvus@2026
rag-collection:
  coll-name: land_standard_knowledge
  vector-dim: 384
  index-type: IVF_FLAT
  metric-type: COSINE
  nlist: 128

Milvus 配置类 MilvusConfig.java(已有 Bean 复用)

java 复制代码
package com.hdsoft.aimcp.config;

import io.milvus.client.MilvusClient;
import io.milvus.client.MilvusServiceClient;
import io.milvus.param.ConnectParam;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MilvusConfig {
    @Value("${milvus.host:127.0.0.1}")
    private String host;
    @Value("${milvus.port:19530}")
    private Integer port;
    @Value("${milvus.username}")
    private String username;
    @Value("${milvus.password}")
    private String password;

    @Bean(destroyMethod = "close")
    public MilvusClient milvusClient() {
        ConnectParam connectParam = ConnectParam.newBuilder()
                .withHost(host)
                .withPort(port)
                .withAuthorization(username, password)
                .build();
        return new MilvusServiceClient(connectParam);
    }
}

MilvusCollectionInitUtil.java(核心实现)

java 复制代码
package com.hdsoft.aimcp.util;

import io.milvus.client.MilvusClient;
import io.milvus.param.R;
import io.milvus.param.collection.*;
import io.milvus.param.index.CreateIndexParam;
import io.milvus.response.HasCollectionResp;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class MilvusCollectionInitUtil {

    @Resource
    private MilvusClient milvusClient;

    @Value("${rag-collection.coll-name}")
    private String collName;

    @Value("${rag-collection.vector-dim}")
    private Integer dim;

    @Value("${rag-collection.index-type}")
    private String indexType;

    @Value("${rag-collection.metric-type}")
    private String metricType;

    @Value("${rag-collection.nlist}")
    private Integer nlist;

    /**
     * 初始化自然资源知识库集合:存在则删除重建,创建字段+构建索引+加载集合
     */
    public void initLandKnowledgeCollection() {
        // 1. 判断集合是否存在
        R<HasCollectionResp> hasCollResp = milvusClient.hasCollection(
                HasCollectionParam.newBuilder()
                        .withCollectionName(collName)
                        .build()
        );
        if (hasCollResp.getData().hasCollection()) {
            // 删除旧集合
            DropCollectionParam dropParam = DropCollectionParam.newBuilder()
                    .withCollectionName(collName)
                    .build();
            milvusClient.dropCollection(dropParam);
            System.out.println("旧集合 " + collName + " 已删除");
        }

        // 2. 定义字段列表,和Python结构完全对齐
        List<FieldType> fieldTypes = new ArrayList<>();
        // 主键id 自增INT64
        fieldTypes.add(FieldType.newBuilder()
                .withName("id")
                .withDataType(DataType.Int64)
                .withPrimaryKey(true)
                .withAutoID(true)
                .build());
        // 384维向量字段
        fieldTypes.add(FieldType.newBuilder()
                .withName("text_vector")
                .withDataType(DataType.FloatVector)
                .withDimension(dim)
                .build());
        // 切片原文
        fieldTypes.add(FieldType.newBuilder()
                .withName("content")
                .withDataType(DataType.VarChar)
                .withMaxLength(2000)
                .build());
        // 文档类型
        fieldTypes.add(FieldType.newBuilder()
                .withName("doc_type")
                .withDataType(DataType.VarChar)
                .withMaxLength(64)
                .build());
        // 入库时间戳
        fieldTypes.add(FieldType.newBuilder()
                .withName("create_time")
                .withDataType(DataType.Int64)
                .build());

        // 3. 创建集合
        CreateCollectionParam createParam = CreateCollectionParam.newBuilder()
                .withCollectionName(collName)
                .withFieldTypes(fieldTypes)
                .build();
        milvusClient.createCollection(createParam);
        System.out.println("集合 " + collName + " 创建完成");

        // 4. 创建向量索引 IVF_FLAT + COSINE
        Map<String, Object> indexParams = new HashMap<>();
        indexParams.put("nlist", nlist);
        CreateIndexParam indexParam = CreateIndexParam.newBuilder()
                .withCollectionName(collName)
                .withFieldName("text_vector")
                .withIndexType(indexType)
                .withMetricType(metricType)
                .withExtraParam(indexParams)
                .build();
        milvusClient.createIndex(indexParam);
        System.out.println("向量索引构建完成");

        // 5. 加载集合到内存
        LoadCollectionParam loadParam = LoadCollectionParam.newBuilder()
                .withCollectionName(collName)
                .build();
        milvusClient.loadCollection(loadParam);
        System.out.println("集合加载完成,初始化完毕");
    }
}

批量入库工具方法(Java 对应 Python batch_insert)

新增至 MilvusUtil.java

java 复制代码
/**
 * 批量插入知识库向量数据
 * @param vectors 向量二维数组
 * @param contentList 原文切片列表
 * @param docType 文档分类
 */
public void batchInsertKnowledge(List<List<Float>> vectors, List<String> contentList, String docType) {
    long nowTs = System.currentTimeMillis() / 1000;
    List<Long> timeList = new ArrayList<>();
    List<String> typeList = new ArrayList<>();
    for (int i = 0; i < contentList.size(); i++) {
        timeList.add(nowTs);
        typeList.add(docType);
    }

    InsertParam insertParam = InsertParam.newBuilder()
            .withCollectionName("land_standard_knowledge")
            .addFieldData(InsertParam.FieldData.newBuilder()
                    .withName("text_vector")
                    .withFloatVectors(vectors)
                    .build())
            .addFieldData(InsertParam.FieldData.newBuilder()
                    .withName("content")
                    .withVarCharData(contentList)
                    .build())
            .addFieldData(InsertParam.FieldData.newBuilder()
                    .withName("doc_type")
                    .withVarCharData(typeList)
                    .build())
            .addFieldData(InsertParam.FieldData.newBuilder()
                    .withName("create_time")
                    .withLongData(timeList)
                    .build())
            .build();
    milvusClient.insert(insertParam);
    // 刷盘持久化
    FlushParam flushParam = FlushParam.newBuilder()
            .addCollectionName("land_standard_knowledge")
            .build();
    milvusClient.flush(flushParam);
    System.out.println("批量入库条数:" + contentList.size());
}

测试初始化 Controller

java 复制代码
@RestController
@RequestMapping("/milvus/collection")
public class MilvusCollectionController {
    @Resource
    private MilvusCollectionInitUtil collectionInitUtil;

    @GetMapping("/init")
    public String initColl() {
        try {
            collectionInitUtil.initLandKnowledgeCollection();
            return "集合初始化、索引创建、加载全部成功";
        } catch (Exception e) {
            return "初始化失败:" + e.getMessage();
        }
    }
}

5.7 实操四:语义相似度检索核心逻辑开发

检索功能是 RAG 服务核心,实现将用户提问转为向量,在 Milvus 中匹配高于设定阈值的行业原文,过滤低匹配噪声片段,按相似度从高到低排序返回,提供给 AI 中台拼接上下文。检索参数(最小相似度阈值、返回 TopK 条数)全部从 Nacos 云端动态读取,无需修改代码重启服务。

完整检索函数代码:

python 复制代码
from pymilvus import Collection
import numpy as np

def search_knowledge(query_vector: np.ndarray, min_score: float, top_k: int, filter_doc_type=None):
    coll = Collection("land_standard_knowledge", using="gis_rag_conn")
    coll.load()
    search_params = {"metric_type": "COSINE", "params": {"nprobe": 10}}
    expr = None
    # 可选按文档类型过滤检索
    if filter_doc_type:
        expr = f'doc_type == "{filter_doc_type}"'
    # 执行相似度检索
    results = coll.search(
        data=[query_vector],
        anns_field="text_vector",
        param=search_params,
        limit=top_k,
        expr=expr,
        output_fields=["content", "doc_type"]
    )
    # 过滤低于阈值的结果,组装返回结构化参考资料
    ref_list = []
    for hits in results:
        for hit in hits:
            score = hit.distance
            # 余弦距离值越高相似度越高,低于阈值丢弃
            if score < min_score:
                continue
            ref_list.append({
                "similar_score": round(float(score), 2),
                "content": hit.entity.get("content"),
                "doc_type": hit.entity.get("doc_type")
            })
    # 按相似度降序排序
    ref_list.sort(key=lambda x: x["similar_score"], reverse=True)
    return ref_list

5.8 实操五:FastAPI RAG 微服务完整工程搭建

本项目 RAG 服务采用 Python FastAPI 框架开发,轻量高性能,适配向量计算、模型调用场景,完整实现三大对外接口:知识库批量入库接口、语义检索接口、完整问答聚合接口,同时接入 Nacos 配置中心与服务注册中心,和 Java 微服务统一云原生规范。

5.8.1 项目目录结构

Plain 复制代码
rag-vector-service/
├── main.py               # 服务启动入口
├── config_nacos.py       # Nacos配置拉取工具
├── embedding_util.py     # 向量化工具类
├── milvus_client.py      # Milvus连接、入库、检索封装
├── requirements.txt     # 项目依赖清单

5.8.2 requirements.txt 依赖清单

txt 复制代码
fastapi==0.104.1
uvicorn[standard]==0.24.0
pymilvus==2.5.0
nacos-sdk-python==0.1.10
transformers==4.35.2
torch==2.1.0
python-multipart

5.8.3 Nacos 配置读取封装(config_nacos.py)

对接第二章统一命名空间、分组、DataID,读取 Milvus 连接参数、RAG 检索阈值、切片参数,支持配置热更新:

python 复制代码
from nacos import NacosClient

# 和项目统一Nacos服务地址、命名空间、分组
nacos_client = NacosClient(server_addresses="127.0.0.1:8848", namespace="gis-ai-rag-namespace")
nacos_client.set_configuration(group="natural-resource-group")

# 拉取rag-milvus-dev.yml全部配置
config_str = nacos_client.get_config(data_id="rag-milvus-dev.yml", group="natural-resource-group")
# 解析yaml配置字典
import yaml
config = yaml.safe_load(config_str)

# 全局导出配置参数
MILVUS_HOST = config["milvus"]["host"]
MILVUS_PORT = config["milvus"]["port"]
MILVUS_USER = config["milvus"]["username"]
MILVUS_PWD = config["milvus"]["password"]
MIN_SCORE = config["rag-search"]["min-similar-score"]
TOP_K = config["rag-search"]["top-k"]
CHUNK_SIZE = config["rag-search"]["chunk-max-length"]
CHUNK_OVERLAP = config["rag-search"]["chunk-overlap"]

5.8.4 服务注册 Nacos 配置

Python 服务通过 nacos-sdk 实现服务自动注册至注册中心,服务名称固定rag-vector-service,Higress 网关通过服务名负载均衡转发请求,注册逻辑写入启动文件,服务启动自动上报实例健康状态。

5.8.5 三大核心业务接口(main.py

python 复制代码
from fastapi import FastAPI
import embedding_util
import milvus_client
from config_nacos import MIN_SCORE, TOP_K

app = FastAPI(title="RAG自然资源向量检索服务")

# 1. 知识库批量入库接口
@app.post("/api/rag/doc/batch-insert")
def batch_insert_doc(raw_text: str, doc_type: str):
    # 文本清洗切片
    clean_txt = embedding_util.clean_document_text(raw_text)
    chunk_list = embedding_util.split_text_chunk(clean_txt)
    # 批量向量化
    vec_list = embedding_util.get_text_embedding(chunk_list)
    # Milvus入库
    milvus_client.batch_insert_knowledge(vec_list, chunk_list, doc_type)
    return {"code": 200, "msg": "知识库文档入库成功", "chunk_count": len(chunk_list)}

# 2. 语义检索参考资料接口
@app.post("/api/rag/search/ref")
def search_reference(question: str, filter_type: str = None):
    # 问题向量化
    q_vec = embedding_util.get_text_embedding_single(question)
    # 执行检索
    ref_data = milvus_client.search_knowledge(q_vec, MIN_SCORE, TOP_K, filter_type)
    return {"code": 200, "data": ref_data}

# 3. 完整RAG问答聚合接口(对接AI中台)
@app.post("/api/rag/chat/complete")
def rag_chat(question: str):
    # 1. 检索行业参考资料
    ref_list = search_reference(question)["data"]
    if len(ref_list) == 0:
        return {"code": 200, "answer": "未检索到相关国土行业规范,无法给出专业解读,请调整提问内容"}
    # 2. 拼接参考上下文
    ref_content = ""
    for item in ref_list:
        ref_content += f"【行业规范原文】{item['content']}\n"
    # 3. 构造提示词,约束大模型依托资料作答
    prompt = f"""请仅根据下面提供的自然资源行业规范原文回答用户问题,禁止编造任何政策、标准、管控规则。
行业参考资料:
{ref_content}
用户问题:{question}
"""
    # 返回拼接完成的prompt与参考资料,交由AI中台调用vLLM生成回答
    return {
        "code": 200,
        "reference_data": ref_list,
        "final_prompt": prompt
    }

if __name__ == "__main__":
    import uvicorn
    # 启动服务并自动注册Nacos
    uvicorn.run("main:app", host="0.0.0.0", port=8083)

5.9 实操六:Higress 网关路由配置与转发校验

第二章我们已经在 Nacos 云端创建higress-gateway-route-dev.yml网关路由配置,其中已预设 RAG 服务路由规则,本节完善路由过滤器、长连接、缓存专项适配,适配 RAG 批量文本上传、大篇幅知识库检索场景。

5.9.1 完善 RAG 专属路由规则

更新 Nacos 内网关路由配置,补充路径剥离、请求体大小限制(适配长政策文档上传)、关闭检索接口缓存:

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        # RAG向量检索服务路由
        - id: rag-vector-service
          uri: lb://rag-vector-service
          predicates:
            - Path=/api/rag/**
          filters:
            - StripPrefix=1
            # 放宽请求体上限,支持长篇政策文档上传入库
            - name: RequestSize
              args:
                maxSize: 10MB
            # 检索接口禁止缓存,保证实时匹配最新知识库
            - name: NoCache
          metadata:
            priority: 80

发布 Nacos 配置后,Higress 网关自动热更新路由规则,无需重启网关容器。

5.9.2 网关访问测试

通过统一网关端口调用 RAG 检索接口,测试地址:http://localhost:8080/api/rag/search/ref,传入国土相关提问,网关自动负载均衡转发至rag-vector-servicePython 微服务,正常返回行业参考资料列表,代表路由转发全链路通畅。

5.10 实操七:全链路联调与 RAG 效果调优

5.10.1 完整业务流程测试

  1. 调用批量入库接口,上传《土地利用现状分类》国标全文,完成切片、向量化、向量入库;

  2. 通过网关调用检索接口,提问 "耕地的管控要求是什么";

  3. 系统自动将问题向量化,在 Milvus 中检索匹配耕地相关国标原文,返回相似度高于阈值的规范段落;

  4. 调用完整 RAG 问答接口,自动拼接行业原文生成约束提示词,传递至 AI 中台调度 vLLM 模型;

  5. 大模型依托检索到的真实国标作答,输出内容全部可溯源至知识库原文,无编造、无幻觉。

5.10.2 检索效果迭代调优实操

  1. 问答匹配度不足:下调 Nacosmin-similar-score最小相似度阈值,扩大检索范围,或调整文本切片长度,减少条款拆分;

  2. 检索返回冗余无关内容:上调相似度阈值,增加按文档类型过滤参数,精准限定国标 / 政策分类;

  3. 长政策条款拆分断裂:修改 Nacoschunk-overlap重叠字符参数,增大重叠长度,保证关联文字留存;

  4. 检索速度缓慢:Milvus 增加索引 nlist、nprobe 参数,或减少单次 TopK 返回条数。

5.10.3 热更新验证

登录 Nacos 控制台修改 RAG 检索参数(min-similar-score、top-k、chunk-max-length),发布配置后,无需重启 Python RAG 服务,再次调用检索接口,自动读取更新后的配置参数,验证云端配置热更新能力生效。

5.11 本章故障排查指南

5.11.1 Milvus 连接失败

  1. 容器未完全启动,等待 etcd、minio 初始化完成再连接;

  2. 宿主机 IP 填写错误,容器内部不能使用localhost

  3. 账号密码与 docker-compose 配置不一致,核对 root 账号密码 Milvus@2026;

  4. 19530 端口被其他程序占用,修改端口映射。

5.11.2 语义检索无任何返回结果

  1. 知识库未批量入库,向量集合为空;

  2. 相似度阈值设置过高,下调 min-similar-score 参数;

  3. 提问语义与现有知识库文本差异过大,补充对应行业文档入库;

  4. 向量维度不匹配,嵌入模型输出维度必须与集合 dim=384 保持一致。

5.11.3 Python 服务无法注册 Nacos

  1. Nacos 命名空间 ID、分组名称与 Java 服务不一致,严格对齐gis-ai-rag-namespacenatural-resource-group

  2. 本地防火墙拦截 8848 端口,关闭防火墙或放行端口;

  3. nacos-sdk 版本过低,更换 requirements.txt 指定版本。

5.11.4 网关调用 RAG 接口 404

  1. Nacos 网关路由未发布,或路由 Path 路径匹配错误;

  2. StripPrefix 过滤器参数配置错误,导致后端接口路径错位;

  3. Python 服务未正常启动、实例离线,Nacos 服务列表无rag-vector-service

5.12 本章工程化验收标准

本章全部实操完成后,逐条核对以下验收项,确认 RAG 模块达到商用落地标准:

  1. Milvus 2.5 容器集群部署正常,向量集合、索引创建完成,文档批量入库、相似度检索无报错;

  2. 自然资源文档清洗、切片、向量化流程标准化,切片参数、相似度阈值全部托管 Nacos,支持热更新;

  3. FastAPI RAG 微服务三大核心接口(入库、检索、问答聚合)功能完整,可通过网关统一端口访问;

  4. Python 服务成功注册至 Nacos 注册中心,服务列表正常展示rag-vector-service健康实例;

  5. Higress 网关 RAG 路由规则配置完善,支持长文本上传、检索接口禁止缓存、请求体大小放宽适配政策文档;

  6. 完整 RAG 问答链路闭环,大模型依托行业知识库作答,无幻觉、无编造国土政策规范;

  7. 可动态调整检索精度、切片规则、返回条数,修改 Nacos 配置无需重启服务实时生效;

  8. 具备完整故障排查能力,可快速定位 Milvus 连接、服务注册、网关转发、检索匹配度各类问题。

5.13 章节小结

本章完整落地整套垂直行业 RAG 向量检索微服务,解决了本项目最核心的大模型幻觉业务痛点,搭建起自然资源专属行业知识库。依托 Milvus 向量数据库实现海量国土政策、国标规范存储与高速语义检索,通过 FastAPI 搭建轻量化 Python 微服务,完全接入项目统一 Nacos 配置与服务治理体系,打通 Higress 网关统一流量入口,形成完整 "文档预处理→向量入库→语义检索→增强大模型问答" 标准化链路。

截至本章,项目已完成云原生底座(Nacos+Higress)、空间算力底座(PostGIS)、行业知识库底座(Milvus+RAG)三大核心底层模块,下一章将落地 vLLM+Ollama 双模型 AI 调度服务,实现影像解析、地图智能样式生成、流式对话等多模态核心 AI 业务,整合 GIS 空间、RAG 知识库能力完成全业务中台编排。