本文记录一次完整的本地向量检索实验,使用 ChromaDB 存储中文文档切片,BGE embedding 模型编码语义,并测量检索准确率与延迟。代码、数据和指标均可复现。
实验环境
软件版本:
- Python 3.11.15
- ChromaDB 1.5.9
- sentence-transformers 6.1.0
- BAAI/bge-small-zh-v1.5 (HuggingFace 中文 embedding 模型)
配置:
- 距离度量: cosine (余弦相似度)
- 文档切片: chunk_size=300, overlap=50
- 测试文档: 3 个中文 TXT 文件,共 1262 字符,切分为 6 个 chunk
文档准备与向量化
实验使用 3 个中文文本文件,内容涵盖深度学习、向量数据库和评估方法:
doc1.txt: 349 字符 → 2 chunks
doc2.txt: 466 字符 → 2 chunks
doc3.txt: 447 字符 → 2 chunks
切片策略: 固定 300 字符窗口,50 字符重叠,保留上下文连续性。
Embedding 耗时: 78.8 ms (6 个 chunk 的向量化时间)
总 Ingestion 耗时: 159 ms (包括文件读取、切片、向量化和写入 ChromaDB)
检索测试
可回答问题 (4 个)
| 查询 | 预期文档 | Top-1 | Top-3 | 距离 | 延迟 |
|---|---|---|---|---|---|
| 什么是检索增强生成? | doc2.txt | doc3.txt ✗ | doc3, doc2, doc3 ✓ | 0.4955, 0.5150, 0.5185 | 11.15 ms |
| Transformer 的核心组件是什么? | doc1.txt | doc1.txt ✓ | doc1, doc2, doc2 ✓ | 0.4473, 0.5736, 0.5769 | 8.16 ms |
| 评估检索系统的常用指标有哪些? | doc3.txt | doc3.txt ✓ | doc3, doc3, doc2 ✓ | 0.3126, 0.4240, 0.5028 | 8.85 ms |
| 中文文本适合用什么 embedding 模型? | doc2.txt | doc2.txt ✓ | doc2, doc2, doc1 ✓ | 0.3269, 0.4381, 0.5067 | 8.42 ms |
实测指标 (基于 6 chunks 的小样本):
- Top-1 准确率: 3/4 = 75%
- Top-3 召回率: 4/4 = 100%
- 平均查询延迟: 9.14 ms
不可回答问题 (1 个)
查询 "deepseek-flash 的推理延迟是多少?" 在文档集中无答案,检索返回 Top-3 上下文供后续生成模型判断拒答。
重要声明: 上述指标为 6 个切片的观察值,样本量极小,不可外推为生产性能。真实应用需要在目标数据规模和查询分布上重新测量。
完整代码
以下是可直接运行的实验脚本 (需提前准备 ./docs/*.txt):
#!/usr/bin/env python3
import sys, time, json, hashlib
from pathlib import Path
from datetime import datetime
import chromadb
from chromadb.utils import embedding_functions
def chunk_text(text, chunk_size=300, overlap=50):
chunks, start, chunk_idx = [], 0, 0
while start < len(text):
end = start + chunk_size
chunk = text[start:end]
if chunk.strip():
chunks.append({'text': chunk.strip(), 'start': start,
'end': min(end, len(text)), 'index': chunk_idx})
chunk_idx += 1
start += chunk_size - overlap
return chunks
# 配置 BGE 中文 embedding
embedding_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="BAAI/bge-small-zh-v1.5"
)
# 初始化 ChromaDB
run_id = hashlib.md5(str(time.time()).encode()).hexdigest()[:8]
client = chromadb.PersistentClient(path=f"./chroma_db_{run_id}")
collection = client.create_collection(
name="rag_docs",
metadata={"hnsw:space": "cosine"},
embedding_function=embedding_fn
)
# 加载文档并切片
all_chunks, all_ids, all_metadatas = [], [], []
for doc_file in sorted(Path("./docs").glob("*.txt")):
content = open(doc_file, encoding='utf-8').read()
for chunk_info in chunk_text(content):
all_chunks.append(chunk_info['text'])
all_ids.append(f"{doc_file.stem}_chunk_{chunk_info['index']}")
all_metadatas.append({"source": doc_file.name})
# 添加到 ChromaDB
collection.add(documents=all_chunks, ids=all_ids, metadatas=all_metadatas)
# 查询测试
for q in [
{"question": "什么是检索增强生成?", "expected": "doc2.txt"},
{"question": "Transformer 的核心组件是什么?", "expected": "doc1.txt"},
]:
result = collection.query(query_texts=[q['question']], n_results=3)
sources = [m['source'] for m in result['metadatas'][0]]
print(f"查询: {q['question']}")
print(f" Top-1: {sources[0]} ({'✓' if sources[0]==q['expected'] else '✗'})")
依赖安装:
pip install chromadb sentence-transformers
检索阶段的局限
- 模型 revision 未固定: 使用最新版本,HuggingFace 模型更新可能导致向量不可复现
- 小样本: 6 个 chunk 仅供观察原理,指标不代表生产性能
- 查询分布: 4 个人工构造的问题无法覆盖真实搜索场景
生成阶段 (未执行)
检索完成后,可将 Top-K 上下文拼接并提交给大语言模型生成答案。典型流程:
from openai import OpenAI
# 检索上下文
result = collection.query(query_texts=["用户问题"], n_results=3)
contexts = result['documents'][0]
# 调用 LLM
client = OpenAI(api_key="YOUR_KEY", base_url="https://api.deepseek.com")
response = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": f"根据上下文回答:\n{contexts}"}]
)
print(response.choices[0].message.content)
注意: 本次实验未配置 API Key,生成阶段未运行。上述代码仅为接入示例,未经实测验证。
可复现性
完整实验代码和结果:
run_canonical.py- 实验脚本results_79732dd2.json- 原始结果report_79732dd2.md- 实验报告
下一步
- 扩大文档集: 从 6 chunks 扩展到数百或数千
- 固定模型版本: 锁定 embedding 模型的 commit hash
- 切片策略对比: 测试不同参数对检索准确率的影响
- 生成质量评估: 评估 LLM 拒答行为和答案质量
- 混合检索: 结合 BM25 等稀疏检索方法
参考资料
- ChromaDB 文档: https://docs.trychroma.com/
- BGE Embedding: https://huggingface.co/BAAI/bge-small-zh-v1.5
- sentence-transformers: https://www.sbert.net/
实验说明: 本文所有数据和指标来自单次本地实验 (Run ID: 79732dd2),环境、配置和结果均已记录。检索阶段完整运行,生成阶段因无 API Key 未执行。
