小红书 Agent 开发一面:SSE、Streamable HTTP、MCP 版本兼容全问细节
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 自我介绍。
- 询问团队人数。
- 询问现在能否正常去实习。
- 拷打实习经历。
- 拷打项目经历。
- SSE 和 Streamable HTTP 的区别?
- 如何适配 stdio、HTTP、SSE、Streamable HTTP?
- 如何处理 MCP 版本兼容问题?
- ChatGPT 这种对话 Agent 的会话表和消息表字段怎么设计?
- MCP 的客户端和服务端交互完整流程?
- MCP 获取工具列表怎么做的?
- 如果 MCP 有很多工具,比如 100 个,Agent 怎么处理?
- Agent 怎么保证工具调用准确性?
- Agent 记忆模块怎么设计?
- 长期记忆和短期记忆分别怎么实现?
- 对话超过模型上下文窗口怎么处理?
- 压缩的具体方案?还能怎么细化?
- 分层记忆怎么分?
- 项目里有用到 MCP 吗?MCP 解决什么问题?
- 站在测试开发角度,怎么验证功能符合预期?
- 项目里用到什么组件?
- MQ 有哪些特性?
- Redis 缓存穿透、击穿、雪崩是什么?怎么解决?
- 布隆过滤器原理?误判怎么处理?
- 数据库和 Redis 如何保持数据一致性?
- 旧缓存删除后旧数据还没更新,请求把旧数据写回缓存怎么办?
- Agent 长期记忆和短期记忆如何设计?哪些内容作为何种记忆?
- 发生记忆冲突怎么处理?
- 用户姓名等信息被污染怎么处理?
- RAG 系统流程?如何提高准确率?
- 手撕:最长递增子序列。
具体项目追问
- 自动切题 Agent 中的 7 步 Pipeline 流程是什么?
- LangGraph 设计中的 3 个子图 Agent 怎么传递?
- 为什么拆成三个子图而不是一条 Pipeline?
- Skills 知识进化流程怎么搭建?
- 为什么不在 Prompt 中添加而用 Skills?
《参考解析》
MCP 的传输方式与版本兼容。 MCP 是让模型以统一协议访问外部工具与数据的标准,传输层有两代:早期是「HTTP + SSE」——客户端向服务端建一条 SSE 长连接接收消息,再用另一个 HTTP POST 端点发消息,两条通道分离、长连接容易被中间层掐断、也没法在无状态的网关后面水平扩展;新的 Streamable HTTP 把收发统一到单个 HTTP 端点上,响应可以是普通 JSON,也可以升级为流式(对客户端请求按需回 SSE 流),必要时用会话标识维持上下文,因而对负载均衡与断线重连更友好,还能兼容无状态部署。适配四种传输(stdio、HTTP、SSE、Streamable HTTP)的工程做法是抽象出统一的传输接口,让上层只看到「发送请求、接收消息、订阅通知」三个动作:stdio 用于本机子进程(最安全、零网络配置,适合桌面端与 CLI),HTTP 用于无状态一次性调用,SSE 与 Streamable HTTP 用于远程长会话,各自实现连接建立、心跳、重连与取消。版本兼容靠协议版本协商与能力声明:初始化握手时双方交换协议版本与各自支持的 capabilities,服务端按客户端版本决定可用特性,客户端对未知字段要与宽松解析(忽略而非报错),不支持的能力降级而不是直接失败;工具与资源的 schema 变更尽量只做向后兼容的追加,破坏性变更走新版本号并保留旧版本一段时间。
记忆分层、压缩与污染治理。 短期记忆是当前会话的上下文(最近若干轮原文加工具结果),长期记忆是跨会话的事实与偏好(用户姓名、岗位、常驻城市、明确说过的约束、历史结论),通常存向量库加结构化字段并带时间戳与来源。分层可以这样切:工作记忆(当前任务的目标、计划与中间产物)、会话记忆(本轮对话的原文与滚动摘要)、用户记忆(跨会话的稳定事实与偏好)、知识记忆(可检索的外部资料,与用户无关)。压缩方案要具体到策略:按轮次滚动摘要(保留结论与未完成事项、丢弃寒暄)、按主题切分再做分主题摘要、工具长输出落盘只留摘要与引用、把结构化事实抽出来写成键值对(这些不该被摘要,直接进结构化存储)、以及用「摘要 + 最近几轮原文 + 检索到的相关历史片段」三段拼装上下文。追问「还能怎么细化」时可以补:摘要要带时间与来源便于追溯、要保留否定与条件(「不要打电话」「只在工作日」这类),并且要用固定 prompt 与模型版本以保证摘要可复现。记忆冲突的处理原则是「新的覆盖旧的、但要留痕」:同一事实按时间取最新,同时保留历史版本与变更原因;置信度不同就保留高置信度并提示存在冲突;用户显式纠正必须最高优先级并立即生效。用户姓名等信息被污染(模型把别人的名字写进记忆)要靠来源校验与写入闸门:只有用户明确陈述或从可信字段抽取的事实才允许写入长期记忆,模型推测一律不写;写入前做一次一致性校验(与已有资料是否矛盾),并对敏感字段做白名单;出问题后要能按来源追溯到具体会话并批量回滚。
缓存与 RAG。 缓存穿透是查不存在的数据导致每次都打到数据库(用布隆过滤器前置拦截、空值缓存加短 TTL);击穿是热点 key 过期瞬间大量请求同时回源(用互斥锁或单飞、热点 key 逻辑过期不物理删除、提前预热);雪崩是大量 key 同时失效或缓存整体不可用(过期时间加随机抖动、多级缓存、限流降级与熔断)。布隆过滤器用多个哈希函数把元素映射到位数组,判断「不存在」一定准确、判断「存在」有误判,所以它只适合做前置拦截;降低误判要靠增加位数组长度、调整哈希个数并估算实际元素量,同时支持删除要用计数布隆过滤器。数据库与 Redis 的一致性主流方案是 Cache-Aside:读先查缓存、未命中查库回填并设随机过期;写先更新数据库再删除缓存(不是更新缓存),必要时叠加延迟双删或订阅 binlog 异步失效。「旧缓存删除后旧数据又被写回」这个经典窗口的解法有几种:延迟双删(写库后延迟再删一次)、给缓存写入加版本号或时间戳(回填时若库中版本更新则丢弃旧值)、用 binlog 驱动的失效队列保证删除顺序、以及把「读回填」做成带短锁的单飞操作把并发回填串行化。真正的兜底是对账与监控,而不是假设缓存永远一致。RAG 流程与提准确率的方向在别处已详述,这一问的要点是:检索侧做混合召回加 rerank、切片要保语义边界并带元数据、查询侧做改写与多查询、生成侧强制引用原文并做事实核查、评测侧建分层评测集并用 badcase 回流。
手撕:最长递增子序列与项目追问。 LIS 的标准解法是贪心加二分:维护一个数组 tails,其中 tails[i] 表示长度为 i+1 的递增子序列的最小结尾值,遍历每个数时用二分找到第一个不小于它的位置并替换,若位置在末尾则长度加一,时间 O(n log n)、空间 O(n);要返回具体序列就再记录每个元素的前驱下标。经典 O(n²) DP 是 dp[i] = max(dp[j]) + 1 (j < i 且 nums[j] < nums[i]),适合先讲清题意与状态定义,再优化到二分。注意区分「子序列」与「子数组」——子数组要求连续,用另一套解法;另外「严格递增」与「非递减」在二分判断上是 >= 还是 > 的区别,写之前先确认。项目追问部分值得提前准备的逻辑是:把流程画成 7 步 Pipeline 并说明每步的输入输出与失败处理;多个 LangGraph 子图之间通过共享状态对象与显式边传递(谁写哪个字段要约定清楚,否则会出现互相覆盖);拆成三个子图而不是一条 Pipeline 的理由通常是职责隔离与可独立重试(每个子图有独立的失败恢复与评测);Skills 本质是把「某类任务的做法、脚本与约束」封装成可版本化、可检索、可复用的能力包,比塞进 prompt 的好处是可控、可测、可增量进化(改能力不用改主提示词,也不会一次把所有细节都占满上下文)。