设计题 01:企业知识库问答(RAG)
权重:★★★ 形式:45 分钟系统设计轮
素材来源:Own — HKGAI 1823 香港政府聊天机器人、HKJC Race Bot 的知识库检索、wiener_project的混合检索
相关知识点:03-rag/(全 6 篇)、05-agent-evaluation/rag-eval.md、06-agent-infra/latency-cost.md
1. 题干#
「我们有个客户,内部有几万份文档 —— 制度、流程、产品手册、会议纪要都有。员工现在靠搜索和问同事找信息,效率很低。他们想做一个内部问答系统,员工用自然语言提问,系统从文档里找答案。你来做,怎么设计?」
注意这个题干有多模糊:没说文档格式、没说多少人用、没说能不能出错、没说更新频率、没说要不要权限。这不是题干写得差,这是刻意的。 面试官在看你什么时候发现信息不足。
2. 第一步:澄清(这一轮最重要的动作)#
绝对不要听完就开始画架构。开场第一句应该是:
「在动手之前,我想先确认几件事 —— 因为这几个答案会显著改变方案。」
必问清单(按对方案的影响排序)#
| # | 问题 | 为什么它会改变方案 |
|---|---|---|
| 1 | 答错的代价是什么? | 制度类问答答错可能导致合规事故 → 需要强制引用来源 + 不确定时拒答;而「找会议纪要」答错只是不方便 → 可以更激进地生成 |
| 2 | 答案必须可溯源吗? | 必须溯源 → 每个 chunk 要带文档 ID 和定位信息,且生成时强制引用;不必须 → 可以省掉一堆工程 |
| 3 | 文档权限分级吗? | 有权限 → 检索层必须做权限过滤(这是在向量库之前的一层,很多人会漏),且不能只靠 prompt 约束 |
| 4 | 文档更新频率? | 每天更新 → 需要增量索引和失效机制;一年一版 → 全量重建即可 |
| 5 | 文档格式分布? | PDF 扫描件占比高 → OCR 是主要工作量,不是 RAG;纯 markdown → 基本可以跳过解析层 |
| 6 | 多少用户、多少 QPS? | 决定要不要自建推理、要不要缓存、成本量级 |
| 7 | 用户问的是「事实查询」还是「综合判断」? | 前者 → 扁平 chunk 检索够用;后者(「我们的报销政策在几个文件里说法不一致,以哪个为准」)→ 需要 GraphRAG 或重排序以上的手段 |
| 8 | 能不能接受「我不知道」? | 决定拒答阈值。企业场景通常必须能拒答,否则用户会信任错误的答案 |
面试技巧:不要机械地把 8 个问题全问完 —— 那像在念清单。挑影响最大的 2–3 个先问,拿到答案后再问下一层。追问本身也是考点。
3. 参考架构#
假设澄清后得到:有权限分级、必须溯源、PDF 为主、日更、事实查询为主、允许拒答。
┌─ 离线索引 ────────────────────────────────────────────┐
│ 文档源 → 解析(OCR/PDF/MD) → 清洗 → 分块 │
│ → 嵌入 → 向量库 + 全文索引(BM25) │
│ → 元数据(权限标签/文档ID/版本/时间) │
└───────────────────────────────────────────────────────┘
↓
┌─ 在线查询 ────────────────────────────────────────────┐
│ 用户问题 │
│ ↓ ① 权限过滤(在检索前,不是检索后) │
│ ↓ ② 查询改写(口语 → 检索友好) │
│ ↓ ③ 混合检索:BM25 ∥ 向量 │
│ ↓ ④ 融合(RRF) │
│ ↓ ⑤ 重排序(cross-encoder,取 top-n) │
│ ↓ ⑥ 上下文组装(带来源标记 + token 预算截断) │
│ ↓ ⑦ 生成(强制引用 + 不确定则拒答) │
│ ↓ ⑧ 引用校验 │
│ 答案 + 来源链接 │
└───────────────────────────────────────────────────────┘画图时要讲的三个点(这是把架构和「背组件」区分开的地方):
- ① 权限过滤必须在检索前。如果先检索再过滤,一是会泄漏「存在这份文档」的信息(通过答案数量和相关性),二是 top-k 被无权限文档占满导致有效结果变少。正确做法是把权限作为向量库的元数据过滤器(pre-filter)。
- ③ 为什么混合检索而不是纯向量。企业文档里全是专有名词和编号(「GB/T 1234」「第三章第二节」「流程 A-07」)。这类精确 token 稠密向量召回很差,而 BM25 恰好擅长。代价是多一路检索和融合逻辑。
- ⑦ 拒答不是一个 prompt 技巧,是架构的一部分。要有明确的「证据不足」判定 —— 比如重排后的最高分低于阈值、或引用校验不通过 —— 并且这个阈值要可调、可评测。
4. 关键决策与权衡#
4.1 分块:按什么切#
- 起步方案:按文档结构切(标题层级 + 递归字符切分,chunk 256–1024 token,overlap 10–20%)。
⚠️ 口径统一:此处原写「~500–1000 / 10–15%」,与03-rag/chunking.md的原口径(200–512)不一致,已一并对齐到 256–1024 / 10–20%。强调这是「起步区间」而非答案 —— 面试里正确的答法是「先落在这个区间,再用带 gold chunk 标注的小评测集扫 chunk size × overlap,以 Recall@k 选优」。 - 为什么不用固定长度:会切断表格和条款,而企业文档的答案常在一个完整条款里。
- 代价:结构解析要针对格式做适配(PDF 的标题层级不一定能解析出来),工程量大。
- 进阶(先不做):父子分块 —— 用小 chunk 检索、把父 chunk 给模型,缓解「检索粒度 vs 生成上下文」的矛盾。但这是第二阶段优化,不是起点。
4.2 要不要用 GraphRAG#
主动说不做,并且给理由 —— 这是资深的信号:
「GraphRAG 擅长的是跨文档的全局性问题(『这几个文件在哪些点上有冲突』)。但这个场景 90% 是事实查询,扁平检索够用。GraphRAG 的索引成本很高、更新很难(改一份文档可能要重建社区摘要),在这个场景是过度设计。我建议先不做,等发现某一类问题检索确实解决不了再评估。」
同样,多智能体也不要上。单轮问答不需要 agent 编排 —— 加进去只是增加延迟和失败面。
4.3 拒答阈值怎么定#
- 不能凭感觉。要构造一批「知识库里没有答案」的问题当评测集,测「该拒答时拒答的比例」和「不该拒答却拒答的比例」,在两条曲线之间选点。
- 这正好呼应
05-agent-evaluation/rag-eval.md里的 abstention correctness 指标。
4.4 更新与失效#
- 增量索引:文档指纹(hash)判断变化,只重建变化的文档。
- 失效比新增难:文档删了,其 chunk 要从索引里彻底移除;文档改了,旧 chunk 要清掉。只做新增不做删除,会长期产生「幽灵答案」。
- 版本意识:答案要能回答「这是哪个版本的制度」。
4.5 明确不做什么#
这一节要主动说出来,不要等面试官问。 把「我判断这些在这个场景下不值得做」讲出口,是资深和初级最大的分界 —— 初级候选人倾向于把知道的组件全堆上去。
| 不做 | 理由 |
|---|---|
| 不上 GraphRAG | 见 4.2。这个场景 90% 是事实查询,扁平检索够用;GraphRAG 索引成本高、文档更新时要重建社区摘要,是过度设计 |
| 不上多智能体 | 单轮问答没有多角色协作需求。加编排只增加延迟和失败面 —— 这是 04-agent-architecture/multi-agent.md 里「上下文隔离是唯一真实收益」的直接推论 |
| 不做微调 | 这个场景要的是「检索到正确的私有知识」,不是「改变模型行为」。微调擅长学格式与风格,不擅长注入易变的事实 —— 知识更新一次就要重训,成本结构不成立 |
| 第一阶段不做父子分块 | 有效但要先证明基础方案不够。先上简单方案、用评测暴露瓶颈,再针对性加复杂度 |
| 不自建推理 | 内部工具的并发量通常撑不起自建 vLLM 的回本线。除非澄清后发现 QPS 很高,否则直接用 API 更划算 |
表述要点:说「不做」时必须带理由和条件 —— 「不做 X,因为 Y;如果发现 Z,我会重新评估」。只说「不做 X」听起来像不会。说「不做 X,因为 Y,除非 Z」才是工程判断。
5. 容量与成本直觉#
面试官常问「这大概多少钱」。不要求精算,但要有量级感觉:
- 索引:1 万份文档 × 平均 20 页 ≈ 20 万页 ≈ 数千万 token。一次性嵌入成本很低(嵌入比生成便宜一两个数量级),通常不是瓶颈。
- 存储:向量 + 原文 + 元数据,量级在几十 GB,普通机器能扛。
- 在线:真正的成本决定项是每次查询注入多少上下文。这也是为什么 ⑥ 要给上下文设 token 预算、⑤ 要做重排序(把 50 个候选压到 5 个高质量 chunk)。
- 要不要自建推理:取决于 QPS。内部工具(几百人)多半直接用 API 更划算 —— 自建 vLLM 要有足够并发才回本,这是
06-agent-infra/latency-cost.md里的核心判断。
说出「这个规模下自建推理不划算」比说「我用 vLLM」更能体现工程判断。
6. 面试官会怎么往下挖#
第一层:澄清与动机#
- Q:你会先问我什么? ← 见第 2 节,这是开场题
- Q:为什么不直接上长上下文模型,把文档全塞进去? ← 成本、延迟、且「塞得进」不等于「找得到」(lost in the middle)
第二层:技术细节#
- Q:检索错了和生成错了,你怎么区分? ← 用 oracle context 反事实实验,见
03-rag/pipeline.md - Q:chunk size 你怎么定? ← 必须有依据,不是「拍一个」
- Q:权限过滤放在检索前还是后?为什么? ← 见 3 节的 ①
- Q:用户问了一个库里没有的问题,系统怎么表现? ← 拒答机制 + 怎么评测
- Q:文档里同一件事有两个说法,系统怎么办? ← 这题很难。诚实答法:当前方案会随机给一个并对一个来源;要正确处理需要冲突检测,属于已知局限
第三层:工程与生产#
- Q:怎么知道系统上线后有没有变差? ← 需要固定的回归评测集 + 线上失败回流
- Q:文档更新了,怎么保证答案跟着更新? ← 增量索引 + 失效机制
- Q:成本怎么控制? ← 上下文预算、重排序压缩、缓存高频问题
- Q:怎么排查线上某个具体错误回答? ← 链路追踪,每步输入输出都要留
第四层:反思与边界#
- Q:这个方案什么时候会不够用? ← 综合判断型问题、跨文档冲突、多跳推理
- Q:如果客户说「答错一次就不能接受」,你怎么回? ← 诚实说做不到 100%,然后把方案改成「强制引用 + 高置信才作答 + 低置信转人工」,并说明这会让可用性下降
- Q:这个项目里最容易被低估的工作量是什么? ← 文档解析和权限体系,不是模型
7. 自测#
1. 为什么权限过滤必须在检索之前,而不是生成之后?说出两个具体后果。
答案要点
① 泄漏信息 —— 用户能通过答案的成败或相关性推断出「存在一份自己无权看的文档」;② 挤占 top-k —— 无权限文档占据召回名额,导致有效结果变少、召回率下降。正确做法是把它作为向量库的元数据 pre-filter。另外要注意:不能只靠 prompt 约束「不要引用无权文档」,那是不可靠的软约束。
2. 这个场景为什么不该上 GraphRAG 和多智能体?请从「方案必要性」角度说明。
答案要点
GraphRAG 的价值在跨文档全局性问题,而该场景 90% 是事实查询,扁平检索够用;其索引成本高、文档更新时可能要重建社区摘要,属于过度设计。多智能体价值在上下文隔离与多角色协作,而单轮问答没有多角色需求,加进去只增加延迟和失败面。主动判断「不需要」并说出理由,比堆组件更能体现工程判断。
3. 客户要求「答错一次都不能接受」,你怎么回应?
答案要点
先诚实说明 LLM 系统无法做到 100% 正确,不能承诺不可能的事。然后把方案改为保守策略:强制引用来源、仅在证据充分时作答、低置信一律转人工,并明确告知客户这会让「能自动回答的比例」下降 —— 把选择权交回客户,让他用可用性换准确性。这体现了 FDE 的诚实与边界意识。
4. 文档被删除后,如果只做增量「新增」不做删除处理,会发生什么?
答案要点
产生「幽灵答案」:已删除文档的 chunk 仍在索引中,系统会继续基于不存在的内容作答,而且引用链接可能已失效。这类错误很难被用户发现,因为答案看起来有来源。因此索引维护必须处理删除与更新,通常用文档指纹比对 + 物理删除旧 chunk。
5. 换成「这个知识库要支持 5000 人同时用,QPS 500」,你的架构哪些部分要改?
答案要点
主要压力在生成侧而非检索侧:需要考虑缓存高频问题、控制单次上下文 token、评估自建推理(vLLM 连续批处理)是否比 API 更划算、以及限流与降级策略。检索层通常可加副本横向扩展;嵌入不需要在线跑。要点是识别出瓶颈从「检索」转移到「生成吞吐与成本」,而不是把所有组件都改一遍。