1. 知识库是什么:先破除三个误解
1.1 定义
企业知识库 ,是对企业内部分散的知识进行采集、治理、索引 ,并以搜索、问答、API 等形式提供给员工和系统消费的软件系统。
它的成熟度通常经历三个阶段:
| 阶段 | 形态 | 用户价值 | 典型技术 |
|---|---|---|---|
| L1 | 文档存储 | 能找到文件 | 网盘、Wiki、DMS |
| L2 | 全文检索 | 能搜到片段 | Elasticsearch、BM25 |
| L3 | 知识服务 | 能直接回答问题 | RAG、Agent、图谱 |
企业级知识库的目标,是从 L1/L2 走到 L3,并且在权限、治理、可运营性上达到企业标准。
1.2 三个常见误解
误解一:知识库 = 文档管理系统。
不是。文档管理系统解决"存和管",知识库解决"找和用"。一个装满 PDF 的网盘不是知识库。
误解二:知识库 = 向量数据库。
不是。向量库只是索引层的一种存储。知识库还包含接入、解析、治理、权限、服务、评估等大量组件。把向量库当知识库,等于把 MySQL 当业务系统。
误解三:接了大模型就有知识库。
不是。大模型解决"生成",不解决"知识从哪来、是否准确、能否追溯、权限是否合规"。没有检索和治理,大模型只会更流畅地编造。
1.3 知识库在整个系统中的位置
flowchart LR U"员工 / 业务系统 / Agent" --> KB"企业知识库" KB --> S"搜索" KB --> QA"问答 / RAG" KB --> API"知识 API" S --> U QA --> U API --> U subgraph SRC"知识来源" D1"Wiki / 文档库" D2"工单 / CRM" D3"业务数据库" D4"代码仓库" D5"网页 / 共享盘" end SRC --> KB
知识库是知识的统一出口,而不是又一个数据孤岛。它的价值在于把分散在各系统里的知识,收敛成一致的、可追溯的、有权限的访问入口。
2. 核心概念速通
在进入架构之前,先统一几个术语。后面的所有设计都建立在这些概念之上。
| 概念 | 含义 | 为什么重要 |
|---|---|---|
| 文档(Document) | 知识的最小来源单位,如一份 PDF、一个网页 | 权限、版本、删除都以文档为单位 |
| 切块(Chunk) | 从文档切出的检索单元 | 切分质量直接决定召回质量 |
| 嵌入(Embedding) | 把文本映射为高维向量 | 让"语义相似"可计算 |
| 索引(Index) | 为快速检索建立的数据结构 | 全文索引、向量索引、结构化索引 |
| 召回(Recall) | 从索引中取出候选片段 | 召回不到,后面全白搭 |
| 重排(Rerank) | 对候选做精细排序 | 提升 Top-N 的准确率 |
| 上下文(Context) | 送入大模型的证据片段 | 决定答案是否"有据可依" |
| 引用(Citation) | 答案对应的原始出处 | 可信度和可审计性的基础 |
一个关键区分:
- 写路径(离线构建) :数据源 → 解析 → 切分 → 索引。关注正确性、时效性、可重建。
- 读路径(在线服务) :提问 → 召回 → 排序 → 生成。关注延迟、准确率、可解释。
两条路径必须共用同一套处理逻辑,否则评估结果和线上表现会严重脱节。
3. 整体架构:六层 + 横切能力
企业级知识库推荐采用分层架构,层与层之间通过稳定接口解耦。这样换模型、加数据源、换索引时,不会牵一发动全身。
flowchart TB subgraph L1"应用层" direction LR A1"企业搜索" A2"智能问答 / RAG" A3"Copilot / Agent" A4"开放 API" end subgraph L2"服务层" direction LR S1"查询理解" S2"检索路由" S3"召回融合" S4"Rerank" S5"上下文组装" S6"生成与引用" end subgraph L3"索引层" direction LR I1"全文索引 BM25" I2"向量索引" I3"结构化索引" I4"图谱(可选)" end subgraph L4"处理层" direction LR P1"解析" P2"清洗" P3"切分" P4"富化 / 权限标注" end subgraph L5"接入层" direction LR D1"文档库 / Wiki" D2"工单 / CRM" D3"数据库" D4"代码仓库" D5"网页 / 共享盘" end subgraph X"横切能力" direction LR X1"身份与权限" X2"元数据标准" X3"审计" X4"监控与评测" end L5 --> L4 L4 --> L3 L2 --> L3 L1 --> L2 X1 -.-> L2 X1 -.-> L3 X2 -.-> L4 X3 -.-> L2 X4 -.-> L2
各层职责
| 层 | 职责 | 关键设计 |
|---|---|---|
| 应用层 | 面向用户/系统的产品形态 | 搜索、问答、API、Copilot |
| 服务层 | 查询理解、召回、排序、生成 | 可配置、可观测、可降级 |
| 索引层 | 多种索引并存 | 支持混合检索与索引重建 |
| 处理层 | 解析、清洗、切分、富化 | 离线在线同源 |
| 接入层 | 连接各数据源 | 增量同步、幂等、删除同步 |
| 横切 | 权限、元数据、审计、监控 | 从第一天就设计进每一层 |
架构建议:横切的三件事(身份权限、元数据标准、审计)是事后最难补的。不要等到"后面再加"。
4. 工作流程:写路径、读路径、运维闭环
知识库本质上是三条流水线。绝大多数"不好用"的问题,出在第一和第三条,而不是检索算法。
4.1 离线构建(写路径)
flowchart LR A"数据源" --> B"增量采集" B --> C"文档解析" C --> D"清洗去噪" D --> E"结构化切分" E --> F"富化\
元数据 · 权限 · 实体" F --> G"Embedding" G --> H"写入索引" H --> I"蓝绿切换 / 发布"
各阶段要点:
- 采集 :支持全量与增量。增量以
updated_at游标或 CDC 为准,必须幂等。 - 解析:PDF、Word、HTML、表格、扫描件分别处理。版面结构(标题层级、表格)要尽量保留。
- 清洗:去页眉页脚、导航、重复模板、乱码。洗太狠会丢语义,洗太轻会污染召回。
- 切分 :优先按标题层级切,其次按段落,最后才用定长滑窗。这是影响效果最大的一步。
- 富化:为每个 chunk 打上来源、部门、密级、有效期、权限标识。元数据决定了后续能否过滤。
- 建索引:同步写入全文索引、向量索引、结构化字段。可选图谱。
- 发布 :通过别名/版本做原子切换,避免用户查到"更新到一半"的数据。
4.2 在线服务(读路径)
flowchart TD Q"用户提问" --> U"查询理解\
改写 · 指代消解 · 多轮补全" U --> F"ACL 与时效条件下推" F --> R{"检索路由"} R -->|"精确术语 / 编号"| K"全文 + 结构化检索" R -->|"语义描述"| V"向量召回" R -->|"实体关系"| G"图谱查询" K --> M"融合去重\
RRF" V --> M G --> M M --> RR"Rerank 精排" RR --> CUT"截断到上下文预算" CUT --> CTX"上下文组装\
去冗余 · 附来源" CTX --> LLM"生成答案" CTX --> RAW"直接返回片段" LLM --> OUT"答案 + 引用" RAW --> OUT
请求时序如下:
sequenceDiagram autonumber participant U as 用户 participant Q as 查询服务 participant RT as 路由 participant IDX as 索引层 participant RR as Rerank participant LLM as 生成模型 U->>Q: 提问 Q->>Q: 改写 / 补全 / 意图识别 Q->>RT: 规范化查询 + 用户身份 RT->>IDX: 并行召回(带 ACL 过滤) IDX-->>RT: 多路候选集 RT->>RR: 融合去重后精排 RR-->>Q: Top-N 证据 Q->>LLM: 问题 + 证据 + 引用要求 LLM-->>U: 答案 + 出处
读路径的三个原则:
- 权限在召回阶段生效,不能事后再筛。
- 召回宁多勿少,排序宁精勿滥。召回和排序是两个目标,分开优化。
- 没有证据就不回答。拒答比编造更有价值。
4.3 运维闭环
flowchart LR A"线上查询日志" --> B"bad case 归集" B --> C{"归因"} C -->|"没召回"| D"补数据 / 调切分" C -->|"召回错"| E"调权重 / 换 Embedding" C -->|"排序差"| F"调 Rerank" C -->|"答非所问"| G"调 Prompt / 拒答策略" C -->|"内容过期"| H"刷新数据源 / 下线" D --> I"更新索引" E --> I F --> I G --> I H --> I I --> J"黄金评测集回归" J --> A
运营闭环是整个系统能否长期存活的关键。没有评测集的团队,所有优化都只能靠"感觉好像好一点"。
5. 技术选型与技术对比
5.1 第一个问题:一定要用向量库吗?
不必须。 向量检索只解决"语义相似"这一类问题。知识库的真实查询是混合的:
| 检索方式 | 擅长 | 不擅长 | 典型实现 |
|---|---|---|---|
| 全文 / BM25 | 精确术语、代码、编号、法规条款 | 同义表达、口语化提问 | Elasticsearch、OpenSearch、SQLite FTS5 |
| 结构化查询 | 元数据过滤(时间/作者/部门/状态) | 非结构化长文本 | SQL、图查询 |
| 知识图谱 | 实体关系推理 | 模糊长文本召回 | Neo4j、RDF |
| 向量检索 | 语义模糊匹配、同义改写、跨语言 | 精确 ID、否定词、数字比较 | FAISS、pgvector、Milvus |
| 混合检索 | 真实企业场景的绝大多数情况 | 增加复杂度 | BM25 + 向量 + Rerank |
结论 :企业级场景默认走混合检索。纯向量在精确匹配上会输给 BM25,纯关键词在同义表达上会输给向量,两者互补。
5.2 检索方式决策树
flowchart TD A"这条查询主要想解决什么?" --> B{"是否包含<br/>精确编号 / 代码 / 条款号?"} B -->|"是"| C"全文检索 / 结构化查询" B -->|"否"| D{"用户会用<br/>不同措辞表达吗?"} D -->|"是"| E"向量检索" D -->|"否"| F"全文检索" C --> G"与向量结果融合 + Rerank" E --> G F --> G G --> H"统一返回 Top-N + 引用"
5.3 向量存储:库 vs 数据库
一个常见误区:做语义检索 ≠ 必须部署向量数据库。
| 方案 | 适合规模 | 运维成本 | 过滤能力 | 适用场景 |
|---|---|---|---|---|
| numpy 暴力检索 | < 10 万 | 极低 | 强(自己写) | 原型、单机小知识库 |
| FAISS | 10 万 -- 千万 | 低(进程内库) | 弱 | 离线批处理、单机服务 |
| sqlite-vec | < 百万 | 极低 | 强 | 桌面应用、边缘部署 |
| pgvector | 百万 -- 千万 | 低(复用 PG) | 强 | 已有 PostgreSQL 的企业 |
| Qdrant | 千万级 | 中 | 强 | 需要独立服务、过滤复杂 |
| Milvus | 亿级 | 高 | 强 | 大规模、多租户 |
| Elasticsearch kNN | 千万级 | 中高 | 强 | 已有 ES,想统一检索 |
| Pinecone / 云服务 | 按需 | 极低 | 中 | 无运维团队、数据可出域 |
选型建议:
- 已有 PostgreSQL 且规模 < 1000 万 → 直接用
pgvector,少一个组件就是少一份运维。 - 已有 Elasticsearch → 优先用 ES 的 BM25 + kNN,统一检索栈。
- 需要独立、大规模、多租户 → Qdrant 或 Milvus。
- 数据不能出域 → 全私有化部署,排除 SaaS 向量库。
5.4 Embedding 模型选型
企业中文场景的常见候选:
| 模型 | 语言 | 部署 | 特点 | 适用 |
|---|---|---|---|---|
| BGE-M3 | 多语言(中英强) | 私有化 | 稠密 + 稀疏 + 多向量,长文本 | 通用首选 |
| BGE-large-zh-v1.5 | 中文 | 私有化 | 成熟稳定,生态好 | 纯中文、资源有限 |
| GTE 系列 | 中英 | 私有化 | 阿里系,中文表现好 | 国内企业 |
| Jina Embeddings v3 | 多语言 | 私有化/API | 长上下文、任务适配 | 多语言文档 |
| OpenAI text-embedding-3 | 多语言 | API | 效果好、免运维 | 数据可出域 |
选型维度:
- 语言:中文为主优先 BGE / GTE。
- 部署合规:金融、政务、医疗通常要求私有化。
- 维度与成本:维度越高,存储和计算成本越高。1024 维是常见平衡点。
- 长文本能力:BGE-M3 支持 8192 token,适合长文档。
- 可替换性 :必须假设未来会换模型,索引要能全量重建。
硬性要求:换 Embedding 模型意味着所有向量都要重算。设计时必须保留原始文档和切分结果,支持"从零重建索引"。
5.5 Rerank 模型
召回负责"别漏",Rerank 负责"排对"。常见选择:
| 方案 | 部署 | 特点 |
|---|---|---|
| BGE-Reranker-v2-m3 | 私有化 | 中英强,性价比高,首选 |
| Cohere Rerank | API | 效果好,数据需出域 |
| Jina Reranker | 私有化/API | 多语言 |
| Cross-Encoder 自训 | 私有化 | 需标注数据,适合垂直领域 |
经验值:Rerank 通常能把 Top-5 的准确率提升一个可观的比例,是 ROI 最高的组件之一。代价是增加几十到几百毫秒延迟,需要做超时降级。
5.6 文档解析
| 工具 | 能力 | 适合 |
|---|---|---|
| Apache Tika | 格式覆盖最广 | Office、邮件、通用格式 |
| Unstructured | 版面感知、元素划分 | 混合格式文档 |
| MinerU | PDF 版面/公式/表格强 | 学术、报告类 PDF |
| Docling | 版面分析 + 表格 | 企业 PDF |
| PyMuPDF / pdfplumber | 轻量、可控 | 结构化 PDF |
| 商业文档智能 | OCR、表格、手写 | 扫描件、票据 |
建议组合:Tika 处理 Office,MinerU/Docling 处理 PDF,OCR 兜底扫描件。解析质量差,后面所有环节都救不回来。
5.7 编排框架
| 方案 | 定位 | 优点 | 风险 |
|---|---|---|---|
| 自研服务层 | 生产系统 | 完全可控、易治理 | 工作量大 |
| LangChain | 开发库 | 生态丰富、灵活 | 抽象重、版本变动快 |
| LlamaIndex | 数据框架 | 索引/检索能力强 | 黑盒较多 |
| Dify | 应用平台 | 低代码、快速验证 | 深度定制受限 |
| RAGFlow | RAG 引擎 | 解析和切分较强 | 与现有系统集成需评估 |
| FastGPT | 知识库应用 | 国内生态、开箱即用 | 大规模治理需扩展 |
企业级建议 :用开源框架做原型验证,用自研服务层承载生产。框架适合"验证可行性",不适合"长期承载权限、审计、多租户"。
5.8 默认推荐组合(典型私有化企业)
| 组件 | 推荐 | 理由 |
|---|---|---|
| 解析 | Tika + MinerU | 格式覆盖 + PDF 质量 |
| 切分 | 自研(按标题层级) | 效果最好,可控 |
| 全文检索 | Elasticsearch / OpenSearch | 成熟、生态好 |
| 向量存储 | pgvector(中小)/ Qdrant、Milvus(大) | 按规模选择 |
| Embedding | BGE-M3 | 中英、长文本、可私有化 |
| Rerank | BGE-Reranker-v2-m3 | 性价比高 |
| 编排 | 自研服务层 | 权限、审计、多租户 |
| 生成模型 | 私有化 Qwen / DeepSeek 或合规 API | 按合规要求 |
6. 为什么这么选:决策依据
技术选型不是选"最强的",而是选"约束下最合适的"。推荐用 约束 → 候选 → 判定标准 → 结论 的方式记录决策(类似 ADR)。
6.1 为什么是混合检索,而不是纯向量
| 维度 | 纯向量 | 纯 BM25 | 混合 |
|---|---|---|---|
| 同义表达 | 强 | 弱 | 强 |
| 精确编号 | 弱 | 强 | 强 |
| 否定词 / 数字 | 弱 | 强 | 强 |
| 可解释性 | 弱 | 强 | 中 |
| 冷启动 | 需 Embedding | 直接可用 | 需 Embedding |
| 成本 | 高 | 低 | 中高 |
企业查询天然混合:既有"怎么申请报销"这种语义问题,也有"ERR-2041 是什么错误"这种精确问题。只做一种,必然有一类查询体验很差。
6.2 为什么中小规模优先 pgvector
- 复用现有 PostgreSQL,少一个运维组件;
- 支持事务、备份、监控,和业务数据同栈;
- HNSW 索引在千万级以内性能足够;
- 迁移成本低,规模上来再换 Milvus/Qdrant 也不难。
什么时候不该用:需要亿级向量、强多租户隔离、超高 QPS 时,再上专用向量库。
6.3 为什么一开始不上 GraphRAG
图谱能解决多跳推理("A 的负责人所在部门的制度有哪些"),但代价是:
- 实体抽取和关系构建成本高;
- 更新维护复杂;
- 对大多数"查文档、答问题"的场景,收益不明显。
建议 :先用"混合检索 + Rerank"覆盖 80% 场景,确认存在明确的多跳关系需求后,再引入图谱作为补充索引,而不是替代。
6.4 为什么保留原始文档和切分结果
这是为了三个能力:
- 索引可重建:换 Embedding、换向量库时可以全量重算。
- 可追溯:答案能回到原文。
- 可调试:切分效果不好时能复现和调整。
如果只存向量不存原文,系统就成了一次性的黑盒。
7. 实现逻辑:关键模块设计
以下用 Python 风格伪代码说明核心逻辑,重点在设计意图,不追求可直接运行。
7.1 数据接入:增量与幂等
python
def sync_source(source, cursor):
"""按更新时间增量拉取,失败可重试,重复执行结果一致。"""
docs = source.fetch(since=cursor.updated_at, limit=500)
for doc in docs:
content_hash = sha256(doc.content)
# 内容未变则跳过,避免无谓的重新索引
if store.get_hash(doc.id) == content_hash:
continue
pipeline.enqueue({
"doc_id": doc.id,
"source": source.name,
"content": doc.content,
"metadata": doc.metadata, # 部门、密级、作者、有效期
"acl": doc.acl, # 权限标识,必须随文档一起流转
"version": doc.version,
"deleted": doc.deleted, # 删除标记,必须同步
})
store.save_hash(doc.id, content_hash)
return docs[-1].updated_at if docs else cursor.updated_at
三个必须做到的点:
- 幂等:同一文档重复处理,结果一致。
- 删除同步:源系统删了,索引必须删(合规要求)。
- 权限随行:ACL 必须从源头带到每个 chunk。
7.2 切分:结构优先,父子关联
python
def chunk_document(doc, max_tokens=800, overlap=100):
"""
优先级:标题层级 > 段落 > 定长滑窗。
每个 chunk 保留父文档路径,便于回溯和上下文扩展。
"""
sections = split_by_heading(doc) # 按 H1/H2/H3 切
chunks = []
for sec in sections:
if estimate_tokens(sec.text) <= max_tokens:
chunks.append(make_chunk(sec, parent=doc))
else:
# 过长段落再按句子边界切,保留 overlap
for piece in sliding_window(sec.text, max_tokens, overlap):
chunks.append(make_chunk(piece, parent=doc, heading=sec.heading))
return chunks
为什么结构优先: 企业文档(制度、手册、API 文档)本身有清晰的标题层级,按结构切出的 chunk 语义完整,召回精度显著更高。定长切分会把一句话拦腰截断。
父子块(small-to-big)思路:用小块做检索(精度高),命中后返回其父段落做上下文(信息全)。这是工业界常用技巧。
7.3 富化与权限标注
python
def enrich(chunk, doc):
return {
**chunk,
"doc_id": doc.id,
"source": doc.source,
"title": doc.title,
"heading_path": chunk.heading_path, # 例:员工手册 > 报销 > 差旅
"department": doc.metadata.department,
"classification": doc.metadata.classification, # 密级
"effective_at": doc.metadata.effective_at,
"expires_at": doc.metadata.expires_at,
"acl": doc.acl, # 关键:权限随 chunk 走
"version": doc.version,
}
元数据不是可选项。 它决定了:
- 能不能按部门/密级过滤;
- 能不能按有效期剔除过期内容;
- 答案能不能给出准确的出处。
7.4 索引构建:多索引 + 蓝绿发布
flowchart LR A"原始文档 + 切分结果" --> B"处理管线" B --> C"索引 vN(蓝)" B --> D"索引 vN+1(绿)" E"别名 kb-live" -.-> C E -.-> D F"切换" --> E
python
def rebuild_index(docs):
new_index = create_index(f"kb-{next_version()}")
for chunk in process(docs):
new_index.add_text(chunk.id, chunk.text)
new_index.add_vector(chunk.id, embed(chunk.text))
new_index.add_metadata(chunk.id, chunk.metadata)
# 校验通过后原子切换别名,读流量无感迁移
new_index.verify(docs_count=len(docs))
alias_swap("kb-live", from_=current, to=new_index)
# 保留上一版本一段时间,便于回滚
schedule_cleanup(old_index, after="24h")
蓝绿发布解决三个问题:更新不中断、可回滚、可校验。没有它,任何一次全量重建都可能造成线上事故。
7.5 查询理解与路由
python
def understand(query, history=None, user=None):
q = normalize(query)
if history:
q = resolve_pronouns(q, history) # "它的有效期是多久?"
intent = classify_intent(q) # 精确查询 / 语义问答 / 关系查询
rewritten = expand_synonyms(q) # 可选:同义词扩展
return Query(
text=rewritten,
intent=intent,
acl_filter=build_acl_filter(user), # 权限条件随查询下推
time_filter=build_time_filter(user), # 只查有权且有效的内容
)
路由策略:
- 命中错误码、订单号、条款号等模式 → 走全文/结构化;
- 自然语言描述 → 走向量;
- 显式关系问题 → 走图谱(如有);
- 不确定时 → 全部并行召回,交给融合层。
7.6 混合召回与 RRF 融合
python
def hybrid_recall(query, top_k=50):
# 权限与时效条件在检索阶段就下推,而不是召回后再筛
filters = query.acl_filter & query.time_filter
bm25_hits = fulltext.search(query.text, top_k=top_k, filters=filters)
vec_hits = vector.search(embed(query.text), top_k=top_k, filters=filters)
return rrf_fuse([bm25_hits, vec_hits], k=60, top_n=30)
def rrf_fuse(result_lists, k=60, top_n=20):
"""Reciprocal Rank Fusion:只用名次,不用分数,天然规避量纲不一致。"""
scores = defaultdict(float)
for results in result_lists:
for rank, hit in enumerate(results, start=1):
scores[hit.id] += 1.0 / (k + rank)
return sorted(scores.items(), key=lambda x: -x[1])[:top_n]
为什么用 RRF: BM25 分数和向量相似度量纲完全不同,直接加权很难调。RRF 只看排名,简单、稳健、无需调参,是混合检索的默认融合方式。
7.7 Rerank 与上下文组装
python
def rerank_and_pack(query, candidates, token_budget=6000):
ranked = reranker.rank(query.text, candidates) # 精排
packed, used = [], 0
seen = set()
for hit in ranked:
if hit.id in seen:
continue
if hit.doc_id in seen_docs(packed): # 同一文档限制条数
continue
if used + hit.tokens > token_budget:
break
packed.append(hit)
used += hit.tokens
seen.add(hit.id)
return packed
组装原则:
- 去重,避免同一段内容重复喂给模型;
- 每份证据都带
source / title / heading_path,供引用; - 控制 token 预算,优先保留高分证据;
- 证据不足时明确拒答,而不是让模型硬编。
7.8 生成与引用
python
def answer(query, evidence):
if not evidence or max(e.score for e in evidence) < REFUSE_THRESHOLD:
return "未在知识库中找到足够依据,建议补充资料或转人工。"
ctx = "\n\n".join(
f"[{i+1}] {e.title} / {e.heading_path}\n{e.text}"
for i, e in enumerate(evidence)
)
result = llm.generate(
system="只能依据给定材料回答;每个结论必须标注引用编号;材料不足时明确说明。",
user=f"问题:{query.text}\n\n材料:\n{ctx}",
)
validate_citations(result) # 校验引用编号是否真实存在
return result
两个硬约束:
- 无证据不回答(拒答阈值可配置)。
- 引用必须真实存在,不能是模型编造的编号。
8. 核心关键点:决定成败的八件事
8.1 权限必须在检索阶段生效(P0)
这不是"最佳实践",是安全底线。
flowchart LR U"用户身份" --> A"构建 ACL 条件" A --> B"全文检索\
(带过滤)" A --> C"向量检索\
(带过滤)" B --> D"候选集" C --> D D --> E"Rerank / 生成"
错误做法:先召回全部,再在应用层筛掉无权文档。问题在于:
- 无权内容的元信息可能已经进入日志或缓存;
- 候选集被无关内容挤占,导致有效结果变少;
- 模型可能从上下文里"推断"出不该知道的信息。
正确做法 :把 ACL 作为过滤条件,下推到每一种索引的查询里。
8.2 切分质量 > 模型选择
同一套检索,切分方式不同,效果可能相差一倍。优先级:
- 按标题层级切(保留
heading_path) - 按段落 / 语义边界切
- 定长滑窗 + overlap(兜底)
8.3 元数据是隐形主角
- 权限、部门、密级 → 决定可见性;
- 有效期、版本 → 决定时效性;
- 来源、标题、路径 → 决定可追溯性。
事后补元数据极其痛苦,必须在接入和切分阶段就打全。
8.4 没有评测集就没有优化
建 20--50 条真实问题的黄金集,每条包含:问题、期望答案、期望来源。
指标建议:
| 指标 | 含义 |
|---|---|
| Recall@K | 相关片段是否进了候选 |
| MRR / nDCG | 排序质量 |
| Faithfulness | 答案是否忠于材料 |
| Context Precision / Recall | 上下文是否精准且完整 |
| 拒答率 | 不知道时是否老实说 |
| 引用准确率 | 引用是否指向真实来源 |
| P95 延迟 | 用户体验 |
每次改切分、换模型、调权重,都跑一遍回归。
8.5 离线与在线必须同源
清洗、切分、Embedding 的逻辑必须共用一套代码。否则:
- 离线评测结果无法代表线上;
- 调试时无法复现问题;
- 版本升级时容易不一致。
8.6 拒答比编造更重要
企业场景里,"不知道"是可接受的,"一本正经地胡说"是不可接受的。做法:
- 设置相似度/重排分数阈值;
- Prompt 明确要求"材料不足时说明";
- 输出阶段校验引用是否真实存在。
8.7 可观测性
记录每次查询的完整链路:
query_id | 原始问题 | 改写后 | 路由 | 各路召回 | 融合结果 | 重排结果 | 最终证据 | 答案 | 延迟 | 用户反馈
没有这条链路,bad case 只能靠猜。
8.8 内容治理是长期工程
- 每个知识域有 owner;
- 权威源唯一,避免多版本并存;
- 有效期 + 复审周期;
- 删除同步;
- 内容质量指标(重复率、过期率、矛盾率)。
9. 注意事项与常见坑
| # | 坑 | 后果 | 对策 |
|---|---|---|---|
| 1 | 权限没做或事后过滤 | 安全事故、被迫回滚 | ACL 下推到检索 |
| 2 | 数据源一锅端 | 召回全是噪音 | 建立准入标准,先治理再入库 |
| 3 | 没有评测集 | 无法判断优化是否有效 | 先建黄金集再优化 |
| 4 | 只上线不运营 | 内容过期,使用率归零 | 运营闭环 + owner 机制 |
| 5 | 过度工程 | 维护成本压垮团队 | 按规模选型,能用 pgvector 不上 Milvus |
| 6 | 只做向量 | 精确查询体验差 | 混合检索 |
| 7 | 切分一刀切 | 语义断裂或噪声过多 | 结构优先 + 父子块 |
| 8 | 忽略删除同步 | 答出已作废内容,合规风险 | 删除事件 + tombstone |
| 9 | 一次全量重建 | 线上抖动、无法回滚 | 蓝绿索引 + 别名切换 |
| 10 | 只看准确率不看拒答 | 模型过度自信 | 阈值 + 引用校验 |
| 11 | 忽略解析质量 | 表格、公式、扫描件全部丢失 | 按格式分而治之 |
| 12 | 权限只做文档级 | 文档内敏感段落泄露 | 段落级权限 / 脱敏 |
合规相关
- 数据不出域:金融、政务、医疗通常要求私有化部署。
- 审计留痕:谁查了什么、返回了什么,需可追溯。
- 删除权:用户/业务要求删除时,索引、缓存、日志都要处理。
- 最小权限:默认不可见,按需授权。
10. 分阶段落地路线
不要一上来就做"全公司知识平台"。企业级项目的典型死法是"大而全地启动,然后没人用"。
| 阶段 | 目标 | 关键交付 | 成功标准 |
|---|---|---|---|
| 0. 选场景 | 找高频、高价值、内容相对干净的场景 | 场景定义、成功指标、数据源清单 | 有明确的使用方和痛点 |
| 1. 最小闭环 | 单数据源、全文检索、返回片段带引用 | 可用的搜索/问答 | 能被真实用户使用 |
| 2. 质量提升 | 混合检索 + Rerank + 评测集 + 权限 | 效果可度量 | 黄金集指标达标 |
| 3. 规模扩展 | 多数据源、增量更新、多租户、审计 | 可推广的平台能力 | 能接入第二个场景 |
| 4. 运营体系 | 贡献机制、复审流程、bad case 闭环 | 长期运营机制 | 使用率与满意度稳定 |
flowchart LR A"阶段0\
选场景" --> B"阶段1\
最小闭环" B --> C"阶段2\
质量与权限" C --> D"阶段3\
规模与治理" D --> E"阶段4\
持续运营" C -.->|"指标不达标"| B E -.->|"bad case 回流"| C
推荐的首选场景:客服问答、运维手册、研发文档、HR 制度。它们的共同点是:查询高频、答案相对确定、内容有明确 owner。
11. 自建还是采购
| 层次 | 建议 | 理由 |
|---|---|---|
| 基础设施(解析、索引、向量库、Rerank) | 采购或开源 | 通用能力,自研不划算 |
| 领域层(接入、切分、权限模型、评测集、Prompt) | 自建 | 这是差异化所在 |
| 产品层(搜索/问答界面) | 视团队而定 | 无专职团队时用成熟平台起步 |
一句话:通用能力买,领域能力建。
12. 附录
12.1 上线检查清单
- 数据源清单、owner、更新频率已明确
- 增量同步与删除同步已验证
- 解析覆盖所有主要格式(含扫描件兜底)
- 切分策略经过评测验证
- 每个 chunk 带完整元数据与权限标识
- 权限在检索阶段生效,且有越权测试
- 全文 + 向量混合检索已启用
- Rerank 有超时降级
- 黄金评测集已建立并通过回归
- 引用可追溯到原文与版本
- 拒答阈值已配置
- 索引蓝绿切换与回滚演练通过
- 审计日志可用
- 监控告警(延迟、错误率、召回量)已配置
12.2 术语表
| 术语 | 说明 |
|---|---|
| RAG | Retrieval-Augmented Generation,检索增强生成 |
| BM25 | 经典全文检索排序算法 |
| Embedding | 文本的向量表示 |
| Chunk | 文档切分后的检索单元 |
| RRF | Reciprocal Rank Fusion,多路结果融合算法 |
| Rerank | 对候选结果做精细重排 |
| ACL | Access Control List,访问控制列表 |
| CDC | Change Data Capture,变更数据捕获 |
| 蓝绿索引 | 新旧索引并存、原子切换的发布方式 |
12.3 参考方向
- 检索:BM25、稠密/稀疏混合检索、RRF
- 模型:BGE 系列、GTE 系列、Rerank 模型
- 评估:RAGAS 等 RAG 评测框架
- 解析:Apache Tika、Unstructured、MinerU、Docling
- 存储:pgvector、Qdrant、Milvus、Elasticsearch
结语
企业级知识库的本质,不是"把文档变成向量",而是把分散的知识变成可信、可控、可运营的服务。
如果只能记住三句话:
- 权限和治理是 P0,检索算法是 P2。
- 混合检索 + 好的切分 + Rerank,能覆盖绝大多数场景。
- 知识库是产品,不是项目------上线只是运营的开始。
先把一个场景做深、做透、做出可度量的效果,再横向复制。这比一开始就搭一个"大而全的平台"更容易成功。