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