小红书 AI Agent 开发 一面:评测集、上下文压缩与 MQ 幂等
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
项目与验收
- 先介绍你自己的工作。
- 你这里的 Agent 平台包括哪些功能?
- 选一个你独立完成的模块,从需求、方案、验收讲一下。
- 你怎么评估这套流程是合理、准确的?
- 如果让你设计边界,你会怎么设计?
- 哪些功能已经投入使用?
- 哪些真实功能已上线?收集到什么反馈?
- 还原一下那个并发异常问题。
- Service 里哪些字段被共享了?
- 怎样的执行顺序最终导致什么报错?
评测与标注
- 混合检索与 recall 的结果是否可信?
- 每道题大概涉及哪些相关文件?
- 正确答案谁来标注?怎么标注?
- 现在有 100 道题,换一批题这个机制还有效吗?
上下文与成本
- 上下文压缩降低 token 消耗是怎么统计的?
- 你怎么理解实际返回的 usage 字段?它代表什么?
- 节省的量包括哪些?
- 用户先用模型 A,后切到模型 B,摘要如何避免保留旧内容?
- 省了这么多 token 后,问答质量有没有下降?
人机协同与校验
- 工具调用里的用户确认,也就是 human-in-the-loop 是怎么做的?
- 用户说“用上次那个模型处理这张图”,但没说清模型和处理内容,Agent 如何补齐参数?
- 服务端是否要做类型、范围、资源存在性、权限等校验?
MQ 与一致性
- 你用 MQ,并通过任务 ID 和状态判断命令幂等;如果发送端一个、接收端多个,会怎样?
- 两个消费者同时接到待执行任务,如何避免多个消费者同时处理?
- 任务结果已写 MySQL,但 ACK 前消费者进程崩了,会怎样?
- 反问。
《参考解析》
平台模块和验收要讲成“能力闭环”,不是功能列表。 一个 Agent 平台通常包含:模型接入与路由、提示词/工具注册、会话与上下文管理、任务编排(planning/执行/重试)、知识库与检索、评测与标注、观测与成本、权限与审计。选一个独立模块时,按“需求—方案—验收”讲:需求来自谁、解决什么痛点、边界在哪;方案里说清数据模型、接口、失败处理、和上下游的契约;验收要有可量化指标,比如任务成功率、P95 延迟、单任务 token 成本、人工介入率、线上错误率,而不是“跑通了”。边界设计要覆盖输入(空值、超长、多语言、恶意注入)、输出(schema、引用、拒答)、权限(谁能调用哪个工具、能访问哪些数据)、执行(超时、重试、幂等、回滚)和人工(哪些操作必须确认)。上线反馈则要区分“功能上线”和“真实使用”:有没有真实用户、调用量、留存、bad case 类型,哪些反馈已经转成迭代项。
评测集的可信度取决于标注规范、一致性和抗污染,不是题目数量。 混合检索的 recall 要先把“相关”的定义写清楚:是文件级、函数级还是段落级;每道题标出标准答案涉及的文件/片段,多人独立标注,算一致率(如 Cohen’s kappa),分歧由第三人仲裁;题目按难度、业务域、问题类型分层,避免全是简单事实题。100 道题换一批后机制是否还有效,要看有没有“锚题”:保留一部分固定题做跨批次对比,新题按同样规范标注,否则换题后分数不可比。还要防污染:评测题不能出现在训练/提示词示例里,检索索引更新后要重跑。除了 recall,还要看 precision、MRR、答案正确率、引用命中率和端到端任务成功率;如果 recall 高但答案差,问题可能在重排、上下文拼接或生成,不要只盯检索。文件定位题则要单独统计 Top-K 命中率和平均排名,因为“找对文件”和“答对问题”是两件事。
token 统计要区分“账面节省”和“真实节省”,并把质量一起看。 模型返回的 usage 通常包含输入 token、输出 token、总 token,有些还细分缓存命中 token、推理 token;统计压缩效果时,要在同一会话、同一请求上对比“不压缩的基线 prompt”与“压缩后的 prompt”,而不是拿压缩后的输入和上一次输出比。节省量包括:历史对话被摘要后减少的 token、重复系统提示/工具描述被裁剪的部分、检索片段去重和截断、缓存命中带来的计费折扣;但也要减去摘要本身的生成成本、额外调用和延迟。按任务聚合而不是按单次调用聚合,否则多轮任务会被拆碎。质量是否下降,要用固定评测集看任务成功率、关键事实召回、人工评分、用户编辑率/追问率/拒答率,做压缩前后的 A/B;如果成本降了但追问率明显上升,实际总成本可能没降。回答时能给出一条“成本—质量曲线”,说明在哪个压缩比例下收益最大,会比单说“省了百分之多少”更有说服力。
模型切换后摘要被旧内容污染,根因是摘要没有版本和来源信息。 如果摘要只存一段自然语言,模型 A 生成的结论、术语和假设会被模型 B 继续沿用,即使 B 的能力和风格不同。工程上要给摘要打元数据:生成它的模型/版本、时间、来源消息范围、置信度;切换模型时,优先用新模型对原始对话重新摘要,至少对关键结论做一次重写;如果原始对话太长,就保留结构化记忆(实体、约束、已确认事实、未完成事项)和最近原文,摘要只作辅助。检索时按模型版本过滤旧摘要,或者把旧摘要标成“历史参考”,不让它进入系统指令。用户显式切换模型时,可以提示“摘要将由新模型重建”,并保留回滚。验证方法是构造一批跨模型切换的对话,比较切换前后的答案一致性、旧结论残留率和任务成功率;如果旧摘要里有关键事实,重建时要保证事实不丢,只是表达和推理链更新。
Human-in-the-loop 的关键是“按风险分级 + 明确确认对象 + 可审计”。 读操作、低风险查询可以自动执行;写操作、花钱、删除、对外发送、修改权限这类高风险动作必须让用户确认,确认界面要展示工具名、参数、影响范围和预期结果,最好有 diff/预览,而不是只弹“是否继续”。确认要有超时、一次性 token、幂等键,防止重复提交;用户拒绝后要能把拒绝原因回传给 Agent,让它重新规划。参数补齐则靠对话状态和槽位:从当前会话、历史会话、用户偏好里解析“上次那个模型”“这张图”的指代,缺关键参数时先追问,不能猜高风险参数;有默认值时也要在确认页显式展示。服务端校验是最后一道闸:类型、范围、枚举、资源是否存在、当前用户是否有权限、配额和频率限制、业务规则、幂等键,全都要在服务端做,不能信任模型生成的参数;校验失败返回结构化错误码和可读原因,让 Agent 能修正或向用户解释。
一个生产者、多个消费者时,先看他们是不是同一个消费组。 集群消费模式下,同一消费组内 MQ 会把消息分给其中一个消费者,不会广播给所有实例;广播模式才会每个实例都收到一份,不同消费组则各自收到一份。所以“接收端多个”本身不是问题,问题在于消费逻辑是否假设“只有我会处理”。防重复处理要用业务状态做 CAS:UPDATE task SET status='RUNNING', owner=?, lease_until=? WHERE id=? AND status='PENDING',受影响行数为 1 的实例才执行;或者用唯一约束插入执行记录、Redis 分布式锁(带 TTL 和续租)。同一任务要保证顺序时,按任务 ID 分区到同一队列,单消费者串行处理。消费者崩溃在“结果已写 MySQL、ACK 前”这个窗口,消息会被重投,消费端必须先查状态:如果已是 SUCCESS 就直接 ACK;如果是 RUNNING 且租约过期,说明上一个消费者死了,可以接管并做幂等重放。这里本质是“至少一次投递 + 幂等消费”,不要指望 MQ 层单独给你端到端 exactly-once;再配 outbox、状态机、补偿和对账,才能覆盖写成功但 ACK 丢失、写一半失败、重复消息这些情况。
Service 里的并发异常,八成是单例 Bean 上放了请求级可变状态。 Spring 默认单例,如果 Service 用成员变量保存当前用户、当前请求参数、临时结果,两个请求会互相覆盖:请求 A 写入字段,请求 B 覆盖,A 后续读到 B 的值,轻则返回错数据,重则空指针或状态机错乱。还原时要画出执行顺序:A 设置字段 → B 设置字段 → A 读取(已是 B 的值)→ A 报错/写错;再用日志里的 request id 和线程名确认交错。修复原则是 Service 无状态:请求数据放方法参数或局部变量,必须跨方法传递时用 ThreadLocal 并在 finally 清理,或者用 request scope/prototype;共享的缓存/Map 要换成线程安全结构并明确并发语义。验证可以写并发测试,用 CountDownLatch 让多个线程同时进入,观察是否复现;上线后加请求级日志和告警。回答这类题不要只说“加锁”,先判断状态该不该共享,再谈锁的粒度和代价。