小红书产品工程师 Agent 开发一面:爬楼梯改编与自注意力机制
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
算法
- 爬楼梯改编:有 N 个台阶,每次可以走 3、5、7 步,上一步是 7 时下一步只能是 3,总共有多少种走法?(N = 2 时答案为 0)
实习拷打
- 先说下你实习的团队业务背景,然后说下你在其中参与的环节。
- 业务系统中的分布式缓存和锁是怎么设计的,作用是什么?
- 异步转存模块的线程池如何设计的?
- 说一下 Agent 设计中你参与的部分,以及实践踩雷的经验。
八股
- 大模型的自注意力机制是什么?
- 说一下你了解的 LLM 和传统 NLP 的区别,以及 LLM 的训练过程。
- 如何防止大模型生成的幻觉?
- 说下线程池的核心参数,以及参数设置的经验?
《参考解析》
爬楼梯改编:把「上一步走了几」编码进状态。 普通爬楼梯的状态只有「还剩几级台阶」,这里额外有一个约束依赖上一步,所以状态要升维成 dp[i][last]:走到第 i 级、且最后一步走了 last(3、5、7)的方案数。转移是:若 last == 7,下一步只能加 3;否则下一步可以加 3、5、7。答案是 dp[N][3] + dp[N][5] + dp[N][7]。小规模可以对一遍:N = 2 无解为 0,N = 3 只有 {3}、N = 7 只有 {7} 各为 1,N = 10 有 {7,3}、{3,7}、{5,5} 三种。实现上就是按台阶从小到大递推,空间可以压到只留最近的 7 个位置;面这类题一定要先说清状态定义和「为什么需要第二维」,再写代码,面试官想看的是你把约束建模进状态的能力,而不是背过斐波那契。
分布式缓存与锁:先讲清保护的是什么临界区。 面试官问「怎么设计的」不是要听 Redis 命令,而是要知道你为什么需要锁、锁的粒度多大、失效了会怎样。顺序是:先说明业务里哪一步出现并发写(例如同一份数据被多个消费者同时转存),再给出方案——缓存侧讲 key 设计、过期时间与随机抖动、更新策略(先更新库再删缓存 + 延迟双删,或订阅 binlog 异步失效),并主动区分穿透(空值缓存 + 布隆过滤器)、击穿(互斥重建 + 逻辑过期)、雪崩(过期时间打散 + 多级缓存);分布式锁讲 Redis 的 SET key value NX PX 加唯一 value、释放用 Lua 比对 value 删、业务没跑完要续期(看门狗),并主动提一句 Redlock 的争议与「锁只是降低概率,最终一致性还要靠幂等和唯一键兜底」——这句话往往比锁本身更加分。异步转存这种场景,锁的粒度要落到「单据维度」而不是全局,否则并发度直接被锁死。
异步转存的线程池:参数背后是队列与拒绝策略的取舍。 标准答法是把七参数先摆出来(corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler),再讲执行顺序:核心线程未满就新建线程,满了进队列,队列满了才扩到最大线程数,再满才走拒绝策略——很多人会误以为先扩容再排队。落到异步转存上要讲清:这是 IO 密集型任务,线程数按「目标 QPS × 单任务耗时」估算并留余量,不要用无界队列(LinkedBlockingQueue 默认容量近似无界,会把内存吃爆且让最大线程数形同虚设),队列要有界并配拒绝策略;拒绝不是丢数据,而是降级为同步执行、写本地重试表或丢进 MQ 稍后重放。再补三条工程细节:任务要幂等(同一单重复执行不产生副作用)、要透传 traceId 与用户上下文(ThreadLocal 不会自动传给线程池里的线程)、以及优雅关闭时先停收新任务再等待在途任务。线程池一定要命名,出问题看线程栈时能一眼认出是谁的池。
「Agent 设计踩过的雷」是这篇里最值钱的一题。 可以按四类讲,每类给现象、原因、修法:一是工具调用不稳定,模型编出不存在的工具名或参数缺字段——修法是服务端按 schema 强校验、不合法就把结构化错误回填让模型自修正并设重试上限;二是死循环与无谓重试,模型反复用同样的参数调同一个工具——修法是最进步数、重复调用指纹检测、状态评估三条叠加;三是上下文膨胀,工具返回的大段原文把窗口占满导致关键指令被挤掉——修法是工具输出先裁剪与摘要、大产物落外部存储只在上下文留引用;四是效果不可回归,改一版 prompt 好了这个坏了那个——修法是攒一个带期望输出的评测集,按版本跑回归。面试官问「踩雷」其实是在看有没有真实迭代经验,没有踩过雷的回答反而不可信。
自注意力与 LLM 训练过程:把公式讲成信息流动。 自注意力是让序列里每个位置都能直接看到其它位置:每个 token 的向量线性映射成 Query、Key、Value,用 softmax(QKᵀ/√d) 得到注意力权重再加权求和 Value;除以 √d 是为了防止点积随维度变大后 softmax 进入饱和区、梯度消失;多头是把表征切到多个子空间并行做注意力,再拼接投影,让模型同时关注语法、指代等不同关系。要说清它是置换等变的,所以必须加位置编码;复杂度是序列长度的平方,这是长上下文推理与显存成本的根本来源,也是各种稀疏、线性注意力方案的动机。LLM 与传统 NLP 的区别可以一句话打底:传统 NLP 一个任务一个模型、依赖大量标注、以判别式为主;LLM 靠「预训练 + 微调 + 对齐」得到通用生成能力,用 prompt 和上下文就能适配新任务,泛化强但可控性和可解释性更差。训练过程按三阶段讲:预训练(海量无标注语料做下一 token 预测,学到语言与世界知识)、指令微调 SFT(用高质量问答对学会遵循指令)、对齐(RLHF 或 DPO,用人类偏好把输出调得更符合期望、更安全)。能主动提「幻觉的根源之一是预训练目标在最大化似然而不是在逼近事实」这一层,深度就出来了。
幻觉抑制与线程池参数的经验值。 幻觉这道题要按「降低发生」和「发生时可控」两层答。降低发生:把答案约束在给定资料上(RAG,要求带引用、检索不到就明确说不知道)、结构化输出与 schema 校验、降低温度、任务拆小并让模型分步自检、对关键结论做交叉验证或自洽投票。可控兜底:输出后加规则校验(数字、金额、链接、是否越权),关键场景让模型只做抽取不做生成,把最终判断留给确定性的代码或人。工程上也别指望完全消灭幻觉——面试时主动说「我们的目标是让幻觉可发现、可拦截、可归因」,比承诺「不会幻觉」更可信。线程池参数的经验值要给出估算依据而不是拍数字:CPU 密集按核数 + 1,IO 密集按 核数 × (1 + 等待时间/计算时间) 估,异步转存这类纯等待任务可以更大但要靠压测校准;队列必须有界、容量按能容忍的排队延迟折算;拒绝策略在数据不能丢的场景用调用方执行或落盘重试,能丢的场景用丢弃最旧;另外补一句「线上要看活跃线程数、队列深度、拒绝次数三个指标,参数是调出来的不是背出来的」。