拼多多 AI Agent 二面:双机位、1M 上下文也不够怎么办?
- 轮次
- 二面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 介绍你的 Agent 代码生成链路全流程。
- 从用户输入到代码输出中间经过哪些步骤?
- 长流程下上下文超限怎么解决?
- 如果 1M 上下文窗口还不够怎么办?
- 长流程任务中途服务重启,怎么恢复未完成任务?
- 断点续传怎么设计?存了哪些状态?存在哪里?
- 多 Agent 同时修改同一个文件怎么防止冲突?
- 有没有用分布式锁?
- Codebase Memory 怎么初始化和更新维护?
- 知识库检索在什么时机使用?具体怎么实现?
- 知识库检索调优做了吗?怎么调的?
- 代码生成有什么多 Agent 保障机制?
- 测试用例是怎么生成的?
- 大规模代码场景下 Agent 上下文怎么维护?
- 代码审查 Agent 怎么做的?全量逐行扫描吗?
- 怎么防止用户重复创建相同任务?
- 你平时常用哪些数据库?
- MySQL 索引底层是什么结构?
- 为什么选用 B+ 树而不是 B 树?
- B+ 树叶子节点存储空间多大?
- 索引存在硬盘还是内存?
- Spring Bean 注入的逻辑是怎样的?
- 大模型的缓存机制了解吗?
- 代码生成系统中长耗时 Agent 占用资源、影响并发,怎么优化?
- Agent 怎么实现不同任务的隔离?
- Agent 如何保证分析、输出结果准确性?
- Agent 评测体系、测试用例覆盖范围?
- LangChain 与 LangGraph 核心区别?
- 使用什么 Agent 开发框架?自研框架和开源框架差异?
- 线程安全的 LRU 怎么实现?
- 手撕:实现一个 LRU Cache,要求 get 和 put 都是 O(1)。追问:支持泛型和过期时间怎么改?过期时间存哪里?多线程环境下怎么保证线程安全?synchronized 和 ReentrantLock 怎么选?
具体项目追问
- 你的多 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 通常是因为要把代码扫描、检索、构建这类可切分的计算任务做工作窃取并行。)