面灵AI→

北京都学网后端面经:项目贡献边界、SingleFlight 与长时面试数据保护

时间
2026-10
来源
牛客网

《面试题目》

项目职责与边界

  1. 你在 AI 面试平台项目中具体负责哪部分?Agent 模块是谁做的?
  2. 简历里写的「负责完成」,具体边界是什么?
  3. 这个项目是实验室开源项目吗?团队几个人?
  4. 另一个 AI Coding Agent 项目是你独立搭建的吗?
  5. 这两个项目目前是持续维护阶段,还是已经完结的历史项目?

技术实现深挖

  1. 为什么引入 SingleFlight 机制?具体怎么实现的?
  2. SingleFlight 的底层实现细节是什么?去重与复用逻辑是怎么做的?
  3. 长时面试数据丢失风险怎么解决?为什么引入 MongoDB 做热存档?
  4. 除了热存档,还有哪些前置防护机制?比如幂等判断、答题锁?
  5. 讯飞 ASR 语音识别转发做了哪些去重和优化处理?
  6. AI Coding Agent 的创新点是什么?为什么做这个项目?
  7. 上下文四层压缩与任务边界切换具体怎么实现?
  8. 任务边界切换中,轻量级判别模型返回什么?性能损耗怎么解决?
  9. 任务标识(哈希值)怎么存、怎么共享状态?
  10. 子 Agent 有哪些?它们之间是什么关系?上下文是否独立?

业务匹配与规划

  1. 后续有什么改进计划?
  2. 能稳定实习多久?是否可以到岗?

《参考解析》

贡献边界怎么答:这类追问不是要否定你,而是在验证「你是不是真的做过」。最稳的结构是「我负责的模块 + 我做的具体决策 + 可验证的结果」。例如:Agent 模块的会话编排与工具调用是我写的,RAG 检索和前端是队友做的;我主导了 SingleFlight 的 key 设计和降级方案,压测数据是我跑的。不要把所有功劳揽到自己身上,也不要因为怕被追问就把自己缩成「只写了几个接口」——面试官更看重你能否清楚说出边界、以及边界内你解决了什么问题。被问「是不是实验室开源项目」时如实说,开源项目也能讲出自己的增量。

SingleFlight 为什么引入、怎么实现:动机是「相同请求在并发时只执行一次,其余请求等待并复用结果」。典型场景是缓存击穿、AI 接口按 token 计费、同一个会话被多次重试。单机实现用一个 map[key]*call 加互斥锁:第一个请求创建 call(含 sync.WaitGroup 和结果字段)并执行,后来的请求拿到同一个 call 后 Wait(),执行完统一读结果;要处理 panic,保证 Done() 一定被调用,否则等待者会永久阻塞。分布式场景要把「选举」放到 Redis:SET key owner NX PX ttl 抢锁,抢到的执行并把结果写回结果 key,没抢到的轮询或订阅结果;关键是锁的过期时间要覆盖执行时间并支持续约,结果要有明确的成功/失败状态,不能让等待者读到半成品。

长时面试为什么用 MongoDB 热存档:一场面试可能持续几十分钟,状态包括当前题目、已答内容、评分中间结果、工具调用记录。只放内存,进程重启或连接断开就丢;只放 MySQL,高频小写入会让行锁和事务开销变大,而且状态结构经常变、字段不固定。MongoDB 的文档模型适合存这种半结构化快照,写入是追加/整文档替换,按 sessionId 建索引读取快,配合 TTL 或归档策略控制体量。设计上要做「写穿」:关键状态变更先同步写热存档再返回,或者用本地队列 + 定时刷盘,并记录版本号防止旧快照覆盖新状态。

前置防护:热存档是兜底,前面还要有三道。① 幂等:每个答题动作带 sessionId + questionId + attemptId 的唯一键,重复提交直接返回已有结果,避免重复计费与重复评分;② 答题锁:同一会话同一题同一时刻只允许一个写者,用 Redis 锁或数据库乐观锁(版本号)实现,防止用户多端同时提交;③ 心跳与断线重连:WebSocket 断开后按 sessionId 恢复,服务端返回最后确认的状态版本,客户端从该版本继续,而不是从头再来。再加一条:所有写操作先落「意图日志」再执行,崩溃后可以重放。

讯飞 ASR 转发的去重与优化:流式识别返回的是带序号的分片结果,可能重复、乱序、跨连接重发。去重靠「会话 ID + 分片序号」做幂等,已经处理过的序号直接丢弃;拼接要按序号排序,遇到缺口先缓存等补包,超时再标记缺失,不能把乱序文本直接拼进上下文。优化上:按 VAD 切句、静音段不转发;连接断开时用最后确认的序号续传;把中间结果和最终结果分开存,最终结果才进面试记录;对同一句话的多次修正取最后一次定稿,避免上下文里出现重复语义。

AI Coding Agent 的创新点:不要只说「用了大模型」。可讲的点:把「任务边界识别」做成显式步骤——先判断当前请求属于哪个任务、需要哪些上下文,再决定加载哪些文件与工具,避免把整个仓库塞进上下文;上下文分层压缩(系统约束、任务目标、近期对话、工具结果各自有不同的保留策略);工具调用带沙箱与权限,写文件前先做 diff 预览;用子 Agent 做「探索/修改/审查」分工,降低单次上下文污染。为什么做:现有助手在长任务里容易跑偏、重复读文件、改一处坏一处,想验证「边界 + 隔离」能不能提升一次通过率。

上下文四层压缩:可以按「不可丢 / 可摘要 / 可检索 / 可丢弃」分层。第一层是系统约束与用户硬性要求,原样保留;第二层是任务目标与当前计划,压缩成结构化列表;第三层是历史对话与工具结果,按时间与相关性做摘要,保留结论、丢弃原始输出,需要细节时再按引用回查;第四层是临时观察(日志、文件片段),只保留最近若干条或转成向量索引。压缩的触发点按 token 预算而不是按轮数,压缩后要保留「谁在什么时候决定了什么」,否则模型会重复已经否决的方案。

任务边界切换与轻量判别模型:判别模型接收最近的对话与工具调用摘要,输出「任务标签 + 置信度 + 是否需要切换」这样的结构化结果,而不是自由文本;置信度低于阈值时不做切换、走保守路径。性能上不用每次请求都跑:用规则先过滤(关键词、工具名、文件路径),命中歧义再调小模型;小模型只做分类、输入做截断,结果按「对话轮次 + 摘要哈希」缓存,避免重复推理。判别失败要能回退到「沿用当前任务」,不能因为分类错误把上下文清空。

任务标识与子 Agent:任务标识用「会话 ID + 任务类型 + 目标对象(文件/模块)」的稳定哈希,存在会话状态里,同时写 Redis 供多个进程共享;子 Agent 启动时继承任务标识但不继承完整上下文,只拿到任务目标、约束和必要的文件引用,结果以结构化摘要回传主 Agent。子 Agent 之间上下文隔离,避免互相污染;共享的是「事实层」(文件内容、命令结果)而不是「推理过程」。常见拆分是探索者(读代码、定位)、修改者(写代码)、审查者(跑测试、挑问题),主 Agent 负责编排与最终决策。

改进计划与实习安排:改进计划要具体到「下一步做什么、怎么衡量」:比如把 SingleFlight 的等待超时与降级做成可配置、给 Agent 加工具调用预算与死循环检测、把上下文压缩从按轮数改成按 token 预算、补一套离线评测集。实习时间如实回答,能到岗就说到岗时间与每周天数;如果还有课程,说明如何协调,不要含糊其辞。