面灵AI→

小红书 Agent 开发一面:多子图架构、Skills 机制与沙箱调度

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

《面试题目》

  1. 自我介绍。
  2. 实习、项目背景讲解。
  3. 自动切题 Agent 中的 7 步 Pipeline 流程是什么?
  4. LangGraph 设计中的 3 个子图 Agent 是怎么传递的?
  5. 为什么你这边选择拆成三个子图,而不是写成一条 Pipeline 呢?
  6. 怎么搭建这个 Skills 知识进化的流程的?
  7. 为什么不在 Prompt 中添加,而使用 Skills 的方式?
  8. Agent 开发框架是用什么?是哪个语言?
  9. MySQL 索引的数据结构是什么样子?有哪几种?
  10. 请你介绍一下 MySQL 中 B+ 树索引特点?
  11. B+ 树和 B 树的区别是啥?
  12. 你能举例平时用到的 MySQL 乐观锁、悲观锁的场景吗?
  13. 什么情况下一般会加表锁,什么情况下会加行锁?
  14. AI Coding 工具使用情况,使用 Claude 有什么习惯?
  15. 总结一套 AI Coding 使用经验?
  16. 手撕:最长递增子序列。
  17. 你的 Agent 项目遇到过最大的工程挑战是什么?怎么解决的?
  18. 多租户场景下,Agent 系统怎么做隔离?模型资源和沙箱资源怎么划分?
  19. 如果并发 1000 个沙箱实例,你怎么设计资源池和调度策略?
  20. 服务重启后,Agent 的会话状态怎么恢复?状态存在哪?
  21. 长周期任务中,Agent 容易「行为漂移」,你怎么检测和纠正?
  22. 你怎么评估 Agent 生成代码的质量?有没有做过自动化评测?
  23. 设计一个代码审查 Agent,怎么保证它不漏掉关键安全漏洞?
  24. RAG 和长上下文模型,你觉得未来是替代关系还是互补?为什么?
  25. 你怎么理解 Agent 中的长短期记忆?实际项目中分别如何实现?
  26. 是否了解 KV Cache?它在大模型推理过程中起到什么作用?
  27. Agent 中的 Skill 机制了解多少?Skill 渐进式加载是如何实现的?为什么需要这种设计?
  28. Redis 缓存击穿、缓存穿透、缓存雪崩三兄弟是什么,如何解决?
  29. 多台机器如何生成唯一 ID?如果要求这个 ID 是自增的呢?
  30. 你怎么理解 Agent 和普通大模型调用的本质区别?除了工具调用,Agent 还多了哪些能力?
  31. 如果 Agent 调用外部服务超时了,你的超时时间怎么设置?重试策略是什么?重试会不会导致重复操作?
  32. 在电商场景下,Agent 如何保证库存扣减的准确性?幂等怎么设计?
  33. 你们的 Agent 系统怎么处理分布式事务?Saga 模式?TCC?还是别的方案?
  34. 如果让你设计一个「智能客服 Agent」来处理退换货,你会怎么设计?涉及哪些工具?
  35. 你们的 Agent 和传统 API 服务有什么区别?在部署、监控、运维上有什么不同?
  36. 手撕题追问:如果要求输出具体的子序列而不只是长度呢?如果数组长度是 10^7,你的 O(n log n) 算法还能优化吗?如果数组是流式输入的,怎么处理?

《参考解析》

为什么拆多个子图,以及 Skills 知识进化机制:线性 Pipeline 的优点是链路清晰、延迟低,但任务一旦出现分支、多轮迭代(生成—检查—修正)或不同阶段要用不同工具集,就会退化成一个巨型 Prompt 加一堆 if。拆子图的价值在于:每个子图有独立的提示词与工具白名单、可单独评测与灰度,失败时能在子图边界重试,配合 LangGraph 的检查点还能断点续跑与人工介入;子图之间用共享 State 传递字段,契约显式。判断标准是「有没有循环、分支、人在环或需要独立观测的阶段」,有就拆,没有别为架构好看而拆。知识做成 Skill 而不是写进 Prompt,是因为长提示词会稀释注意力、抬高成本,改知识还要改提示词发版;Skill 把方法与经验封装成独立单元,模型先看简短索引、需要时再按需读入正文,这就是渐进式加载。进化闭环是「Trace 收集—提炼共性—评审—入库—评测验证」,同时分清 RAG 管事实性知识、Skill 管程序性知识。

MySQL 索引与锁:B+ 树是索引的基础,锁的粒度取决于索引。 InnoDB 的索引结构是 B+ 树,主键索引(聚簇索引)的叶子节点直接存整行数据,二级索引的叶子节点存主键值,所以二级索引查非索引列需要回表,覆盖索引能避免这次回表。B+ 树相对 B 树的关键差异是:只有叶子节点存数据、内部节点只存键,所以同样大小的页能放下更多键、树高更低(通常 3 到 4 层就能覆盖千万级数据)、IO 次数更少;叶子节点之间用双向链表串起来,天然支持范围扫描与排序,而 B 树的范围查询需要中序遍历回溯。索引类型还有哈希索引(等值快、不支持范围)、全文索引与空间索引。锁方面,乐观锁不是数据库锁,而是「版本号或时间戳 + 更新时带条件判断」的应用层方案(UPDATE ... WHERE version = x),适合读多写少、冲突少的场景;悲观锁是 SELECT ... FOR UPDATE,适合冲突激烈、必须串行化的场景。InnoDB 默认加行锁,但行锁是加在索引记录上的——如果 WHERE 条件没有走索引,就会退化成锁全表(扫描到的所有记录都加锁);表锁常见于显式 LOCK TABLES、DDL 操作、以及无法走索引的大范围更新。可以补一句:RR 隔离级别下还会加间隙锁来防幻读,所以「没命中记录」也可能阻塞插入。

Agent 的记忆、KV Cache 与 RAG:三个容易混淆的概念要分清。短期记忆是当前会话的上下文,实现方式是消息列表加上长度管理策略(滑动窗口、摘要压缩、把早期对话落库后只把摘要放回上下文);长期记忆是跨会话沉淀的用户偏好、事实与经验,通常落在向量库或结构化存储里,写入时机要设计(显式「记住这个」、或异步抽取实体与偏好),召回时按相关度与时效加权,还要有过期与删除机制避免错误记忆长期生效。KV Cache 是推理侧的优化:自注意力计算中已经算过的键值对按层缓存下来,生成下一个 token 时直接复用,把每步复杂度从与序列长度的平方关系降到线性——它和上下文长度、并发批大小强相关,显存占用随序列变长线性增长,因此有 PagedAttention、前缀共享(同一系统提示词复用缓存)这类工程手段,Agent 场景里把固定前缀提到最前面能显著命中缓存。RAG 与长上下文不是简单的替代关系:长上下文适合少量文档的深度理解与全局推理,RAG 适合海量、频繁更新、需要权限过滤与可溯源的场景,实践中常见组合是「RAG 负责召回候选,长上下文负责精读与多跳推理」,评估时要看成本、延迟、准确率与可解释性四个维度而不是只看窗口大小。

工程化追问:多租户、沙箱调度、状态恢复与行为漂移。多租户隔离分三层:数据层(每张表带租户 ID 并强制在查询构造器里注入过滤,或按租户分库分表),计算层(模型调用的并发配额、Token 配额、限流按租户记账,避免一个租户把号池占满),执行层(沙箱按租户分组、网络与文件系统隔离、凭据不落沙箱)。1000 个并发沙箱的资源池设计要讲清四件事:资源池化(预创建并维护热池,减少冷启动延迟)、调度策略(按优先级加公平队列,短任务优先避免队头阻塞,设置租户级并发上限防止单租户打满)、弹性伸缩(按队列长度与水位扩容,空闲超时回收并保留少量热实例)、健康与兜底(心跳探活、超时强杀、失败重试走新实例)。会话状态恢复的答案是「状态外置」:把会话上下文、图状态、工具调用记录与中间产物持久化(Redis 放热状态、对象存储放大产物、数据库放结构化记录),服务重启后按 session_id 重新加载,正在执行的长任务要能从最近一个检查点续跑,而不是从头再来。行为漂移的检测可以靠三类信号:目标一致性(周期性用轻量模型或规则校验「当前动作是否还服务于原始目标」)、进度停滞(超过 N 步没有产生新的有效产出)、以及异常模式(重复调用同一工具、参数在兜圈);纠正手段是在提示词里重新注入目标与已完成事项、对步骤数设上限并触发重新规划(re-plan)、必要时中断并交回人工。

Agent 的可靠性设计:超时、重试、幂等、事务与唯一 ID。超时时间应该分层设置并与下游的 P99 对齐(一次调用给 2~3 秒、整体步骤给 30 秒),并区分「连接超时」与「读超时」;重试只对可重试的错误做(网络抖动、限流、5xx),对参数错误和 4xx 不重试,退避策略用指数退避加随机抖动,同时设总时长与次数上限,防止放大下游压力。重试必然带来重复执行的隐患,所以写操作必须幂等:由调用方生成幂等键(会话 ID + 步骤 ID + 工具名 + 参数哈希),服务端以幂等键建唯一索引,把「创建」实现成 upsert,重复请求直接返回首次结果;库存扣减是把幂等键与「条件更新」结合的典型——UPDATE stock SET num = num - 1 WHERE sku = ? AND num >= 1 依靠数据库的行锁保证不超卖,再配一条幂等流水去重,绝不能「先查再减」。分布式事务这块,Agent 编排跨服务写操作时优先选柔性方案:本地消息表或事务消息保证最终一致,长流程用 Saga(每一步定义补偿动作,失败逆序补偿),强一致要求高且链路短时可用 TCC,但要把空回滚、悬挂与幂等三种异常处理写清楚。多机唯一 ID 常用雪花算法(时间戳 + 机器 ID + 序列号,趋势递增且本地生成),要求严格自增就只能用集中式发号器(数据库自增段、Redis INCR 批量取号)或号段模式,代价是单点与可用性权衡。缓存三兄弟要逐个给出机制与解法:穿透是查不存在的数据,用布隆过滤器或空值缓存挡住;击穿是热点 key 过期瞬间大量请求打到数据库,用互斥重建(分布式锁)或逻辑过期加异步刷新;雪崩是大量 key 同时失效或缓存服务整体故障,用过期时间加随机抖动、多级缓存与限流降级兜底。最后,Agent 系统与传统 API 服务的运维差异在于:它是不确定性的(要按分布看指标、要做评测集回归)、长时延(要异步任务化与进度可观测)、成本直接受 token 影响(要按租户与任务记账),所以监控要覆盖 Trace、工具调用成功率、Token 与成本、以及业务级质量指标。

手撕最长递增子序列及其追问:从 O(n²) 到 O(n log n),再到方案还原与流式处理。基础解法是 dp[i] 表示以 i 结尾的最长长度,转移 dp[i] = max(dp[j]) + 1 (j < i, a[j] < a[i]),复杂度 O(n²);优化解是维护一个「长度为 k 的递增子序列的最小结尾值」数组 tails,它对每个输入做二分查找(lower_bound),整体 O(n log n),注意 tails 的长度是答案、内容不是答案本身。要求输出具体子序列时,DP 版本记录每个位置的前驱下标,最后从最优结尾回溯;O(n log n) 版本则维护 pos[i] 记录「长度为 i 的子序列结尾元素在原数组中的下标」和 prev[i] 前驱,每次替换时同步更新前驱,回溯照样可行。n 到 1e7 时,瓶颈已经从时间复杂度转到内存带宽与二分查找的缓存不友好,可行方向是按值域分桶加稀疏结构、用值域线段树 / 树状数组的 O(n log V)、或分块预处理,并且要考虑用位运算与顺序扫描优化常数;严格来说 1e7 规模下 O(n log n) 仍是可行量级,但要警惕内存占用与递归栈。流式输入(不允许保存全部数据)时,精确解需要保存所有「候选结尾」信息,空间上界与当前 LIS 长度相关,标准做法是维护 tails 数组加每条链的链表指针,空间 O(n) 但可按需压缩;如果允许近似,可以用基于采样或分位数的方法给出误差有界的估计。面试时先写清 O(n log n) 版本,再逐步回应三个追问,比一上来就说最优解更能展示思路。