ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

手撕代码题(二):RAG 检索链路(10 题)

手撕代码题(二):RAG 检索链路(10 题) RAG 手撕题的死法很典型:九步流程背得一字不差,面试官说「那把 BM25 写出来」,就卡在 IDF 公式上;说「两路召回怎么合」,就把余弦分数和 BM25 分数直接相加。这类题筛的不是流程认知,是有没有把 RAG 当工程系统跑过:分块的 overlap 怎么算、过滤放在检索前还是后、索引更新要不要全量重嵌,全是踩过坑才答得出细节的点。本篇按出现频率排了 10 道,从分块一路手撕到增量索引。题目结构说明:每题四部分。考点定位讲面试官到底在筛什么;解题思路是白板上先写什么后写什么的框架;伪代码逐行注释,# [n_chunks, dim]这种标注就是面试时口述数据结构的话术,照着说;追问链是写完代码后 80% 会跟上的问题,附一句话答法。标记:⭐ 高频(出现率过半)、🔥 近两年新增。Q1 手撕分块:固定滑窗与递归切分(含 overlap)⭐考点定位:RAG 手撕的开场题。固定切分几乎人人会写,overlap 的步长计算和递归切分的降级顺序是两个卡人的点,写错步长会切出指数级数量的块,等于宣告没跑过。解题思路:固定切分就一句:步长等于 chunk_size 减 overlap。递归切分记优先级:分隔符从段落到句子到词逐级降级,全都切不动了才退化为固定切分。伪代码:def fixed_chunk(text, chunk_size=500, overlap=50): # text: str,chunk_size/overlap 单位是字符 step = chunk_size - overlap # 步长 450,不是 500 chunks = [] for start in range(0, len(text), step): piece = text[start : start + chunk_size] # 相邻块共享 50 字符 if len(piece) overlap: # 尾巴太短没信息量 if chunks: chunks[-1] += piece # 并进上一块,避免碎块 break chunks.append(piece) if start + chunk_size = len(text): break return chunks # List[str] ​ def recursive_chunk(text, chunk_size=500, overlap=50): separators = ["\n\n", "\n", "。", ",", " "] # 优先级从高到低 if len(text) = chunk_size: return [text] for sep in separators: if sep not in text: continue parts = text.split(sep) # 只用当前层级切 chunks, cur = [], "" for p in parts: # 贪心装包:能塞就塞 cand = cur + sep + p if cur else p if len(cand) chunk_size and cur: chunks.append(cur) cur = cur[-overlap:] + p # 新块开头带上 overlap 尾巴 else: cur = cand if cur: chunks.append(cur) # 单块仍超长(一个超长段落):对它递归降级切 out = [] for c in chunks: out += recursive_chunk(c, chunk_size, overlap) if len(c) chunk_size else [c] return out return fixed_chunk(text, chunk_size, overlap) # 没有任何分隔符,兜底追问链:「overlap 设多少?」- chunk_size 的 10% 到 20%,比如 500/50;太小切断跨边界语句,太大块之间冗余高、检索回来重复内容。「语义切分怎么做?」- 算相邻句子的 embedding 余弦,相似度跌破阈值就切一刀,最准但每句都要嵌入,慢。「Parent-Child 切分解决什么?」- 检索用小块保精确,命中后返回其所属大块保完整,两全。Q2 手撕 BM25 打分 ⭐考点定位:考对经典稀疏检索的真懂程度。IDF 公式写不出的占多数,词频饱和项和长度归一化项说得出作用的更少。写得出 BM25,面试官才信你理解向量检索补的是什么。解题思路:总分等于每个查询词的贡献求和。每个词贡献 = IDF × 词频饱和项。饱和项分子分母
返回列表