面灵AI→

字节Agent秋招二面 全程深挖RAG系统与一道随机算法

轮次
二面
时间
2026-09
来源
牛客网

《面试题目》

  1. 介绍一下你在学校做的 RAG 系统,整体流程是怎样的?
  2. Markdown 文档的分块逻辑是怎样的?是否先按标题结构划分模块,再把模块拆成小 chunk?检索时召回小 chunk,再补充对应的父模块吗?
  3. 分块之后与前面的内容做聚合,具体是怎么实现的?相邻块之间是否会存在重合?
  4. 如果一段内容有 1000 个 token、chunk size 是 400,最后剩余的 200 怎么处理?会不会向前补充内容凑成 400?
  5. 大部分 chunk 是不是已经接近设定大小,实际很少触发向前补充上下文的逻辑?
  6. 分块大小、聚合时的 chunk size 分别设置为多少?
  7. Markdown 里的图片、无序列表这些内容是怎么处理的?每张图片都要做一次模型推理,会不会导致文档保存、入库的耗时过长?
  8. 项目使用的 Embedding 模型和向量数据库分别是什么?
  9. 用户问题改写是怎么实现的?能不能举例说明改写逻辑,以及改写前后的问题分别是什么?
  10. 子问题是如何拆分的?拆分的目的是不是让每次检索只围绕一个问题、召回与该问题相关的知识?
  11. 问题改写和子问题拆分,是在一次模型推理中完成,还是需要多次调用模型?
  12. 这一步模型推理无法直接向用户流式输出答案,会不会导致用户等待答案的时间过长?
  13. 混合检索是怎么实现的?检索后的重排是怎么做的?
  14. 简历里写的「企业级 RAG」应该怎么理解?企业级 RAG 与学习型 Demo 有哪些不同?
  15. 实习经历深挖。
  16. 算法题:已知 random5() 等概率返回 0 到 4 的整数,仅使用该随机函数实现 random7(),使其等概率返回 0 到 6 的整数,不得使用其他随机数生成方法。

《参考解析》

Markdown 分块与父子块检索:按标题层级把文档切成树是最自然的做法——标题本身就是作者给出的语义边界,按层级拆出的模块不容易被切断语义。但模块大小不均,所以通常做两层:先按标题结构切成父块,再把过长的父块按长度或句子边界切成子块;检索时用子块算相似度,命中后把它的父块或相邻上下文一起送给模型。好处很直接:子块向量表示更聚焦、召回更准,返回父块又能补回被切断的上下文,避免答案断章取义;代价是要维护父子映射并去重——同一个父块被多个子块命中时只能取一次。相邻块留 overlap 是为了防止完整语义被硬切在边界上,重合量通常在几十个 token 的量级,重合太多会让同一段内容被反复召回、白占上下文预算。

尾块不足一个 chunk 怎么处理:这个问题的答案取决于检索策略,而不是某条固定规则。常见三种:保留短尾块(实现简单,但过短的块向量表示很弱、很难被召回)、把尾块并进前一个块(保证每块信息量足够,代价是最后一块略超设定长度)、向前或向后补充上下文凑成完整块(相当于用 overlap 补齐,但会和相邻块重复)。判断依据是「这个尾块单独会不会被召回」:如果它是正文而不是签名、版权这类噪声,就应该合并或补齐。反过来,如果大多数 chunk 都已经接近设定大小,说明切分点分布均匀,补齐逻辑很少被触发——面试官追问这句,往往是在怀疑分块实现只是把参数填上了,并没有真的按内容去切。

图片与富文本内容怎么入库:Markdown 里的图片、列表、表格要分开对待。列表和表格本质是文本,按普通文本参与分块即可,但要注意别把表格切碎——表格被拦腰切断后行列对应关系就丢了,检索到也用不了。图片若要参与检索,得先转成文本:OCR 抽图中文字,或者用多模态模型生成描述,再把描述当作文本块入库。这笔推理开销是实打实的,一篇文章几十张图就是几十次模型调用,同步做会把保存和入库接口拖到几十秒。工程上的处理是异步化:先存原文并返回成功,把图片描述生成放进消息队列后台跑,完成后再增量写入向量库并更新文档的可检索状态;同时做幂等与失败重试,避免同一张图被反复推理。常见优化还包括按 URL 或内容哈希对相同图片去重、只对信息量大的图做处理、以及给入库流程设超时与降级。

问题改写与子问题拆分:多轮对话和口语化提问会带指代、省略和多个意图,直接拿原句去检索往往召不回东西。改写解决的是把问题补全成自包含的检索语句(补主语、替换代词、把口语术语归一到文档用词),拆分解决的是一个提问里含多个子问题(比较类、多跳类问题需要分别检索再汇总)。两者可以在一次模型推理里完成:一个提示词同时输出改写结果和子问题列表,省一次调用、延迟更低,但对模型的指令遵循能力要求高;也可以串行两次调用,可控性更好、成本更高。关键在于这层推理发生在检索之前,用户此时看不到任何输出,所以必须做延迟预算——限定输出长度、设置超时,超时就退回用原句检索,或者先给用户一个「正在检索资料」的状态提示。上线后要把改写前后的 query 与召回结果记下来做评测,否则很容易出现改写把问题带偏却没人发现的情况。

混合检索与重排:向量检索擅长语义相近但字面不同的匹配,BM25 这类关键词检索擅长专有名词、编号、代码符号的精确匹配,两者互补,所以生产上多用混合检索再融合。融合有两种常见做法:按分数加权(前提是把两路分数归一化,实际很难对齐)或按排名融合(只看名次,鲁棒性更好)。融合之后通常还要过一遍重排:用交叉编码器或专门的重排模型把 query 与候选文档拼在一起打分,效果明显优于只用向量相似度,代价是每个候选都要过一次模型,所以只能对前面召回的少量候选(几十条)做。工程上要注意:召回与重排分两级控制延迟;重排后的 topN 才是真正塞进上下文的内容,N 由上下文预算决定;多路召回要去重,避免同一文档被多个子块命中后占满上下文。

企业级 RAG 与学习型 Demo 的差别:Demo 关心「能不能答对」,企业级关心「能不能长期稳定地答对」。差别至少在这几处:数据层要多源接入、增量更新、删除与权限同步,还要处理文档改版后旧向量失效;权限与租户隔离必须做在检索层,不同用户召回范围不同,不能只在前端藏;质量上要有评测集与回归流程(问题集、期望要点、定期跑分),而不是靠人肉试几个问题;可观测要看召回命中率、无答案率、引用准确率、端到端延迟与成本;失败要有兜底——检索不到就明确说不知道,给出处让用户自己核对,低置信度转人工。这些非模型部分的工作量,往往比换一个更强的模型更能决定上线效果。

random5 构造 random7:思路是用 random5 生成一个更大范围的均匀随机数,再用拒绝采样削成 7 的倍数。最直接的写法是把两次 random5 组合成 0 到 24 的均匀整数:x = 5 * random5() + random5(),25 种取值等概率;因为 21 是 7 的倍数,当 x 小于 21 时返回 x % 7(每个结果恰好对应 3 个 x),x 大于等于 21 时重试。每个结果的概率都是 3/25 再除以 3 等于 1/7,严格均匀;期望迭代次数是 25/21,重试概率只有约 16%,实际很快。另一种等价思路是拒绝采样构造二进制位:用 random5 拒绝掉两个取值得到均匀的 1 bit,凑够 3 bit 得到 0 到 7,等于 7 就重试——本质一样,但每次迭代要消耗更多次 random5。要强调的是不能用 (random5() + random5()) % 7:这个和的分布是 1、2、3、4、5、4、3、2、1,取模后 3、4、5、6 出现得更频繁,概率不均,这正是这类题的陷阱。另外别把重试写成深递归,用循环更稳。