拼多多 Agent 开发一面:RAG 切块、混合检索与 BM25
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
代码题
- 手写三数之和。
- 手写轮转数组。
项目与 RAG / Agent 追问
- 你的 chunking 方案是什么?块切得太大、切得太小分别会出现什么问题?
- 你项目里这套切块方案的优点和缺点是什么?
- Top-K 召回结果有噪声时,有哪些优化策略?
- 端到端延迟偏高,你的解决方案是什么?
- 你是否用过 jev 模型?它能不能适配你的项目场景?
- 你的项目里怎么做混合检索?
- BM25 的原理是什么?
- 一个 MCP 服务的全链路流程是怎样的?
- 你怎么评估项目的效果?
- 文档版本更新怎么处理?文档同时有多个版本共存又怎么处理?
- 你的记忆机制包含哪些信息?
- 反馈机制是否有效?(指用户对回答的反馈)
- 测试集怎么创建和评估?
- 你的项目有联网搜索功能吗?
- 项目怎么部署的,架构是什么?
- 沙箱的作用是什么?
反问
- 团队用的开发语言是什么?
- Agent 的实际效果和发展方向如何?
《参考解析》
切块粒度:先想清楚检索和生成要的不是同一种块
块切太大,单块里塞进了多个主题,向量被稀释成「语义平均」,相似度排序就不准;同时进上下文的 token 变多,成本和延迟一起涨,长上下文中间的信息还容易被忽略。块切太小,语义不完整、指代丢失,检索命中了也答不全,模型只能靠拼接硬凑,幻觉概率上升。工程上的折中是「小块检索、大块生成」:索引时按语义或标题层级切成小单元,命中后再回溯它的父块或前后窗口一起送进模型,块间留少量重叠防止答案正好被切在边界上。至于某套方案好不好,判断标准就三条:跨段落的答案能不能被召回、块内主题是否单一、以及解析出来的结构(标题、表格、代码)有没有被破坏——按 markdown 标题切分实现简单、结构清晰,但对超长段落和表格就无能为力,这是最常见的取舍点。
混合检索与 BM25
BM25 是词袋精确匹配的代表,本质是 TF-IDF 的改良:词频用饱和函数(参数 k1)抑制「出现一百次就等于重要一百倍」的线性增长,再用文档长度归一化(参数 b)避免长文档天然占优,有些实现还给 query 中的词加权重。它的长处是专有名词、编号、代码符号这类字面匹配,短板是不懂同义改写和语义泛化——而向量检索恰好相反。所以混合检索就是两路并行召回后融合,最省事的融合方式是 RRF(倒数排名融合):只看排名不看分数,省掉了两套分数量纲对齐的麻烦;要更精细就用带权重的分数归一化,权重按业务调。Top-K 里的噪声有几个递进的解法:先上 rerank(cross-encoder 逐对精算相关性)做精排,再用分数阈值直接截断低质结果,用 MMR 之类做去冗余,配合 metadata 过滤把明显不相关的范围排掉;再往前一步是改 query——改写、拆解、HyDE 生成假设答案再检索,以及多路召回投票。要注意 rerank 是延迟大户,条数和模型大小都得压着用。
延迟拆解、MCP 全链路与部署架构
端到端延迟要按环节拆开量:query 改写与 embedding、检索(向量库与 BM25 可以并行)、rerank、以及 LLM 生成的首 token 与总时长,不拆开就只能凭感觉优化。常见的组合拳是召回并行化、缓存(embedding 结果、高频问题的语义缓存、prompt 前缀缓存)、用一个小模型承担路由和改写、流式输出先把首 token 吐出去、控制送进模型上下文的条数与长度。MCP 的全链路是:宿主作为 client 与 server 建立连接(本地 stdio 或远程 HTTP),initialize 握手协商协议版本与能力,tools/list 拉到工具清单注入模型;模型决定调用后 client 发 tools/call 带上参数,server 执行并以 content 形式返回(文本、图片或资源引用),结果回填给模型继续推理,结束再走关闭流程。这条链路上必须自带参数 schema 校验、超时与错误回传(标记 isError 让模型自纠而不是直接崩)、鉴权与最小权限;沙箱正是在这里起作用——把模型生成的代码或高权限工具的副作用关进隔离环境,限制文件与网络访问,防止一次 prompt injection 就造成数据外泄,所以哪怕当前项目里它「没有实际作用」,也值得说清它在什么情况下是必需的。部署架构通常是不挑会话的无状态服务多副本加网关,向量库、关系库、Redis 各自独立,文档解析与 embedding 入库这类重活走异步任务队列,避免拖慢在线请求。
文档版本、记忆、评估与反馈
文档多版本共存的核心是给每个 chunk 打上 doc_id + version + status,检索时按 metadata 过滤只命中当前生效版本;更新用「先写新版本、再切生效指针」的方式,出问题能一键回滚,旧版本同时还能支撑「某个时间点的历史版本问答」这类需求。删除要软删并定期清理向量,否则删了还能被检索出来是最常见的脏数据事故。记忆机制要分层说清:会话内是最近 N 轮原文加滚动摘要,跨会话是用户画像、偏好与历史结论这类事实槽位;每一类都要回答什么时候写、什么时候召回、新旧冲突谁覆盖谁、过期怎么失效,只答「存了哪些信息」会显得没设计过。反馈机制是否有效,判据不是有没有点赞点踩按钮,而是它能不能变成动作:点踩要能带上原因分类,badcase 要能被归集和抽样标注,再回流成评测集或检索、prompt 的改动,最后在下一轮评测里看到指标变化——没有闭环的反馈只是装饰。测试集要从真实日志里分层抽样(按意图、难度、是否需要多跳、有没有标准答案),检索层看 recall@k 和 MRR,生成层看要点覆盖、引用正确率和人工可用性打分;LLM 打分只做初筛、必须人工抽验,每次改动都跑回归,避免修好一个坏掉一个。
两道手写题
三数之和是排序加双指针的固定套路:排序后固定第一个数,剩下两个指针向中间收,复杂度 O(n²);真正的考点是去重——第一个数的位置要跳过重复值,双指针收缩时左右两侧也各自要去重,否则结果里全是重复三元组,另外还有首数大于 0 就直接剪枝。轮转数组的最优解是三次反转:先把 k 对 n 取模,整体反转、再反转前 k 个、最后反转剩下的,时间 O(n)、空间 O(1);用额外数组是 O(n) 空间但写起来最不容易错,环形替换法空间最优却要处理 gcd(n, k) 个环的收尾。开口先把边界说全(空数组、n 小于 2、k 为 0 或 n 的倍数),再动手,比直接写代码更容易拿分。