最近在做一个知识库检索的需求,传统的关键词搜索效果很差——用户搜"怎么退货",但文档里写的是"退款流程",关键词匹配不上。调研了一圈,最后用向量数据库+语义搜索来解决。这篇记录下 Milvus 的入门使用。
为什么需要向量数据库
传统数据库做搜索靠的是精确匹配或者 LIKE 模糊查询,本质上还是字符串匹配。但自然语言里同一个意思有无数种表达方式,关键词搜索根本搞不定。
向量数据库的思路不同:先用模型把文本转成向量(Embedding),语义相近的文本在向量空间里距离也近。搜索时把查询文本也转成向量,然后找距离最近的几个向量,就是语义最相关的结果。
Embedding 原理简述
Embedding 就是把文本映射到一个高维向量空间。比如用 OpenAI 的 text-embedding-ada-002 模型,每段文本会被转成一个 1536 维的向量。
关键特性是:语义相似的文本,向量之间的余弦相似度高。"退货流程"和"怎么退款"的向量距离会很近,而和"今天天气不错"的向量距离会很远。
Milvus 安装
Milvus 是开源的向量数据库,性能不错,社区也活跃。用 Docker 最方便:
# 下载 docker-compose 文件
wget https://github.com/milvus-io/milvus/neleases/download/v2.3.0/milvus-standalone-docker-compose.yml -O docker-compose.yml
# 启动
docker-compose up -d
# 检查状态
docker-compose ps
默认端口是 19530。
Python pymilvus 基本操作
安装客户端库:
pip install pymilvus
连接与创建集合
from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection
# 连接 Milvus
connections.connect("default", host="localhost", port="19530")
# 定义 Schema
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=2048),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1536),
]
schema = CollectionSchema(fields, description="知识库文档集合")
# 创建集合
collection = Collection("knowledge_base", schema)
插入向量
import openai
import numpy as np
def get_embedding(text):
# 调用 OpenAI 获取文本 embedding
resp = openai.Embedding.create(input=text, model="text-embedding-ada-002")
return resp["data"][0]["embedding"]
# 准备数据
texts = [
"退款流程:用户提交退款申请后,客服审核通过即可原路退回",
"商品上架需要填写标题、描述、价格、库存等基本信息",
"配送费满99元包邮,偏远地区另计",
]
embeddings = [get_embedding(t) for t in texts]
# 插入
collection.insert([texts, embeddings])
collection.flush()
print(f"当前文档数: {collection.num_entities}")
创建索引与搜索
# 创建 IVF_FLAT 索引
index_params = {
"metric_type": "IP", # 内积,也可以用 L2(欧氏距离)或 COSINE
"index_type": "IVF_FLAT",
"params": {"nlist": 128}
}
collection.create_index("embedding", index_params)
# 加载到内存
collection.load()
# 搜索
query = "怎么退货"
query_embedding = get_embedding(query)
results = collection.search(
data=[query_embedding],
anns_field="embedding",
param={"metric_type": "IP", "params": {"nprobe": 10}},
limit=3,
output_fields=["text"]
)
for hits in results:
for hit in hits:
print(f"距离: {hit.distance:.4f} | 内容: {hit.entity.get('text')}")
输出类似:
距离: 0.9231 | 内容: 退款流程:用户提交退款申请后,客服审核通过即可原路退回
距离: 0.7845 | 内容: 商品上架需要填写标题、描述、价格、库存等基本信息
距离: 0.7102 | 内容: 配送费满99元包邮,偏远地区另计
"怎么退货"和"退款流程"的相似度最高,说明语义搜索是有效的。
与传统数据库的对比
| 特性 | MySQL/PostgreSQL | Milvus |
|---|---|---|
| 搜索方式 | 关键词匹配、全文索引 | 向量相似度 |
| 语义理解 | 无 | 依赖 Embedding 模型 |
| 数据类型 | 结构化数据 | 向量 + 标量 |
| 适用场景 | CRUD、事务 | 相似搜索、推荐 |
| 索引类型 | B-Tree、Hash | IVF、HNSW、DiskANN |
实际使用中,向量数据库不是用来替代传统数据库的,而是互补。我的做法是:结构化数据存 MySQL,文档的 Embedding 向量存 Milvus,搜索时先走 Milvus 找到相关文档 ID,再从 MySQL 取完整信息。
几个注意点
- Embedding 模型的选择很关键:不同模型生成的向量维度不同、效果也不同。中文场景可以试试
m3e-base或bge-large-zh,不一定要用 OpenAI。 - 索引类型影响精度和速度:IVF_FLAT 精度高但慢,HNSW 速度快但占内存多。数据量小的时候差别不大,几百万条以上就需要根据场景选了。
- 向量维度不能混用:一个集合里所有向量必须是同一维度。如果换了 Embedding 模型,需要重新建集合并重新生成所有向量。
向量数据库配合 LLM 做 RAG(检索增强生成)是目前很火的方案,后面有机会再单独写一篇。