第五章 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 微服务完整落地,实操密度拉满,学完掌握以下稀缺全栈工程化能力:
-
清晰区分通用大模型幻觉产生根源,透彻理解 RAG 检索增强生成的完整运行链路,明白自然资源 GIS 项目必须搭建行业向量知识库的核心业务价值;
-
熟练使用 Docker Compose 一键部署 Milvus 稳定版向量数据库,解决容器端口冲突、连接超时、持久化丢失、权限认证失败等部署高频问题;
-
掌握自然资源行业文档标准化处理流程:政策文档清洗、文本分段切片、重叠度优化、过滤无效噪声文本,适配国土规范、分类标准、审批文件等非结构化数据;
-
精通向量集合创建、索引构建、相似度距离算法选型,掌握 L2、内积、余弦距离适用场景,针对国土长文本知识库做检索性能专项调优;
-
完整搭建 Python FastAPI RAG 微服务,实现文档入库接口、语义检索接口、知识库问答聚合接口三层核心业务能力;
-
实现 Python 微服务无缝对接 Nacos3.2.2 配置中心,Milvus 连接参数、检索阈值、切片长度、TopK 返回条数全部云端托管,支持配置热更新动态调参;
-
掌握 Python 服务注册至 Nacos 注册中心完整配置,实现与 GIS 服务、AI 模型服务、中台调度服务统一服务发现体系;
-
打通 Higress 网关路由转发,前端通过统一网关端口调用 RAG 检索接口,完成从用户提问→向量检索→大模型生成的全链路闭环;
-
掌握 RAG 效果调优全套实操:相似度阈值调整、切片大小迭代、重排开关控制、过滤低匹配噪声文本,解决问答答非所问、参考资料不匹配、检索冗余等常见问题;
-
具备独立排查 Milvus 连接失败、向量检索无结果、Nacos 配置拉取失败、Python 服务注册异常、SSE 问答知识库拼接错误等全链路故障的运维能力。
5.3 前置理论:RAG 如何解决自然资源大模型幻觉问题
5.3.1 通用大模型幻觉的行业危害
通用多模态大模型训练数据以全网通用文本为主,极少收录完整、细分的自然资源专业政策、国标分类、地方管控细则。在国土业务使用场景下,幻觉带来的负面影响不可忽视:第一,混淆土地利用类型编码标准,将林地、耕地、园地管控规则混为一谈,导致地块合规研判结论完全错误;第二,编造不存在的审批年限、红线管控范围、处罚标准,业务人员依据 AI 结论开展核查工作会产生工作失误;第三,无法区分新旧国土政策,混用作废标准与现行规范,输出过时、失效的政策解读;第四,面对区域专属国土管控文件,无任何参考资料支撑,只能凭空猜测作答。
对于普通互联网问答产品,幻觉仅影响用户体验,但自然资源系统服务国土、规划、执法业务,错误输出会直接带来业务风险,因此单纯调用大模型 API 的模式完全无法满足项目上线标准,RAG 架构是行业落地的硬性刚需。
5.3.2 本项目 RAG 完整执行链路
整套检索增强流程分为五大固定步骤,所有环节代码、配置均在本章完整落地:
-
文档预处理:导入自然资源国标、土地管控政策、用地预审规范、变更调查细则等文档,清洗空格、换行、无效注释、重复段落,按固定字符长度切片,设置段落重叠度保证上下文连贯;
-
文本向量化:调用嵌入模型将每一段行业文本转换为固定维度浮点向量,将文字语义转化为可数值计算的向量数据;
-
向量入库:将生成的向量与对应原文段落、文档来源、文档类型存入 Milvus 向量集合,构建向量索引加速检索;
-
语义检索:用户输入国土相关提问,将问题向量化后在 Milvus 中执行相似度匹配,筛选高于阈值的高匹配行业原文;
-
增强生成:将检索得到的多条行业规范原文拼接为参考上下文,连同用户问题一起送入 vLLM 大模型,限定模型仅依据提供的行业资料作答,禁止凭空编造内容,最终输出合规、专业、可溯源的问答结果。
5.3.3 为什么本项目选用 Milvus 作为向量数据库
市面上可选向量数据库包含 FAISS、Chroma、PGVector、Milvus 等,结合本项目 GIS 微服务云原生架构、海量国土文档存储、高并发检索需求,Milvus 具备不可替代的适配优势:
-
云原生原生适配:官方提供完整 Docker 镜像,支持容器化部署,兼容 Nacos、Higress 整套微服务生态,可独立部署为单独服务,不与 PostGIS 数据库资源争抢;
-
海量文本高性能检索:支持百万、千万级向量数据存储,内置多种索引算法,针对国土数千份政策文档毫秒级返回检索结果,不会出现接口超时;
-
灵活的检索参数调优:可自由配置相似度阈值、返回条数、距离计算方式,适配不同国土业务问答精度需求;
-
多语言完整 SDK 支持:提供成熟 Python SDK,完美适配本章 RAG 微服务 FastAPI 开发技术栈,调用逻辑简洁稳定;
-
持久化与高可用:支持数据持久化磁盘存储,重启容器向量数据不丢失,开发单机版、生产集群版无缝切换,适配项目分阶段迭代。
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 文档清洗规则
编写统一清洗工具函数,过滤文档内无效内容,减少向量库冗余数据,提升检索匹配精准度:
- 清除连续换行、多余空格、制表符、特殊分隔符;
- 删除文档页眉、页脚、页码、文件编号等无业务语义内容;
- 过滤纯数字、纯符号、空白段落、重复复制的政策条款;
- 统一中文标点符号,替换全角半角混乱字符;
- 剔除作废、过期政策条文,仅保留现行有效国土规范文本。
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 完整业务流程测试
-
调用批量入库接口,上传《土地利用现状分类》国标全文,完成切片、向量化、向量入库;
-
通过网关调用检索接口,提问 "耕地的管控要求是什么";
-
系统自动将问题向量化,在 Milvus 中检索匹配耕地相关国标原文,返回相似度高于阈值的规范段落;
-
调用完整 RAG 问答接口,自动拼接行业原文生成约束提示词,传递至 AI 中台调度 vLLM 模型;
-
大模型依托检索到的真实国标作答,输出内容全部可溯源至知识库原文,无编造、无幻觉。
5.10.2 检索效果迭代调优实操
-
问答匹配度不足:下调 Nacos
min-similar-score最小相似度阈值,扩大检索范围,或调整文本切片长度,减少条款拆分; -
检索返回冗余无关内容:上调相似度阈值,增加按文档类型过滤参数,精准限定国标 / 政策分类;
-
长政策条款拆分断裂:修改 Nacos
chunk-overlap重叠字符参数,增大重叠长度,保证关联文字留存; -
检索速度缓慢: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 连接失败
-
容器未完全启动,等待 etcd、minio 初始化完成再连接;
-
宿主机 IP 填写错误,容器内部不能使用localhost;
-
账号密码与 docker-compose 配置不一致,核对 root 账号密码 Milvus@2026;
-
19530 端口被其他程序占用,修改端口映射。
5.11.2 语义检索无任何返回结果
-
知识库未批量入库,向量集合为空;
-
相似度阈值设置过高,下调 min-similar-score 参数;
-
提问语义与现有知识库文本差异过大,补充对应行业文档入库;
-
向量维度不匹配,嵌入模型输出维度必须与集合 dim=384 保持一致。
5.11.3 Python 服务无法注册 Nacos
-
Nacos 命名空间 ID、分组名称与 Java 服务不一致,严格对齐
gis-ai-rag-namespace、natural-resource-group; -
本地防火墙拦截 8848 端口,关闭防火墙或放行端口;
-
nacos-sdk 版本过低,更换 requirements.txt 指定版本。
5.11.4 网关调用 RAG 接口 404
-
Nacos 网关路由未发布,或路由 Path 路径匹配错误;
-
StripPrefix 过滤器参数配置错误,导致后端接口路径错位;
-
Python 服务未正常启动、实例离线,Nacos 服务列表无
rag-vector-service。
5.12 本章工程化验收标准
本章全部实操完成后,逐条核对以下验收项,确认 RAG 模块达到商用落地标准:
-
Milvus 2.5 容器集群部署正常,向量集合、索引创建完成,文档批量入库、相似度检索无报错;
-
自然资源文档清洗、切片、向量化流程标准化,切片参数、相似度阈值全部托管 Nacos,支持热更新;
-
FastAPI RAG 微服务三大核心接口(入库、检索、问答聚合)功能完整,可通过网关统一端口访问;
-
Python 服务成功注册至 Nacos 注册中心,服务列表正常展示
rag-vector-service健康实例; -
Higress 网关 RAG 路由规则配置完善,支持长文本上传、检索接口禁止缓存、请求体大小放宽适配政策文档;
-
完整 RAG 问答链路闭环,大模型依托行业知识库作答,无幻觉、无编造国土政策规范;
-
可动态调整检索精度、切片规则、返回条数,修改 Nacos 配置无需重启服务实时生效;
-
具备完整故障排查能力,可快速定位 Milvus 连接、服务注册、网关转发、检索匹配度各类问题。
5.13 章节小结
本章完整落地整套垂直行业 RAG 向量检索微服务,解决了本项目最核心的大模型幻觉业务痛点,搭建起自然资源专属行业知识库。依托 Milvus 向量数据库实现海量国土政策、国标规范存储与高速语义检索,通过 FastAPI 搭建轻量化 Python 微服务,完全接入项目统一 Nacos 配置与服务治理体系,打通 Higress 网关统一流量入口,形成完整 "文档预处理→向量入库→语义检索→增强大模型问答" 标准化链路。
截至本章,项目已完成云原生底座(Nacos+Higress)、空间算力底座(PostGIS)、行业知识库底座(Milvus+RAG)三大核心底层模块,下一章将落地 vLLM+Ollama 双模型 AI 调度服务,实现影像解析、地图智能样式生成、流式对话等多模态核心 AI 业务,整合 GIS 空间、RAG 知识库能力完成全业务中台编排。