知识库建好了,但够不够用?向量检索量化覆盖率,找到你的知识盲区
背景
政务热线RAG系统上线前,最怕的不是"回答错了",而是"答不上来"。
知识库是从各种政策文档、办事指南里整理出来的,几千条问答。热线那边积累了十几万条真实来电记录。问题来了:知识库能覆盖多少真实问题?哪些问题是知识库的盲区?
不能拍脑袋说"差不多够了吧"。得量化。
思路
把热线问题逐条向量化,去Milvus知识库里搜,看最高相似度是多少。
- 相似度 ≥ 0.85:知识库里有高度匹配的答案,覆盖了
- 相似度 0.6 ~ 0.85:知识库里有相关的,但不精确,可能需要补充
- 相似度 < 0.6:知识库里几乎没有相关内容,纯盲区
逐条跑完,统计三个区间的比例,覆盖率一目了然。
实现
第一步:热线问题向量化
def vectorize_text(text):
url = "http://localhost:11434/api/embeddings"
data = {"model": "bge-m3:latest", "prompt": text}
response = requests.post(url, json=data)
if response.status_code == 200:
return response.json().get('embedding', [])
return None
本地Ollama + bge-m3,零API费用。
第二步:逐条搜索知识库
from pymilvus import MilvusClient
client = MilvusClient(
uri="http://your_milvus_host:19530",
token="your_token",
db_name="default"
)
search_params = {"metric_type": "COSINE", "params": {"radius": 0.0}}
注意radius=0.0——我们要看到每条问题和知识库的最高相似度,不管多低都返回,不能设过滤阈值。
pathname = "d:\\q\\覆盖率分析.txt"
file = open(pathname, "w", encoding="utf-8")
for item in hotline_questions:
question_text = item["Question"]
question_vector = vectorize_text(question_text)
# 在知识库中搜索最相似的10条
res = client.search(
collection_name="knowledge_base",
data=[question_vector],
limit=10,
output_fields=["uid", "Question"],
search_params=search_params
)
if res[0].__len__() > 0:
best_match = res[0][0]
file.write(
question_text + "\t" +
str(best_match["entity"]["uid"]) + "\t" +
str(best_match["distance"]) + "\t" +
best_match["entity"]["Question"] + "\n"
)
else:
file.write(question_text + "\t无匹配\t0.0\t\n")
file.close()
第三步:统计覆盖率
输出文件每行四列:热线问题\t知识库uid\t最高相似度\t知识库问题
按相似度分三档统计:
| 区间 | 含义 | 建议动作 |
|---|---|---|
| ≥ 0.85 | 高度覆盖 | 直接可用 |
| 0.6 ~ 0.85 | 部分覆盖 | 需补充精确答案 |
| < 0.6 | 知识盲区 | 需新建知识条目 |
实际跑出来,覆盖率大致分布(脱敏示意):
高度覆盖(≥0.85):约 60%
部分覆盖(0.6~0.85):约 25%
知识盲区(<0.6):约 15%
这15%就是知识库的盲区。拿着这份清单去找业务部门补知识,比拍脑袋"差不多够了吧"靠谱得多。
进阶:按业务分类统计覆盖率
盲区不是均匀分布的。"社保缴费"这种高频话题,知识库覆盖率高;"特殊工种提前退休"这种低频但专业的问题,覆盖率可能很低。
把热线问题先分类(参保、缴费、社保卡、养老、医疗……),再按类别统计覆盖率,就知道哪个业务线的知识库最缺:
参保类:覆盖率 82% ← 不错
缴费类:覆盖率 78% ← 还行
社保卡:覆盖率 65% ← 得补了
特殊工种:覆盖率 31% ← 严重不足,优先补
分类可以用LLM做,一句话搞定:
def classify_question(question_text):
prompt = (
question_text +
" 分类为参保、缴费、社保卡、养老保险、失业保险、医疗保险、" +
"工伤保险、生育保险、人事、就业培训、就业、社保档案、其他档案、" +
"其他之间那一类问题,只需返回分类的那几个字"
)
response = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": prompt}],
stream=False
)
return response.choices[0].message.content
分类成本很低——一条问题一次LLM调用,几厘钱。
关键参数
| 参数 | 值 | 为什么 |
|---|---|---|
radius | 0.0 | 不设过滤,全部返回,需要看到每条的真实相似度 |
limit | 10 | 只看Top-10,取最高的那个distance作为覆盖率指标 |
metric_type | COSINE | 余弦相似度,和去重时的标准一致 |
| Embedding | bge-m3 | 和知识库入库时的模型必须一致,否则向量空间不同 |
踩坑
1. Embedding模型必须和知识库一致
知识库入库时用的bge-m3,检索时也必须用bge-m3。换成别的模型(比如bge-large-zh-v1.5),向量空间不同,相似度没有可比性。我在换模型时就踩过——换了之后覆盖率从60%跳到85%,不是知识库变好了,是两套模型的相似度标尺不一样。
2. radius=0.0不是所有Milvus版本都支持
早期版本radius不支持0.0,会报错。升到2.4+就行了。
3. limit别设太小
有些问题的最佳匹配可能不在Top-3里。设10比较稳妥,既能看到最好的匹配,又不会因为排序噪声误导判断。
总结
- 知识库覆盖率可以量化:把真实问题逐条向量化,搜知识库,看最高相似度
- 分三档统计(≥0.85 / 0.6~0.85 / <0.6),盲区一目了然
- 按业务分类统计覆盖率,知道该补哪个方向的知识
- 全程Embedding用本地Ollama,唯一花钱的是分类那一步LLM调用,成本忽略不计
2000

被折叠的 条评论
为什么被折叠?



