企业知识库(本质是 RAG)这件事,最典型的翻车路径是:团队花大力气选大模型、选向量数据库,把文档一灌、问答跑通 demo 就宣布上线,结果真到业务里一问就胡说。问题几乎从来不出在模型,而出在文档和检索这两头没人管。这篇把最容易踩的坑和对应的做法讲清楚。
文档治理先行
"垃圾进垃圾出"在 RAG 场景下被放大得特别明显。模型再强,喂进去的文档本身是过期的、格式混乱的、版本冲突的,答案就一定是错的。很多人把 80% 的精力放在选模型和选向量库上,却没人管文档这一层。
正确做法是把文档当成一个需要持续治理的数据资产,而不是一次性导入。至少要解决三件事:格式清洗(扫描件、表格、PDF 里的脏字符)、版本管理(每次更新标记旧版过期)、责任到人(每个知识条目有负责人和更新机制)。没有持续运营,知识库上线三个月就烂掉。
分块与表格
分块是决定检索上限的一步,也是最容易被"固定字数切开"带偏的一步。把文档机械地按 500 字一段切开,会把一句话、一个表格、一个操作步骤从中间斩断,检索出来的片段语义不完整,模型只能拿着半截内容硬编。
分块要贴着文档结构走:以标题、段落、列表项为边界,每个块带上它所属的章节标题当上下文头。表格是重灾区——结构化内容向量化之后几乎失效,应该改写成自然语言描述再入库。下面是一个贴着结构分块的示意,核心是用标题切块而不是用字数切块:
def split_by_heading(markdown: str) -> list[dict]:
"""按标题切块,每个块带上所属章节标题作为上下文头。"""
chunks, current_title, current_body = [], "", []
for line in markdown.splitlines():
if line.startswith("#"):
if current_body:
chunks.append({"heading": current_title, "text": "\n".join(current_body)})
current_title, current_body = line.strip("# "), []
else:
current_body.append(line)
if current_body:
chunks.append({"heading": current_title, "text": "\n".join(current_body)})
return chunks
块大小通常落在 200 到 500 字之间,太大噪声多,太小信息不全。但没有万能值,最终要以评估集为准调。
混合检索与重排
纯向量检索的短板是对精确术语、编号、型号这类"字面匹配"很弱,而企业文档里偏偏全是这些。纯关键词检索又抓不住语义相近的换种说法。所以生产上基本是混合检索:向量检索加 BM25 关键词检索,两边各取一部分再合并排序。
重排是性价比最高的一步。召回阶段可以放宽多捞几条(怕漏),再用重排模型按相关性精排,把真正相关的前几条顶上去(怕错)。这一步通常只增加百毫秒延迟,却能把"答非所问"大幅压下去。一个简化的合并思路是权重融合,向量分和关键词分各自归一化后加权:
def fuse(vector_hits, bm25_hits, w_vector=0.6, w_bm25=0.4):
scores = {}
for hit in vector_hits:
scores[hit["id"]] = scores.get(hit["id"], 0) + w_vector * hit["score"]
for hit in bm25_hits:
scores[hit["id"]] = scores.get(hit["id"], 0) + w_bm25 * hit["score"]
return sorted(scores.items(), key=lambda kv: kv[1], reverse=True)
权重怎么定别拍脑袋,靠评估集跑出来。
查询改写与场景边界
不同岗位的提问习惯差别很大:研发会问"SDK 版本兼容性",销售会问"这个软件能和钉钉一起用吗"。直接把口语化的问题丢进检索,往往召回不到专业文档。可以在检索前加一步查询改写,把口语转成接近文档语料的标准表达,或做多意图拆分再分别检索。这一步很轻,但对召回率的提升直接。
还有一层边界要先想清楚:RAG 擅长的是"有明确答案的事实性问题",不适合开放式讨论、创意写作、或者需要跨多源深度推理的复杂决策。把知识库当成"万能搜索"是第一个也是最常见的误区——场景边界不清,用户期望就会失控,答不出来不是技术问题,是当初选错了场景。
权限与更新
知识库的权限是 demo 阶段几乎没人做、生产阶段最容易出事的一环。员工不该看到的知识不能被检索出来——权限要在检索时过滤,而不是生成答案之后再拦。正确做法是从源系统继承权限:文档放在受限的目录里,切出来的块就带着同样的限制,检索时先按权限过滤候选集。
更新机制同样不能省。文档更新后,旧版本的向量还在库里,问答就可能返回过期结论。要么做版本指纹、更新即重新索引,要么至少标记过期并定期清理。
评估集
没有评估集,所有优化都是盲调。上线前先收集 50 到 100 个真实用户问题,人工标注正确答案和应召回的文档片段,之后每次改分块策略、调权重、换模型,都自动跑一遍看召回率和准确率。答案再漂亮,忠实度不过关——也就是引用不到源文档——就是幻觉,不能上线。
最后一条是让模型"承认不知道"。在提示词里强制"只依据检索到的内容回答,不足时明确说知识库里没有",并把来源片段标出来让用户核对。这比任何检索优化都更能保住信任,因为它把"一本正经地编"这个最大雷点直接关掉了。