
我把公司 3000 份内部文档喂给了 AI:用 RAG + 重排序搭建"问不倒"的企业知识库
从“翻不到”到“问不倒”,一个周末搭完,效果炸裂。
一、背景:我们公司不缺文档,缺的是“找到文档的能力”
我们是一家中等规模的科技公司,飞书、Confluence、GitLab Wiki、腾讯文档、本地 Markdown……五年下来,零零散散攒了 3000 多份内部文档。
覆盖范围挺全:
- 产品需求文档(PRD)
- 技术方案设计
- 线上故障复盘报告
- 数据库 ER 图与字段说明
- 运维部署手册
- 新人 onboarding 指南
- 商务报价与合同模板
但问题是——没人能找到它们。
新人来了问“订单状态机在哪改”,老员工甩过去一个 Confluence 链接,结果链接早失效了。搜索框输入“订单状态”,出来 200 条结果,前 20 条全是无关的会议纪要。最后大家选择“问身边人”,而那个“身边人”每天要被拉去回答 20 遍同样的问题。
于是,我决定做一个试验:把 3000 份文档全喂给 AI,做一个真正“问不倒”的企业知识库。
二、技术选型:为什么是 RAG + 重排序,而不是微调?
一开始团队内部有两种声音:
- 微调开源大模型——把文档当训练数据,微调一个专属模型。
- RAG(检索增强生成)——保持模型不变,动态检索文档作为上下文。
我选了后者,理由很简单:
| 对比维度 | 微调 | RAG |
|---|---|---|
| 知识更新 | 重新训练,耗时耗钱 | 文档更新后立刻生效 |
| 幻觉问题 | 仍会编造 | 引用原始文档,可溯源 |
| 权限控制 | 难以精细 | 可按检索范围控制 |
| 成本 | 高(GPU 训练) | 低(仅推理 + 向量存储) |
| 可解释性 | 黑盒 | 白盒(展示检索到的文档片段) |
对于企业知识库,可溯源和实时更新是刚需。RAG 天然适合。
但纯 RAG 有个致命问题:检索不准,生成全废。
我测试过纯向量检索(OpenAI embedding + Faiss),Top-5 准确率(Hit Rate)大概只有 68%。也就是说,近三分之一的问题,AI 看到的上下文是错的或无关的。
于是引入第二道防线:重排序(Re-ranking)。
三、整体架构:一条从问题到答案的流水线
用户提问
↓
【Query 改写】—— 处理模糊问法,补充上下文
↓
【向量检索】—— 从 3000 份文档中召回 Top-50 候选片段
↓
【重排序】—— 用交叉编码器精排,选出 Top-5 最相关片段
↓
【Prompt 组装】—— 将问题和检索片段拼成上下文
↓
【LLM 生成】—— 大模型基于给定上下文生成答案
↓
【答案 + 引用来源】返回用户
核心思想是粗筛 + 精排:
- 向量检索负责“快”,用海量文档中快速捞出一批候选。
- 重排序负责“准”,用更强大的模型对候选做精细排序。
四、数据预处理:把 3000 份文档“切碎”成可检索的颗粒
4.1 格式统一
3000 份文档格式各异:
- Confluence 导出为 HTML
- 飞书文档导出为 PDF
- Markdown 文件直接读取
- Word/Excel 转成文本
我用 Python 统一抽取正文文本,保留标题层级和段落结构。这一步没什么黑科技,就是 python-docx、PyPDF2、markdown 库轮番上阵。
4.2 文档分块(Chunking)
这是 RAG 中最容易被低估的一步。
分块太小:丢失上下文,比如“该接口需要鉴权”就不知道“该接口”是哪个。
分块太大:向量检索时语义被稀释,且容易超出 LLM 上下文窗口。
我的策略是多级分块:
- 按标题层级切分(H1/H2/H3),保证每个 chunk 有完整的章节语义。
- 对超长 chunk(>800 tokens)再按段落切分,但保留父标题作为元数据。
- 设置重叠区(overlap 为 200 tokens),避免关键信息被截断。
最终 3000 份文档被切成了 约 4.7 万个 chunk,平均每个 chunk 约 650 tokens。
4.3 元数据注入
每个 chunk 附带元数据:
{
"chunk_id": "doc_1234_chunk_56",
"source_file": "订单系统技术方案_v3.2.docx",
"doc_type": "技术方案",
"department": "交易平台部",
"last_updated": "2026-03-15",
"title": "订单状态机设计",
"headings": ["交易系统", "订单状态机", "状态流转规则"]
}
元数据有两个作用:
- 检索时可按部门/文档类型过滤(权限控制)
- 生成答案时作为引用来源展示给用户
五、向量检索:选对 Embedding 模型,事半功倍
5.1 模型选型测试
我测试了三种 embedding 模型:
text-embedding-ada-002(OpenAI,闭源)BAAI/bge-large-zh-v1.5(开源,中文优化)sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2(轻量级)
测试集是我手工标注的 200 个问答对(来自真实员工提问)。
| 模型 | Hit Rate@10 | 平均延迟(ms) |
|---|---|---|
| ada-002 | 82.3% | 120 |
| bge-large-zh-v1.5 | 87.1% | 380 |
| MiniLM | 71.5% | 45 |
最终选了 bge-large-zh-v1.5——准确率最高,且完全开源可控。
5.2 向量存储
用 Faiss 做索引(IVF1024, PQ64),4.7 万条向量在 CPU 上检索耗时 < 50ms。
存储结构:
索引: Flat IP (内积相似度)
向量维度: 1024 (bge-large-zh)
数据量: 47,231 条
内存占用: ~190 MB
六、重排序:让“最相关”真正浮出水面
向量检索有一个天然缺陷:它用余弦相似度衡量全局语义,但“语义相似”不等于“问答相关”。
举例:
- 用户问:“订单超时后怎么自动取消?”
- 向量检索可能召回一篇讲“超时重试机制”的文档(语义相似),而不是“订单状态机”那篇(才是真正回答问题的)。
重排序(Re-ranking) 解决的就是这个问题。
6.1 原理
向量检索是用双编码器(Bi-encoder)将 Query 和 Document 分别编码成向量,再算相似度——快,但 Query 和 Document 之间没有交互。
重排序用交叉编码器(Cross-encoder):把 Query 和 Document 拼接在一起,输入一个 Transformer 模型,让模型在注意力层直接做交互,输出一个相关性分数。
代价是慢——但没关系,我们只对 Top-50 做重排,而不是对全部 4.7 万条。
6.2 重排序模型选择
测试了:
BAAI/bge-reranker-v2-m3(开源,多语言)cross-encoder/ms-marco-MiniLM-L-6-v2(轻量,英文优化)Cohere rerank(商业 API)
| 模型 | Hit Rate@5(重排后) | 延迟(50 条) |
|---|---|---|
| 无重排(仅向量) | 68.2% | - |
| ms-marco-MiniLM | 82.5% | 320ms |
| bge-reranker-v2-m3 | 89.3% | 680ms |
| Cohere rerank | 90.1% | 450ms(API) |
最终选了 bge-reranker-v2-m3——开源、中文支持好、准确率接近商业 API。
6.3 重排序带来的实际改善
上线后统计了 500 次真实问答:
- Top-1 准确率(用户点赞或直接采用答案):从 54% → 79%
- 平均引用文档相关度:从 3.2/5 → 4.6/5(用户评分)
- “再问一次”率:从 37% → 11%
七、Query 改写:别让用户为“问法不好”买单
真实用户的提问往往是这样的:
- “那个订单的 bug 修了没?”(指哪个 bug?)
- “上周那个故障怎么处理的?”(哪个故障?)
- “支付回调咋配置?”(哪个环境?什么业务?)
直接用这些原话去检索,效果很差。
我加了一个 Query 改写层,用 LLM 将用户问题扩展成多个检索变体:
原始问题:支付回调咋配置
改写后:
1. 支付回调配置方法(主查询)
2. 如何配置支付回调接口(同义改写)
3. 支付回调 URL 设置步骤(关键词拓展)
4. 支付结果通知回调配置(领域术语)
然后对 4 个变体分别做向量检索,合并结果后再去重,输入重排序。
这一步额外增加了约 200ms 延迟,但 Hit Rate@50 从 82% 提到了 91%。
八、Prompt 工程:让模型“有据可依”地回答问题
检索到的内容再好,如果 Prompt 设计不当,模型要么忽略文档,要么自己瞎编。
我的 Prompt 模板核心原则:
你是一个企业知识助手。请严格基于以下文档片段回答用户问题。
【文档片段】(按相关性排序):
--- 片段 1 ---
{chunk_1}(来源:{source_1})
--- 片段 2 ---
{chunk_2}(来源:{source_2})
...
【用户问题】:{query}
规则:
1. 如果文档片段中没有相关信息,请直接说“知识库中暂无相关内容”,不要编造。
2. 如果信息不完整,说明哪些信息缺失。
3. 引用时标注来源文档名称。
4. 回答要简洁、结构化,面向内部同事。
【回答】:
关键点:
- 强制引用:每个论断都必须能在文档片段中找到对应依据。
- 允许拒绝回答:防止幻觉,宁可说“不知道”也不要乱说。
- 来源标注:建立用户对 AI 的信任,也方便二次核实。
九、效果数据:从“没人用”到“每天 200+ 查询”
上线后两周的真实数据(公司内部约 800 名员工):
| 指标 | 数据 |
|---|---|
| 日均查询量 | 217 次 |
| 平均首次回答时间 | 2.3 秒 |
| 答案采纳率(用户点击“有用”) | 83.6% |
| 需要二次人工介入的比例 | 12.4% |
| 覆盖的文档利用率 | 从 <5% 提升到 67% |
最让我意外的是——新员工入职培训周期从 2 周缩短到了 5 天。因为他们可以直接问 AI:“XX 系统的测试环境数据库密码在哪看?”“发布流程第一步做什么?”每一个问题都有文档引用,不用再求人。
十、踩过的坑与解决方案
坑 1:文档中有大量表格和图表
向量检索处理不了表格结构。
解决:对表格单独抽取,用 Markdown 表格格式保留行列结构,并在 chunk 中增加“表格说明”字段。同时用 OCR 提取图片中的文字(仅限流程图和架构图)。
坑 2:同义词与缩写
“OMS”和“订单管理系统”、“交易中心”在文档中混用。
解决:在预处理阶段建立了一个企业术语词典(约 200 条),在 chunk 中统一替换为“术语(别名)”格式。同时在 Query 改写中也加入术语映射。
坑 3:权限隔离
有些文档只有特定部门能看。
解决:检索阶段注入用户身份(部门 + 角色),根据元数据过滤 chunk。实际实现是在向量检索前加一层 metadata_filter,只检索当前用户有权限的文档。
坑 4:文档版本混乱
同一份文档有多个版本,旧版本没归档。
解决:预处理时按“最新版本优先”去重,对相同标题和相似内容的文档只保留最新一份。同时对过期文档单独建一个“归档索引”,只有管理员可查。
十一、成本与资源
- GPU 服务器:1 台 A10G(24GB),做 embedding 和重排序推理
- LLM 推理:调用内部部署的 Qwen2.5-32B(4-bit 量化)
- 向量存储:内存 190MB,磁盘 2.3GB(含元数据)
- 总开发周期:2 人 × 5 天(含数据清洗 + 模型选型测试)
- 月度运行成本:约 ¥1,200(含 GPU 租用 + API 调用)
十二、未来优化方向
当前系统已经稳定运行,但我计划继续做三件事:
- 多模态支持:让 AI 能直接看懂架构图、流程图、时序图。
- 主动问答:当文档更新时,自动生成“变更摘要”并推送给相关订阅者。
- 反馈闭环:用户点击“答案没用”时,自动记录并触发人工审核,优化检索策略。
十三、如果你也想做,这是最简启动路径
- Day 1:挑出 200 份核心文档,切成 chunk,用开源 embedding 建一个简单 RAG。
- Day 2:接一个开源 LLM(Qwen / ChatGLM),写出最简 Prompt,跑通 end-to-end。
- Day 3:引入重排序,对比有无重排序的效果差异(你会被震撼到)。
- Day 4-5:加 Query 改写 + 元数据过滤,打磨用户体验。
- 之后:逐步扩充文档库,迭代优化分块策略和模型选型。
不用一上来就追求完美。先跑通,再跑稳,最后跑快。
结语
3000 份文档,原来是一堆数字废墟。现在它们变成了一个“问不倒”的知识引擎。
这个项目的本质,不是“用 AI 替代人”,而是把组织沉淀下来的文字,重新变成流动的经验。那些写在文档里的最佳实践、故障教训、设计决策,不再沉睡在某个过时的链接里,而是随时准备回答下一个问题。
如果你的公司也有成堆的文档和无数重复的“找人问问”,不妨试试这条路。RAG + 重排序,门槛比想象中低,效果比预期中好。
毕竟,最好的知识管理系统,不是“存得好”,而是“问得响”。
推荐阅读:看我如何管理我的电子书籍
963

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



