之前在做企业级知识库时,最头疼的问题往往不是大模型本身,而是“资料明明很多,模型却什么都不知道”。直接把文档丢给大模型,既有 Token 成本压力,又有上下文长度限制;用传统数据库做关键词检索,又难以处理用户口语化、同义改写类的提问。后来把整套方案切换到 RAG + 向量数据库的架构,用 Milvus 存储文档向量,查询时先做语义召回,再把召回内容交给大模型生成答案,整个链路才算真正跑通。
这篇文章会围绕 Milvus 3.0 展开,从 RAG 的概念、架构设计、环境部署,到文档加载、文本切分、Embedding、向量写入、检索服务,以及可视化工具 Attu 的连接和常见问题排查,完整搭建一套可以运行的企业级 RAG 知识库。无论你是刚开始接触 Milvus 的新手,还是已经用过 FAISS、ES 的开发者,都能在这篇文章里找到可以直接落地的内容和避坑指南。
1. RAG 与 Milvus:为什么需要向量数据库
1.1 RAG 解决什么问题
RAG 的全称是 Retrieval-Augmented Generation,也就是检索增强生成。它的核心思路很直接:不直接让大模型凭空回答,而是先从企业内部的文档、数据库、知识库中检索出与问题相关的片段,再把这些片段作为上下文,和用户问题一起交给大模型生成回答。
这样做有三个主要好处。第一,解决知识时效性问题,大模型的训练数据有截止日期,但企业内部的制度文档、产品手册、项目经验是持续更新的,RAG 可以让模型随时读取最新内容。第二,控制幻觉,模型回答时有了明确的参考资料,编造内容的概率会显著下降。第三,保护私有数据,企业不需要把自己内部数据拿去微调大模型,而是把数据留在自己的知识库中,使用的时候只检索匹配片段即可。
一个典型的 RAG 流程可以简化为:文档加载 → 文本切分 → Embedding 向量化 → 写入向量数据库 → 用户提问向量化 → 相似度检索 → 拼接上下文 → 大模型生成回答。这个链路中,“向量数据库”是决定检索效果和查询性能的关键一环,而 Milvus 就是这个位置上最常用的开源方案之一。
1.2 Milvus 在 RAG 中的定位
Milvus 是一个开源的分布式向量数据库,专门用来存储和检索海量向量数据。和 FAISS 这种向量检索库不同,Milvus 提供的是完整的数据库能力,包括数据模型、索引管理、数据持久化、权限控制、健康检查、可观测性,以及分布式部署能力。你可以把它理解成“专门给向量用的 MySQL”,而 FAISS 更像是“一个高性能的 ANN 搜索算法库”。
在企业级 RAG 项目中,选择 Milvus 而不是自己维护 FAISS,主要原因是工程成本。FAISS 只是一个库,要自己处理数据持久化、并发查询、多机扩展、故障恢复;Milvus 则自带 etcd 元数据管理、MinIO/S3 对象存储、消息队列等组件,直接以服务的方式对外提供能力。Milvus 在单机模式下可以用 Docker Compose 一键启动,在数据量增长后也能平滑扩展到集群模式,这也是它被很多企业选择做知识库底座的原因。
1.3 Dense Vector Search 与 Agentic RAG 的概念边界
在 RAG 相关的资料中,你经常会看到 Dense Vector Search 这个概念。它指的是用稠密向量表示文本语义,然后通过向量距离计算相似度。和传统 Elasticsearch 的 BM25 关键词匹配不同,Dense Vector Search 能理解“苹果公司的发布会”和“Apple 的新品活动”之间的语义等价关系,这对中文场景尤其有价值。Milvus 支持 dense vector、sparse vector 以及混合检索,实际项目中可以根据业务场景选择。
另外,当前 RAG 正在从“单轮检索 + 单轮生成”向更复杂的形态演进。比如 Agentic RAG,由 Agent 自主决定需要检索几个问题、调用几个知识库、是否需要二次检索;比如 Graph RAG,将实体和关系抽取成语图谱后再进行检索;再比如 Ontology RAG,在检索前先基于领域本体约束概念和关系。这些都属于 RAG 的进阶方向,但底层依然离不开 Milvus 这种向量数据库提供的召回能力。本文先聚焦最基础、也最稳定的经典 RAG 流程,把这套链路跑通之后,再往 Agentic、Graph RAG 方向扩展就不难了。
2. 企业级 RAG 知识库的整体架构
2.1 一个最小可落地的架构
在动手部署之前,先明确整体架构。一套完整的 RAG 知识库至少包含四个部分:数据处理层、向量存储层、检索服务层、大模型生成层。
对应到具体组件,可以这样划分:
- 数据处理层:负责读取 PDF、Word、Markdown、TXT 等文件,做格式解析、清洗、切分。
- 向量化服务:使用 Embedding 模型将文本片段转成向量。中文场景推荐 BGE、M3E 等模型。
- 向量存储层:使用 Milvus 存储向量和原始文本的对应关系,同时负责索引构建与相似度检索。
- 编排与生成层:使用 LangChain、Spring AI、LangChain4j 等框架进行流程编排,最后调用 LLM 生成回答。
- 可视化运维:使用 Attu 等工具查看 Collection、向量数据、索引状态。
整体流程可以用下面这个简化的链路来表示:
文档上传 → 解析清洗 → 文本切分 → Embedding → 写入 Milvus → 用户提问 → 问题 Embedding → Milvus 相似度检索 → 结果重排 → 拼接 Prompt → LLM 生成回答 → 返回给前端。
2.2 各组件职责与选型
在选型时,很多团队会纠结一个问题:到底要不要用 LangChain?这里需要明确,LangChain 只是工具,不是必选。如果你的团队以 Python 为主,直接用 LangChain 的 DocumentLoader、TextSplitter 可以省掉不少重复代码;如果你的团队以 Java 为主,则可以考虑 LangChain4j 或者 Spring AI 2.0 的 RAG 模块。
Embedding 模型是决定检索效果的关键变量。对中文企业文档,推荐使用BAAI/bge-base-zh-v1.5或BAAI/bge-large-zh-v1.5,如果是英文场景,BAAI/bge-base-en-v1.5、sentence-transformers/all-MiniLM-L6-v2也比较常用。需要特别注意的是,写入知识库时用的模型和检索时用的模型必须保持一致,否则向量的语义空间不同,检索结果会完全不可用。
大模型生成层可以接 OpenAI 兼容接口,也可以接国内大模型厂商的 API,或者自建 vLLM 服务。LLM 本身并不是本文的重点,只要它支持标准的 Chat Completions 接口,就可以被 RAG 编排层调用。
2.3 版本与兼容性说明
由于“Milvus 3.0”是较新的版本,在写这篇文章时,我并不想给你编造一个固定的 release 版本号。Milvus 3.0 继续强化了索引能力、查询性能和云原生部署体验,但 RAG 项目最关心的概念,例如 Collection、Schema、Partition、Index、Search,并没有发生颠覆性变化。
因此,本文的实操代码会采用 pymilvus 中稳定的 API 风格来编写。如果你本机安装的是 Milvus 2.4.x 或 2.5.x,也可以直接参考本文操作;如果你拿到的是 Milvus 3.0 正式版,建议先查阅官方 Release Notes,确认接口是否有细微调整。生产环境部署时,也建议把 Milvus、Attu、pymilvus 的版本都固定下来,不要长期使用latest标签。
3. 环境准备:本地部署 Milvus 3.0
3.1 环境要求与部署方式选择
Milvus 有两种最常见的部署形态。一种是 Standalone 单机模式,适合开发测试、中小规模数据量,通过 Docker Compose 启动一个 Milvus 实例;另一种是 Cluster 分布式集群模式,适合大规模生产环境,通过 Kubernetes 部署。
本文的实战阶段使用 Docker Compose 单机模式,这也是目前 RAG 知识库原型项目最常见的启动方式。Milvus Standalone 本身并不复杂,但它依赖三个核心组件:
- etcd:负责存储元数据,比如 Collection 的 Schema、索引信息、集群节点状态。
- MinIO:负责存储向量数据和日志数据,本质上是 S3 兼容的对象存储。
- Milvus Standalone:负责查询、索引、写入等核心能力。
因此,即使是最小化的 Docker Compose 文件,也至少需要定义这三个服务。
宿主机建议 8GB 内存以上,如果数据量大或者要跑本地 Embedding 模型,建议 16GB。磁盘需要预留至少 20GB 空间,因为 MinIO 里会保存向量数据文件。
3.2 使用 Docker Compose 启动 Milvus
在项目目录下创建docker-compose.yml,内容如下。这里为了演示方便,部分镜像使用了latest标签,生产环境请固定到你确认过的具体版本。
version: '3.5' services: etcd: container_name: milvus-etcd image: quay.io/coreos/etcd:v3.5.5 environment: - ETCD_AUTO_COMPACTION_MODE=revision - ETCD_AUTO_COMPACTION_RETENTION=1000 - ETCD_QUOTA_BACKEND_BYTES=4294967296 - ETCD_SNAPSHOT_COUNT=50000 volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd ports: - "2379:2379" minio: container_name: milvus-minio image: minio/minio:latest environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data command: minio server /minio_data --console-address ":9001" ports: - "9000:9000" - "9001:9001" standalone: container_name: milvus-standalone image: milvusdb/milvus:latest command: ["milvus", "run", "standalone"] environment: ETCD_ENDPOINTS: etcd:2379 MINIO_ADDRESS: minio:9000 volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus ports: - "19530:19530" - "9091:9091" depends_on: - etcd - minio这个 Compose 文件里需要注意几个点。etcd 是 Milvus 的元数据存储中心,2379是它的默认端口;MinIO 提供对象存储,9000是 API 端口,9001是控制台端口;Milvus 对外提供 gRPC 服务,端口是19530,9091是健康检查和 metrics 端口。
启动命令很简单,在docker-compose.yml所在目录执行:
docker compose up -d首次启动会自动拉取镜像,需要等待几分钟。启动完成后执行:
docker compose ps正常情况下,可以看到milvus-etcd、milvus-minio、milvus-standalone三个容器都处于 Up 状态。
3.3 验证 Milvus 服务可用
Milvus 启动之后,可以通过健康检查接口验证服务是否就绪:
curl http://localhost:9091/healthz如果返回OK,说明 Milvus 的 core 组件已经正常运行。接着验证 gRPC 端口是否可连接,最简单的方式是用 pymilvus 写一段连接测试脚本。
先安装 Python 依赖:
pip install pymilvus然后执行:
# 文件路径:test_connection.py from pymilvus import connections connections.connect( alias="default", host="127.0.0.1", port="19530" ) print("Milvus connection success!")如果脚本输出Milvus connection success!,说明 Milvus 的数据面已经可以正常访问,接下来就可以创建 Collection 并开始构建知识库了。
4. 核心原理拆解:从文档到向量的链路
4.1 文档加载与解析
RAG 的第一步是把非结构化数据变成模型可以处理的文本。企业里常见的文档格式包括 PDF、Word、Markdown、TXT,甚至还有扫描件。
这里最需要注意的是 PDF 的解析。PDF 分为文本型 PDF 和扫描型 PDF,文本型 PDF 可以直接用PyMuPDF、pdfplumber提取文字;扫描型 PDF 本质是图片,需要先调用 OCR 服务才能得到文本。如果不做 OCR,直接把扫描件切分并向量化,召回效果会非常差。
在 Python 项目中,可以按文件后缀选择不同的加载逻辑。下面是一个简单的目录加载示例:
# 文件路径:loader.py import os from markdown import markdown from bs4 import BeautifulSoup def load_documents(data_dir: str): docs = [] for root, _, files in os.walk(data_dir): for file in files: path = os.path.join(root, file) if file.endswith(".txt"): with open(path, "r", encoding="utf-8") as f: docs.append({"content": f.read(), "source": path}) elif file.endswith(".md"): with open(path, "r", encoding="utf-8") as f: html = markdown(f.read()) text = BeautifulSoup(html, "html.parser").get_text() docs.append({"content": text, "source": path}) return docs加载得到的每个文档对象应该至少包含content和source两个字段,source用来记录来源,方便后续追溯答案出处。
4.2 文本切分策略
原始文档不能直接向量化。一方面,一篇文章可能很长,直接做 embedding 会超出模型的最大输入长度;另一方面,向量检索是在“片段”层面进行的,片段太长会稀释语义,片段太短又容易丢失上下文。
常用的切分工具是 LangChain 的RecursiveCharacterTextSplitter,它可以根据段落、句子、字符层级递归切分,尽可能保留完整的语义边界。核心参数有两个:chunk_size控制每个片段的最大字符数,chunk_overlap控制相邻片段之间的重叠字符数。
# 文件路径:splitter.py from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=100, separators=["\n\n", "\n", "。", "!", "?", " ", ""] ) text = "这里是一段很长的企业文档内容……" chunks = text_splitter.split_text(text) print(f"切分后共 {len(chunks)} 个片段")为什么需要 overlap?因为如果一句话刚好被切在中间,检索时可能因为缺少后半句而导致语义不完整,重叠部分可以缓解这个问题。
此外,业务场景不同,切分策略也不同。如果文档有清晰的标题层级,可以先按 Markdown 标题切块,再对块内文本做二次切分;如果是问答对数据,则应该按“一问一答”作为一个整体单位,而不是机械地按字符切分。
4.3 Embedding 模型选择
Embedding 模型负责把文本映射成向量。选择模型时,需要重点关注这么几个指标:向量维度、中文效果、最大输入长度、推理速度。
以常见的BAAI/bge-base-zh-v1.5为例,它输出 768 维向量,对中文语义的理解效果不错,是目前中文 RAG 项目中使用率较高的模型之一。在代码中加载方式如下:
# 文件路径:embedder.py from sentence_transformers import SentenceTransformer model = SentenceTransformer("BAAI/bge-base-zh-v1.5") sentences = [ "Milvus 是一个分布式向量数据库", "RAG 是检索增强生成" ] embeddings = model.encode(sentences, normalize_embeddings=True) print(embeddings.shape)这里有一个很关键的细节:编码时指定了normalize_embeddings=True,这样向量会被归一化为单位向量。配合 Milvus 的 COSINE 距离,计算出的相似度分数会更加稳定和直观。
另外,使用 BGE 系列模型做检索时,官方建议在查询语句前面加上指令前缀“为这个句子生成表示以用于检索相关文章:”。这是因为 BGE 在训练时使用了指令微调,查询和文档的处理方式并不完全相同。实际项目中,建议把这条规则固化到检索服务里。
4.4 向量写入与索引构建
文本向量化之后,需要写入 Milvus。写入前要先创建 Collection,也就是“表”,并定义字段 Schema。
根据经验,一个 RAG 知识库的 Collection 通常至少包含以下字段:
id:主键,使用自增 ID。text:原始文本内容。source:文档来源,便于追溯。embedding:向量字段,维度必须和 Embedding 模型一致。
创建 Collection 的同时,还需要选择索引类型。Milvus 常见的索引包括 FLAT、IVF_FLAT、HNSW 等。数据量较小时,FLAT 暴力检索速度足够且精度最高;数据量大后,HNSW 是兼顾性能和召回率的选择。
下面是用 pymilvus 创建 Collection 的完整示例:
# 文件路径:init_collection.py from pymilvus import connections, FieldSchema, CollectionSchema, Collection, DataType connections.connect(alias="default", host="127.0.0.1", port="19530") fields = [ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True), FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=65535), FieldSchema(name="source", dtype=DataType.VARCHAR, max_length=1024), FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=768) ] schema = CollectionSchema( fields=fields, description="企业 RAG 知识库 Collection" ) collection = Collection(name="knowledge_base", schema=schema) index_params = { "index_type": "HNSW", "metric_type": "COSINE", "params": {"M": 16, "efConstruction": 200} } collection.create_index(field_name="embedding", index_params=index_params) print("Collection created and index built!")这里需要特别留意dim=768,它必须和 Embedding 模型的输出维度完全一致。如果模型输出的是 1024 维,这里写成 768,插入数据时就会报维度错误。
5. 完整实战:搭一套可运行的 RAG 知识库
5.1 项目结构设计
为了让代码易于维护,建议把数据导入和查询服务拆开。完整的项目结构如下:
rag-project/ ├── docker-compose.yml ├── requirements.txt ├── data/ │ ├── product-intro.md │ └── faq.txt ├── ingest.py ├── init_collection.py ├── embedder.py ├── app.py └── test_connection.py其中data目录存放企业知识文档,ingest.py负责把文档处理并写入 Milvus,app.py是基于 FastAPI 的查询服务。
requirements.txt内容如下:
pymilvus>=2.4.0 sentence-transformers>=2.2.0 fastapi>=0.110.0 uvicorn>=0.29.0 langchain>=0.1.0 pypdf>=4.0.0 openai>=1.0.0版本号是一个范围,实际安装时 pip 会自动解析。如果你的 Python 环境较老,可以适当降低版本要求。
5.2 初始化 Collection
在实际项目中,索引策略可能根据数据量动态调整,但 Collection 创建逻辑通常是稳定的。本节可以直接使用 4.4 小节中的init_collection.py脚本。
执行方式:
python init_collection.py如果之前已经创建过同名 Collection,再次执行会报错。可以在创建前先判断是否存在:
from pymilvus import utility if utility.has_collection("knowledge_base"): print("Collection already exists, skip creation") else: # 创建逻辑 pass使用auto_id=True后,写入数据时不需要指定id字段,Milvus 会自动分配唯一主键。
5.3 数据写入:把知识库灌入 Milvus
ingest.py需要完成三件事:加载文档、切分文本、向量化并写库。
# 文件路径:ingest.py import os from pymilvus import connections, Collection from sentence_transformers import SentenceTransformer from langchain.text_splitter import RecursiveCharacterTextSplitter connections.connect(alias="default", host="127.0.0.1", port="19530") model = SentenceTransformer("BAAI/bge-base-zh-v1.5") text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=100, separators=["\n\n", "\n", "。", "!", "?", " ", ""] ) def read_and_split(data_dir: str): chunks = [] sources = [] for root, _, files in os.walk(data_dir): for file in files: path = os.path.join(root, file) if not (file.endswith(".txt") or file.endswith(".md")): continue with open(path, "r", encoding="utf-8") as f: text = f.read() parts = text_splitter.split_text(text) for part in parts: chunks.append(part) sources.append(path) return chunks, sources chunks, sources = read_and_split("data") print(f"共切分出 {len(chunks)} 个文本片段") embeddings = model.encode(chunks, normalize_embeddings=True).tolist() collection = Collection("knowledge_base") # 注意:text 字段和 embedding 字段必须按 Schema 中定义的顺序排列 data = [ chunks, sources, embeddings ] collection.insert(data) collection.flush() print(f"成功写入 {len(chunks)} 条向量数据")执行:
python ingest.py输出类似:
共切分出 86 个文本片段 成功写入 86 条向量数据这里需要强调字段顺序。由于 Schema 中id是auto_id,插入时不需要传,剩余字段的顺序是text、source、embedding,所以data列表也必须按照这个顺序排列。如果把embeddings放在第二项,插入时就会报数据类型错误。
5.4 检索服务:用 FastAPI 暴露查询接口
数据导入完成后,需要对外提供检索接口。这里使用 FastAPI 编写一个/query接口,流程是:接收用户问题 → 生成问题向量 → 在 Milvus 中检索相似片段 → 拼接上下文 → 调用大模型生成回答。
# 文件路径:app.py from fastapi import FastAPI from pydantic import BaseModel from pymilvus import connections, Collection from sentence_transformers import SentenceTransformer from openai import OpenAI connections.connect(alias="default", host="127.0.0.1", port="19530") model = SentenceTransformer("BAAI/bge-base-zh-v1.5") collection = Collection("knowledge_base") # 注意:生产环境中 base_url 和 api_key 要放到配置中心或环境变量 llm_client = OpenAI( base_url="https://your-llm-api-endpoint", api_key="your-api-key" ) app = FastAPI(title="RAG Knowledge Base API") class QueryRequest(BaseModel): question: str top_k: int = 5 def build_prompt(question: str, contexts: list[str]) -> str: context_text = "\n\n".join(contexts) return f"""你是企业知识库助手,请严格基于以下资料回答问题。如果资料中没有相关信息,请明确回答“知识库中暂无相关内容”。 资料: {context_text} 问题:{question} """ @app.post("/query") def query(req: QueryRequest): # 1. 向量化用户问题 query_embedding = model.encode( ["为这个句子生成表示以用于检索相关文章:" + req.question], normalize_embeddings=True ).tolist() # 2. 在 Milvus 中执行向量检索 results = collection.search( data=query_embedding, anns_field="embedding", param={"metric_type": "COSINE", "params": {"ef": 64}}, limit=req.top_k, output_fields=["text", "source"] ) # 3. 提取检索到的文本片段 contexts = [] sources = [] for hits in results: for hit in hits: contexts.append(hit.entity.get("text")) sources.append(hit.entity.get("source")) # 4. 调用大模型生成回答 prompt = build_prompt(req.question, contexts) resp = llm_client.chat.completions.create( model="your-model-name", messages=[ {"role": "system", "content": "你是一个严谨的企业知识库问答助手。"}, {"role": "user", "content": prompt} ] ) return { "answer": resp.choices[0].message.content, "sources": list(set(sources)), "contexts": contexts }启动服务:
uvicorn app:app --host 0.0.0.0 --port 8000调用接口:
curl -X POST http://localhost:8000/query \ -H "Content-Type: application/json" \ -d '{"question": "Milvus 支持哪些索引类型?", "top_k": 3}'返回结果是一个 JSON,包含answer、sources和contexts三个字段。其中sources可以帮助用户在界面上看到答案出处,这是企业级知识库的基本要求。
5.5 运行与验证
整体链路验证可以按以下顺序进行:
- 启动 Docker Compose,确认 Milvus 三个容器正常。
- 执行
test_connection.py,确认 Python 能连接 Milvus。 - 执行
init_collection.py,创建 Collection 和索引。 - 在
data目录放入企业文档,执行ingest.py写入数据。 - 启动
app.py,用 curl 调用/query接口验证答案。
如果检索到内容但 answer 为空,重点检查 LLM 接口的配置;如果 answer 正常但内容答非所问,重点检查切分粒度和召回阈值。
6. 可视化与管理:Attu 连接本地 Milvus
6.1 Attu 是什么
Attu 是 Milvus 的可视化管理工具,用来查看 Collection 列表、Schema 结构、向量数据分布、索引状态,以及直接执行查询操作。它的作用是降低 Milvus 的使用门槛,尤其在排查数据和验证检索效果时非常方便。
在 RAG 项目开发阶段,Attu 几乎是必备工具。比如执行完ingest.py后,可以打开 Attu 查看实际写入的向量条数和文本字段,避免“以为写入了数据,实际 Collection 是空的”这种问题。
6.2 Attu 与 Milvus 版本匹配
这是很多人踩坑的地方。Attu 的版本和 Milvus 的版本需要保持兼容,某些旧版 Attu 连接新版 Milvus 时,会出现正常连接但 Collection 列表加载不出来的情况。
最稳妥的方式是到 Attu 官方 GitHub Releases 页面查看当前支持范围,或者直接使用最新版 Attu。如果你使用的是 Docker 方式启动 Milvus,也建议用 Docker 方式启动 Attu,这样网络配置最简单:
docker run -p 8000:3000 \ -e MILVUS_URL=http://127.0.0.1:19530 \ zilliz/attu:latest启动后,浏览器访问http://localhost:8000,在连接页面填写 Milvus 地址。
6.3 连接步骤与常用操作
在 Attu 连接页面中,Host 填127.0.0.1,Port 填19530。如果你是在 Docker 容器中运行 Attu,并且要连接宿主机上的 Milvus,这里不能直接填写localhost,因为容器内的 localhost 指向的是 Attu 容器本身,需要填写宿主机在 Docker 网络中的 IP,或者把 Attu 和 Milvus 放到同一个 Compose 网络中。
连接成功后,常见的操作包括:
- 查看 Collection 列表,确认
knowledge_base已经创建。 - 点击 Collection 进入详情页,查看字段 Schema、向量维度、索引类型。
- 在 Query 标签页手动输入向量或文本条件,检查是否有数据返回。
- 查看索引状态,确认索引构建完成。
Attu 不适合用来做全量数据导出,它更偏向开发和排错。正式环境的数据备份、迁移,建议使用 Milvus 官方提供的数据备份工具。
7. 常见问题与排查思路
7.1 高频报错汇总
下面这张表汇总了 RAG 知识库搭建过程中最常见的几类问题:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 容器启动后反复重启 | 内存不足、etcd 数据目录权限异常 | 检查docker compose logs,分配至少 8GB 内存,清理 volumes 重新启动 |
| pymilvus 连接超时 | Milvus 服务未启动或端口映射错误 | 确认容器状态,检查宿主机 19530 端口是否被占用 |
| Attu 加载不出 Collection | Attu 与 Milvus 版本不兼容,或连接地址指向了容器内部 | 升级 Attu,填写宿主机可达的 IP 地址 |
| 插入数据报维度错误 | Embedding 模型输出维度与 Collection 定义的 dim 不一致 | 统一模型,修改 dim 后重建 Collection |
| 检索结果为空 | Collection 中没有数据,或向量字段未建索引 | 执行collection.num_entities查看数据量,确认索引创建成功 |
| 检索结果耗时很高 | 使用了 FLAT 索引且数据量较大 | 切换 HNSW 索引,调整 ef 参数,或者使用 GPU 索引 |
| 大模型回答和资料无关 | 切分粒度太大或检索到无关片段 | 调整 chunk_size、overlap,增加重排环节,降低 top_k |
| Docker 中无法连接宿主机 Milvus | container 网络隔离,localhost 指向容器自身 | 使用host.docker.internal或宿主机局域网 IP |
7.2 排查顺序与通用思路
遇到问题不要乱试,按下面顺序排查通常是最高效的:
第一步,确认状态。运行docker compose ps和curl http://localhost:9091/healthz,确保 Milvus 本身是健康的。
第二步,确认数据。在 Attu 中查看 Collection 中的数据量,或在 Python 中执行collection.num_entities,确认数据真的写进去了。
第三步,确认维度。打印 embedding 的 shape,和 Collection Schema 中的 dim 比对。
第四步,确认索引。执行collection.index().params,查看索引类型和参数是否生效。
第五步,确认检索参数。metric_type必须和创建索引时一致,否则会出现相似度计算方式不一致的问题。
这类问题的根因大部分集中在“数据没写入”“维度不一致”“版本不兼容”这三个方向,把这三项排查完,问题基本就解决了。
8. 企业级落地的最佳实践
8.1 数据治理与 Collection 规划
企业级 RAG 和 Demo 最大的区别在于数据治理。在多业务线并用一套 Milvus 时,建议按业务域创建不同 Collection,或者在 Collection 中使用 Partition Key 做物理隔离,避免不同业务的数据互相干扰,也方便做权限和生命周期管理。
命名规范也非常重要。Collection 名称建议包含业务域和用途,比如hr_policy_v1、product_faq_v1。Embedding 模型一旦更换,向量空间就会发生变化,旧的向量不能继续使用,所以 Collection 名称里带上模型版本或数据版本,可以避免线上事故。
同时,企业文档通常会包含敏感信息,写入 Milvus 前要经过脱敏、合规审查。Milvus 侧可以通过网络策略限制访问来源,只允许应用服务器连接 19530 端口,不建议把 gRPC 端口直接暴露到公网。
8.2 检索效果与 RAG 评估
很多团队把 RAG 搭起来后,凭感觉判断“回答还行”,这是不够的。检索效果需要一套可量化的评估方案。
基础的评估指标体系包括:
- 召回率 Recall@K:正确答案是否出现在 Top K 结果中。
- 命中率 Hit Rate:在测试集上的检索命中比例。
- MRR:正确答案在结果列表中的排序位置。
- Answer 相关性:大模型生成的答案和用户问题是否相关。
具体做法是准备一批“问题 - 标准答案文档”的评测集,然后批量执行检索和生成,计算以上指标。也可以引入 RAGAS 等框架做自动化评估。关于“RAG 测评怎么做”这个问题,我的建议是:先做文档级命中率评估,再做答案相关性人工评估,两者结合才完整。
在检索链路中加入重排(Rerank)是提升效果最直接的手段。第一次先用向量检索召回 Top 50,再用 Cross-Encoder 重排模型选出 Top 5,效果通常比直接 Top 5 好很多。代价是多一次推理耗时,但对企业知识库来说,这个成本是值得的。
8.3 性能、安全与稳定性
Milvus 在生产环境运行需要注意几个方面:
- 容量规划:向量数据会占用大量内存和磁盘,建议根据文档数量和向量维度提前估算存储量。Milvus 的向量会存储到 MinIO 中,查询时依赖内存中的索引,所以内存大小直接影响查询性能。
- 索引优化:数据量超过百万级后,FLAT 索引基本不可用,建议使用 HNSW。HNSW 的
M和efConstruction参数会影响索引构建速度和召回率,需要根据数据分布做调参。 - 高可用:单机模式存在单点风险,生产环境建议使用 Milvus Cluster,并通过 Kubernetes 管理节点调度。
- 监控:Milvus 暴露了 Prometheus 格式的 metrics 接口,地址是
http://localhost:9091/metrics,可以接入 Grafana 做可视化监控。
另外,在 Java 技术栈中,如果团队不使用 Python,也可以基于 LangChain4j 或 Spring AI 2.0 的 RAG 模块操作 Milvus。它们的底层模型和检索流程与本文一致,只是接口风格不同。对于已经用 Spring Boot 构建后端的企业来说,用 Spring AI 2.0 实现 RAG 可以减少跨语言部署成本。
9. 总结与学习路线
这篇文章围绕 Milvus 3.0 实际搭建了一套企业级 RAG 知识库,从概念到部署、从数据写入到查询服务、从可视化排错到生产落地建议,完整覆盖了一条可运行的链路。
你至少可以带走以下几个关键能力:理解 RAG 的核心流程和 Milvus 在其中的定位;用 Docker Compose 快速部署 Milvus 单机环境;掌握 Collection、Schema、索引、插入、检索等核心操作;独立编写一套从文档处理到查询 API 的 Python 实现;知道用 Attu 排查数据问题和常见报错的处理方式。
如果你想继续深入,有几个方向值得优先探索。第一是混合检索,把 Milvus 的 dense vector 和 sparse vector 结合 Elasticsearch 的 BM25 关键词检索,再用 Rerank 融合排序;第二是 Agentic RAG,让 Agent 根据用户问题自主决定是否检索、检索几次、调用哪个知识库;第三是 Graph RAG 和 Ontology RAG,适合知识关联复杂、需要多跳推理的企业场景;第四是评估体系建设,把 RAG 从“能回答”提升到“稳定可靠地回答”。
最后留一个建议:不要一上来就追求大而全的架构。先把文档切分、向量化、检索、生成这条最小链路跑通,再逐步加安全、加评估、加高可用。能稳定回答 100 个核心问题的知识库,远比一个看起来功能丰富但回答不可用的系统有价值。