# 小红书 AI 研发一面:HR 说业务很感兴趣,面试官却说“感觉没啥东西”
面经时间:2026年10月12日
前面那篇快手 Agent 开发一面,聊到了可观测性和评测。这次小红书 AI 研发工程师一面,直接围着蚂蚁实习里的 Agent 采集、观测平台和评测回放往下问。

但这场面试最让人记住的,还真不是题。
很爽的一次面试。本来我都主动结束流程了,HR 还是找我,说业务对我很感兴趣,让我再参加一下。那就参加吧。
没想到,面试官有点傲。那股“阿里味”一下就上来了,我心里都想问:你是阿里来的吧?!当然,这是当时的吐槽,不知道他实际是什么背景。
既有问题分享给群友,又可以有乐子。来,按顺序聊。
下面的面试顺序、冲突和情绪来自原始记录;技术回答是补充的口述参考,第一人称不代表当事人真实说过或做过。 材料没有提供完整架构,采集、查询和回放统一用一套 Agent 观测平台设计示例来解释;多智能体另用“资料检索与报告整理”的最小示例,不把它编成蚂蚁的真实业务。
本次面经问题:
- 一、开头自我介绍
- 二、介绍在蚂蚁实习的内容
- 三、Agent 的采集怎么做?这么多 Agent,要一个个接吗?
- 四、Agent 观测平台的查询怎么优化?
- 五、Agent 评测回放是什么流程?怎么搭建?
- 六、手写最长公共子序列
- 七、多智能体项目做什么?怎么协同?协议是什么?
- 八、Redis 分布式锁、MySQL 索引原理与索引失效场景
- 九、反问:你们的业务怎么做?
# 一、开头自我介绍
开头还是正常流程,自我介绍。
这一段别把简历从上到下念一遍。我会把重点放在后面能展开的两件事上:蚂蚁实习里的 Agent 工程工作,以及多智能体项目。
口述可以这样组织:“您好,我主要关注 AI 应用研发和后端工程。实习部分,我想重点介绍 Agent 采集、观测查询和评测回放这条链路;项目部分,可以展开讲多智能体的任务拆分、结果汇总和异常处理。”
接着明确自己具体负责哪一段、交付了什么。学校、技术栈、实习时间按真实情况补,没参与的模块别顺手认领。
自我介绍里说出去的每个词,后面都有可能变成一道追问。 什么都往上塞,面试官真顺着问了,自己先难受。
# 二、介绍在蚂蚁实习的内容
这一题我会先讲业务为什么需要平台,再讲个人工作。
以这套示例来说,Agent 出问题时,只看最终回复很难定位:是检索没找到资料、模型选错工具、接口报错,还是重试把耗时拖长了?平台要把一次任务的执行过程串起来,让开发者能查到具体哪一步出了问题。
我的介绍会沿着“采得到、查得动、能复盘”展开。采集侧把不同接入方的数据转成统一事件;查询侧支持任务列表和单次 Trace 详情;评测侧从失败任务里整理样本,拿来验证后续改动。
如果个人只负责查询接口,就直接说:“采集协议是团队已有的,我负责查询链路和对应的数据组织。”自己改了什么 SQL、接口返回怎么变、验证怎么做,再往下展开。
没有实测记录,就讲验证方法,别临时报一个“性能提升 80%”。面试官追问一句数据怎么来的,气势再足也没用。
# 三、Agent 的采集怎么做?这么多 Agent,要一个个接吗?
到这里开始有意思了。
我刚讲采集,面试官就说,他觉得我们做这个没用,这么多 Agent,一个个接很麻烦。
我直接怼了:“我们有规范协议,只是分类。你要慢慢听我说,你着急干什么?”
问接入成本当然可以。但我方案还没讲完,你先把“没用”下了,那咱们还聊什么?
技术上,这里要先解释清楚:Agent 有很多种,不等于平台为每个 Agent 单独写一套采集链路。 我会把业务名称和事件类型分开。业务侧可以有不同 Agent,观测侧统一收任务、模型调用、工具调用等事件,通过字段区分归属。
下面是自定义采集协议的示意字段,不代表某个标准协议的原文:
{
"schema_version": "1",
"event_id": "event-001",
"tenant_id": "demo-team",
"agent_id": "report-agent",
"trace_id": "trace-001",
"span_id": "span-002",
"parent_span_id": "span-001",
"event_type": "tool.end",
"status": "error",
"duration_ms": 120,
"payload_ref": "payload-001"
}
2
3
4
5
6
7
8
9
10
11
12
13
框架适配器或者统一执行层负责把实际调用转换成这类事件。接入一种新框架,可能确实要补适配,但不需要为每个业务 Agent 重写存储、查询和展示。trace_id 串起任务,父子 Span 表达调用关系,event_id 用于识别重复上报,协议版本用于处理字段演进。
采集也不能只讲成功路径。我会用有界缓冲做异步批量上报,设超时和重试;接收端校验字段,按事件 ID 去重。队列满了怎么丢、上报失败多少、任务是不是缺了一段,都要可见。对必须留存的审计记录,另设可靠落盘和补偿机制,不能默认普通遥测采集就够了。

图里任务级和步骤级的拆分,正好解释为什么不同 Agent 能统一展示。大段输入输出可以按权限脱敏后另存,查询先拿摘要和引用,也能接上后面的性能优化。
我也会承认:统一协议有维护成本,适配器有覆盖不到的地方。但要讨论的是接入成本、字段够不够、排障有没有用。你得先听完,再判断值不值得做。
# 四、Agent 观测平台的查询怎么优化?
我讲了一些优化点,他又来一句:“你这不就是啥啥啥,感觉也没有啥东西啊。”
这我就不太想惯着了。结合业务场景,这些就是最有效的,那你怎么做?总不能因为方案听着熟,就当问题不存在吧。
如果补完整回答,我会先分清两类请求:任务列表查摘要,Trace 详情查步骤。 列表只展示时间、状态、耗时、Token 等字段,不把每一步的完整 Prompt 和工具输出都拉回来。点进某个 Trace,再按需加载步骤和大字段。
以 MySQL 存任务摘要为例,如果常见请求是“查某租户最近的失败任务”,我会评估 (tenant_id, status, start_time, id) 这样的联合索引。这里是示例设计,不能拿它覆盖所有筛选条件。经常不带状态查最近任务,就还要单独分析那类请求的执行计划。
翻页也有区别。往后翻很多页时,大 OFFSET 会让数据库扫描并丢弃大量记录。我会按 start_time, id 做稳定排序,用上一页末尾的两个值继续向后查。代价是它更适合连续翻页,不能直接保留任意跳页的交互。
Trace 详情则要检查有没有 N+1 查询:前端要展示一组步骤,服务端却按步骤逐个查库。能批量拿的就批量拿,超长 Trace 分段返回。最近数据和历史归档可以分开处理,但得让用户知道查不到是没有数据,还是还没加载归档。
这些改动怎么证明有效?我会固定查询条件和数据分布,对照执行计划、扫描行数、返回字节数以及 P95、P99。还要看瓶颈是在 SQL、数据反序列化,还是页面渲染,不能只盯着数据库。
优化有没有价值,得看瓶颈有没有被解决。 索引、分页、减少大字段传输,确实都不新鲜。可用户打开列表就是慢,那把它改快了,有什么问题?
真觉得不够,可以继续问索引写入代价、复杂筛选怎么办、历史数据怎么查。这些我都愿意聊。“感觉没东西”这一句,反而没给多少技术信息。
# 五、Agent 评测回放是什么流程?怎么搭建?
这题估计是前面被我怼了,他没再直接说那些贬低的话。但我当时还是能感觉到轻视。
那就继续讲细节。有理有据,方案哪里有问题你指出来。
我会先把“回放”分成两件事:展示历史执行过程,和重新执行一个评测任务。 前者读取已记录的事件,重建当时的调用关系;后者要准备输入、运行配置和工具环境,启动一次新任务。看着历史日志播放,不等于已经具备可重复执行能力。
示例里的流程是:从失败 Trace 中筛选案例,脱敏并确认能否用于评测,整理为固定样本,再交给评测执行器运行。样本里记录原始输入、必要上下文或数据快照、预期结果或判定规则,以及 Prompt、模型、工具等版本信息。原始 Trace ID 留作关联,新运行分配新 ID,避免混淆两次执行。
工具怎么处理,是这里最该讲清楚的地方。离线回放可以用录制响应替代真实工具结果,用于隔离模型和 Prompt 的变化;参数要经过规范化匹配,匹配不到应显式报错,不能随便塞一个旧结果。需要验证真实接口时,就在隔离环境重新执行。发消息、写数据这类有副作用的操作,要用沙箱或替身处理。
两种方式测到的东西不同:录制回放不能证明今天的真实接口还能用,重新执行也可能因为外部数据变化而与历史结果不同。即便参数尽量固定,模型输出也未必逐字一致,所以要按任务目标评估,不追求把旧答案原封不动复刻出来。
评测结果我会拆开看:任务是否完成、关键步骤是否符合约束、工具错误和重试情况、总耗时和 Token。能用规则判的先用规则;开放回答需要模型评分时,明确评分标准,并用人工抽检检查偏差。不能只因为另一模型说“挺好”,就认定通过。

图里从 Trace 到异常回放、再到回归评测的连接,就是这套平台的用途。具体怎么组织指标,可以接着看Agent 评估方法;采集和失败归因可以看Agent Harness 可观测性。
方案可以继续被追问:数据快照不全怎么办,工具版本没保存怎么办,评分器自己不稳定怎么办?这些都是实际问题。说清楚哪里能复现、哪里只能近似复现,比一句“我们支持回放”有用多了。
# 六、手写最长公共子序列
然后写题,最长公共子序列。
这个基本上二维 DP 开撕了。他还继续问我问题。前面聊得再上头,这里也得把状态定义讲清楚,别一边怼人一边把递推写错,那就真有乐子了。
原材料没说用什么语言,下面用 Python 给出参考。按常见题意,返回两个字符串的最长公共子序列长度。子序列允许不连续,但相对顺序不能变。 别写成最长公共子串。
定义 dp[i][j] 为 text1 前 i 个字符和 text2 前 j 个字符的最长公共子序列长度。空串与任意字符串的答案都是 0,因此第 0 行和第 0 列初始化为 0。
当前字符相同,可以接在两边各去掉一个字符的最优结果后面;不同,就分别尝试舍弃一边的当前字符,取较大值。
def longest_common_subsequence(text1: str, text2: str) -> int:
m, n = len(text1), len(text2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i - 1] == text2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
return dp[m][n]
2
3
4
5
6
7
8
9
10
11
12
比如 abcde 和 ace 的答案是 3,对应 ace;abc 和 def 是 0。时间复杂度 O(mn),空间复杂度 O(mn)。
如果要求输出具体序列,就从右下角回溯:字符相同时收集字符并往左上走,否则沿着保持最优值的方向走,最后反转结果。只求长度时可以滚动数组压缩空间,面试现场先把二维版本写稳。
# 七、多智能体项目做什么?怎么协同?协议是什么?
这一题别一上来报一串角色名。Planner、Executor、Reviewer 都说完了,面试官还是不知道项目到底在干什么。
材料没给真实项目用途,我用“资料检索与报告整理”举例:用户提出一个研究问题,主 Agent 拆出几个边界明确的检索子任务,子 Agent 各自查资料并返回证据,主 Agent 核对冲突、整理报告。只有子任务能独立推进、上下文适合隔离时,才考虑这样拆。
如果几个步骤本来就是固定顺序,普通工作流就够了。多个角色轮流改写同一段话,也不一定比单 Agent 更好,反而多了耗时和信息损失。
协同时,执行层负责派发任务和维护状态。主 Agent 提出任务拆分后,调度器校验并启动子任务;子 Agent 返回结构化结果,执行层记录完成、失败或超时,再由主 Agent 汇总。模型说“已派发”,不等于任务真的已经运行。
协议我会落到字段上讲。任务包里包含 task_id、父任务 ID、目标、必要上下文引用、允许的工具、输出格式和预算;结果包返回状态、结论、证据引用、未解决问题和失败原因。重试用同一任务关联新的尝试编号,汇总时防止旧尝试覆盖新结果;超时、取消和部分失败都要有明确状态。

图里的任务包解释了为什么不能只传一句“帮我查一下”。子 Agent 得知道查到什么程度、能用哪些工具,以及该怎么交付。更多通信与汇总细节,可以看多 Agent 通信与编排。
“协议是什么”也别急着回答 MCP。要先确认问的是观测事件协议、Agent 任务消息格式,还是工具接入协议。这几个层次不同。没接过某个标准,就说项目使用自定义消息格式,把字段、状态流转和兼容方式讲明白,比硬报名字强。
# 八、Redis 分布式锁、MySQL 索引原理与索引失效场景
后面开始八股。Redis 分布式锁、MySQL 索引原理、索引失效场景。
还好自己还记得!!咱没有丢!
Redis 分布式锁怎么答?
以常见的单实例锁为例,用 SET key token NX PX ttl,把“仅在不存在时写入”和“设置过期时间”放到一次原子操作里。token 每次获取都不同,释放时用 Lua 原子比较持有者标识再删除,不能自己直接 DEL,也不能分成客户端的先查再删。
但过期时间到了,旧客户端不一定已经停止工作。它可能卡住后恢复,此时另一个客户端已经拿到锁。续租能减少正常长任务超时的问题,不能消除所有暂停和网络故障风险。对严格互斥的写入,还得考虑由下游校验递增的 fencing token,拒绝过期持有者的操作,并结合数据库约束等机制守住结果。
再补一句主从切换:异步复制时,锁尚未复制主节点就故障,新主可能再次允许获取。有了 Redis 锁,不等于所有故障条件下都能保证互斥。 在评测任务示例里,锁可以减少重复启动,但任务状态更新和结果落库仍要幂等。更完整的基础可以看Redis 分布式锁。
MySQL 索引为什么常用 B+ 树?
这里以 InnoDB 常见索引结构为前提。B+ 树内部节点主要保存用于导航的键和子节点指针,扇出较大,树可以比较矮;叶子节点按键有序并相互链接,定位到起点后适合继续做范围扫描。
聚簇索引叶子保存行记录,二级索引叶子包含索引列和主键。二级索引里没有查询所需字段时,通常还要根据主键回表;字段被索引覆盖时,可以减少这部分访问。回到观测列表,为什么避免 SELECT *、为什么设计联合索引,这里就接得上了。结构细节可以看MySQL B+ 树索引原理。
索引失效有哪些场景?
我会先把话说准:有些写法难以构造有效的索引查找范围,有些是优化器判断扫描成本更低,不是索引突然坏了。
- 对普通索引列做函数或计算,比如
DATE(start_time) = ...,可能无法直接用原索引定位;查某一天可以改写成原列上的时间范围。匹配的函数索引等情况要另看。 LIKE '%关键词'这类前导通配符,通常不能按普通 B+ 树索引做前缀范围查找;仍可能出现覆盖索引扫描,不能说完全不会读索引。- 联合索引
(a, b, c)不约束前导列时,通常难以按常规左前缀方式定位,但符合条件的 Skip Scan 等优化需要结合版本和执行计划判断。 - 隐式类型转换、字符集或排序规则不匹配,可能影响索引使用;不能背成“只要转换就一定失效”。
- 返回数据比例很大、表很小,或统计信息估计不准时,优化器可能选择扫描。
OR也不能一刀切,有时会采用 Index Merge。
最后落到 EXPLAIN,需要实际执行信息时在可控环境用 EXPLAIN ANALYZE。看访问方式、选了什么索引、扫描多少行、是否回表和排序,而不是背完“索引失效十大场景”就收工。
评论
验证登录状态...