面灵AI→

网易 Agent 开发一面:SSE 与 Streamable HTTP、MCP 版本兼容、记忆分层设计

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

  1. 请做一下自我介绍。
  2. 你们团队现在有多少人?
  3. 现在能否正常来实习?
  4. 实习经历主要做了哪些事情?
  5. 项目经历里你负责的是哪部分?
  6. SSE 和 Streamable HTTP 的区别是什么?
  7. 如何适配 stdio、HTTP、SSE、Streamable HTTP 这几种传输方式?
  8. 如何处理 MCP 的版本兼容问题?
  9. ChatGPT 这类对话 Agent 的会话表和消息表字段怎么设计?
  10. MCP 的客户端和服务端交互完整流程是怎样的?
  11. MCP 获取工具列表是怎么做的?
  12. 如果 MCP 有很多工具,比如 100 个,Agent 怎么处理?
  13. Agent 怎么保证工具调用的准确性?
  14. Agent 的记忆模块怎么设计?
  15. 长期记忆和短期记忆分别怎么实现?
  16. 对话超过模型上下文窗口怎么处理?
  17. 压缩的具体方案是什么?还能怎么细化?
  18. 分层记忆怎么分?
  19. 项目里有用到 MCP 吗?MCP 解决什么问题?
  20. 站在测试开发角度,怎么验证功能符合预期?
  21. 项目里用到哪些组件?
  22. MQ 有哪些特性?
  23. Redis 的缓存穿透、击穿、雪崩分别是什么?怎么解决?
  24. 布隆过滤器的原理是什么?误判怎么处理?
  25. 数据库和 Redis 如何保持数据一致性?
  26. 旧缓存删除后旧数据还没更新,请求把旧数据写回缓存怎么办?
  27. Agent 的长期记忆和短期记忆如何设计?哪些内容作为何种记忆?
  28. 发生记忆冲突怎么处理?
  29. 用户姓名等信息被污染怎么处理?
  30. RAG 系统的流程是怎样的?如何提高准确率?
  31. 手撕:最长递增子序列。

具体项目追问

  1. 自动切题 Agent 中的 7 步 Pipeline 流程是什么?
  2. LangGraph 设计中的 3 个子图 Agent 之间怎么传递数据?
  3. 为什么拆成三个子图而不是一条 Pipeline?
  4. Skills 知识进化流程怎么搭建?
  5. 为什么不在 Prompt 中添加而用 Skills?

《参考解析》

  1. SSE 与 Streamable HTTP:SSE 是单向的服务端推送——客户端发一个 HTTP 请求建立 text/event-stream 长连接,服务端只能往客户端推文本事件,客户端想发消息得另开一个 POST 请求。Streamable HTTP 是 MCP 后来引入的传输:只暴露一个 HTTP 端点,POST 的响应体既可以是单次返回的 JSON,也可以是 text/event-stream 流;它补上了 SSE 方案缺的东西——用 Mcp-Session-Id 维护会话、用 Last-Event-ID 支持断线续传、通过 GET 打开流让服务端主动推送,并把旧版「GET /sse 建流 + POST /messages 发消息」的双端点合并成一个。回答时把「谁只能推、谁能收发、几个端点、能不能断线恢复」这四点对齐,差异就清楚了。
  2. 传输方式适配与 MCP 版本兼容:适配的思路是先把「协议消息」和「传输」解耦,抽一层 transport 接口统一 send / onmessage / close,stdio 用子进程标准输入输出的按行 JSON-RPC 实现,HTTP/SSE 与 Streamable HTTP 各写一个实现,上层 Agent 代码不感知差异。版本兼容分两步:握手时客户端在 initialize 里带上自己支持的 protocolVersion,服务端回它选定的版本与 capabilities,双方按协商结果决定走哪些能力(tools / resources / prompts、是否支持列表变更通知);客户端侧做多版本支持,把版本相关分支收在 adapter 里按协商结果开关,未知字段忽略、不认识的版本明确报错而不是猜着跑。
  3. MCP 交互流程与工具列表:initialize(带协议版本与客户端能力)→ 服务端回 serverInfo 与能力清单 → 客户端发 initialized 通知 → 之后按需调用 tools/list、tools/call、resources/read、prompts/get。工具列表就是 tools/list,返回每个工具的 name、description、inputSchema,支持 cursor 分页;服务端工具集变化时发 notifications/tools/list_changed,客户端收到就失效本地缓存重拉。工具到 100 个以上时不能把 schema 全塞进上下文:按领域分组只加载当前任务需要的那组、用检索按 query 召回 Top-K 工具、给一组工具暴露一个聚合入口再往下钻、描述和 schema 精简到必要字段,再配上列表缓存与按需刷新。
  4. 工具调用准确性:描述里写清「什么时候该用、什么时候不该用、返回什么」,参数用 schema 严格约束并给示例;粒度要适中,太细模型要串很多步,太粗参数说不明白;调用前做参数校验,非法入参直接拦住并把错误作为观测回灌给模型;有副作用的工具做幂等、超时、重试上限和二次确认;工具结果结构化并控制长度;再加结构化输出、少量示例和「先规划再调用」。工程侧要有完整的调用轨迹用于归因,最后用真实任务集做回归——重点看「不该调用时是否克制」和「调用失败后能不能自己换路」。
  5. 会话表与消息表设计:会话表存 id、user_id、标题、系统提示与模型版本、状态、创建/更新时间、累计 token 与成本、最后一条消息 id;消息表存 id、session_id、role、内容、内容类型、tool_calls 与 tool_call_id、parent_id(支持重新生成与分支)、token 数、状态、创建时间,按 (session_id, created_at) 建索引。内容很长或含附件时把正文拆出去放对象存储,消息表只留引用;再单独建工具调用记录表和长期记忆表(user_id + 记忆类型 + 向量索引)。量大以后按 session_id 做冷热分离与分片。
  6. 记忆模块与分层:短期记忆是当前会话的原始消息窗口,保证最近若干轮的细节不丢;中期记忆把更早的对话滚动压缩成摘要,摘要必须保留数字、人名、结论这类硬信息,不能只剩流水账;长期记忆把稳定事实(用户偏好、项目背景)写进外部存储,需要时按 query 检索召回而不是每轮全量带上。写入要过滤(低信息量和重复内容不进库),冲突要覆盖(改了口径旧记忆必须失效),召回要限额并标注来源与时效,任务清单这类状态外置成结构化数据由程序维护,比让模型从聊天记录里反推可靠。
  7. 上下文超限与压缩细化:常规手段是滑动窗口丢弃最老的消息、滚动摘要、把长工具结果摘要化或外置成文件只留引用。能细化的是压缩的粒度(轮级 / 话题级 / 实体级,话题切换时压缩比按 token 阈值压缩更自然)、触发时机(按 token 水位、按话题边界、按工具返回大小)、保真校验(拿几个关键约束反过来提问,检查摘要是否丢了长度限制、字段口径、指代关系),以及把稳定不变的内容固定放系统提示最前面以便复用前缀缓存。
  8. 缓存穿透、击穿、雪崩:穿透是查的 key 数据库里根本不存在,每次都打到库,解法是缓存空值加短 TTL、布隆过滤器前置拦截、入参校验。击穿是热点 key 过期的瞬间大量并发一起回源,解法是单飞(只放一个请求去加载,其余等待)、逻辑过期(不设 TTL 由后台异步刷新)、热点 key 常驻加主动更新。雪崩是大批 key 同时过期或 Redis 整体不可用,解法是过期时间加随机抖动、本地加 Redis 多级缓存、集群与哨兵保证高可用、限流熔断降级加缓存预热。
  9. 布隆过滤器:一个位数组加 k 个哈希函数,插入时把 k 个位置置 1;查询时只要有任意一位是 0 就说明一定不存在,全是 1 只能说可能存在。误判来自不同元素的哈希落到了同一批位上。降低误判的办法是加大位数组、把哈希个数调到接近最优(k ≈ (m/n)·ln2)、控制实际元素数不超过设计容量,超了就扩容重建;另外它不支持删除,需要删除时得换计数布隆过滤器或布谷鸟过滤器。
  10. 缓存与数据库一致性:主流是 Cache Aside——读未命中就回源并回填,写的时候先更新数据库再删除缓存(删比更新好,避免并发写入把旧值覆盖进去)。删除失败用延迟双删或订阅 binlog 异步补偿兜底,强一致场景直接加分布式锁或者读库,接受最终一致就要把不一致窗口和兜底 TTL 说清楚。旧值回写这个具体场景的根因是:读请求 A 未命中读到旧值,写请求 B 更新库并删了缓存,A 随后把旧值写回缓存,缓存就长期停在旧值上。解法是给回填加条件——值里带版本号或更新时间戳,回填前比对、旧版本不写;或者用 setnx 抢占回填权、让回源加回填走同一把锁;再叠加延迟双删和短 TTL 兜底。
  11. RAG 流程与准确率:入库侧是解析、清洗、切分、向量化、建索引;查询侧是 query 改写与多路召回、结果融合与 rerank、拼装上下文、生成并输出引用。提高准确率要分环节定位:切分按语义和文档结构而不是定长,检索做向量加关键词的混合召回,加一层 rerank 精排,用元数据过滤缩小范围,系统提示里明确「证据不足就说不知道」,最后用带标注的问答集算召回率与端到端正确率,看问题出在召回还是生成。
  12. MQ 的特性:异步、解耦、削峰填谷;可靠性上靠持久化、消费确认与重试、死信队列、幂等消费;顺序性靠分区内有序来保证;此外还有广播与集群消费、延迟与定时消息、事务消息与最终一致、积压监控和消费位点管理。选型上 Kafka 偏吞吐与流式,RocketMQ 偏事务与延迟消息,RabbitMQ 偏灵活路由。
  13. 手撕最长递增子序列:朴素做法是 dp[i] 表示以 i 结尾的最长长度,转移是往前面找比它小的最大值加一,复杂度 O(n²)。更优的是贪心加二分:维护 tails[len] 表示长度为 len 的递增子序列的最小结尾值,每来一个数就二分找到第一个大于等于它的位置替换掉,答案就是 tails 的长度,复杂度 O(n log n)。注意 tails 本身不是某个真实的子序列,只是各长度下的最优结尾,要还原具体序列得额外记前驱。
  14. 为什么拆子图、为什么用 Skills:拆成多个子 Agent 是为了让每个节点有独立的职责、上下文与评测口径,可以单独替换模型、单独做复用,出错时能定位到具体节点,也方便并行;一条大 Pipeline 的问题是全流程状态挤在一个上下文里,任何一步的偏差都会污染后面。子图之间传数据靠一份显式 schema 的共享 state(或事件消息),state 要可序列化以便中断恢复。把知识从 Prompt 挪进 Skills 的理由是:Prompt 只保留路由与决策,知识与长文档按需加载,既省 token 又能独立版本化和评测;知识进化的闭环就是收集失败案例、归纳成规则、写进对应 skill、再跑回归验证有没有真的变好。