20 分钟搭一个语义搜索引擎:Chroma + Qdrant 手把手教程
🩺 摘要
上篇文章做了 Chroma vs Qdrant vs Milvus 的全面对比,这篇直接动手。...
📝 详情
上篇文章做了 Chroma vs Qdrant vs Milvus 的全面对比,这篇直接动手。
目标:用 20 分钟搭一个能理解「语义」的搜索引擎——你搜「怎么退会员费」它能找到「取消订阅操作指南」,因为两者意思相近,哪怕文字完全不同。
准备工作
你需要: - Python 3.10+ - pip(国内用清华镜像装更快)
# 安装依赖
pip install chromadb qdrant-client sentence-transformers
国内加速:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple chromadb qdrant-client sentence-transformers
第一步:准备中文 Embedding 模型
向量数据库的核心是把文字变成数字(向量),这个转化工具叫做 Embedding 模型。对于中文,我用 BAAI/bge-small-zh-v1.5——很小(33MB),中文效果不错。
from sentence_transformers import SentenceTransformer
# 加载模型(首次运行会自动下载)
# 如果下载慢,可以先用 HF 镜像
# export HF_ENDPOINT=https://hf-mirror.com
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
# 测试一下
vec = model.encode("如何取消自动续费")
print(f"向量维度: {len(vec)}") # 输出: 512
print(f"前5个值: {vec[:5]}") # 向量长这样
BGE-small-zh 输出 512 维向量,对于绝大多数项目够用了。如果你需要更高精度,可以上 BAAI/bge-large-zh-v1.5(1024 维,大 3 倍,慢 2 倍)。
第二步:用 Chroma 实现(5 分钟)
Chroma 适合快速原型,不需要启动任何服务:
import chromadb
from sentence_transformers import SentenceTransformer
# 1. 初始化
client = chromadb.Client()
collection = client.create_collection("knowledge_base")
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
# 2. 准备数据(一些客服常见问题)
documents = [
"如何取消会员自动续费",
"退款需要多长时间到账",
"修改绑定的手机号码",
"忘记密码怎么办",
"如何下载历史订单数据",
]
# 3. 生成向量并存入
embeddings = model.encode(documents).tolist()
collection.add(
documents=documents,
embeddings=embeddings,
ids=[f"doc{i}" for i in range(len(documents))]
)
print(f"已存入 {len(documents)} 条数据")
# 4. 搜索测试
query = "我想退订会员"
query_vec = model.encode(query).tolist()
results = collection.query(
query_embeddings=[query_vec],
n_results=3
)
print("搜索结果:")
for i, (doc, score) in enumerate(zip(results["documents"][0], results["distances"][0])):
print(f" {i+1}. {doc} (距离: {score:.4f})")
输出:
1. 如何取消会员自动续费 (距离: 0.2341)
2. 退款需要多长时间到账 (距离: 0.4587)
3. 如何下载历史订单数据 (距离: 0.6123)
「想退订会员」和「取消会员自动续费」虽然字面不同,但语义上最近——这就是向量搜索的意义。
第三步:用 Qdrant 实现(10 分钟)
Qdrant 需要启动服务,但性能更强,适合正式项目:
# 启动 Qdrant
docker run -d --name qdrant \
-p 6333:6333 \
-p 6334:6334 \
-v $(pwd)/qdrant_data:/qdrant/storage \
qdrant/qdrant
# 验证
curl http://localhost:6333/healthz
# 返回 OK 就对了
from qdrant_client import QdrantClient
from qdrant_client.http import models
from sentence_transformers import SentenceTransformer
# 连接
client = QdrantClient("localhost", port=6333)
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
VECTOR_SIZE = 512
# 1. 建集合
client.recreate_collection(
collection_name="knowledge_base",
vectors_config=models.VectorParams(
size=VECTOR_SIZE,
distance=models.Distance.COSINE
)
)
# 2. 存入数据
documents = [
"如何在 Linux 上安装 Docker",
"Docker 容器和虚拟机的区别",
"如何将 Spring Boot 应用容器化",
"Docker Compose 常用命令",
"优化 Docker 镜像体积的 5 个技巧",
]
vectors = model.encode(documents)
points = [
models.PointStruct(
id=i,
vector=vectors[i].tolist(),
payload={"text": documents[i], "category": "docker"}
)
for i in range(len(documents))
]
client.upsert(
collection_name="knowledge_base",
points=points
)
# 3. 搜索
query = "怎么搞容器化"
query_vec = model.encode(query).tolist()
results = client.search(
collection_name="knowledge_base",
query_vector=query_vec,
limit=3
)
for result in results:
print(f" {result.payload['text']} (打分: {result.score:.4f})")
输出:
如何将 Spring Boot 应用容器化 (打分: 0.8123)
Docker 容器和虚拟机的区别 (打分: 0.6754)
如何在 Linux 上安装 Docker (打分: 0.5321)
第四步:进阶——带上元数据过滤
Qdrant 的杀手级功能是可以在搜索的同时做标量字段过滤:
# 只搜索 "category" 为 "docker" 的数据
results = client.search(
collection_name="knowledge_base",
query_vector=query_vec,
query_filter=models.Filter(
must=[
models.FieldCondition(
key="category",
match=models.MatchValue(value="docker")
)
]
),
limit=3
)
这在做多租户应用或分类搜索时非常有用。比如你有个电商知识库,用户可以同时搜「退货政策」但只看「电子产品」分类下的。
第五步:配套 LLM——完整 RAG
向量搜索只是 RAG(检索增强生成)的一半。另一半是把搜到的结果喂给 LLM,让它总结回答:
from openai import OpenAI
client_llm = OpenAI(
api_key="sk-xxx",
base_url="https://api.deepseek.com/v1"
)
# 向量搜索
results = qdrant_client.search(
collection_name="knowledge_base",
query_vector=query_vec,
limit=3
)
# 把结果拼成上下文
context = "\n\n".join([r.payload["text"] for r in results])
# 让 LLM 基于搜索结果回答
response = client_llm.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "基于以下资料回答问题。"},
{"role": "user", "content": f"资料:\n{context}\n\n问题:{query}"}
]
)
print(response.choices[0].message.content)
一个完整的 RAG 脚本参考
我把上面所有步骤整合成了一个可以直接跑的脚本。保存为 mini_rag.py:
import chromadb
from sentence_transformers import SentenceTransformer
from openai import OpenAI
# Embedding
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
# 向量库
client = chromadb.Client()
collection = client.create_collection("wiki")
# 数据(可以用你自己的文档替代)
docs = ["文档1内容...", "文档2内容..."]
embeddings = model.encode(docs).tolist()
collection.add(documents=docs, embeddings=embeddings, ids=["1", "2"])
# 搜索
query = "你的问题"
q_vec = model.encode(query).tolist()
results = collection.query(query_embeddings=[q_vec], n_results=3)
# LLM 回答
llm = OpenAI(api_key="sk-xxx", base_url="https://api.deepseek.com/v1")
resp = llm.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "user", "content": f"资料:{results['documents'][0]}\n问题:{query}"}
]
)
print(resp.choices[0].message.content)
什么时候用 Docker 版的 Qdrant?
很多人问「为什么 Chroma 不需要 Docker 而 Qdrant 需要」——因为 Chroma 把所有数据存在当前目录的 chroma.sqlite3 文件里,而 Qdrant 是一个独立的数据库服务。如果你的项目:
- 只需一个人用 → Chroma 就够了
- 需要多人同时访问 → Qdrant
- 部署到服务器上 → Qdrant 更可靠
- 嵌入到移动 App → Chroma 更方便
没有绝对的好坏,看场景。
💬 评论 (0)