2026-10-10 AI Agent 面试训练
配置读取状态:成功。实际使用配置版本
version: 2(last_reviewed: 2026-10-10)。
说明:配置文件实际文件名为题库配置.md.md(双扩展名),路径为better/题库配置.md.md,内容格式正常,已按配置唯一来源读取,未做任何修改。
历史题库状态:better/下此前无任何训练文件、复盘文件或精选索引,本次为首次生成,无历史去重约束。
一、今日学习主题与题型分布
主题:能力边界与工程可靠性——上下文工程、无状态互操作协议、持久化执行、轨迹级评测、最小代理原则。
今天刻意选取配置 v2 中新增或大幅扩充的五个一级类型,覆盖”模型之上、协议之下”这一层,也就是中高级 Agent 工程师最容易与普通 LLM 应用开发者拉开差距的地方。
| 编号 | 题型 | 一级类型 | 考察方向 |
|---|---|---|---|
| B1 | 基础定义 | 上下文工程与能力封装 | 压缩 / 摘要 / 外部记忆载体的边界与上下文退化归因 |
| B2 | 基础定义 | 工具与互操作协议 | MCP 无状态协议核心与 MCP / A2A 双层架构 |
| B3 | 基础定义 | AI Agent 核心 | 编排器-工作者与子 Agent 上下文隔离 |
| B4 | 基础定义 | Agent 评测与可观测性 | 轨迹级评测、结果指标与多次运行一致性 |
| B5 | 基础定义 | AI 安全与治理 | 最小代理原则、Agent 身份与代持令牌 |
| S1 | 真实场景 | 有状态工具服务迁移到无状态协议核心 | 协议迁移、状态外置、网关治理、灰度与扩容 |
| S2 | 真实场景 | 长任务持久化执行、暂停恢复与人工审批 | 事件历史、幂等与补偿、人工信号、版本兼容 |
| L1 | LeetCode | 哈希表 + 双向链表 | 146. LRU 缓存(Medium) |
二、今日题目(建议先独立作答)
B1|基础定义题|上下文工程与能力封装
一个长任务 Agent 跑了数十轮之后,上下文接近上限。团队里有三种声音:做”上下文压缩”、做”对话摘要”、把大对象写到文件里当”草稿本”。
请说明:上下文压缩、摘要、外部记忆载体这三者的定义、适用边界和相互关系是什么? 更进一步:当线上任务成功率下降时,你如何判断这是”上下文退化”造成的,并且归因到具体是哪一层?
难度:中高。考察方向:概念边界、信息损失不可逆性、缓存成本、分层归因能力。
B2|基础定义题|工具与互操作协议
MCP 2026-07-28 版本把协议层会话(session)和
initialize握手移除了,官方称其为”stateless protocol core”。
请说明:什么是 MCP 的无状态协议核心?它具体移除了什么、带来了什么工程后果?应用层的跨调用状态应该放哪里? 并说明 MCP 与 A2A 构成的双层架构中,两者的职责边界分别在哪。
难度:中高。考察方向:协议演进理解、有状态/无状态的分层、跨组织互操作设计。
B3|基础定义题|AI Agent 核心
多 Agent 系统中常见的”编排器-工作者(orchestrator-worker)“模式:主 Agent 拆解任务,派发给多个子 Agent,子 Agent 各自检索与执行,最后回传结果。
请说明:子 Agent 上下文隔离(context isolation)到底解决什么问题?它的代价是什么?什么情况下反而不该隔离?
难度:中高。考察方向:上下文污染控制、并行化代价、结果回传契约设计。
B4|基础定义题|Agent 评测与可观测性
你们的 Agent 在离线评测集上任务成功率从 62% 提升到 78%,但上线后用户投诉量没有下降。
请说明:什么是轨迹级评测(trajectory-level evaluation)?它与任务成功率这类结果指标的关系是什么? 并解释 为什么”多次运行一致性”是和”单次成功率”不同维度的指标,以及它该怎么测。
难度:中高。考察方向:评测层次、指标失真、统计意义上的可靠性、 Reward Hacking 识别。
B5|基础定义题|AI 安全与治理
产品同学说:“这个 Agent 是代表我干活的,直接用我的权限不就行了?“
请说明:最小代理原则(least agency / least privilege for agents)的含义是什么?为什么”Agent 复用用户权限”是错误设计?Agent 身份(非人类身份)与代持令牌(on-behalf-of token)应该如何设计?
难度:中高。考察方向:权限模型、混淆代理问题、审计链与归因。
S1|真实研发场景题|有状态工具服务迁移到无状态协议核心
背景:公司内部有一个”浏览器自动化”MCP 服务(对外暴露 browser.open / browser.click / browser.snapshot / browser.close 等工具),基于旧版(2025-11-25)协议,采用 Streamable HTTP 传输。当前架构:客户端先 initialize 拿 Mcp-Session-Id,网关做 sticky session,服务端用 Redis 存会话状态(当前页面、cookie、打开的 tab),网关靠解析 JSON-RPC body 里的 method/name 做路由和粗粒度限流。
现状与故障现象:
- 单实例上限约 800 QPS,再扩容需要共享会话存储,Redis 成为热点;一次 Redis 抖动导致 12% 的请求
session not found。 - 网关 DPI 解析 body 在 TLS 卸载后成本很高,且对 body 超过 64KB 的请求直接跳过策略检查。
- 业务侧要求把调用量扩到 10 倍,并要求按工具名限流、每一次工具调用都能审计到”哪个 Agent 代表哪个人调用”。
目标与约束:
- 迁移到 2026-07-28 无状态协议核心,支持普通 round-robin 负载均衡与水平扩容。
- 不停机迁移,需要同时服务新旧客户端至少 3 个月。
- 浏览器会话这类跨调用状态不能丢。
- 合规要求:审计链必须能归因到人或 Agent,且不得因为迁移引入越权风险。
请给出:迁移方案、状态外置设计、灰度与回滚策略、失败路径与幂等、安全与成本分析、评测与上线观测指标。
难度:高。考察方向:协议工程、状态外置设计、网关治理、灰度迁移、安全归因。
S2|真实研发场景题|长任务持久化执行、暂停恢复与人工审批
背景:企业级”合同审阅 Agent”。一次任务包含:拉取合同与附件 → 版面解析 → 条款抽取 → 风险定级 → 生成修改建议 → (高风险)在合同系统落章 / 向对方发送函件。单次任务时长从 2 小时到 3 天不等,涉及法务人工审批。
已知现象:
- 任务中断(模型超时、worker 重启、部署)后从零重跑,成本翻倍;用户投诉”进度丢失”。
- 重跑导致同一封函件被重复发送 3 次,造成对外事故。
- 审批等待期间,workflow worker 一直占用执行槽位,队列堆积,P95 排队时长超过 40 分钟。
- 模型升级后,重放旧事件历史出现不兼容,恢复的任务行为与中断前不一致。
目标与约束:
- 任务成功率 ≥ 95%,中断后必须从断点恢复,不得丢失已完成步骤。
- 高风险动作必须人工审批,审批 SLA 4 小时,超时要有确定处理。
- 恢复与重试绝对不能重复发送外部函件或重复落章。
- 成本可接受,审计可追溯(谁批的、谁执行的、依据哪个版本的 prompt 与模型)。
请给出:整体架构、持久化执行与事件历史设计、幂等与补偿策略、人工信号与超时、版本兼容、三层能力划分、失败路径、观测指标、上线方式。
难度:高。考察方向:长任务状态机、持久化执行、副作用边界、审批与人机协同、版本化。
L1|LeetCode|146. LRU 缓存(LRU Cache,Medium)
设计并实现一个满足 LRU(最近最少使用)约束的数据结构,要求
get和put均为 O(1) 平均时间复杂度。
官方链接(已核验):https://leetcode.cn/problems/lru-cache/ | https://leetcode.com/problems/LRU-Cache/
难度:Medium。考察方向:哈希表 + 双向链表(设计题)、哨兵节点、边界处理。
✋ 建议先独立作答(每题给自己计时:基础题 1-2 分钟口头、场景题 3-5 分钟、LeetCode 20 分钟手写),再往下看答案。
三、完整参考答案
B1 上下文压缩、摘要与外部记忆载体
1. 考察点
- 能否区分”改变上下文内容”与”改变信息存放位置”这两类完全不同的手段。
- 是否理解摘要是有损不可逆变换,以及它对缓存(prompt cache)的破坏性。
- 能否给出可执行的归因方法,而不是泛泛说”上下文太长了”。
2. 准确定义与适用边界
上下文压缩(compaction / context editing):在已进入模型上下文的内容上做粒度缩减,不改变其语义载体形式——裁剪旧的工具返回全文、折叠重复调用参数、删除已失效的系统提醒、把长 diff 换成 diffstat。适用边界:内容在近期仍可能被引用,但不需要逐字节保留;压缩后仍需留在上下文里。
摘要(summarization):把一段历史语义重写为更短文本,是一次有损、不可逆的变换。适用边界:这段历史只需要保留结论、约束与待办,不再需要原文细节(如”尝试方案 A 失败”的过程)。
外部记忆载体(external memory artifacts):草稿本、进度文件、结构化状态对象——信息不进入模型上下文,落到文件系统/数据库/Agent 状态中,需要时按引用按需读回。适用边界:体量大、结构化、要求精确复现的数据。
判据链:
- 需要精确复现 → 外部载体,按需加载。
- 需要语义留存、细节可丢 → 摘要。
- 近期仍会引用但可降低粒度 → 压缩。
- 三者是并列关系,不是递进或替代关系。
3. 简洁示例
一个代码重构 Agent 跑到第 40 轮:
- 第 3 轮读入的 800 行文件内容 → 现在只需函数签名 → 压缩(保留路径 + 签名,删除正文)。
- 第 1-15 轮”尝试方案 A,发现与 B 模块循环依赖后放弃” → 摘要为一句约束:“方案 A 已排除:与 B 模块循环依赖”。
- 生成的 600 行 patch → 写入
patches/001.diff,上下文只保留路径与 diffstat → 外部载体。
4. 与相近概念的区别
| 概念 | 处理对象 | 关键区别 |
|---|---|---|
| RAG 检索 | 外部知识库中的新知识 | 处理”本会话尚未见过”的信息;把会话历史塞进向量库检索会引入语义漂移与引用丢失 |
| 长期记忆 | 跨会话、经写入治理的提炼结果 | 草稿本是会话内可丢弃的工作区,不该自动晋升为长期记忆 |
| Prompt 缓存 | 重复前缀的计算与计费成本 | 缓存降低单位成本;压缩会改变前缀本身,可能让缓存断点后全部失效——两者必须一起算账 |
5. 在 Agent 系统中的典型应用
- 接近上下文上限时触发分层压缩(先裁剪工具输出,再摘要历史)。
- 子 Agent 只回传结构化结论,避免过程污染主上下文。
- 大工具输出落盘后回传引用(URI + 摘要 + 关键字段)。
- 在多轮工具调用中,把历史
tool_result中的大 blob 替换为占位符。
6. 常见误区
- 把压缩当成”上下文永远够用”的解法——压缩本身消耗 token、引入延迟,且可能击穿缓存。
- 在已缓存前缀的中部做压缩——导致后续全部 cache miss,成本不降反升。正确做法是只压缩缓存断点之后的活跃段,或接受一次可控的重新缓存。
- 摘要时丢失约束(“用户明确说不要改 public API”),导致后期违规。缓解:摘要模板中显式保留「约束 / 已做决策 / 待办」三类不可压缩项。
- 把草稿本当长期记忆,任务结束不清理,造成下次会话上下文污染与敏感数据残留。
- 只用 token 数做触发阈值——长任务中”token 未超限但注意力已分散(lost-in-the-middle)“同样会退化,需要结合轮次与任务阶段判定。
7. 面试现场参考回答(1-2 分钟)
这三件事常被混为一谈,但本质上是两类操作。压缩和摘要是在”already in context”的内容上做手脚,改变的是上下文里的信息粒度;外部记忆载体是把信息移出上下文,改变的是存放位置。
选择依据是三条判据:需不需要精确复现——需要就落盘,按需读回;能不能丢细节——能丢就摘要;近期还要不要引用——要引用但可以降低粒度,就压缩裁剪。
工程上最容易踩的坑有两个。第一,摘要是有损不可逆的,摘要时如果不显式保留约束和已做决策,后面会反复违反用户要求。第二,压缩会改前缀,如果压在已缓存的前缀里,prompt cache 会整体失效,成本反而上升,所以要只在缓存断点之后的活跃段压。
归因上我不只看 token 数,而是看三层:召回层(是不是该给的信息没给)、上下文层(是不是该留的被压掉了、是不是信息在窗口中部被忽略)、模型层(是不是这个子任务本身超出能力)。做法是固定模型和 prompt,只切换压缩策略做 A/B,再看失败样本集中出现在压缩触发后的第几轮。
8. 面试官追问及回答要点
追问:压缩上线后任务成功率反而掉了 5 个点,你怎么定位是不是压缩导致的?
回答要点:
- 控制变量 A/B:固定模型版本、prompt、工具集,只切换压缩策略,跑同一评测集,看成功率差异是否显著(需要说明样本量与置信区间,避免把噪声当信号)。
- 快照对比:导出压缩前后的上下文快照,对失败样本做人工 diff,区分丢的是”事实/约束”还是”过程/冗余”。丢约束 → 摘要模板问题;丢事实 → 召回或外部载体读回问题。
- 排除成本混淆:同时看 prompt cache 命中率。如果质量没变但成本涨了,那是缓存击穿,不是质量问题。
- 看滞后分布:失败是否集中在压缩触发后的第 N 轮。若是立即失败,多半是关键信息被压掉;若是 10 轮后才失败,更像是”约束丢失”。
- 反证法:把未压缩的原始上下文喂给同一模型,若成功率恢复,则可确认归因于压缩。
B2 MCP 无状态协议核心与 MCP / A2A 双层架构
本节事实依据 MCP 官方 2026-07-28 规范发布博客与官方 changelog(本次已核验,见第九节延伸资料)。
1. 考察点
- 是否理解”协议层无状态”与”应用层无状态”是两回事。
- 是否知道跨调用状态在无状态协议下应该如何实现,以及它带来的新安全责任。
- 是否清楚 MCP(垂直:Agent→工具)与 A2A(水平:Agent↔Agent)的分层。
2. 准确定义与适用边界
无状态协议核心:MCP 从双向有状态协议转变为请求/响应式的无状态协议。具体变更:
- 移除
initialize/notifications/initialized握手(SEP-2575)。协议版本、客户端能力与客户端身份改为随每个请求的_meta字段携带(io.modelcontextprotocol/protocolVersion、clientCapabilities、clientInfo),HTTP 上还带MCP-Protocol-Version头。版本不匹配返回UnsupportedProtocolVersionError。 - 移除
Mcp-Session-Id头与协议级会话(SEP-2567)。任意请求可落到任意实例,不再需要 sticky 路由与共享会话存储。 - 新增
server/discover:服务端必须实现,用于宣告支持的协议版本、能力与身份;客户端可在需要时先行调用,不是强制前置步骤。 - 服务端发起的交互改为多轮往返请求(MRTR, SEP-2322):取代此前需要常开 SSE 流的
sampling/createMessage、elicitation/create、roots/list。服务端返回resultType: "input_required"+inputRequests+requestState,客户端补齐答案后重试原请求并带上inputResponses。服务端发起的请求只允许在处理客户端请求期间发出(SEP-2260)。 - 基于 HTTP 头的路由(SEP-2243):Streamable HTTP 请求必须带
Mcp-Method与Mcp-Tool类命名头(Mcp-Name),网关/限流器/WAF 可直接按头路由与计量,无需解析 body;服务端必须拒绝头与 body 不一致的请求。 - 列表结果可缓存(SEP-2549):
tools/list、resources/list、prompts/list、resources/read等返回ttlMs与cacheScope;服务端应保证tools/list顺序确定,以提升客户端缓存与 LLM prompt cache 命中率。 - 授权硬化:授权服务器按 RFC 9207 返回
iss,客户端必须先校验再兑换 code;客户端凭据绑定到签发它的 issuer,不得跨授权服务器复用;DCR 正式弃用,转向 Client ID Metadata Documents(CIMD)。 - 正式弃用策略:12 个月最小窗口。
适用边界:无状态核心适用于远程 MCP 服务的水平扩容与标准 HTTP 基础设施接入;STDIO / 本地进程场景收益有限,但仍需适配协议版本。
3. 简洁示例(请求形态对比)
旧版(2025-11-25):先 initialize 拿 Mcp-Session-Id,之后每个请求都要带上,被钉在某个实例上。
新版(2026-07-28):
POST /mcp HTTP/1.1MCP-Protocol-Version: 2026-07-28Mcp-Method: tools/callMcp-Name: search
{"jsonrpc":"2.0","id":1,"method":"tools/call", "params":{"name":"search","arguments":{"q":"otters"}}, "_meta":{"io.modelcontextprotocol/clientInfo":{"name":"my-app","version":"1.0"}}}任何一个实例都能处理,前面挂普通 round-robin 负载均衡即可。
4. 应用层跨调用状态放哪里
协议层无状态不等于应用层无状态。官方推荐模式是显式句柄(explicit handle):由某个工具签发一个句柄(如 basket_id、browser_id),模型把它当作普通参数在后续调用中传回。
- 优点:状态对模型可见,模型可以跨工具组合、在子 Agent 之间传递、在会话恢复后继续引用;恢复时无需额外保存
Mcp-Session-Id。 - 代价与责任:句柄会出现在聊天日志、子 Agent 输入、复制文本里。服务端必须在每一次调用校验「句柄 + 认证主体」的组合;无认证场景下句柄本身即是访问凭证,因此规范要求句柄使用至少 128 位密码学安全随机值并设置过期时间。
5. MCP 与 A2A 的职责边界(双层架构)
| 维度 | MCP | A2A |
|---|---|---|
| 方向 | 垂直:Agent → 工具 / 数据 / 应用 | 水平:Agent ↔ 对等 Agent |
| 解决的问题 | 我的 Agent 如何访问外部世界 | 我的 Agent 如何把任务委派给别的 Agent |
| 发现机制 | server/discover、工具列表(ttlMs/cacheScope 可缓存) | Agent Card(/.well-known/agent-card.json,v1.0 支持签名) |
| 交互形态 | 同步请求/响应为主,MRTR 处理中途索取输入 | 任务(task)为中心,异步、长生命周期,支持流式与回调 |
| 信任边界 | 通常组织内 / 同一主体 | 跨组织、跨部署、不同身份体系 |
| 治理 | Agentic AI Foundation(AAIF) | 同为 AAIF(A2A 于 2026-08-27 被接受为 Growth Stage 项目) |
双层关系:编排 Agent 通过 A2A 把任务委派给专业 Agent,专业 Agent 再通过 MCP 访问自己的工具与数据。生产系统里两者同时存在、各司其职。A2A v1.0 于 2026-03-12 发布(v1.0.1 于 2026-05-28),引入多协议绑定与版本协商、多租户、签名 Agent Card。
6. 在 Agent 系统中的典型应用
- 远程 MCP 服务部署在标准 K8s + 普通 LB 之后,按
Mcp-Method做每工具限流、审计与路由。 - 客户端缓存
tools/list(按ttlMs),减少重复拉取并保持 prompt 前缀稳定。 - 有状态工具(浏览器、购物车、事务)用显式 handle 表达状态。
- 跨组织协作(供应链、金融)用 A2A 委派 + 签名 Agent Card 建立信任链。
7. 常见误区
- “无状态 = 我的服务不能有状态”——错。状态上移到应用层显式 handle,只是不再藏在传输层。
- 把 handle 当权限——handle 是引用不是凭证,每次调用都要做主体校验。
- 以为移除握手就不需要兼容旧客户端——需要双版本并存,通过
server/discover探测,并依赖 12 个月弃用窗口安排迁移。 - 忽略头与 body 一致性校验——服务端必须拒绝
Mcp-Method与 body 不一致的请求,否则网关路由与实际执行会脱节,形成策略绕过。 - 把 MRTR 当成普通重试——MRTR 的重试必须携带
requestState,并需要按请求 ID 做幂等去重,否则会触发重复副作用。 - 拿 MCP 去做 Agent 间委派——那是 A2A 的领域,混用会导致身份、审计和异步任务生命周期全部要靠自己造。
8. 面试现场参考回答(1-2 分钟)
MCP 2026-07-28 这次改版的核心是:协议层不再管会话。它移除了
initialize握手和Mcp-Session-Id,协议版本、客户端能力和身份改成每个请求的_meta里带;同时加了可选的server/discover让客户端按需探测能力。直接后果是任意请求可以落到任意实例,远程 MCP 服务可以挂在普通 round-robin 负载均衡后面水平扩容,不再需要 sticky session 和共享会话存储。但协议层无状态不等于应用层无状态。需要跨调用状态的工具,比如浏览器会话,官方推荐的做法是工具签发一个显式句柄,模型把它当普通参数传回来。这个模式比藏在传输层的 session 更好,因为模型看得见这个句柄,能跨工具组合、能在子 Agent 之间传递、会话恢复后还能接着用。代价是句柄会出现在聊天日志里,所以服务端每次调用都必须校验”句柄加认证主体”的组合,而且句柄本身要用至少 128 位随机值并设过期。
配套还有三件重要的事:服务端发起的采样和征询改成了多轮往返请求,不再需要常开 SSE 流;请求必须带
Mcp-Method、Mcp-Name头,网关可以按头做路由、限流和审计,不用解析 body;列表结果带ttlMs和cacheScope,客户端能缓存工具目录,也让 prompt 前缀更稳定。至于 MCP 和 A2A,是两层不是竞争关系。MCP 是垂直的,解决 Agent 怎么接工具和数据源;A2A 是水平的,解决 Agent 之间怎么发现、委派和协作,尤其跨组织边界。生产里一般是编排 Agent 用 A2A 把活派给专业 Agent,专业 Agent 再用 MCP 调自己的工具。
9. 面试官追问及回答要点
追问:既然协议层不再有 session,那我怎么保证一次多步操作的原子性?比如”扣库存 + 创建订单”。
回答要点:
- 先指出问题本身的层次错位:原子性是应用层事务问题,从来就不该由传输层 session 保证——旧协议里的 session 也不提供原子性。
- 正确做法一:把多步操作建模为单个工具(
create_order内部用数据库事务),让副作用的原子性落在确定性系统里,这是首选。 - 正确做法二:确需跨调用时,用显式 handle 表达工作单元(如
checkout_session_id),状态机在服务端,配合幂等键。 - 正确做法三:跨服务的长流程用 Saga + 补偿,接受最终一致,而不是假装分布式事务。
- 强调:模型是概率性参与者,绝不能让”模型记得调用第二个工具”来承担原子性责任。
B3 编排器-工作者与子 Agent 上下文隔离
1. 考察点
- 是否理解上下文隔离解决的是”污染”与”注意力稀释”,而不只是”省 token”。
- 是否能说出隔离的代价(信息丢失、重复劳动、结果契约问题)。
- 是否能判断何时不该隔离。
2. 准确定义与适用边界
编排器-工作者(orchestrator-worker):一个编排器负责理解目标、拆解子任务、派发、汇总与验收;多个工作者(子 Agent)各自拥有独立的上下文窗口,在自己的循环里检索、调用工具、试错,最后只把产物回传给编排器。
子 Agent 上下文隔离:工作者执行过程中产生的全部中间信息——失败的检索、错误的工具调用、冗长的原文、反复的推理——不进入编排器的上下文,只有约定的结构化结果被回传。
适用边界:
- 适合:子任务可独立验收、中间过程噪音大、需要并行、子任务上下文量大。
- 不适合:子任务之间强耦合、需要共享逐步推理、编排器必须掌握细节才能判断质量、子任务本身很轻(隔离的通信成本大于收益)。
3. 简洁示例
一次”竞品分析报告”任务,编排器派 5 个子 Agent 各查一家竞品。每个子 Agent 平均消耗 60k token(大量网页正文、失败的抓取、重试)。若不做隔离,编排器上下文需要 300k token,直接超限。隔离后每个子 Agent 只回传一份 400 token 的结构化摘要:公司名、核心指标、来源 URL、置信度、未找到的字段。编排器总成本约 2k token。
4. 与相近概念的区别
| 概念 | 区别 |
|---|---|
| 上下文压缩 | 压缩是在同一个上下文内降粒度;隔离是根本不让中间信息进入主上下文 |
| 多 Agent 协作(对等) | 编排器-工作者是层级式、单向委派;对等协作是水平协商,需要冲突解决机制 |
| 函数/工具调用 | 工具调用是确定性的、无独立推理循环;子 Agent 有自己的规划与反馈循环 |
| 并行 map-reduce | 形态相似,但子 Agent 的每次执行是非确定性的,同输入可能不同输出,必须有验收 |
5. 在 Agent 系统中的典型应用
- 广度优先的调研/检索任务(多来源并行探索)。
- 需要”污染隔离”的场景:某个子任务会引入大量无关文档,污染主上下文影响后续判断。
- 需要不同工具权限的子任务(工作者只拿到自己需要的工具,天然实现最小权限)。
- 需要独立沙箱的代码执行与验证。
6. 常见误区
- 把隔离当省 token 手段——省 token 是副作用,核心价值是质量(避免主上下文被污染)与并行。
- 回传契约过于自由(让子 Agent 自己写一段自然语言)→ 编排器无法程序化校验、无法聚合。应定义结构化 schema 并强制校验。
- 子 Agent 失败被静默吞掉——编排器必须接收显式的失败/部分成功状态与置信度,否则会基于残缺信息汇总。
- 过度拆分——子任务太细,通信与重复理解成本超过收益,且全局一致性变差。
- 以为隔离解决了幻觉——隔离只防止污染,不防止子 Agent 自身产生幻觉;仍需要引用溯源与校验。
- 共享可变状态——多个子 Agent 写同一个草稿本导致竞态;共享写入必须走确定性系统(数据库/队列),不能靠模型自觉。
7. 面试现场参考回答(1-2 分钟)
上下文隔离解决的核心问题不是 token 不够,而是质量。多步任务里,子任务会产生大量噪音:失败的检索、错误的工具调用、几百行的原文。如果全部进主上下文,会稀释编排器的注意力,让它在汇总阶段抓不住重点,也就是所谓的上下文污染。隔离让每个工作者在自己的上下文里把脏活干完,只把可验收的产物交回来。
代价有三个。第一是信息丢失:编排器看不到过程,就无法判断这个结果是怎么来的,所以回传必须带来源、置信度和”哪些字段没查到”,而不是一段自由文本。第二是重复劳动:多个子 Agent 可能重复检索同一个东西,需要任务拆分时划清边界或共享一个只读索引。第三是全局一致性变差,每个子 Agent 只对自己的局部负责,最终必须由编排器做交叉校验和冲突消解。
什么时候不该隔离:子任务之间强耦合、编排器必须掌握细节才能判断质量、或者子任务太轻、隔离的通信成本大于收益的时候。判断标准其实很简单——如果这个子任务能独立验收,就隔离;如果验收标准依赖其他子任务的中间结论,就别隔离。
8. 面试官追问及回答要点
追问:子 Agent 回传的结果相互矛盾,编排器怎么办?
回答要点:
- 不要靠模型”再想一次”解决——把冲突变成确定性问题:要求每个子 Agent 回传带来源与置信度的断言,冲突即来源冲突。
- 溯源优先:定位到具体文档/记录,比较时间戳与权威性,用确定性规则(更新时间、来源等级)裁决。
- 无法自动裁决时升级:把冲突与双方证据一起呈现,交人工或触发一次”仲裁子任务”(只针对冲突点做定向复核)。
- 对下游诚实:汇总结果中标注冲突字段与不确定性,不要让下游以为数据是确定的。
- 把冲突率做成指标——冲突率上升往往意味着任务拆分边界不清或数据源本身不一致,是系统性问题信号。
B4 轨迹级评测、结果指标与多次运行一致性
1. 考察点
- 是否理解”结果对”不等于”过程对”,以及为什么过程质量决定可维护性与成本。
- 是否能识别结果指标的失真(奖励黑客、评测集污染、偶然正确)。
- 是否能给出一致性的可操作测量方法。
2. 准确定义与适用边界
结果级评测(outcome-level):只看最终产出是否满足验收标准,典型指标是 Task Success Rate(任务成功率)。适用边界:面向用户体验的最终验收;但对中间过程不敏感,容易被”碰巧做对”掩盖。
轨迹级评测(trajectory-level):评估 Agent 达到结果的路径——工具选择是否正确、调用顺序是否合理、参数是否正确、是否走了不必要的往返、是否在关键步骤做了校验。
常见轨迹指标:
- 工具调用正确性:该调的是不是调了(召回)、不该调的是不是没调(精确)、参数是否正确。
- 路径质量:完成任务的步数 / 与最优路径的偏差、冗余调用次数、错误后是否正确恢复。
- 关键节点达成率:必须出现的中间动作(如”高风险操作前必须校验权限”)是否发生。
适用边界:轨迹评测需要可比的参照路径(golden trajectory 或规则化约束)。对于开放式创作类任务,定义”唯一正确路径”不合理,应退化为约束满足检查(必须满足的硬约束 + 禁止行为清单)。
多次运行一致性(consistency / reliability across runs):同一任务在相同输入下重复运行 N 次,成功率的分布。与单次成功率是两个维度:
- 单次成功率 78% 可能是”78% 的任务每次都成功,22% 每次都失败”(稳定系统),也可能是”每个任务都有 78% 概率成功”(高度不稳定的系统)——这两种系统的运维与产品含义完全不同。
- 常用表达:
pass@k(k 次内至少一次成功)与pass^k(k 次全部成功)。前者衡量”辅助可用”,后者衡量”可托付”。
3. 简洁示例
一个”查订单并退款”的 Agent:
- 结果级:钱退了 → 成功。
- 轨迹级:它是否先校验了用户身份?是否在退款前查了订单状态?是否对已退款订单做了幂等检查?是否调用了不该调用的管理员接口?——即使最终退款成功,跳过身份校验也是严重缺陷,只是这次没出事。
4. 与相近概念的区别
| 概念 | 区别 |
|---|---|
| LLM-as-a-Judge | 一种打分手段,可用于结果级也可用于轨迹级;本身有位置偏差、冗长偏差、自我偏好等已知偏差,需校准 |
| 单元测试 / 集成测试 | 确定性断言,覆盖编排与工具层;不覆盖模型的概率性行为 |
| 线上 A/B | 真实分布下的效果验证,是最终裁判,但反馈慢、成本高、无法解释 |
| Golden Dataset | 评测数据而非指标;需要防污染(不能把评测集混进训练/提示)并定期轮换 |
5. 在 Agent 系统中的典型应用
- 每次 prompt / 模型 / 工作流变更的回归门禁:同时看成功率、轨迹合规率、p50/p95 成本与延迟。
- 用轨迹数据定位缺陷类别:是工具选择错、参数错,还是恢复了但走了冤枉路。
- 用一致性分层:高一致性任务可自动化,低一致性任务必须人工复核或加约束。
- 把”必须发生的动作”做成断言(如”高风险操作前必须有审批事件”),作为合规与安全的硬门禁。
6. 常见误区
- 只用成功率——掩盖了过程违规与成本爆炸;也容易被偶然正确掩盖。
- 用公开榜单替代自建评测——存在榜单污染与分布不匹配;必须建自己的 golden set。
- 把评测集混进 prompt 或 few-shot 示例——自我污染,指标虚高。
- LLM-as-a-Judge 不做校准——不与人工标注对齐、不看一致率,直接采信分数。
- 只测一次——单次运行结果是随机变量,必须多次运行取分布,并报告方差/置信区间。
- 把 pass@k 当成可靠性指标——“试 k 次能成一次”对用户(尤其是高风险场景)没有意义,要看
pass^k和失败模式。
7. 面试现场参考回答(1-2 分钟)
结果指标和轨迹指标回答的是不同问题。任务成功率回答”用户拿到他要的东西了吗”,轨迹级评测回答”它是怎么拿到的、这条路能不能复现、能不能维护”。一个 Agent 可以把钱退了但全程跳过身份校验——结果指标会记它成功,轨迹指标会记它违规。这在合规场景里是不可接受的。
轨迹评测落地的关键是别去定义”唯一正确路径”,那在开放任务里不成立。可行的是两层:硬约束检查(必须发生的动作、禁止的行为、必须校验的前置条件),加上软指标(工具调用的精确率召回率、步数与最优路径的偏差、冗余调用次数、错误后的恢复能力)。
至于”线上投诉没降”,第一个要怀疑的是一致性。78% 的单次成功率有两种可能:一种是 78% 的任务稳定成功、22% 稳定失败,那投诉会集中在特定类型,用户可预期;另一种是每个任务都有 78% 概率成功,用户感知就是”时灵时不灵”,投诉量不会随均值上升而下降。这两个系统的产品含义完全不同。所以我会把评测从单次改成每个任务跑 N 次,同时看 pass@k 和 pass^k,并对失败模式做聚类——如果失败是随机分布的,优先做的是加确定性的护栏和校验,而不是继续调 prompt。
8. 面试官追问及回答要点
追问:你说要跑 N 次,N 取多少?如果预算只够跑一次呢?
回答要点:
- N 取决于你想分辨的效应量与方差:方差大(开放式、多工具)需要更大 N;常见做法是核心集 N=5
10、全量集 N=13 的分层策略。 - 分层而不是均匀:高风险/高频任务多跑,长尾任务少跑;把预算花在决策敏感的地方。
- 只跑一次时,至少报告置信区间而不是点估计,并且用对照变更(与上一版本同集对比)降低方差。
- 用低成本代理指标先筛:轨迹合规率、工具调用正确率比端到端成功率更敏感、方差更小,可以在小样本下先发现问题。
- 说明统计纪律:不要在没达到显著性时就宣布提升,也不要在同一评测集上反复挑选最优 checkpoint(等于过拟合评测集)。
B5 最小代理原则、Agent 身份与代持令牌
1. 考察点
- 能否说清”权限”之外还有”自主性(agency)“这一维度。
- 是否知道混淆代理(confused deputy)问题及其在 Agent 场景的放大。
- 是否能设计可审计的身份传播链。
2. 准确定义与适用边界
最小代理原则(least agency):给 Agent 的自主程度应当与其任务、可验证性与风险相匹配——不只是最小权限(least privilege,能访问什么),还包括最小自主性(能不经 human 确认走到哪一步)、最小持久化(能记住多久、记什么)、最小作用域(能影响哪些数据与系统)。
为什么”复用用户权限”是错的:
- 权限放大:用户权限是为人类交互设计的宽权限集合;Agent 以用户身份批量、高速、不知疲倦地行动,把”用户有权”放大成”任意规模地行使”。
- 混淆代理(confused deputy):Agent 可能被外部内容(网页、邮件、文档、工具返回值)中的注入指令诱导,用用户的合法权限去做用户从未意图的操作。权限校验通过,但意图不是用户的。
- 归因断裂:审计日志只记录”用户 X 做了 Y”,无法区分是人做的还是 Agent 做的、是哪个 Agent 版本、基于哪次指令。事后无法追责也无法止损。
- 无法单独吊销:要停用 Agent 只能停用用户账号。
Agent 身份(非人类身份 / Non-Human Identity, NHI):Agent 拥有自己独立、可验证的身份与凭证,权限单独授予、单独吊销、单独审计。
代持令牌(on-behalf-of token):Agent 调用下游系统时携带的令牌中,同时编码两个主体:actor(Agent 身份)与 subject(被代表的用户)。下游据此做交集授权:effective_scope = agent_grant ∩ user_entitlement,并记录完整归因链。
适用边界:代持适用于”Agent 明确代表某个用户”的交互式场景;对于后台自治 Agent(无具体用户),应使用服务身份 + 任务级作用域,不应伪造用户上下文。
3. 简洁示例
场景:财务 Agent 帮员工报销。
- 错误设计:Agent 用员工 SSO 令牌调用报销系统 → 可以报销任何人、任意金额,日志显示”员工本人操作”。
- 正确设计:Agent 有自己身份
agent:expense-bot,被授予 scopeexpense:create(且额度 ≤ 5000);员工授权产生代持令牌actor=agent:expense-bot, subject=user:alice, scope=expense:create:alice, amount<=5000;报销系统校验交集,日志同时记录 actor 与 subject;超额转人工审批。
4. 与相近概念的区别
| 概念 | 区别 |
|---|---|
| 最小权限(least privilege) | 只管”能访问什么”;最小代理还管”能自主走多远、能记多久、能影响多大范围” |
| OAuth 委托(delegation) | 机制层面的授权;代持令牌强调双主体编码与归因,是 OAuth 之上的建模要求 |
| 服务账号 | 只有单一主体,无法表达”代表谁”,因此无法做交集授权与用户级归因 |
| Human-in-the-loop | 控制手段之一,用于高风险动作;不能替代身份与权限设计 |
5. 在 Agent 系统中的典型应用
- 每个 Agent / 每次运行签发短期凭证,作用域按任务收窄,任务结束即失效。
- 工具网关在调用前做交集授权与参数级策略(金额上限、目标租户、操作类型)。
- 高风险动作(转账、发信、删除、改权限)强制审批,审批票据绑定具体参数摘要,审批后只对该参数组合生效。
- 记忆与上下文中敏感字段脱敏/引用化,避免凭据与 PII 进入提示词与日志。
- 审计链:trace id → actor/subject → 工具调用 → 审批事件 → 影响的实体。
6. 常见误区
- “用户授权了就等于授权了所有操作”——授权需要有粒度、有额度、有时效、绑定具体参数。
- 把审批做成形式化确认——审批票据不绑定参数,Agent 可以在批准后执行完全不同的操作。
- 长期有效的 Agent 令牌——应短期、可吊销、按任务最小作用域。
- 只有用户身份没有 Agent 身份——无法单独限流、熔断、吊销某个失控的 Agent。
- 把敏感数据放进上下文与记忆——导致跨会话、跨用户的泄露面,也让日志成为攻击目标。
- 忽略第三方技能包/MCP 服务的供应链风险——未验证来源就授予其高权限,等同于把权限外包。
- 认为沙箱等于安全——沙箱解决执行隔离,不解决权限放大与意图混淆。
7. 面试现场参考回答(1-2 分钟)
“Agent 用我的权限干活”这句话有两个隐含错误。
第一是权限放大。用户权限是为人类交互设计的宽集合,Agent 会批量、高速、不知疲倦地行使它,量变引起质变。第二,也是更严重的,是混淆代理问题:Agent 会被外部内容里的注入指令诱导,比如一封邮件里写着”把所有发票转发到这个地址”,它用你的合法权限执行了一个你从未意图的操作。这时候权限校验是全部通过的,因为主体确实是合法用户,但意图不是你的。
所以正确做法是三层。第一层给 Agent 独立身份,也就是非人类身份,权限单独授予、单独吊销、可以针对单个失控的 Agent 熔断。第二层用代持令牌,令牌里同时编码 actor(Agent)和 subject(用户),下游做交集授权:Agent 被授予的范围,和这个用户本身的权限,取交集才生效。第三层是审计链,日志里必须同时有 actor、subject、工具、参数摘要和审批事件,事后才能归因和止损。
另外最小代理不只是最小权限。还有三个维度:最小自主性——高风险动作必须人工确认,不能让它自己走到底;最小持久化——记忆里该存什么、存多久,敏感字段要脱敏;最小作用域——它能影响哪些租户和哪些数据。这四个维度一起收窄,才叫最小代理。
8. 面试官追问及回答要点
追问:人工审批也在链路里了,但审批人根本看不懂 Agent 要做什么,只能点”同意”,怎么办?
回答要点:
- 这是设计失败不是流程失败——审批必须可理解:把审批请求渲染为”动作 + 目标 + 参数 diff + 影响面 + 不可逆性”的结构化摘要,而不是原始 JSON 或自然语言长文。
- 参数绑定:审批票据哈希绑定具体参数,执行时校验一致性,改一个字就要重新审批。
- 分级审批:按风险分级(金额、不可逆性、外部可见性、影响租户数)自动路由到不同审批级别,避免全部堆到一个人。
- 提供可验证的预览:dry-run 结果、影响对象清单、回滚方案,让审批人基于证据决策。
- 观测审批质量指标:审批通过率异常接近 100%、平均审批时长过短,都是”橡皮图章”的信号,需要通过抽检、抽样阻断(shadow deny)来检验。
- 承认边界:如果人类无法在合理时间内理解并判断,说明该动作不应该被授权给 Agent,应改为 Agent 只准备、人执行。
S1 场景题:有状态 MCP 工具服务迁移到无状态协议核心
1. 背景、目标、约束与已知现象(回顾)
见第二部分 S1 题干:浏览器自动化 MCP 服务,旧版协议 + sticky session + Redis 会话存储 + body DPI 网关;目标为迁移至 2026-07-28 无状态核心、10 倍扩容、按工具限流、全量审计;约束为不停机、新旧客户端并存 ≥ 3 个月、浏览器跨调用状态不能丢、合规审计到人/Agent。
2. 核心考察点
- 状态外置设计:协议层无状态后,浏览器会话这类跨调用状态如何用显式 handle 表达,以及 handle 的生成、绑定、过期、撤销与每次校验。
- 双版本灰度迁移:新旧协议并存、能力探测、流量切换、回滚窗口。
- 网关治理:基于
Mcp-Method/Mcp-Name头的路由、限流、审计与一致性校验,替代 body DPI。 - 安全归因:handle 不等于凭证;代持身份;每次调用独立鉴权。
- 成本与性能:Redis 热点消除、
tools/list缓存(ttlMs/cacheScope)、prompt cache 前缀稳定。
3. 作答框架(3-5 分钟可复用)
第一步:分层拆解(30 秒) —— 把系统拆成四层:协议层(无状态化)、应用层状态(handle 外置)、流量治理层(网关/限流/审计)、数据层(会话存储与缓存)。
第二步:先说结论(30 秒) —— 迁移的本质不是”删掉 session”,而是把状态从传输层上移到应用层,同时把治理从 body 解析上移到 HTTP 头。
第三步:核心方案(2 分钟) —— handle 设计 → 双协议网关 → 灰度与回滚 → 幂等与失败路径。
第四步:量化与观测(1 分钟) —— 给出关键指标与上线门禁。
第五步:风险与边界(30 秒) —— 主动指出迁移中最危险的点(handle 泄露、头 body 不一致、缓存击穿)。
4. 参考回答(结论先行)
结论:这次迁移要做三件事,而不是一件。第一,把会话状态从协议层上移到应用层,用显式 handle 表达浏览器会话,并且每次调用都校验「handle + 认证主体」。第二,把网关的策略执行从解析 body 上移到
Mcp-Method/Mcp-Name头,这样限流、审计、路由不再依赖 DPI。第三,用双协议版本并存 +server/discover探测做灰度,按客户端类型逐步切流,保留至少 12 个月的兼容窗口。一个必须先讲清楚的误区:无状态核心不意味着这个浏览器服务能变成无状态。它本来就是有状态的,只是以前把状态藏在
Mcp-Session-Id指向的 Redis 里,现在要变成模型可见、服务可校验的显式句柄。如果只做”删掉 session”而不做状态外置,服务会直接不可用。
5. 问题拆解、核心方案与关键执行流程
拆解:
- 状态维度:浏览器上下文(页面、cookie、tab)是有状态;单次
snapshot是无状态。应把前者建模为资源,后者保持无状态。 - 治理维度:路由/限流/审计/成本归因需要从 body 上移到头。
- 兼容维度:新旧客户端 3 个月并存。
- 安全维度:handle 引入新的凭证化风险。
核心方案:
-
显式 handle 建模
browser.open返回session_handle(≥128 位 CSPRNG,服务端签名或存于带 TTL 的存储)。- 后续
browser.click/browser.snapshot以session_handle为普通参数传入。 - 服务端在每次调用校验:handle 存在 + 未过期 + 绑定主体 == 当前认证主体。
- 存储从 Redis 单点改为按 handle 分片的带 TTL 存储(或随浏览器 Pod 本地 + 句柄路由到指定 Pod),消除全局热点。
- 空闲 TTL(如 30 分钟)自动回收,显式
browser.close立即撤销。
-
双协议网关
- 网关同时接受旧版(带
Mcp-Session-Id)与新版(带MCP-Protocol-Version: 2026-07-28)请求。 - 新版请求:读取
Mcp-Method/Mcp-Name头 → 路由、限流(按工具名分桶)、审计(actor/subject/trace)。 - 服务端必须拒绝头与 body 不一致的请求,防止路由标签与实际执行脱节造成策略绕过。
- 旧版请求继续走 sticky + Redis,直到客户端全部迁移。
- 网关同时接受旧版(带
-
灰度与切换
- 阶段一:服务端双栈上线,内部客户端先切(可控)。
- 阶段二:
server/discover返回支持版本,客户端按能力协商;对 Tier 1 SDK 用户按 5% → 25% → 50% → 100% 逐步放量。 - 阶段三:社区 SDK / 未知客户端保留旧路径,设监控告警与截止日期;利用 12 个月最小弃用窗口排期。
- 回滚:网关按客户端 ID / 版本头一键切回旧路径,双栈期间无数据迁移,回滚成本极低。
-
执行流程(新版一次调用)
客户端 → 网关(读Mcp-Method/Mcp-Name,限流、审计、注入 traceparent)→ LB round-robin → 任意实例 → 校验MCP-Protocol-Version→ 鉴权(代持令牌)→ 校验 handle+主体 → 执行 → 结果写入审计与 trace。
6. 技术选型、依据与备选
| 决策 | 选择 | 依据 | 备选与为何不选 |
|---|---|---|---|
| 跨调用状态 | 显式 handle(工具参数) | 官方推荐;模型可见、可跨工具组合、可跨会话恢复 | 共享 Redis session:热点、需要 sticky,正是要消除的 |
| handle 存储 | 带 TTL 的分片 KV / 实例本地 + 一致性路由 | 消除单点热点,随扩容线性扩展 | 全局 Redis:曾导致 12% 故障,不作为目标态 |
| 网关策略 | 基于 HTTP 头 | 无需解析 body,支持大 body 请求,成本低 | body DPI:超过 64KB 直接跳过策略,是已知安全缺口 |
| 客户端缓存 | 按 ttlMs/cacheScope 缓存 tools/list | 减少重复拉取,稳定 prompt 前缀 | 每次会话拉取:浪费且破坏缓存 |
| 兼容性探测 | server/discover | 规范要求的标准能力宣告 | 客户端硬编码版本:无法平滑迁移 |
7. 失败路径、边界条件、重试、幂等与恢复
- handle 过期/被撤销:返回结构化错误(区分
handle_not_found/handle_expired/handle_forbidden),让 Agent 能理解并重新browser.open,而不是盲目重试造成风暴。 - MRTR 重试:客户端重试必须携带
requestState;服务端按requestState或客户端生成的幂等键去重,避免重复点击/重复提交。 - 网关头 body 不一致:服务端返回错误而非静默执行;客户端 SDK 需要升级到能正确设置头,否则整体拒绝并计入迁移监控。
- 重试风暴:对同类错误做指数退避 + 抖动 + 熔断;限流按工具名而非全局,避免一个 noisy 工具拖垮全部。
- 灰度期双写不一致:旧路径与新路径的 handle 不互通,网关必须保证同一客户端会话始终走同一路径(按客户端 ID 粘性到”路径”而非”实例”)。
- 回滚:双栈保留旧代码路径,回滚仅需网关配置切换,不需要数据迁移。
8. 安全、权限、成本、延迟与可靠性
- 安全:handle ≥128 位随机 + 过期 + 每次校验主体绑定;无认证部署下 handle 即凭证,因此不允许无认证暴露该服务。代持令牌编码 actor/subject,下游取交集。第三方/社区 SDK 应作为供应链风险单独评审。
- 权限:按工具名授权(某些 Agent 只能用
snapshot,不能用click),在网关以头信息执行,不依赖模型自觉。 - 成本:Redis 集群下线、DPI 计算下线与
tools/list缓存是主要节省;新增成本来自每请求_meta体积与重复鉴权——鉴权结果可做短 TTL 本地缓存,但 handle 校验不可省。 - 延迟:移除握手省一个 RTT;移除 Redis 会话读取省一次跨服务 IO;风险是每请求鉴权,用本地校验(签名令牌/JWKS 缓存)而非远程 introspection。
- 可靠性:无 sticky 后实例可随意调度,故障域缩小;浏览器 Pod 有状态,需要 handle→Pod 的路由或状态持久化,这是唯一残留的调度约束,必须显式承认。
9. 评测指标、测试方法与上线观测
- 指标:p50/p95/p99 延迟;按工具名的错误率与限流拒绝率;handle 相关错误分布(找不到 / 过期 / 越权);审计完整率(每次调用都有 actor+subject+trace);
tools/list缓存命中率;单位调用成本;新旧路径流量占比。 - 测试:
- 契约测试:头与 body 一致性、版本不匹配返回
UnsupportedProtocolVersionError。 - 并发/扩容测试:round-robin 下 10x 压测,验证无 sticky 也能正确工作。
- 故障注入:Redis 宕机(应不影响新路径)、handle 被撤销、网关限流生效。
- 安全测试:用 B 用户的令牌传 A 用户的 handle,必须拒绝;过期 handle 必须拒绝。
- 灰度回归:新旧路径同任务的端到端成功率与轨迹对比,差异超阈值即暂停放量。
- 契约测试:头与 body 一致性、版本不匹配返回
- 上线观测:trace 贯穿网关→服务→浏览器(
traceparent传播);按客户端版本分桶看指标;放量门禁为”新路径错误率不高于旧路径 + 审计完整率 100%”。
10. 三个渐进追问及回答要点
追问 1:浏览器 Pod 本身是有状态的(页面在它内存里),无状态协议怎么解决这个矛盾?
要点:协议层无状态解决的是”任意实例可处理任意请求”,但有状态资源需要定向。方案三选一:(a) handle 中包含实例标识,网关做一致性路由(牺牲一点调度自由);(b) 浏览器状态外部化(CDP 会话持久化 / 状态快照存 KV),Pod 可随时替换;(c) 接受 handle→Pod 的粘性并把浏览器层独立为有状态服务池,与无状态 MCP 接入层分离。关键是要显式承认这层约束,不能假装整个系统无状态。
追问 2:如果某个社区 SDK 长期不升级,不支持新协议,你怎么办?
要点:不要为它阻塞主线。策略:双栈保留到弃用窗口结束;在网关对该 SDK 的 User-Agent/客户端标识限流并降级(例如降低配额);提供明确的迁移 deadline 与告警;必要时在网关侧做协议转换(把新版请求翻译成旧版语义,或反之)作为过渡垫片,但要清楚垫片本身是技术债且是审计链的薄弱环节。
追问 3:迁移后单位成本反而上升了,你从哪些维度查?
要点:分三层。请求层——每请求
_meta与重复鉴权是否引入额外 IO,鉴权结果是否可本地短缓存。缓存层——tools/list是否真的按ttlMs缓存了,客户端 SDK 是否遵守;prompt 前缀是否因为工具顺序不稳定而击穿(服务端应保证tools/list顺序确定)。行为层——handle 让模型更容易复用会话,可能改变了调用模式(更多长会话),要看任务级成本而不是单次调用成本。
11. 常见误区(3 个)
- 把”无状态”理解成”服务不能有状态” → 直接删 session 而不做 handle 外置,服务不可用或状态错乱。
- 把 handle 当权限 → 只在创建时校验,后续调用不校验主体绑定,形成越权(任何拿到 handle 的人都能操作别人的浏览器会话)。
- 只改服务端不改网关 → 仍靠 body DPI 做限流和审计,无法利用新头,也无法覆盖大 body 请求,安全缺口依旧存在。
12. 30-60 分钟小型实践
用 FastAPI 写一个最小 MCP-lite 服务:
- 实现
/mcp单端点,要求MCP-Protocol-Version、Mcp-Method、Mcp-Name三个头,并校验头与 body 一致,不一致返回错误。 - 实现一个
browser.open工具返回session_handle(secrets.token_urlsafe(32),服务端 dict 存 TTL 与 owner),browser.click接收 handle 并校验 owner == 当前认证主体。 - 写一个中间件:按
Mcp-Name做令牌桶限流,并打印一条审计日志(actor/subject/tool/trace_id)。 - 用
httpx起 20 并发打 2000 次请求,验证:(a) 无 sticky 也能正确处理;(b) 用错 owner 的 handle 被拒绝;(c) 限流生效。 - 加一个
server/discover返回支持版本与能力。
S2 场景题:长任务持久化执行、暂停恢复与人工审批
1. 背景、目标、约束与已知现象(回顾)
见第二部分 S2 题干:合同审阅 Agent,任务 2 小时至 3 天;重跑成本高、重复发函 3 次、审批占槽导致队列堆积、模型升级后重放不兼容。
2. 核心考察点
- 持久化执行与事件历史:确定性重放所需的前提,以及非确定性操作(LLM 调用、检索、时间、随机)必须如何记录。
- 副作用边界与幂等:不可撤销动作(发函、落章)如何做到”重放不重做”。
- 长时挂起与人工信号:审批等待期间如何不占执行槽、如何超时、如何唤醒。
- 版本化与兼容:工作流/事件历史/prompt/模型版本变更时的恢复策略。
- 三层能力划分:模型的概率性能力、Agent 编排与状态能力、确定性系统的能力,各自负责什么。
3. 作答框架(3-5 分钟可复用)
第一步:先划清能力边界(45 秒) —— 明确哪些属于确定性系统(发信、落章、事务、计时、权限),哪些属于编排层(状态机、重试、审批、幂等键),哪些只能交给模型(理解条款、判断风险、生成建议)。
第二步:结论(30 秒) —— 核心不是”让 Agent 更聪明”,而是把进度与副作用的确定性从模型手里拿走,放进持久化执行层。
第三步:方案主体(2.5 分钟) —— 事件历史 + 检查点 → 副作用幂等与补偿 → 审批信号与超时 → 版本化 → 观测。
第四步:失败路径与上线(1 分钟) —— 重放安全、模型变更、审批超时、队列积压的处理与放量方式。
4. 参考回答(结论先行)
结论:这四个现象其实是同一个根因的两面——任务的进度和副作用都依赖”进程还活着”和”模型还会再想一遍”。修法是把它们分成三层:确定性系统负责真正的副作用(发信、落章、事务),编排层负责状态机、事件历史、幂等键和审批信号,模型只负责理解和生成。
具体是四件事。第一,引入持久化执行:每一步的输入输出都作为事件落盘,恢复时重放事件历史而不是重跑模型;所有非确定性结果(模型输出、检索结果、时间戳)必须在第一次执行时就固化成事件。第二,副作用出门必带幂等键:发函和落章这类不可逆动作,用任务 ID + 动作类型 + 参数摘要生成幂等键,下游去重;重放时命中幂等键就直接返回首次结果,不重做。第三,审批用信号 + 定时器,等待期间工作流挂起、释放执行槽,审批到达后信号唤醒,超时走预设分支(升级或自动拒绝并通知)。第四,工作流与事件历史都要版本化,模型/prompt 升级后旧任务可选择继续用旧版本跑完,或做事件 upcast。
这样重复发函这类事故从”靠流程约束”变成”结构上不可能”。
5. 问题拆解、核心方案与关键执行流程
拆解:
- 进度问题(重跑成本高)→ 持久化执行 + 检查点。
- 重复副作用(发函 3 次)→ 幂等键 + 副作用边界。
- 资源问题(审批占槽)→ 挂起 + 信号。
- 一致性问题(模型升级后行为漂移)→ 版本化 + 事件固化。
核心方案:
-
持久化执行(durable execution)
- 工作流定义为一个确定性的状态机:节点、分支、重试策略、超时都是代码。
- 每个节点的输入与输出写入事件历史(append-only 的事件日志),形成检查点。
- 恢复时:重放事件历史重建内存状态,已完成且已记录的节点不再执行,从第一个未完成的节点继续。
- 关键约束:非确定性结果必须固化——模型输出、检索结果、随机数、
now(),第一次执行即写入事件,重放时读事件而不是重新调用。这是确定性重放成立的前提,也是最容易违反的一条。 - 长输出(合同全文、抽取结果)不进事件历史,落对象存储,事件里存引用 + 摘要 + 哈希。
-
副作用幂等与补偿
- 所有外部副作用统一走一个副作用网关,强制携带
idempotency_key = hash(task_id, action_type, normalized_params)。 - 网关侧用幂等表(唯一索引)记录首次结果;重复请求直接返回首次结果,不重复执行。
- 不可逆动作先审批后执行,且审批票据绑定参数摘要(改参数即失效)。
- 可补偿动作(如创建草稿、占位)登记补偿动作;跨服务的多步流程用 Saga,接受最终一致,不用分布式事务假装强一致。
- 发函这类动作在调用前写入
outbox表(同事务),由独立投递器发送,保证”至少一次 + 下游幂等”,而不是”最多一次丢信”。
- 所有外部副作用统一走一个副作用网关,强制携带
-
审批:信号 + 定时器
- 工作流执行到审批节点 → 写入
pending_approval记录 → 挂起并释放执行槽(不是 sleep,不是轮询)。 - 审批人动作通过 API 写入,向工作流发送信号(signal);调度器唤醒工作流继续。
- 同时注册定时器:4 小时未决 → 升级到二级审批人;24 小时未决 → 自动拒绝并通知发起人(策略可配,但必须有确定结局)。
- 审批事件完整记录:审批人、时间、看到的参数摘要、决策、客户端信息。
- 工作流执行到审批节点 → 写入
-
版本化
- 工作流定义版本、prompt 版本、模型版本、工具 schema 版本全部记录在事件历史中。
- 恢复中的任务默认沿用启动时的版本(避免行为漂移);需要升级时提供显式迁移路径 + 事件 upcast 函数 + 灰度。
- 模型升级的回归门禁:golden set 跑 N 次,看成功率、轨迹合规率、成本与一致性,达标才允许新任务使用。
6. 技术选型、依据与备选
| 决策 | 选择 | 依据 | 备选与为何不选 |
|---|---|---|---|
| 执行模型 | 持久化执行(事件历史 + 确定性重放) | 中断恢复、长时挂起、审计都能自然支持 | 自己写状态表 + 定时轮询:需要处理竞态、重复执行、挂起占槽,长期成本更高 |
| 承载方式 | 现成持久化执行引擎(如 Temporal 类)或自建事件溯源 | 前者成熟、有 signal/timer/版本化能力 | 完全自研:除非有强定制需求,否则是重复造轮子 |
| 副作用 | 幂等键 + outbox + 副作用网关 | 结构性杜绝重复发函 | “小心一点” + 代码 review:不可靠,已出过事故 |
| 审批 | 信号 + 定时器 | 释放槽位、可超时、可审计 | 工作流内 sleep/轮询:占槽,正是队列堆积的原因 |
| 模型结果 | 首次执行即固化到事件 | 确定性重放的前提 | 恢复时重新调用模型:成本翻倍且结果不一致 |
7. 失败路径、边界条件、重试、幂等与恢复
- 进程崩溃/部署:工作流重放事件历史恢复,已完成节点不重跑。
- 模型超时/限流:节点级重试(指数退避 + 抖动 + 熔断 + 降级模型);重试次数与耗时都进事件,超过预算进入人工接管。
- 副作用部分成功(发信返回超时但对方已收到):靠幂等键 + 下游查询确认;绝不能盲目重试;用 outbox + 确认回执。
- 审批人离职/不可用:定时器升级;超过最长时限自动拒绝并通知,不允许无限挂起。
- 恢复后模型版本不同:默认沿用旧版本;需要升级时走显式迁移。
- 事件历史损坏:append-only + 定期快照 + 校验和;有快照可回滚到最近一致点。
- 任务已发出不可逆动作后收到取消:无法真正取消,只能进入”补偿/通知”分支——设计上就应避免把不可逆动作放在无审批的位置。
- 队列积压:审批挂起不再占槽后自然缓解;仍需对工作流启动做限流与优先级(高价值合同优先)。
8. 三层能力划分(明确区分,本题核心)
| 层 | 负责 | 性质 | 本题中的具体职责 |
|---|---|---|---|
| 模型(概率性) | 理解条款、抽取要素、风险定级、生成修改建议 | 不确定、可重复执行但结果可变 | 只在”纯计算/生成”节点调用;输出必须固化到事件 |
| Agent 编排与工作流 | 任务拆解、状态机、重试、审批路由、上下文组装、失败恢复 | 确定性的控制流(即使内部调用模型) | 决定下一步做什么、何时暂停、何时升级、如何恢复 |
| 传统软件系统(确定性) | 发信、落章、事务、定时器、权限校验、幂等表、审计存储 | 确定性、可测试、可审计 | 承担所有不可逆副作用与裁决逻辑;Agent 只能”请求”,不能”直接做” |
关键判断:本题中”重复发函”的根因是把确定性副作用交给了概率性流程去控制。修法不是让模型更小心,而是把副作用移到确定层并用幂等键锁死。
9. 安全、权限、成本、延迟与可靠性
- 安全:Agent 使用独立身份 + 代持令牌(actor=agent, subject=法务用户);落章/发函工具单独授权且必须审批票据;审批票据绑定参数哈希。
- 权限:草稿生成与最终发函是不同权限;Agent 默认只有前者。
- 成本:恢复不重跑 → 直接消除重跑成本;按任务归因 token/成本(trace 级别),识别”检索过多""重复抽取”等浪费。
- 延迟:端到端受审批 SLA 主导(小时级),因此不应优化模型延迟而应优化排队时长与审批触达(挂起释放槽位后 P95 排队应显著下降)。
- 可靠性:任务成功率 ≥95% 的达成路径是”重试 + 降级 + 人工接管”三层兜底,而不是指望模型一次做对。
10. 评测指标、测试方法与上线观测
- 指标:
- 任务成功率(按任务类型分层)+ 多次运行一致性(核心任务 N=5)。
- 中断恢复成功率 与 恢复后已完成步骤的重跑率(目标 0)。
- 重复副作用事故数(目标 0,硬门禁)。
- 审批等待时长分布、超时率、升级率、自动拒绝率。
- 队列 P95 排队时长、执行槽利用率。
- 每任务 token/成本/时长归因;人工接管率。
- 轨迹合规率(高风险动作前必须有审批事件)。
- 测试:
- 故障注入:在任意节点 kill worker、断网、模型返回 429/超时,验证恢复且不重做副作用。
- 幂等测试:同一幂等键并发/重复调用 100 次,验证只执行一次。
- 审批测试:不响应 → 4h 升级、24h 自动拒绝;审批后改参数 → 拒绝执行。
- 重放测试:模型升级后重放旧事件历史,验证行为一致或走显式迁移。
- 混沌测试:副作用网关超时但下游已执行,验证 outbox + 回执确认。
- 上线观测:先影子模式(只跑不动副作用)→ 内部法务小流量 → 按合同类型逐步放量;每阶段门禁为”重复副作用 0、恢复成功率达标、成本不劣化”。
11. 三个渐进追问及回答要点
追问 1:你说”已完成节点不重跑”,但如果那一步的模型输出本身就是错的,恢复后不就永远错下去了?
要点:区分正确性与一致性。持久化执行保证的是一致性与不浪费,不保证正确性。正确性靠三层:节点级校验(schema、引用存在性、关键字段)、下游人工复核(高风险结论必须法务确认)、以及”可回滚重做”设计——关键节点保存足够信息,允许显式触发”从该节点重做”(invalidate 其后的事件,形成新分支),而不是默认重做。默认不重跑是成本与一致性选择,不是正确性保证。
追问 2:模型升级后,旧任务应不应该用新模型继续跑?
要点:默认不应该——继续用启动时的版本,避免行为漂移和不可复现。三个例外:旧版本要下线(需提供迁移窗口 + 显式迁移 + 回填校验);任务是长周期且新版本显著更优(走灰度并双写对比);旧版本有安全缺陷(强制升级并通知)。任何情况下都要记录”该任务经历了哪些版本”,否则审计链断裂。
追问 3:审批等待 3 天,工作流挂起 3 天,状态放哪里?会不会有资源泄漏?
要点:挂起时内存中不保留任何状态,工作流状态就是持久化的事件历史(数据库里的行),成本等价于一条记录。定时器也是持久化的(到期由调度器扫描触发),不是内存定时器。资源泄漏风险在于:外部已申请但未释放的资源(如浏览器会话、文件锁、临时审批单)——这些必须登记在案并由独立的清理任务按 TTL 回收,不能依赖工作流恢复时自己清理(它可能永远不恢复)。
12. 常见误区(4 个)
- 把持久化执行理解成”保存聊天记录” —— 保存的是结构化的事件历史与状态机位置,不是对话文本;靠对话文本恢复等于让模型重新理解一遍,成本高且不一致。
- 恢复时重新调用模型 —— 违反确定性重放前提,成本翻倍且结果漂移。
- 用 sleep 等待审批 —— 占执行槽,是队列堆积的直接原因;必须用信号 + 挂起。
- 幂等键只覆盖”同一秒内”或”同一进程” —— 幂等必须跨进程、跨重启、跨时间窗,落在带唯一索引的持久存储里,并设置合理的保留期。
13. 30-60 分钟小型实践
用 Python + SQLite 实现一个最小持久化执行原型:
- 建
events(task_id, seq, node, input_ref, output_ref, created_at)表,append-only。 - 写一个 4 节点的工作流:
parse → extract → [approval] → send。parse/extract调用一个假的 “LLM”(返回随机结果)。 - 实现重放:进程重启后从事件历史重建状态,已完成节点直接读事件不重跑(打印”replayed”以验证)。
- 实现
approval节点:写入pending_approval后return释放(不 sleep),模拟信号唤醒。 - 实现
send的幂等表(唯一索引idempotency_key),连续调用 5 次只打印一次 “SENT”。 - 手动 kill 进程两次,验证恢复后不重复发送。
L1 LeetCode 146. LRU 缓存
1. 题目信息(已核验)
| 项 | 内容 |
|---|---|
| 题号 | 146 |
| 中文名 | LRU 缓存 |
| 英文名 | LRU Cache |
| 难度 | Medium(力扣标记为中等;LeetCode 全球站 Acceptance Rate 约 48.2%) |
| 官方链接 | 中文:https://leetcode.cn/problems/lru-cache/ | 英文:https://leetcode.com/problems/LRU-Cache/ |
| 核验状态 | 已核验(2026-10-10 通过官方题目页核对题号、难度、描述、约束与示例) |
2. 题目描述
设计并实现一个满足 LRU(Least Recently Used,最近最少使用) 约束的缓存数据结构。
实现 LRUCache 类:
LRUCache(capacity: int):以正整数capacity初始化缓存。get(key: int) -> int:若key存在返回其值,否则返回-1。put(key: int, value: int) -> None:若key已存在则更新其值;否则插入该键值对。若插入后键数量超过capacity,则逐出最久未使用的键。
get 与 put 必须都是 O(1) 平均时间复杂度。
3. 输入、输出、约束与示例
约束:
1 <= capacity <= 30000 <= key <= 10^40 <= value <= 10^5get与put调用总次数最多2 * 10^5
示例:
输入["LRUCache","put","put","get","put","get","put","get","get","get"][[2],[1,1],[2,2],[1],[3,3],[2],[4,4],[1],[3],[4]]输出[null,null,null,1,null,-1,null,-1,3,4]解释:容量为 2。put(1,1)、put(2,2) 后缓存为 {1=1, 2=2};get(1) 返回 1 并使 1 变为最近使用;put(3,3) 容量超限,逐出最久未使用的 2;get(2) 返回 -1;put(4,4) 逐出 1;get(1) 返回 -1;get(3) 返回 3;get(4) 返回 4。
4. 主要考察的数据结构与算法
- 哈希表:O(1) 定位 key 对应的节点。
- 双向链表:O(1) 完成删除与插入,维护”使用顺序”。
- 哨兵节点(dummy head/tail):消除头尾插删的边界判断。
- 设计题:考察把”两个 O(1) 能力”组合成一个结构的能力。
5. 渐进式提示(不泄露完整答案)
提示 1:拆开需求。“按键快速找到值”和”维护最近使用顺序”是两个正交需求,各自最优的数据结构分别是什么?
提示 2:如果只用链表,删除任意节点是 O(1),但找到它是 O(n);如果只用哈希表,找到是 O(1),但没法表达顺序。让哈希表的值指向链表节点,会发生什么?
提示 3:如何避免”头尾插入/删除”的一堆 if 判断?考虑加两个不存数据的哨兵节点。另外,put 已存在的 key 时,是”更新值”还是”删除再插入”?两者对顺序的影响一样吗?
6. 直观方案及其局限
方案 A:数组/列表 + 字典。用 list 按使用顺序存 key,字典存值。
- 局限:把某个 key 移到队首是 O(n);
get虽 O(1) 但更新顺序退化,整体不满足 O(1) 要求。
方案 B:OrderedDict(Python)/ LinkedHashMap(Java)。内置结构,几行代码。
- 局限:面试中通常要求手写底层结构以考察数据结构功底;且要明确
move_to_end与popitem(last=False)的语义,否则仍可能在边界上出错。工程上它是完全合理的首选,但本题作为 Medium 设计题,考察点正是”你能不能造出它”。
方案 C:单链表 + 哈希表。
- 局限:删除节点需要前驱指针,单链表下找前驱是 O(n),除非额外维护前驱映射——那基本就是双向链表了。
7. 推荐方案的推导过程
- 需求分解:(a) O(1) 按 key 查值 → 哈希表;(b) O(1) 调整”最近使用”顺序 + O(1) 淘汰最久未用 → 链表,且需要双向(删除任意节点要知道前驱)。
- 组合:哈希表
key -> node,双向链表维护顺序,链表头为最近使用(MRU),尾为最久未使用(LRU)。 - 消除边界:加
head/tail两个哨兵节点,任何插入都是”插到 head 之后”,任何删除都是标准的prev.next = next; next.prev = prev,不需要判空。 - 操作定义:
get命中 → 从链表摘下 → 插到 head 后 → 返回 value。put命中 → 更新 value → 移到 head 后。put未命中 → 新建节点插到 head 后 → 若len > capacity→ 删除tail.prev并从哈希表移除。
- 正确性关键:哈希表和链表必须同步更新,任何一条路径漏掉另一边都会产生幽灵节点或悬空引用。
8. 正确性说明
不变式(Invariant):
- 链表中的节点集合与哈希表的键集合一一对应(双向:链表里有 ⟺ 哈希表里有)。
- 链表从头到尾按”最近使用”降序排列,head 之后是 MRU,tail 之前是 LRU。
- 链表节点数 ≤ capacity。
证明要点:
get命中:摘下再插入头部,仅改变位置,不破坏集合对应关系;顺序不变式成立(该 key 变为最新使用)。put命中:更新 value 后移到头部,同样保持两条不变式,且节点数不变。put未命中:插入新节点,集合对应关系由”哈希表写入 + 链表插入”成对完成;若超过容量,删除tail.prev同时删除哈希表条目,成对完成,节点数回落至 capacity。由不变式 2,被删除的正是最久未使用的键,符合 LRU 定义。- 时间复杂度:所有操作均为常数次指针/哈希操作,故 O(1) 平均。
9. 复杂度
- 时间:
get与put均为 O(1) 平均。 - 空间:O(capacity),哈希表与链表各存至多 capacity 个条目。
10. Python 参考代码(可直接运行)
class _Node: __slots__ = ("key", "value", "prev", "next")
def __init__(self, key: int = 0, value: int = 0) -> None: self.key = key self.value = value self.prev: "_Node | None" = None self.next: "_Node | None" = None
class LRUCache: def __init__(self, capacity: int) -> None: if capacity <= 0: raise ValueError("capacity must be a positive integer") self.capacity = capacity self.cache: dict[int, _Node] = {} # 哨兵节点:head 之后是最近使用,tail 之前是最久未使用 self.head = _Node() self.tail = _Node() self.head.next = self.tail self.tail.prev = self.head
def _remove(self, node: _Node) -> None: """从双向链表中摘除节点。""" prev, nxt = node.prev, node.next prev.next = nxt nxt.prev = prev node.prev = node.next = None
def _insert_after_head(self, node: _Node) -> None: """插入 head 之后,即标记为最近使用。""" first = self.head.next node.prev = self.head node.next = first self.head.next = node first.prev = node
def get(self, key: int) -> int: node = self.cache.get(key) if node is None: return -1 self._remove(node) self._insert_after_head(node) return node.value
def put(self, key: int, value: int) -> None: node = self.cache.get(key) if node is not None: node.value = value # 更新值 self._remove(node) self._insert_after_head(node) return node = _Node(key, value) self.cache[key] = node self._insert_after_head(node) if len(self.cache) > self.capacity: lru = self.tail.prev # 最久未使用 self._remove(lru) del self.cache[lru.key]11. 关键边界情况与测试用例
边界情况清单:
capacity == 1:每次put新 key 都会立即淘汰唯一元素。get一个从未存在 / 已被淘汰的 key → 返回-1。put已存在的 key:更新值,不改变容量,但要刷新为最近使用。get命中后该 key 必须变为 MRU,否则下一次淘汰会误删它。- 淘汰时必须同时从链表和哈希表删除,漏删哈希表会导致 key 永远”存在”但节点已不在链表。
key/value可以为0(0是合法值,不能用 falsy 判断存在性)。- 连续
get同一 key 不应出错(自己摘下再插回)。
测试用例:
def run(ops, args): obj = None out = [] for op, a in zip(ops, args): if op == "LRUCache": obj = LRUCache(*a) out.append(None) elif op == "put": obj.put(*a); out.append(None) else: out.append(obj.get(*a)) return out
# 用例 1:官方示例assert run(["LRUCache","put","put","get","put","get","put","get","get","get"], [[2],[1,1],[2,2],[1],[3,3],[2],[4,4],[1],[3],[4]]) == \ [None,None,None,1,None,-1,None,-1,3,4]
# 用例 2:capacity = 1assert run(["LRUCache","put","get","put","get","get"], [[1],[1,1],[1],[2,2],[1],[2]]) == [None,None,1,None,-1,2]
# 用例 3:value 为 0 的合法值assert run(["LRUCache","put","get","get"], [[2],[5,0],[5],[6]]) == [None,None,0,-1]
# 用例 4:put 已存在 key 只更新值、不增容量c = LRUCache(2); c.put(1,1); c.put(2,2); c.put(1,10)assert c.get(1) == 10 # 值已更新c.put(3,3) # 1 刚被刷新为 MRU,应淘汰 2assert c.get(2) == -1 and c.get(1) == 10
# 用例 5:get 刷新顺序c = LRUCache(2); c.put(1,1); c.put(2,2)assert c.get(1) == 1 # 1 变 MRUc.put(3,3) # 淘汰 2assert c.get(2) == -1 and c.get(3) == 3 and c.get(1) == 1print("all tests passed")12. 变体 / 追问
变体:如果需要 LFU(Least Frequently Used) 而不是 LRU,并且 get/put 仍要求 O(1),怎么设计?(LeetCode 460)
要点:需要”按频率分桶”——哈希表
key -> node之外,再维护freq -> 双向链表的映射,以及一个min_freq指针。访问时把节点从当前频率桶移到freq+1桶;淘汰时从min_freq桶的尾部删除。难点在于min_freq的维护与”同频率内的 LRU 次序”。
追问:如果 capacity 很大(百万级)且 key 是字符串,工程上你会调整什么?
要点:链表指针开销与内存碎片会显著上升;可用数组实现的侵入式双向链表(
prev/next存下标而非对象引用)降低 GC 压力;考虑分片(sharding)降低锁竞争;引入 TTL 与容量之外的淘汰维度;命中率需要用真实访问分布压测而不是拍脑袋。
13. 与 AI Agent 工程的关系(自然联系,非牵强)
LRU 是 Agent 系统里上下文与工具结果缓存的基础淘汰策略:多轮对话中缓存检索片段、工具返回值、embedding 查询结果,用 LRU 决定哪些内容保留在”热上下文”里、哪些回落到外部存储按需读回。这与 B1 的上下文压缩直接相关——LRU 决定”哪些留在上下文”,压缩决定”留下的以什么粒度留”,两者是同一套上下文预算管理机制的上下半场。
对应到今日 B2 题:MCP 2026-07-28 的 ttlMs/cacheScope 就是协议层的缓存契约,客户端据此缓存 tools/list;而缓存条目的淘汰策略在客户端侧往往就是一个 LRU(或 TTL+LRU)。
14. 可迁移解题能力
- 需求正交分解:把”快速查找”和”顺序维护”拆成两个独立需求,各自选最优结构再组合——这是应对所有”设计题”的通用起手式。
- 哨兵技巧:用哑节点消除边界判断,把”特判一堆 if”转化为”统一路径”,在链表类问题中几乎总是适用。
- 双结构同步不变式:凡是”一个结构加速查询、另一个结构维护顺序”的组合(LFU、带过期时间的缓存、时间轮 + 索引),都必须显式维护两者的一致性并能在面试中说出不变式。
- 从 API 反推结构:当题目给出”O(1) 时间复杂度”这个硬约束时,它实际上是在指定数据结构——把约束翻译成结构要求,是设计题的核心解码能力。
四、今日重点与建议复习
今日重点(按优先级)
- 分层思维是今天所有题目的共同主线。协议层无状态 ≠ 应用层无状态(B2/S1);结果正确 ≠ 过程正确(B4);模型能力 ≠ 编排能力 ≠ 确定性系统能力(S2)。面试中能主动做层次划分,是中高级最明显的信号。
- “约束被移除”不等于”责任消失”,而是”责任上移”。MCP 移除 session 后,状态、鉴权、幂等、审计全部从协议层上移到应用层实现者身上——这是 B2、S1、B5 的同一条逻辑。
- 确定性重放的前提是非确定性结果固化(S2)。这句话是持久化执行类问题的高频得分点,也是最高频的失分点。
- 一致性是独立于成功率的维度(B4)。“78% 但用户投诉不降”这类题,答案永远是分分布而不是提均值。
- 最小代理 ≠ 最小权限(B5)。自主性、持久化、作用域三个额外维度是拉开差距的地方。
建议复习内容
- 概念对比卡片(建议做成 Anki 或手写):
- 压缩 vs 摘要 vs 外部记忆载体 vs RAG vs 长期记忆
- 协议层无状态 vs 应用层无状态 vs 服务无状态
- 结果级评测 vs 轨迹级评测 vs 一致性;
pass@kvspass^k - 最小权限 vs 最小代理;服务账号 vs 代持令牌
- 持久化执行 vs 保存聊天记录;挂起/信号 vs sleep/轮询
- 薄弱点自测:能否在 90 秒内讲清”为什么 Agent 不能复用用户权限”?能否说出 MCP 2026-07-28 至少四项具体变更?能否给出幂等键的构造方式?这三问任一答不出,建议重做对应题目。
- 动手:S1 与 S2 各有一个 30-60 分钟实践,建议本周内完成其一。LeetCode 146 建议闭卷手写两遍(第一遍用哨兵链表,第二遍用
OrderedDict并解释差异)。
下一次训练的轮换建议(供去重参考)
本次已覆盖的一级类型:上下文工程与能力封装、工具与互操作协议、AI Agent 核心、Agent 评测与可观测性、AI 安全与治理。
下次建议优先覆盖尚未触及的启用类型:编程语言与运行时、数据库与存储、分布式系统、操作系统与网络、软件工程与平台工程、AI 与大模型基础、LLM 应用工程、RAG 与搜索。
LeetCode 本次类型为哈希表 + 双向链表(设计题),下次建议轮换到双指针/滑动窗口或基础动态规划。
五、延伸资料
以下链接中标注”已核验”的,均在本轮(2026-10-10)通过检索核对了标题、日期与核心内容;标注”未核验”的仅来自本地题库配置记录,使用前请自行核对原文。
官方文档与规范(优先级 1)
- MCP 2026-07-28 规范发布公告 —— https://blog.modelcontextprotocol.io/posts/2026-07-28/
已核验。无状态协议核心、MRTR、基于头的路由、可缓存列表结果、授权硬化、扩展框架、12 个月弃用策略的官方一手说明。 - MCP Key Changes(官方 changelog) —— https://modelcontextprotocol.io/specification/2026-07-28/changelog
已核验。逐条列出相对 2025-11-25 的增删,含 SEP 编号(SEP-2575 / SEP-2567 / SEP-2322 / SEP-2243 / SEP-2549 / SEP-2663 / SEP-414 等),是准备面试细节的最佳材料。 - A2A 加入 Agentic AI Foundation 官方公告(2026-08-27) —— https://a2a-protocol.org/latest/blog/2026/08/27/a-new-chapter-for-a2a-joining-the-agentic-ai-foundation/
已核验。明确”MCP 为垂直集成层、A2A 为水平编排层”的双层定位,以及 AAIF 中立治理下的项目关系(MCP、goose、AGENTS.md、agentgateway)。
算法(优先级 1)
- LeetCode 146. LRU Cache(官方题目页) —— 中文 https://leetcode.cn/problems/lru-cache/ | 英文 https://leetcode.com/problems/LRU-Cache/
已核验(题号、难度 Medium、约束、示例、Acceptance Rate 48.2%)。
标准与规范(优先级 2,本轮未核验原文)
- RFC 9207 OAuth 2.0 Authorization Server Issuer Identification —— https://www.rfc-editor.org/rfc/rfc9207
未核验(本轮仅由 MCP 官方博客提及)。用于理解 iss 校验关闭的”授权服务器混淆”漏洞。 - OWASP GenAI Security:《Top 10 for Agentic Applications 2026》
未核验(来自本地题库配置记录,本轮未访问原文)。涉及 B5 的风险分类,引用前请核对官方发布页。 - OpenTelemetry GenAI 语义约定 —— https://opentelemetry.io/docs/specs/semconv/gen-ai/
未核验(本轮未访问原文;本地配置记录其 Agent 与 MCP 相关约定状态为 Development,以仓库实际状态为准)。
补充阅读(优先级 3-4)
- MCP 2026-07-28 Release Candidate 说明 —— https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/
已核验。比正式公告更详细地给出新旧请求形态对比与迁移注意事项,适合准备 S1 场景题。
六、题目分类与标签
| 编号 | 一级类型 | 二级标签 | 难度 |
|---|---|---|---|
| B1 | 上下文工程与能力封装 | 上下文压缩、摘要、外部记忆、缓存断点、退化归因 | 中高 |
| B2 | 工具与互操作协议 | MCP 2026-07-28、无状态核心、MRTR、显式 handle、A2A、AAIF | 中高 |
| B3 | AI Agent 核心 | 编排器-工作者、上下文隔离、结果契约、多 Agent | 中高 |
| B4 | Agent 评测与可观测性 | 轨迹级评测、Task Success Rate、pass@k、一致性、榜单污染 | 中高 |
| B5 | AI 安全与治理 | 最小代理原则、混淆代理、非人类身份、代持令牌、审计链 | 中高 |
| S1 | 有状态工具服务迁移到无状态协议核心 | 协议迁移、状态外置、网关治理、灰度回滚、按工具限流 | 高 |
| S2 | 长任务持久化执行与人工审批 | 事件历史、确定性重放、幂等键、Saga、信号与定时器、版本化 | 高 |
| L1 | LeetCode / 哈希表 + 双向链表 | 设计题、LRU、哨兵节点、O(1) | Medium |
覆盖的启用类型(5/13):上下文工程与能力封装、工具与互操作协议、AI Agent 核心、Agent 评测与可观测性、AI 安全与治理。
未覆盖但已启用:编程语言与运行时、数据库与存储、分布式系统、操作系统与网络、软件工程与平台工程、AI 与大模型基础、LLM 应用工程、RAG 与搜索。
暂停类型:截至 2026-10-10 配置中无暂停类型。
观察中的新技术:本次未从中出题(配置未标记为”允许试学”)。
七、本次参考过的历史题库文件
本次为 better/ 目录下首次生成,此前不存在任何年月训练文件、周复盘文件或精选索引文件,因此无历史去重结果、无历史薄弱点记录、无已练习记录。
- 读取的配置文件:
better/题库配置.md.md(version: 2,last_reviewed: 2026-10-10) - 读取的目录:
better/、better/2026-10/(原为空,本次创建) - 查找并确认不存在:
AGENTS.md、CLAUDE.md等说明文件(在better/、Plan/、仓库根三级目录中均不存在) - 个人记忆参考:
日记/MEMORY.md(无与本题库冲突的条目)
去重结论:本次 8 道题均为新题,无重复题、无换皮题,无需标记”复习题”。
周期复盘:本次为第 1 次训练,未达到累计 7 次阈值,未触发周期复盘。