拼多多 AI Agent开发三面:上下文、断点恢复与多 Agent 协作
- 轮次
- 三面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 自我介绍。
- 介绍你的 Agent 代码生成链路全流程,从用户输入需求到最终代码交付,完整流程是什么?中间有几个环节?每个环节的输入输出是什么?
- 长流程任务中,上下文超限问题你怎么解决?如果 1M 的上下文窗口也不够怎么办?别说分块,我要听具体的工程方案。
- 断点恢复是怎么做的?服务重启后,如何加载未完成的任务状态?你存了哪些状态信息?存在哪里?恢复后怎么保证状态一致性?如果恢复失败了怎么办?
- 多 Agent 同时修改同一个文件时,怎么防止冲突?有没有用分布式锁?锁的粒度是什么?
- Codebase Memory 怎么初始化和更新维护?
- 知识库检索在什么时机使用?具体怎么实现的?知识库检索调优做了吗?怎么调的?
- 代码生成有什么多 Agent 保障机制?测试用例是怎么生成的?
- 大规模代码场景下,Agent 上下文怎么维护?
- 代码审查 Agent 怎么做的?是全量逐行扫描吗?怎么优化?
- 怎么防止用户重复创建相同任务?幂等性怎么设计?
- 你平时常用哪些数据库?MySQL 索引底层是什么结构?为什么选用 B+ 树而不是 B 树?
- B+ 树叶子节点存储空间大小是多少?索引存在硬盘还是内存?
- Spring Bean 注入的逻辑是怎样的?
- 大模型的缓存机制了解吗?
- 代码生成系统中长耗时 Agent 占用资源、影响并发,怎么优化?
- Java 类加载机制,JVM 如何根据类名查找 class 文件?如何从网络加载字节码?
- Python 多线程和 Java 多线程核心区别?GIL 全局解释器锁产生的原因?
- FastAPI URL 路由底层实现原理?请求如何匹配对应处理函数?
- Qdrant 向量数据库特点以及选型理由?
- HNSW 底层实现?优点?朴素向量检索如何实现?
- 详细讲一下项目流程和架构,难点是什么?是否端到端打通了,卡点是什么?
- Agent 输出效果如何量化评估?
- RAG 如果存在图片类非文本内容如何处理?
- 职业发展规划,公司选择倾向?
- 如何看待工作强度大、节奏快的问题?
- 你的多 Agent 架构,每个 Agent 之间怎么传递消息?是否有人工介入?怎么控制 Agent 的?是否有观测的方式?
- 数据存在云端会有安全问题吗?
- 为什么采用 ForkJoinPool 线程池?
- 讲讲实习中的难点?
- 你的 Agent 怎么实现不同任务的隔离?
- Agent 如何保证分析、输出结果准确性?
- Agent 评测体系,测试用例覆盖范围?
- LangChain 与 LangGraph 核心区别?
- 使用什么 Agent 开发框架?自研框架和开源框架差异?
- 手撕题:实现一个 LRU Cache,要求 get 和 put 都是 O(1)。追问:如果要求这个 LRU 支持泛型和过期时间,你怎么改代码?过期时间存哪里?多线程环境下怎么保证线程安全?synchronized 和 ReentrantLock 怎么选?
《参考解析》
代码生成链路的讲法:把环节和产物列清楚:从需求到交付要能报出编号的环节,例如「需求解析与澄清 → 仓库检索与上下文装配 → 任务拆解(文件级计划)→ 多 Agent 并行生成补丁 → 静态检查与编译 → 测试生成与执行 → 代码审查 → 生成 PR 与说明」。每个环节都要说清输入输出(比如「上下文装配」的输入是任务描述加检索到的文件片段,输出是带 token 预算的 prompt 包),因为面试官问的就是输入输出格式。链路里要标出「哪一步是确定性的、哪一步依赖模型」——确定性的部分(编译、测试、diff 应用)必须做成硬门禁,模型输出只能作为候选,这样链路的可靠性才有底座。
上下文超限与断点恢复:预算管上下文,状态机管恢复:把上下文当预算来管,而不是当容器来塞。四件事:一是分层——系统指令与工具定义固定占一部分(走前缀缓存),当前任务与最近几轮对话占一部分,代码与文档按需检索注入,历史用摘要压缩;二是按需检索替代全量装载,代码场景用符号索引(函数、类、调用关系)先定位再取片段,而不是把整个仓库塞进去;三是摘要要有结构(已完成、当前状态、待办、关键实体),并且关键事实单独落结构化存储,不依赖摘要保真;四是预算分配要显式,比如 40% 给系统与工具、30% 给检索片段、20% 给历史、10% 留给输出。1M 窗口也不够时,本质问题不是窗口小,而是任务粒度太粗——正确方向是任务拆解加子 Agent 隔离,让每个子任务只装自己需要的上下文,再把结果汇总。要能被中断恢复,先要有落库的状态机。状态信息至少包含:任务 ID、当前步骤、每步的输入输出摘要、对外副作用单号、重试计数、版本号与时间戳。持久化选型上,流程状态放关系库(要事务与查询)或带事务的 KV,大对象(代码片段、日志)放对象存储只存引用;用 LangGraph 这类框架的话 checkpoint 机制本身就是这个思路。恢复时按状态机找未完成任务,从最后一个成功的检查点继续,并且做两件事保证一致性:一是写操作全程幂等(幂等键由任务与步骤推导,重复执行返回首次结果);二是恢复前对外部状态做一次对账(查 PR 是否已创建、文件是否已改),以外部真实状态为准修正本地状态。恢复失败要落死信表、告警并给出人工接管入口,别让它无声无息地卡在中间。
多 Agent 并发写同一文件:先隔离,再串行,最后才谈锁:文件级并发冲突的最优解不是加分布式锁,而是让不同 Agent 不写同一处:任务拆解阶段就按文件或模块分配所有权,避免重叠;对必须共享的文件,用「生成补丁(diff)而不是直接写文件」的模式,Agent 只产出针对固定基线版本的 patch,由中心调度串行应用,冲突时重新取最新基线再生成。锁确实是备选,但粒度要选对:按仓库加锁太粗(吞吐掉到不能忍),按文件加锁更合理,实现上用带租约和超时的锁(Redis 或数据库行锁),并处理持锁者崩溃后的过期释放。更完整的做法是乐观并发:应用 patch 时比对基线版本号,不一致就拒绝并让 Agent 基于新版本重做,这样大部分情况下根本不需要锁。
Codebase Memory 与检索调优:Codebase Memory 的目标是让 Agent 不必每次重读仓库。初始化可以分三层:符号层(解析 AST 建函数、类、导入关系与调用图)、语义层(对文件与函数做 embedding 存向量库,用于自然语言检索)、经验层(历史任务的改动记录、常见坑、评审意见)。更新维护靠事件驱动:增量索引在提交或合并时更新受影响文件,定期全量重建兜底,索引与 commit 版本对齐以便回溯。检索调优的顺序是「先看召回再看排序」:切块粒度按语法结构切(函数级、类级),别按固定字符数切;查询侧做查询改写(把口语需求扩成代码术语与同义词);召回用向量加关键词(BM25)混合再重排;最后用一组真实任务做离线评测,看「命中正确文件的比例」和「最终任务成功率」两个指标一起动,别只盯召回率。
框架与八股:把常用原理答出关键点:LangChain 偏组件与链式编排,提供模型、检索、工具的积木;LangGraph 在其之上做有状态图编排,核心是节点、边、条件跳转和 checkpoint,适合需要循环、人工介入和可恢复的长流程——这个区别要能一句话说清。B+ 树考的是「为什么」:非叶节点只存键、不存数据,扇出更大,树高更低(通常 3~4 层就能撑千万级行),叶子节点用双向链表串起来,天然支持范围扫描与排序,因此比 B 树更适合磁盘上的索引;InnoDB 页默认 16KB,非叶节点能存几百到上千个键,索引本身是存在磁盘上的、查询时按页加载进缓冲池。GIL 是 CPython 解释器级互斥锁,为了保护引用计数等内部状态,所以多线程无法并行执行字节码,CPU 密集要靠多进程或 C 扩展,IO 密集则因释放 GIL 仍能受益。FastAPI 路由本质是启动时把路径注册成路由表(按路径与方法匹配,路径参数走正则或转换器),请求进来遍历匹配到 endpoint 再走依赖注入;HNSW 则是分层可导航小世界图,上层稀疏做长跳、下层稠密做精查,查询复杂度接近对数级,代价是索引内存占用高。
手撕 LRU 及其扩展:标准答案是哈希表加双向链表——哈希表存 key 到节点的映射保证 O(1) 定位,双向链表维护访问顺序(头部最新、尾部最旧),get 命中后把节点移到头部,put 超容量时淘汰尾部节点并同步删除哈希表项。扩展问题分别答:支持泛型就把 key 与 value 参数化(Java 里 LinkedHashMap 开 accessOrder 并重写 removeEldestEntry 就是现成实现,但要能自己手写);支持过期时间就给节点存过期时间戳并在 get 时惰性删除,再配一个按过期时间排序的结构(堆或时间轮)做定期清理;线程安全最省事是整体加锁,要更高并发就用分段锁,或 ConcurrentHashMap 加链表操作加锁。synchronized 与 ReentrantLock 的取舍:前者语法简单、由 JVM 管理、有锁升级优化,适合竞争不激烈的短临界区;后者支持可中断、超时、公平锁与条件变量,需要这些能力时选它。