字节一面:RAG七连问,前3题筛掉一半,最后一题几乎没人扛得住
周末有个读者找我,说他面字节 Agent 开发岗,一面聊了四十分钟项目,其中将近半小时全在追问他的 RAG 系统。
不是那种"你用了什么向量数据库""chunk size 设了多少"的表面问题。
是从数据源头开始,一层一层往下扒,扒到第七层,他发现自己简历上写的"基于 RAG 的智能问答系统",其实只做了皮毛。
他说面试官的追问节奏特别有意思——每个问题看起来都在上个问题的基础上往下挖,你第一个答得虚,第二个就接不住,第三个直接露馅。
我听完他复述的七个问题,第一反应是:这不是在考 RAG,这是在考你到底有没有真正做过数据工程。
太多人的 RAG 项目是这套流程:找几份 PDF,用 LangChain 的 loader 读进来,recursive splitter 切一刀,往向量库一扔,接个 LLM 生成回答,完事。
简历上写"构建了完整的 RAG 知识库",面试官一追问,发现数据清洗没做、增量更新没有、冲突处理没想过、版本控制是什么都不知道。
今天我就把这七个问题拆开聊。每一个我都给你讲到落地细节,不是给你背概念。
如果你也在做 RAG 项目准备面试,这篇文章建议存下来反复看。
第一问:你的数据来源是什么?
这题看起来最温和,其实是陷阱。
很多人张口就来:“我们用的是公司内部文档,主要是 PDF 和 Markdown。”
面试官不说话,等你往下讲。
然后你就卡住了。因为你不知道面试官想听什么。
这个问题真正在考的是:你有没有想清楚你的知识库到底要覆盖什么,以及这些数据源的异构性会带来什么后果。
你得这么答。
先分类。数据源不是"PDF 和 Markdown"这种按文件格式分的,而是按结构化程度分:
| 数据类型 | 举例 | 处理难度 | 关键挑战 |
|---|---|---|---|
| 结构化数据 | 数据库表、API 返回的 JSON、Excel 表格 | 低 | 语义对齐,字段映射 |
| 半结构化数据 | Markdown、HTML、Confluence Wiki | 中 | 噪声清洗,结构提取 |
| 非结构化数据 | PDF、Word、扫描件、图片 | 高 | 格式解析,版面恢复 |
然后你得说出每个数据源的具体问题。
比如内部 Wiki,最恶心的是什么?是它有大量的历史版本、草稿、模板页、纯导航页。你如果不过滤直接入库,用户问"报销流程是什么",检索出来三条结果,一条是三年前的旧版,一条是草稿箱的半成品,一条是模板说明。回答能靠谱才怪。
再比如 API 文档,Swagger 自动生成的那种,里面全是 schema 定义和示例 JSON,信息密度极低。你按 512 token 切 chunk,一个 chunk 里可能就一个字段定义,检索命中了也没法回答问题。
所以这个问题的高分回答是:
“我们的数据源主要分三类:内部 Wiki 文档、产品 PDF 手册、以及业务系统的结构化数据接口。三类数据的异构性是后续所有处理的起点——Wiki 需要做噪声过滤和历史版本去重,PDF 需要做版面解析和表格抽取,结构化数据需要做语义映射转成自然语言描述。数据源的分类不是按格式分,而是按结构化程度分,因为它直接决定了你的 preprocessing pipeline 要分几条。”
一句话,让面试官知道你不是随便拿几份 PDF 就开干的。
第二问:数据源有很多冗余的无关信息,入库前怎么处理的?
这题是第一问的自然延伸。面试官已经知道你的数据源了,现在要听你做没做数据清洗。
太多人在这题上翻车。翻车的方式高度一致——说"我们做了一些清洗",然后讲不出任何具体细节。
数据清洗不是一句"去掉了无关信息"就能糊弄的。你得讲清楚:洗什么、怎么洗、用什么工具、怎么判断洗得对。
一个真正做过数据工程的 RAG 系统,入库前的清洗 pipeline 至少包含五层:
第一层:去重
不是简单的文件级去重,是内容级去重。
同一个知识点可能在三份文档里都出现了,描述还略有不同。你全入库,检索时三条都召回,LLM 拼在一起生成回答,可能自相矛盾。
怎么做?对每个 chunk 算一个内容指纹。不是 MD5(MD5 对空格、标点敏感,稍微改一个字就变了),而是用SimHash或者MinHash,对文本做分词后生成局部敏感哈希。两个 chunk 的 SimHash 海明距离小于 3,就判定为近似重复。
from simhash import Simhashdef compute_simhash(text: str) -> int: # 分词后计算 SimHash tokens = jieba.lcut(text) return Simhash(tokens).valuedef is_duplicate(hash1: int, hash2: int, threshold: int = 3) -> bool: # 海明距离小于阈值视为重复 return bin(hash1 ^ hash2).count('1') <= threshold保留策略:同内容多版本时,保留来源权威性最高的那份。什么叫权威性高?官方文档 > 内部 Wiki > 个人笔记。这个优先级要在 metadata 里标好。
第二层:噪声过滤
PDF 和 HTML 里最多的不是正文,是噪声。
HTML 文档有导航栏、侧边栏、页脚、广告、cookie 提示。PDF 有页眉页脚、目录页、空白页、纯封面页。
过滤策略分两类:
基于规则的过滤:删掉长度低于阈值的 chunk(通常是 50 字以下,大概率是页眉页脚碎片)、删掉重复出现的固定文本(跨页出现的页眉)、删掉纯数字/纯符号 chunk。
基于模型的过滤:用一个轻量分类模型(甚至可以是规则 + 关键词)判断 chunk 是不是"实质内容"。比如判断一个 chunk 包含的实词比例,低于 30% 的直接扔掉。
第三层:PII 脱敏
内部文档里大概率有手机号、身份证号、内部 IP、员工工号。这些进向量库一是安全风险,二是对检索没用。
正则 + NER 模型双保险。正则抓格式固定的(手机号、身份证号),NER 模型抓格式不固定的(人名、公司名)。
第四层:编码与格式统一
PDF 提取出来的文本编码可能是 UTF-8、GBK、Latin-1 混着来。全量转 UTF-8,全角半角统一,不可见字符清掉。
听着简单,但这步不做,后面 embedding 出来的向量全是垃圾。
第五层:质量评分
不是所有清洗完的 chunk 都值得入库。给每个 chunk 打一个质量分:
- 信息密度(实词占比)
- 语义完整性(是否是一个完整的知识点)
- 来源权威性(官方文档 > 个人笔记)
低于阈值的不入库。宁可少一点,不要烂的进来污染检索。
这五层讲完,面试官知道你是真做过数据清洗的,不是嘴上说说。
第三问:PDF 中的各种形式你是怎么处理的?
这题是上一题的深入版。面试官专门拎 PDF 出来问,因为 PDF 是非结构化数据里最难啃的骨头。
很多人答这题就是一句"我们用了 PyPDF2 提取文本"。
不够。远远不够。
PDF 难处理,是因为它本质上是一个排版描述语言,不是文档格式。它记录的是"在坐标 (x, y) 处画一个字",不是"这是第一段第二行"。
所以 PDF 解析的核心挑战是:从位置信息还原语义结构。
你得把 PDF 里的内容分成五种类型,分别处理:
1. 纯文本段落
用 PyMuPDF(fitz)提取,比 PyPDF2 强不少。PyMuPDF 能拿到文本块的坐标信息,可以基于坐标做版面分析。
关键点:多栏排面的处理。很多技术文档是双栏的,你按从上到下、从左到右读,文本是乱的。得先做栏检测(基于文本块的 x 坐标聚类),把左右栏分开读,再合并。
2. 表格
这是 PDF 解析里最恶心的部分。
表格在 PDF 里就是一堆线条和文字,没有"表格"这个语义标签。你得自己判断哪些线条围成了一个表格,哪些文字属于同一个单元格。
工具选择:
| 工具 | 适用场景 | 局限 |
|---|---|---|
| Camelot | 有明确边框线的表格 | 无线框表格识别不了 |
| Tabula | 规则表格 | 复杂合并单元格不行 |
| PaddleOCR PP-Structure | 扫描件表格、复杂表格 | 依赖模型,有误检 |
| LLM 辅助 | 不规则表格、语义复杂的表格 | 成本高,速度慢 |
实际工程里的做法:先用 Camelot 试,提不出来的用 PP-Structure 跑 OCR 版面分析,最后对关键复杂表格用 LLM 做 structured extraction。
提取出来的表格不要直接当文本塞进 chunk。转成 Markdown 表格或者 JSON 结构,保留行列关系。否则 LLM 拿到一堆没有结构的数字,根本没法理解。
3. 图片
图片分两类:装饰性图片(logo、背景图、分隔线)和信息性图片(流程图、架构图、数据图表)。
装饰性的直接扔掉。信息性的要处理——但不是做 OCR 就完了。
你得判断图片里有没有文字。有文字的,OCR 提取。没有文字但是是流程图的,用多模态模型生成文字描述。把描述和原图的引用一起存进 chunk。
4. 页眉页脚
跨页重复出现的固定文本,基于频率检测:如果一个文本块在超过 30% 的页面里出现,判定为页眉/页脚,全局清除。
5. 目录和书签
PDF 的书签是结构化信息,直接提取作为文档的章节层级。这个层级信息后面切 chunk 的时候有大用——按章节切比按固定长度切靠谱得多。
讲完这五种,再补一句总结:
“PDF 处理的核心思路是分类处理:纯文本走版面分析,表格走结构化提取,图片走 OCR + 多模态描述,噪声走频率检测清除。不是一把梭用 PyPDF2 read 一下就完事的。不同内容类型的处理策略完全不同,混在一起处理是 RAG 质量上不去的主要原因之一。”
第四问:有没有做知识抽取,怎么做的?
前三问在考数据处理,从这题开始,面试官转向了知识层面的处理。
很多人把"知识抽取"和"分块"搞混了。
分块(chunking)是把文档切成片段,知识抽取是从片段里提炼结构化信息。这是两件事。
知识抽取做什么?三个层面:
实体抽取
从文本里识别出关键实体:产品名、技术名词、人名、组织名、版本号。
为什么重要?因为用户提问往往是以实体为锚点的。“Spring Boot 3.0 的自动配置和 2.7 有什么区别?”——这个问题里,"Spring Boot 3.0"和"Spring Boot 2.7"是核心实体,你得能识别出来才能做精准检索。
做法:NER 模型(spaCy、HanLP)+ LLM 辅助。模型抓通用实体(人名、地名),LLM 抓领域实体(产品名、技术名词)。
关系抽取
实体之间什么关系。“Spring Boot 依赖 Spring Framework”“Kafka 用 ZooKeeper 做协调”。
这些关系不只是为了建知识图谱,更重要的是在检索时做关联扩展。用户问 Kafka 的协调机制,你不仅检索 Kafka 相关的 chunk,还能通过关系图谱扩展到 ZooKeeper 相关的内容。
元数据增强
每个 chunk 除了存原文和向量,还要存结构化元数据:
{ "chunk_id": "chunk_0823","content": "Spring Boot 3.0 引入了 spring-boot-starter-web 的自动配置重构...","source_doc": "spring-boot-3-migration-guide.pdf","page": 23,"section": "自动配置变更","entities": ["Spring Boot 3.0", "spring-boot-starter-web"],"keywords": ["自动配置", "starter", "迁移"],"doc_version": "v3.0.1","ingest_time": "2025-06-15T10:30:00Z"}这些元数据后面每一问都会用到。增量更新靠它,冲突处理靠它,版本控制也靠它。
如果你只存了 content + embedding,后面六问全部答不上来。
知识抽取的落地,推荐用 LLM function calling:
extraction_prompt = """从以下文本中抽取结构化信息:文本:{chunk_text}请抽取:1. 关键实体(产品名、技术名词、版本号等)2. 实体间关系3. 核心关键词(3-5个)4. 一句话摘要以 JSON 格式返回。"""用 GPT-4o-mini 或 Claude Haiku 做抽取,成本可控,效果远好于纯规则。
第五问:增量更新是怎么做的?
这题是分水岭。
前四问你答得好,说明你做过 RAG。这题你答得好,说明你上线运营过RAG。
没上线过的人,数据是一次性全量灌进去的,从来没想过增量更新。
但真实场景里,知识库是活的。文档会更新,API 会改版,产品手册会出新版。你不能每次都全量重建。
增量更新的核心问题是:怎么知道哪些文档变了,变了之后怎么最小化更新。
第一步:变更检测
每个文档入库时算一个内容哈希(不是 SimHash,是精确哈希,用 SHA-256):
import hashlibdef compute_doc_hash(filepath: str) -> str: with open(filepath, 'rb') as f: return hashlib.sha256(f.read()).hexdigest()维护一张文档注册表:
| doc_id | filename | content_hash | ingest_time | chunk_ids | status |
|---|---|---|---|---|---|
| doc_001 | api-guide-v2.pdf | a3f8b2… | 2025-06-01 | [c01, c02, c03] | active |
| doc_002 | api-guide-v3.pdf | 7e1c09… | 2025-06-15 | [c04, c05, c06] | active |
定时扫描源目录,对比哈希:
- 新文件 → content_hash 不在表里 → 新增
- 文件变了 → content_hash 不同 → 更新
- 文件删了 → 注册表里有但扫描不到 → 软删除
第二步:最小化更新
文档变了,不是全量删掉重新切重新嵌入。那太贵了。
做法是文档级 diff:
- 把旧文档和新文档分别切块
- 对新旧 chunk 列表做对齐(用 SimHash 找近似匹配)
- 没变的 chunk → 跳过
- 变了的 chunk → 删旧的,嵌新的
- 新增的 chunk → 嵌入入库
- 删除的 chunk → 标记失效(软删除,不是物理删,留版本回溯用)
def incremental_update(doc_id: str, new_chunks: list, old_chunks: list): # 用 SimHash 对齐新旧 chunk old_hashes = {c.id: compute_simhash(c.text) for c in old_chunks} new_hashes = {c.id: compute_simhash(c.text) for c in new_chunks} to_add = [] # 新增 to_update = [] # 更新 to_delete = [] # 删除 for new_chunk in new_chunks: matched = False for old_id, old_hash in old_hashes.items(): if is_duplicate(new_hashes[new_chunk.id], old_hash): # 近似匹配,内容可能有微调,判断是否需要更新 if content_changed_beyond_threshold(new_chunk, old_chunks[old_id]): to_update.append((old_id, new_chunk)) matched = True break ifnot matched: to_add.append(new_chunk) for old_id in old_hashes: if old_id notin [m[0] for m in to_update]: to_delete.append(old_id) # 执行最小化操作 batch_embed_and_insert(to_add) batch_embed_and_replace(to_update) soft_delete(to_delete)第三步:Embedding 版本管理
这里有个坑:如果你换了 embedding 模型,旧向量和新向量不在同一个空间,相似度计算全是错的。
所以 embedding 模型版本也要存进 metadata。换模型时走全量重新嵌入的流程,而不是增量。
面试官追问"换模型怎么办"时,你的回答是:“全量重建,走灰度切换——新模型先在全量数据上跑一遍,存到新 collection,双写一段时间验证检索质量,确认没问题再切流量。”
第六问:如果不同文档里的内容冲突了,你是怎么处理的?
这题考的是你的系统有没有冲突感知能力。
什么是冲突?用户问"我们的报销审批流程是什么",检索到两个 chunk:
- chunk A(来自 2024 年版的行政手册):“报销需部门经理审批后交财务”
- chunk B(来自 2025 年新版行政手册):“报销需直属主管审批后交财务共享中心”
内容矛盾。LLM 拿到这两个 chunk,要么随机选一个,要么把两个揉在一起生成一个不存在的流程。两种都是错的。
冲突处理的策略分三层:
第一层:预防——来源权威性排序
入库时就给每个文档打权威性标签:
| 权威性等级 | 来源 | 示例 |
|---|---|---|
| L1 最高 | 官方文档、最新版规范 | 产品官方手册 v3 |
| L2 高 | 内部正式文档 | 公司Wiki正式发布的页面 |
| L3 中 | 内部非正式文档 | 团队笔记、会议纪要 |
| L4 低 | 外部非正式来源 | 博客、论坛帖子 |
检索时,如果多个 chunk 语义相似但内容冲突,优先取权威性高的。
实现方式:在向量检索后加一层 rerank,对冲突的 chunk 按权威性排序:
def rerank_by_authority(results: list) -> list: # 检测冲突:语义相似但内容不同的 chunk conflict_groups = detect_conflicts(results) for group in conflict_groups: # 按权威性排序,低权威的降权或过滤 group.sort(key=lambda x: x.metadata['authority_level']) # 保留最高权威的,其余降权但不删除(保留给 LLM 参考) for i, chunk in enumerate(group): if i > 0: chunk.score *= 0.3 # 降低相似度分数 return sorted(results, key=lambda x: x.score, reverse=True)第二层:检测——语义矛盾识别
光有权威性排序不够,你得先知道冲突发生了。
冲突检测方法:对召回的多个 chunk 做两两比较。如果语义相似度高于 0.85(说明在讲同一件事),但关键实体/数值不同,判定为冲突。
更靠谱的做法是用 LLM 做冲突判断:
conflict_detection_prompt = """以下是从知识库中检索到的多个片段,请判断它们之间是否存在内容矛盾:片段A:{chunk_a}片段B:{chunk_b}如果存在矛盾,指出矛盾点。如果只是表述不同但语义一致,不算矛盾。"""第三层:处理——冲突消解策略
检测到冲突后,四种处理方式:
- 权威性优先:取高权威来源的内容,低权威的丢弃
- 时效性优先:取更新时间更晚的(适用于文档版本更新场景)
- LLM 融合:把冲突信息连同来源信息一起给 LLM,让它在生成回答时标注差异:“根据 2025 年最新版手册,报销流程为……(注:旧版流程为……,已更新)”
- 上报人工:关键业务冲突(比如安全规范、合规要求),不要自动消解,标记冲突上报知识库管理员
第三种是最实用的。不是非此即彼,而是让 LLM 在回答里体现信息差异,把判断权交给用户。
第七问:有没有做版本控制?如果新同事把错误文档入库了,怎么办?
最后一问,也是最深层的一问。
前六问都在考系统设计能力,这题在考你的系统有没有可运营性和容错能力。
版本控制不是 Git 那种代码级别的版本控制,是知识库级别的版本管理。
要回答这个问题,你得有一套完整的知识库治理方案:
文档版本管理
每个文档入库时记录版本信息:
doc_id: doc_0823filename: api-guide.pdfversion: v3.2content_hash: a3f8b2...parent_version: v3.1 # 上一版本ingest_time: 2025-06-15T10:30:00Zingest_by: zhang_sanstatus: active每次更新不是覆盖,是新增一个版本。旧版本的 chunk 不删除,标记为superseded。这样可以随时回溯到任意历史版本。
入库审核机制
错误文档入库的问题,本质是缺少入库前的审核环节。
分级审核:
| 文档类型 | 审核要求 |
|---|---|
| 全局知识库文档 | 需管理员审核 + 预览检索效果 |
| 团队级文档 | 需团队负责人审核 |
| 个人沙箱文档 | 无审核,但仅对本人可见,不进全局检索 |
入库不是直接写向量库,而是先进暂存区:
[上传文档] → [暂存区] → [自动质检] → [人工审核] → [正式入库]自动质检包括:
- 文档完整性检查(是否空文件、是否乱码)
- 内容质量评分(信息密度是否达标)
- 重复检测(是否与已有文档高度重复)
- 敏感信息扫描(是否包含不该入库的内容)
回滚机制
万一错误的文档已经入库了,怎么处理?
三步:
第一步:隔离。不是直接删除,而是把该文档关联的所有 chunk 标记为quarantined,从检索中排除。这比物理删除安全——万一判断错了还能恢复。
def quarantine_document(doc_id: str, reason: str): chunks = get_chunks_by_doc(doc_id) for chunk in chunks: chunk.metadata['status'] = 'quarantined' chunk.metadata['quarantine_reason'] = reason chunk.metadata['quarantine_time'] = datetime.now().isoformat() # 从检索索引中移除,但保留存储 vector_store.update_metadata(chunks)第二步:回滚。如果文档更新导致问题,回滚到上一个版本。因为有版本管理,只需要把旧版本的 chunk 状态从superseded改回active,新版本的 chunk 改为quarantined。
第三步:影响评估。检查在错误文档入库期间,有没有用户问答命中了这些 chunk。如果有,记录影响范围,必要时通知相关用户。
操作审计
所有入库、更新、删除操作全部记录审计日志:
| 时间 | 操作人 | 操作类型 | 文档ID | 版本 | 状态 |
|---|---|---|---|---|---|
| 2025-06-15 10:30 | zhang_san | ingest | doc_0823 | v3.2 | active |
| 2025-06-15 14:00 | li_si | quarantine | doc_0823 | v3.2 | quarantined |
| 2025-06-15 15:00 | li_si | rollback | doc_0823 | v3.1 | active |
出问题能追溯,能定位,能恢复。
七问之后:面试官到底在找什么样的人
回过头看这七个问题,你会发现一条主线:
面试官不是在考你知不知道 RAG 的概念,而是在考你有没有真正从数据源头到上线运营,完整地趟过一遍。
第一问考你对数据源的理解——你知不知道数据异构性是所有问题的起点。
第二问考你的数据工程能力——你有没有做过清洗 pipeline。
第三问考你的细节处理能力——PDF 这种难啃的骨头你有没有真碰过。
第四问考你的知识建模能力——你有没有从"存文本"升级到"存结构化知识"。
第五问考你的工程运营能力——系统上线后怎么维护。
第六问考你的系统鲁棒性思维——数据冲突这种真实场景你有没有预案。
第七问考你的容错和治理能力——出了问题能不能兜底。
七问层层递进,每一问都在前一问的基础上往下挖。你前面答得虚,后面就接不住。
很多人觉得 RAG 简单——加载文档、切块、嵌入、检索、生成,五步搞定。
面试官也知道简单。所以他不会问你五步是什么,他问你每一步里面的坑是什么、你怎么处理的、有没有兜底方案。
能答上来,说明你真做过。答不上来,说明你看了个教程就写简历了。
最后说一句。
RAG 项目的深度,不在于你用了什么先进的 embedding 模型或者什么花哨的 rerank 策略。在于你对数据的理解有多深,对工程细节的把控有多严。
模型是别人的,数据是你的。数据工程做到位了,即使用最朴素的 embedding + cosine similarity,检索效果也不会差。数据工程没做到位,用再贵的模型也是垃圾进垃圾出。
这句话,面试的时候你可以不说,但心里一定要有。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~