面灵AI→

拼多多 AI Agent 二面:双机位、1M 上下文也不够怎么办?

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

《面试题目》

  1. 介绍你的 Agent 代码生成链路全流程。
  2. 从用户输入到代码输出中间经过哪些步骤?
  3. 长流程下上下文超限怎么解决?
  4. 如果 1M 上下文窗口还不够怎么办?
  5. 长流程任务中途服务重启,怎么恢复未完成任务?
  6. 断点续传怎么设计?存了哪些状态?存在哪里?
  7. 多 Agent 同时修改同一个文件怎么防止冲突?
  8. 有没有用分布式锁?
  9. Codebase Memory 怎么初始化和更新维护?
  10. 知识库检索在什么时机使用?具体怎么实现?
  11. 知识库检索调优做了吗?怎么调的?
  12. 代码生成有什么多 Agent 保障机制?
  13. 测试用例是怎么生成的?
  14. 大规模代码场景下 Agent 上下文怎么维护?
  15. 代码审查 Agent 怎么做的?全量逐行扫描吗?
  16. 怎么防止用户重复创建相同任务?
  17. 你平时常用哪些数据库?
  18. MySQL 索引底层是什么结构?
  19. 为什么选用 B+ 树而不是 B 树?
  20. B+ 树叶子节点存储空间多大?
  21. 索引存在硬盘还是内存?
  22. Spring Bean 注入的逻辑是怎样的?
  23. 大模型的缓存机制了解吗?
  24. 代码生成系统中长耗时 Agent 占用资源、影响并发,怎么优化?
  25. Agent 怎么实现不同任务的隔离?
  26. Agent 如何保证分析、输出结果准确性?
  27. Agent 评测体系、测试用例覆盖范围?
  28. LangChain 与 LangGraph 核心区别?
  29. 使用什么 Agent 开发框架?自研框架和开源框架差异?
  30. 线程安全的 LRU 怎么实现?
  31. 手撕:实现一个 LRU Cache,要求 get 和 put 都是 O(1)。追问:支持泛型和过期时间怎么改?过期时间存哪里?多线程环境下怎么保证线程安全?synchronized 和 ReentrantLock 怎么选?

具体项目追问

  1. 你的多 Agent 架构里,每个 Agent 之间怎么传递消息?是否有人工介入?怎么控制 Agent?是否有观测方式?数据存在云端会有安全问题吗?为什么采用 ForkJoinPool?

《参考解析》

上下文超限与「1M 也不够」怎么办。 上下文超限不能靠删消息解决,要靠重塑信息结构:一是分层——系统约束与当前目标常驻,最近几步保原文,更早的历史压缩成结论、未完成事项与产物引用;二是外部化——把大产物(完整文件、工具长输出、检索结果)落盘,上下文里只留摘要与路径,需要时按符号或行区间精确取回;三是按需检索替代全量装载——改哪个符号就只取该符号的实现、调用方与测试;四是任务分片,把大改动拆成可独立验证的小步,每步只带与该步有关的最小上下文。1M 窗口仍然不够是必然的,因为限制不在窗口大小而在「有效注意力」:上下文越长,中间信息越容易被忽略,成本与延迟也线性上涨。所以正确回答是「窗口变大只是把问题推后,真正的解法是选择性地不看不相关的东西」,再叠加具体的预算控制——每步 token 上限、工具输出截断与摘要、超预算就分片或落盘。

断点续传与状态存储。 长任务必须假设会被重启。状态设计上要区分三类:任务级状态(任务 ID、目标、计划 DAG、各节点状态与尝试次数、预算消耗)、步骤级产物(每步的输出与产物引用、校验结果)、以及会话级消息(给模型看的上下文,可重建)。持久化位置按访问频率与一致性要求选:热点状态放 Redis(带 TTL、支持原子更新),权威状态与产物索引放数据库(关系库或 KV),大产物放对象存储。恢复时读任务状态机,从第一个未完成节点继续,已完成的节点直接复用产物而不是重跑——这也是幂等的要求:每个节点要有稳定的幂等键,重复执行结果一致。要特别注意「执行中」这个中间态:重启后它既可能已生效也可能没生效,所以恢复逻辑必须做对账(查产物是否存在、版本是否匹配)而不是简单地重跑或跳过;同时用租约(lease)心跳判断原 worker 是否还活着,避免两个 worker 同时接管同一任务。

多 Agent 改文件的冲突与隔离。 防止冲突要三层配合:规划层做归属划分,把改动按文件或符号分给不同 Agent,尽量避免交叉;执行层做互斥,同一文件同一时刻只允许一个 Agent 持有写权,进程内用锁、跨机器用分布式锁(Redis 的 SET NX PX 加唯一持有者标识、释放时用 Lua 校验标识,或用 Redisson 的可重入锁加看门狗续期);写入层做乐观校验,落盘前比对文件内容哈希,不匹配就拒绝并重新读取合并,最后还要有一层合并与冲突检测(类似 git 的 diff/merge),冲突无法自动解决时挂起来让人处理。分布式锁的边界要讲清:单实例 Redis 在主从切换时可能丢锁,所以关键路径要叠加幂等与文件版本校验,锁只是降低冲突概率,不是正确性的唯一保证。

Codebase Memory、检索时机与代码审查 Agent。 Codebase Memory 通常是「符号表 + 依赖图 + 向量索引 + 摘要」的组合:初始化时全量扫描建索引(按文件解析 AST 抽符号、抽 import 与调用关系、生成文件与函数级摘要、切块做 embedding),更新维护靠增量——监听 git 提交或文件变更,只重建受影响文件的符号与向量,并定期全量校验(比如用文件哈希判断哪些块过期)。检索的时机有四处:定位改动点、找调用方与影响面、找同类实现与规范、以及生成测试时找已有测试样例。调优方向是混合检索(关键词加向量)、按符号而非按文件返回、加元数据过滤(语言、目录、是否测试文件)、以及 rerank 精排。代码审查 Agent 不该做全量逐行扫描(成本高、噪音大、且大部分行没改),正确做法是只审查变更 diff 加其影响面:先用静态分析工具跑规则类问题,再让模型聚焦逻辑正确性、边界条件、并发与安全,并要求每个意见给出文件、行号、原因与建议,最后按严重程度分级,避免把「风格偏好」混成「必须改」。

手撕 LRU 与三个追问。 基础实现是哈希表加双向链表:哈希表做 O(1) 定位,双向链表维护访问顺序,get 命中就把节点移到头部,put 时若已存在则更新并前移,容量超了就淘汰尾节点;用哨兵头尾节点可以省掉大量边界判断(这是最容易写错的地方:容量为 1、更新已有 key 忘记移动、指针顺序写反)。支持泛型用 Map<K, Node<V>> 与泛型节点即可。加过期时间有两种设计:一是每个节点存 expiresAt,get 时惰性判断(过期就删并返回 null);二是额外维护一个按过期时间排序的结构(最小堆或时间轮)做主动清理,前者实现简单但过期数据会占内存,后者内存更可控但要多一份结构;过期时间存哪里取决于语义——按 key 独立过期就存节点上,全局统一 TTL 存配置里并由清理线程或时间轮统一驱动。线程安全的做法有粗有细:整体加 synchronized 或 ReentrantLock 最简单但会串行化全部读写;更细的粒度是只对链表结构调整加锁、或用分段锁(按 key 哈希分桶,桶内独立加锁),工业界通常直接用 Caffeine(W-TinyLFU,读多写少命中率更好)或 ConcurrentLinkedHashMap。synchronized 与 ReentrantLock 的选择:前者是 JVM 内置、自动释放(异常安全)、有偏向锁与锁升级优化,适合锁范围小、无额外需求的场景;后者支持可中断获取、超时获取(tryLock)、公平锁与多条件变量,适合需要避免死等或要按条件唤醒的场景。面试时把「锁保护的是不变式(哈希表与链表的对应关系),而不是某个方法」这句话讲出来,比背 API 更有说服力。(第 32 问的项目追问要点:Agent 之间通过共享的消息总线或内存中的任务图传递结构化消息,关键写操作与高风险改动设人工确认点,控制靠预算、权限与终止条件,观测靠全链路 trace 与每步的输入输出留痕;数据上云的顾虑要靠租户隔离、传输加密与最小必要上传来回答;用 ForkJoinPool 通常是因为要把代码扫描、检索、构建这类可切分的计算任务做工作窃取并行。)