22668 字
113 分钟
2026-10-10 AI Agent 面试训练

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真实场景长任务持久化执行、暂停恢复与人工审批事件历史、幂等与补偿、人工信号、版本兼容
L1LeetCode哈希表 + 双向链表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 代表哪个人调用”。

目标与约束:

  1. 迁移到 2026-07-28 无状态协议核心,支持普通 round-robin 负载均衡与水平扩容。
  2. 不停机迁移,需要同时服务新旧客户端至少 3 个月。
  3. 浏览器会话这类跨调用状态不能丢。
  4. 合规要求:审计链必须能归因到人或 Agent,且不得因为迁移引入越权风险。

请给出:迁移方案、状态外置设计、灰度与回滚策略、失败路径与幂等、安全与成本分析、评测与上线观测指标。

难度:高。考察方向:协议工程、状态外置设计、网关治理、灰度迁移、安全归因。


S2|真实研发场景题|长任务持久化执行、暂停恢复与人工审批#

背景:企业级”合同审阅 Agent”。一次任务包含:拉取合同与附件 → 版面解析 → 条款抽取 → 风险定级 → 生成修改建议 → (高风险)在合同系统落章 / 向对方发送函件。单次任务时长从 2 小时到 3 天不等,涉及法务人工审批。

已知现象:

  1. 任务中断(模型超时、worker 重启、部署)后从零重跑,成本翻倍;用户投诉”进度丢失”。
  2. 重跑导致同一封函件被重复发送 3 次,造成对外事故。
  3. 审批等待期间,workflow worker 一直占用执行槽位,队列堆积,P95 排队时长超过 40 分钟。
  4. 模型升级后,重放旧事件历史出现不兼容,恢复的任务行为与中断前不一致。

目标与约束:

  • 任务成功率 ≥ 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. 常见误区#

  1. 把压缩当成”上下文永远够用”的解法——压缩本身消耗 token、引入延迟,且可能击穿缓存。
  2. 在已缓存前缀的中部做压缩——导致后续全部 cache miss,成本不降反升。正确做法是只压缩缓存断点之后的活跃段,或接受一次可控的重新缓存。
  3. 摘要时丢失约束(“用户明确说不要改 public API”),导致后期违规。缓解:摘要模板中显式保留「约束 / 已做决策 / 待办」三类不可压缩项。
  4. 把草稿本当长期记忆,任务结束不清理,造成下次会话上下文污染与敏感数据残留。
  5. 只用 token 数做触发阈值——长任务中”token 未超限但注意力已分散(lost-in-the-middle)“同样会退化,需要结合轮次与任务阶段判定。

7. 面试现场参考回答(1-2 分钟)#

这三件事常被混为一谈,但本质上是两类操作。压缩和摘要是在”already in context”的内容上做手脚,改变的是上下文里的信息粒度;外部记忆载体是把信息移出上下文,改变的是存放位置。

选择依据是三条判据:需不需要精确复现——需要就落盘,按需读回;能不能丢细节——能丢就摘要;近期还要不要引用——要引用但可以降低粒度,就压缩裁剪。

工程上最容易踩的坑有两个。第一,摘要是有损不可逆的,摘要时如果不显式保留约束和已做决策,后面会反复违反用户要求。第二,压缩会改前缀,如果压在已缓存的前缀里,prompt cache 会整体失效,成本反而上升,所以要只在缓存断点之后的活跃段压。

归因上我不只看 token 数,而是看三层:召回层(是不是该给的信息没给)、上下文层(是不是该留的被压掉了、是不是信息在窗口中部被忽略)、模型层(是不是这个子任务本身超出能力)。做法是固定模型和 prompt,只切换压缩策略做 A/B,再看失败样本集中出现在压缩触发后的第几轮。

8. 面试官追问及回答要点#

追问:压缩上线后任务成功率反而掉了 5 个点,你怎么定位是不是压缩导致的?

回答要点:

  1. 控制变量 A/B:固定模型版本、prompt、工具集,只切换压缩策略,跑同一评测集,看成功率差异是否显著(需要说明样本量与置信区间,避免把噪声当信号)。
  2. 快照对比:导出压缩前后的上下文快照,对失败样本做人工 diff,区分丢的是”事实/约束”还是”过程/冗余”。丢约束 → 摘要模板问题;丢事实 → 召回或外部载体读回问题。
  3. 排除成本混淆:同时看 prompt cache 命中率。如果质量没变但成本涨了,那是缓存击穿,不是质量问题。
  4. 看滞后分布:失败是否集中在压缩触发后的第 N 轮。若是立即失败,多半是关键信息被压掉;若是 10 轮后才失败,更像是”约束丢失”。
  5. 反证法:把未压缩的原始上下文喂给同一模型,若成功率恢复,则可确认归因于压缩。

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.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-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 的职责边界(双层架构)#

维度MCPA2A
方向垂直: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. 常见误区#

  1. “无状态 = 我的服务不能有状态”——错。状态上移到应用层显式 handle,只是不再藏在传输层。
  2. 把 handle 当权限——handle 是引用不是凭证,每次调用都要做主体校验。
  3. 以为移除握手就不需要兼容旧客户端——需要双版本并存,通过 server/discover 探测,并依赖 12 个月弃用窗口安排迁移。
  4. 忽略头与 body 一致性校验——服务端必须拒绝 Mcp-Method 与 body 不一致的请求,否则网关路由与实际执行会脱节,形成策略绕过。
  5. 把 MRTR 当成普通重试——MRTR 的重试必须携带 requestState,并需要按请求 ID 做幂等去重,否则会触发重复副作用。
  6. 拿 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,那我怎么保证一次多步操作的原子性?比如”扣库存 + 创建订单”。

回答要点:

  1. 先指出问题本身的层次错位:原子性是应用层事务问题,从来就不该由传输层 session 保证——旧协议里的 session 也不提供原子性。
  2. 正确做法一:把多步操作建模为单个工具(create_order 内部用数据库事务),让副作用的原子性落在确定性系统里,这是首选。
  3. 正确做法二:确需跨调用时,用显式 handle 表达工作单元(如 checkout_session_id),状态机在服务端,配合幂等键。
  4. 正确做法三:跨服务的长流程用 Saga + 补偿,接受最终一致,而不是假装分布式事务。
  5. 强调:模型是概率性参与者,绝不能让”模型记得调用第二个工具”来承担原子性责任。

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. 常见误区#

  1. 把隔离当省 token 手段——省 token 是副作用,核心价值是质量(避免主上下文被污染)与并行。
  2. 回传契约过于自由(让子 Agent 自己写一段自然语言)→ 编排器无法程序化校验、无法聚合。应定义结构化 schema 并强制校验。
  3. 子 Agent 失败被静默吞掉——编排器必须接收显式的失败/部分成功状态与置信度,否则会基于残缺信息汇总。
  4. 过度拆分——子任务太细,通信与重复理解成本超过收益,且全局一致性变差。
  5. 以为隔离解决了幻觉——隔离只防止污染,不防止子 Agent 自身产生幻觉;仍需要引用溯源与校验。
  6. 共享可变状态——多个子 Agent 写同一个草稿本导致竞态;共享写入必须走确定性系统(数据库/队列),不能靠模型自觉。

7. 面试现场参考回答(1-2 分钟)#

上下文隔离解决的核心问题不是 token 不够,而是质量。多步任务里,子任务会产生大量噪音:失败的检索、错误的工具调用、几百行的原文。如果全部进主上下文,会稀释编排器的注意力,让它在汇总阶段抓不住重点,也就是所谓的上下文污染。隔离让每个工作者在自己的上下文里把脏活干完,只把可验收的产物交回来。

代价有三个。第一是信息丢失:编排器看不到过程,就无法判断这个结果是怎么来的,所以回传必须带来源、置信度和”哪些字段没查到”,而不是一段自由文本。第二是重复劳动:多个子 Agent 可能重复检索同一个东西,需要任务拆分时划清边界或共享一个只读索引。第三是全局一致性变差,每个子 Agent 只对自己的局部负责,最终必须由编排器做交叉校验和冲突消解。

什么时候不该隔离:子任务之间强耦合、编排器必须掌握细节才能判断质量、或者子任务太轻、隔离的通信成本大于收益的时候。判断标准其实很简单——如果这个子任务能独立验收,就隔离;如果验收标准依赖其他子任务的中间结论,就别隔离。

8. 面试官追问及回答要点#

追问:子 Agent 回传的结果相互矛盾,编排器怎么办?

回答要点:

  1. 不要靠模型”再想一次”解决——把冲突变成确定性问题:要求每个子 Agent 回传带来源与置信度的断言,冲突即来源冲突。
  2. 溯源优先:定位到具体文档/记录,比较时间戳与权威性,用确定性规则(更新时间、来源等级)裁决。
  3. 无法自动裁决时升级:把冲突与双方证据一起呈现,交人工或触发一次”仲裁子任务”(只针对冲突点做定向复核)。
  4. 对下游诚实:汇总结果中标注冲突字段与不确定性,不要让下游以为数据是确定的。
  5. 把冲突率做成指标——冲突率上升往往意味着任务拆分边界不清或数据源本身不一致,是系统性问题信号。

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. 常见误区#

  1. 只用成功率——掩盖了过程违规与成本爆炸;也容易被偶然正确掩盖。
  2. 用公开榜单替代自建评测——存在榜单污染与分布不匹配;必须建自己的 golden set。
  3. 把评测集混进 prompt 或 few-shot 示例——自我污染,指标虚高。
  4. LLM-as-a-Judge 不做校准——不与人工标注对齐、不看一致率,直接采信分数。
  5. 只测一次——单次运行结果是随机变量,必须多次运行取分布,并报告方差/置信区间。
  6. 把 pass@k 当成可靠性指标——“试 k 次能成一次”对用户(尤其是高风险场景)没有意义,要看 pass^k 和失败模式。

7. 面试现场参考回答(1-2 分钟)#

结果指标和轨迹指标回答的是不同问题。任务成功率回答”用户拿到他要的东西了吗”,轨迹级评测回答”它是怎么拿到的、这条路能不能复现、能不能维护”。一个 Agent 可以把钱退了但全程跳过身份校验——结果指标会记它成功,轨迹指标会记它违规。这在合规场景里是不可接受的。

轨迹评测落地的关键是别去定义”唯一正确路径”,那在开放任务里不成立。可行的是两层:硬约束检查(必须发生的动作、禁止的行为、必须校验的前置条件),加上软指标(工具调用的精确率召回率、步数与最优路径的偏差、冗余调用次数、错误后的恢复能力)。

至于”线上投诉没降”,第一个要怀疑的是一致性。78% 的单次成功率有两种可能:一种是 78% 的任务稳定成功、22% 稳定失败,那投诉会集中在特定类型,用户可预期;另一种是每个任务都有 78% 概率成功,用户感知就是”时灵时不灵”,投诉量不会随均值上升而下降。这两个系统的产品含义完全不同。所以我会把评测从单次改成每个任务跑 N 次,同时看 pass@k 和 pass^k,并对失败模式做聚类——如果失败是随机分布的,优先做的是加确定性的护栏和校验,而不是继续调 prompt。

8. 面试官追问及回答要点#

追问:你说要跑 N 次,N 取多少?如果预算只够跑一次呢?

回答要点:

  1. N 取决于你想分辨的效应量与方差:方差大(开放式、多工具)需要更大 N;常见做法是核心集 N=510、全量集 N=13 的分层策略。
  2. 分层而不是均匀:高风险/高频任务多跑,长尾任务少跑;把预算花在决策敏感的地方。
  3. 只跑一次时,至少报告置信区间而不是点估计,并且用对照变更(与上一版本同集对比)降低方差。
  4. 用低成本代理指标先筛:轨迹合规率、工具调用正确率比端到端成功率更敏感、方差更小,可以在小样本下先发现问题。
  5. 说明统计纪律:不要在没达到显著性时就宣布提升,也不要在同一评测集上反复挑选最优 checkpoint(等于过拟合评测集)。

B5 最小代理原则、Agent 身份与代持令牌#

1. 考察点#

  • 能否说清”权限”之外还有”自主性(agency)“这一维度。
  • 是否知道混淆代理(confused deputy)问题及其在 Agent 场景的放大。
  • 是否能设计可审计的身份传播链。

2. 准确定义与适用边界#

最小代理原则(least agency):给 Agent 的自主程度应当与其任务、可验证性与风险相匹配——不只是最小权限(least privilege,能访问什么),还包括最小自主性(能不经 human 确认走到哪一步)、最小持久化(能记住多久、记什么)、最小作用域(能影响哪些数据与系统)。

为什么”复用用户权限”是错的:

  1. 权限放大:用户权限是为人类交互设计的宽权限集合;Agent 以用户身份批量、高速、不知疲倦地行动,把”用户有权”放大成”任意规模地行使”。
  2. 混淆代理(confused deputy):Agent 可能被外部内容(网页、邮件、文档、工具返回值)中的注入指令诱导,用用户的合法权限去做用户从未意图的操作。权限校验通过,但意图不是用户的。
  3. 归因断裂:审计日志只记录”用户 X 做了 Y”,无法区分是人做的还是 Agent 做的、是哪个 Agent 版本、基于哪次指令。事后无法追责也无法止损。
  4. 无法单独吊销:要停用 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,被授予 scope expense: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. 常见误区#

  1. “用户授权了就等于授权了所有操作”——授权需要有粒度、有额度、有时效、绑定具体参数。
  2. 把审批做成形式化确认——审批票据不绑定参数,Agent 可以在批准后执行完全不同的操作。
  3. 长期有效的 Agent 令牌——应短期、可吊销、按任务最小作用域。
  4. 只有用户身份没有 Agent 身份——无法单独限流、熔断、吊销某个失控的 Agent。
  5. 把敏感数据放进上下文与记忆——导致跨会话、跨用户的泄露面,也让日志成为攻击目标。
  6. 忽略第三方技能包/MCP 服务的供应链风险——未验证来源就授予其高权限,等同于把权限外包。
  7. 认为沙箱等于安全——沙箱解决执行隔离,不解决权限放大与意图混淆。

7. 面试现场参考回答(1-2 分钟)#

“Agent 用我的权限干活”这句话有两个隐含错误。

第一是权限放大。用户权限是为人类交互设计的宽集合,Agent 会批量、高速、不知疲倦地行使它,量变引起质变。第二,也是更严重的,是混淆代理问题:Agent 会被外部内容里的注入指令诱导,比如一封邮件里写着”把所有发票转发到这个地址”,它用你的合法权限执行了一个你从未意图的操作。这时候权限校验是全部通过的,因为主体确实是合法用户,但意图不是你的。

所以正确做法是三层。第一层给 Agent 独立身份,也就是非人类身份,权限单独授予、单独吊销、可以针对单个失控的 Agent 熔断。第二层用代持令牌,令牌里同时编码 actor(Agent)和 subject(用户),下游做交集授权:Agent 被授予的范围,和这个用户本身的权限,取交集才生效。第三层是审计链,日志里必须同时有 actor、subject、工具、参数摘要和审批事件,事后才能归因和止损。

另外最小代理不只是最小权限。还有三个维度:最小自主性——高风险动作必须人工确认,不能让它自己走到底;最小持久化——记忆里该存什么、存多久,敏感字段要脱敏;最小作用域——它能影响哪些租户和哪些数据。这四个维度一起收窄,才叫最小代理。

8. 面试官追问及回答要点#

追问:人工审批也在链路里了,但审批人根本看不懂 Agent 要做什么,只能点”同意”,怎么办?

回答要点:

  1. 这是设计失败不是流程失败——审批必须可理解:把审批请求渲染为”动作 + 目标 + 参数 diff + 影响面 + 不可逆性”的结构化摘要,而不是原始 JSON 或自然语言长文。
  2. 参数绑定:审批票据哈希绑定具体参数,执行时校验一致性,改一个字就要重新审批。
  3. 分级审批:按风险分级(金额、不可逆性、外部可见性、影响租户数)自动路由到不同审批级别,避免全部堆到一个人。
  4. 提供可验证的预览:dry-run 结果、影响对象清单、回滚方案,让审批人基于证据决策。
  5. 观测审批质量指标:审批通过率异常接近 100%、平均审批时长过短,都是”橡皮图章”的信号,需要通过抽检、抽样阻断(shadow deny)来检验。
  6. 承认边界:如果人类无法在合理时间内理解并判断,说明该动作不应该被授权给 Agent,应改为 Agent 只准备、人执行。

S1 场景题:有状态 MCP 工具服务迁移到无状态协议核心#

1. 背景、目标、约束与已知现象(回顾)#

见第二部分 S1 题干:浏览器自动化 MCP 服务,旧版协议 + sticky session + Redis 会话存储 + body DPI 网关;目标为迁移至 2026-07-28 无状态核心、10 倍扩容、按工具限流、全量审计;约束为不停机、新旧客户端并存 ≥ 3 个月、浏览器跨调用状态不能丢、合规审计到人/Agent。

2. 核心考察点#

  1. 状态外置设计:协议层无状态后,浏览器会话这类跨调用状态如何用显式 handle 表达,以及 handle 的生成、绑定、过期、撤销与每次校验。
  2. 双版本灰度迁移:新旧协议并存、能力探测、流量切换、回滚窗口。
  3. 网关治理:基于 Mcp-Method/Mcp-Name 头的路由、限流、审计与一致性校验,替代 body DPI。
  4. 安全归因:handle 不等于凭证;代持身份;每次调用独立鉴权。
  5. 成本与性能: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 引入新的凭证化风险。

核心方案:

  1. 显式 handle 建模

    • browser.open 返回 session_handle(≥128 位 CSPRNG,服务端签名或存于带 TTL 的存储)。
    • 后续 browser.click / browser.snapshot 以 session_handle 为普通参数传入。
    • 服务端在每次调用校验:handle 存在 + 未过期 + 绑定主体 == 当前认证主体。
    • 存储从 Redis 单点改为按 handle 分片的带 TTL 存储(或随浏览器 Pod 本地 + 句柄路由到指定 Pod),消除全局热点。
    • 空闲 TTL(如 30 分钟)自动回收,显式 browser.close 立即撤销。
  2. 双协议网关

    • 网关同时接受旧版(带 Mcp-Session-Id)与新版(带 MCP-Protocol-Version: 2026-07-28)请求。
    • 新版请求:读取 Mcp-Method / Mcp-Name 头 → 路由、限流(按工具名分桶)、审计(actor/subject/trace)。
    • 服务端必须拒绝头与 body 不一致的请求,防止路由标签与实际执行脱节造成策略绕过。
    • 旧版请求继续走 sticky + Redis,直到客户端全部迁移。
  3. 灰度与切换

    • 阶段一:服务端双栈上线,内部客户端先切(可控)。
    • 阶段二:server/discover 返回支持版本,客户端按能力协商;对 Tier 1 SDK 用户按 5% → 25% → 50% → 100% 逐步放量。
    • 阶段三:社区 SDK / 未知客户端保留旧路径,设监控告警与截止日期;利用 12 个月最小弃用窗口排期。
    • 回滚:网关按客户端 ID / 版本头一键切回旧路径,双栈期间无数据迁移,回滚成本极低。
  4. 执行流程(新版一次调用)
    客户端 → 网关(读 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 必须拒绝。
    • 灰度回归:新旧路径同任务的端到端成功率与轨迹对比,差异超阈值即暂停放量。
  • 上线观测: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 个)#

  1. 把”无状态”理解成”服务不能有状态” → 直接删 session 而不做 handle 外置,服务不可用或状态错乱。
  2. 把 handle 当权限 → 只在创建时校验,后续调用不校验主体绑定,形成越权(任何拿到 handle 的人都能操作别人的浏览器会话)。
  3. 只改服务端不改网关 → 仍靠 body DPI 做限流和审计,无法利用新头,也无法覆盖大 body 请求,安全缺口依旧存在。

12. 30-60 分钟小型实践#

用 FastAPI 写一个最小 MCP-lite 服务:

  1. 实现 /mcp 单端点,要求 MCP-Protocol-Version、Mcp-Method、Mcp-Name 三个头,并校验头与 body 一致,不一致返回错误。
  2. 实现一个 browser.open 工具返回 session_handle(secrets.token_urlsafe(32),服务端 dict 存 TTL 与 owner),browser.click 接收 handle 并校验 owner == 当前认证主体。
  3. 写一个中间件:按 Mcp-Name 做令牌桶限流,并打印一条审计日志(actor/subject/tool/trace_id)。
  4. 用 httpx 起 20 并发打 2000 次请求,验证:(a) 无 sticky 也能正确处理;(b) 用错 owner 的 handle 被拒绝;(c) 限流生效。
  5. 加一个 server/discover 返回支持版本与能力。

S2 场景题:长任务持久化执行、暂停恢复与人工审批#

1. 背景、目标、约束与已知现象(回顾)#

见第二部分 S2 题干:合同审阅 Agent,任务 2 小时至 3 天;重跑成本高、重复发函 3 次、审批占槽导致队列堆积、模型升级后重放不兼容。

2. 核心考察点#

  1. 持久化执行与事件历史:确定性重放所需的前提,以及非确定性操作(LLM 调用、检索、时间、随机)必须如何记录。
  2. 副作用边界与幂等:不可撤销动作(发函、落章)如何做到”重放不重做”。
  3. 长时挂起与人工信号:审批等待期间如何不占执行槽、如何超时、如何唤醒。
  4. 版本化与兼容:工作流/事件历史/prompt/模型版本变更时的恢复策略。
  5. 三层能力划分:模型的概率性能力、Agent 编排与状态能力、确定性系统的能力,各自负责什么。

3. 作答框架(3-5 分钟可复用)#

第一步:先划清能力边界(45 秒) —— 明确哪些属于确定性系统(发信、落章、事务、计时、权限),哪些属于编排层(状态机、重试、审批、幂等键),哪些只能交给模型(理解条款、判断风险、生成建议)。

第二步:结论(30 秒) —— 核心不是”让 Agent 更聪明”,而是把进度与副作用的确定性从模型手里拿走,放进持久化执行层。

第三步:方案主体(2.5 分钟) —— 事件历史 + 检查点 → 副作用幂等与补偿 → 审批信号与超时 → 版本化 → 观测。

第四步:失败路径与上线(1 分钟) —— 重放安全、模型变更、审批超时、队列积压的处理与放量方式。

4. 参考回答(结论先行)#

结论:这四个现象其实是同一个根因的两面——任务的进度和副作用都依赖”进程还活着”和”模型还会再想一遍”。修法是把它们分成三层:确定性系统负责真正的副作用(发信、落章、事务),编排层负责状态机、事件历史、幂等键和审批信号,模型只负责理解和生成。

具体是四件事。第一,引入持久化执行:每一步的输入输出都作为事件落盘,恢复时重放事件历史而不是重跑模型;所有非确定性结果(模型输出、检索结果、时间戳)必须在第一次执行时就固化成事件。第二,副作用出门必带幂等键:发函和落章这类不可逆动作,用任务 ID + 动作类型 + 参数摘要生成幂等键,下游去重;重放时命中幂等键就直接返回首次结果,不重做。第三,审批用信号 + 定时器,等待期间工作流挂起、释放执行槽,审批到达后信号唤醒,超时走预设分支(升级或自动拒绝并通知)。第四,工作流与事件历史都要版本化,模型/prompt 升级后旧任务可选择继续用旧版本跑完,或做事件 upcast。

这样重复发函这类事故从”靠流程约束”变成”结构上不可能”。

5. 问题拆解、核心方案与关键执行流程#

拆解:

  • 进度问题(重跑成本高)→ 持久化执行 + 检查点。
  • 重复副作用(发函 3 次)→ 幂等键 + 副作用边界。
  • 资源问题(审批占槽)→ 挂起 + 信号。
  • 一致性问题(模型升级后行为漂移)→ 版本化 + 事件固化。

核心方案:

  1. 持久化执行(durable execution)

    • 工作流定义为一个确定性的状态机:节点、分支、重试策略、超时都是代码。
    • 每个节点的输入与输出写入事件历史(append-only 的事件日志),形成检查点。
    • 恢复时:重放事件历史重建内存状态,已完成且已记录的节点不再执行,从第一个未完成的节点继续。
    • 关键约束:非确定性结果必须固化——模型输出、检索结果、随机数、now(),第一次执行即写入事件,重放时读事件而不是重新调用。这是确定性重放成立的前提,也是最容易违反的一条。
    • 长输出(合同全文、抽取结果)不进事件历史,落对象存储,事件里存引用 + 摘要 + 哈希。
  2. 副作用幂等与补偿

    • 所有外部副作用统一走一个副作用网关,强制携带 idempotency_key = hash(task_id, action_type, normalized_params)。
    • 网关侧用幂等表(唯一索引)记录首次结果;重复请求直接返回首次结果,不重复执行。
    • 不可逆动作先审批后执行,且审批票据绑定参数摘要(改参数即失效)。
    • 可补偿动作(如创建草稿、占位)登记补偿动作;跨服务的多步流程用 Saga,接受最终一致,不用分布式事务假装强一致。
    • 发函这类动作在调用前写入 outbox 表(同事务),由独立投递器发送,保证”至少一次 + 下游幂等”,而不是”最多一次丢信”。
  3. 审批:信号 + 定时器

    • 工作流执行到审批节点 → 写入 pending_approval 记录 → 挂起并释放执行槽(不是 sleep,不是轮询)。
    • 审批人动作通过 API 写入,向工作流发送信号(signal);调度器唤醒工作流继续。
    • 同时注册定时器:4 小时未决 → 升级到二级审批人;24 小时未决 → 自动拒绝并通知发起人(策略可配,但必须有确定结局)。
    • 审批事件完整记录:审批人、时间、看到的参数摘要、决策、客户端信息。
  4. 版本化

    • 工作流定义版本、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 个)#

  1. 把持久化执行理解成”保存聊天记录” —— 保存的是结构化的事件历史与状态机位置,不是对话文本;靠对话文本恢复等于让模型重新理解一遍,成本高且不一致。
  2. 恢复时重新调用模型 —— 违反确定性重放前提,成本翻倍且结果漂移。
  3. 用 sleep 等待审批 —— 占执行槽,是队列堆积的直接原因;必须用信号 + 挂起。
  4. 幂等键只覆盖”同一秒内”或”同一进程” —— 幂等必须跨进程、跨重启、跨时间窗,落在带唯一索引的持久存储里,并设置合理的保留期。

13. 30-60 分钟小型实践#

用 Python + SQLite 实现一个最小持久化执行原型:

  1. 建 events(task_id, seq, node, input_ref, output_ref, created_at) 表,append-only。
  2. 写一个 4 节点的工作流:parse → extract → [approval] → send。parse/extract 调用一个假的 “LLM”(返回随机结果)。
  3. 实现重放:进程重启后从事件历史重建状态,已完成节点直接读事件不重跑(打印”replayed”以验证)。
  4. 实现 approval 节点:写入 pending_approval 后 return 释放(不 sleep),模拟信号唤醒。
  5. 实现 send 的幂等表(唯一索引 idempotency_key),连续调用 5 次只打印一次 “SENT”。
  6. 手动 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 <= 3000
  • 0 <= key <= 10^4
  • 0 <= value <= 10^5
  • get 与 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. 推荐方案的推导过程#

  1. 需求分解:(a) O(1) 按 key 查值 → 哈希表;(b) O(1) 调整”最近使用”顺序 + O(1) 淘汰最久未用 → 链表,且需要双向(删除任意节点要知道前驱)。
  2. 组合:哈希表 key -> node,双向链表维护顺序,链表头为最近使用(MRU),尾为最久未使用(LRU)。
  3. 消除边界:加 head/tail 两个哨兵节点,任何插入都是”插到 head 之后”,任何删除都是标准的 prev.next = next; next.prev = prev,不需要判空。
  4. 操作定义:
    • get 命中 → 从链表摘下 → 插到 head 后 → 返回 value。
    • put 命中 → 更新 value → 移到 head 后。
    • put 未命中 → 新建节点插到 head 后 → 若 len > capacity → 删除 tail.prev 并从哈希表移除。
  5. 正确性关键:哈希表和链表必须同步更新,任何一条路径漏掉另一边都会产生幽灵节点或悬空引用。

8. 正确性说明#

不变式(Invariant):

  1. 链表中的节点集合与哈希表的键集合一一对应(双向:链表里有 ⟺ 哈希表里有)。
  2. 链表从头到尾按”最近使用”降序排列,head 之后是 MRU,tail 之前是 LRU。
  3. 链表节点数 ≤ 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. 关键边界情况与测试用例#

边界情况清单:

  1. capacity == 1:每次 put 新 key 都会立即淘汰唯一元素。
  2. get 一个从未存在 / 已被淘汰的 key → 返回 -1。
  3. put 已存在的 key:更新值,不改变容量,但要刷新为最近使用。
  4. get 命中后该 key 必须变为 MRU,否则下一次淘汰会误删它。
  5. 淘汰时必须同时从链表和哈希表删除,漏删哈希表会导致 key 永远”存在”但节点已不在链表。
  6. key/value 可以为 0(0 是合法值,不能用 falsy 判断存在性)。
  7. 连续 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 = 1
assert 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,应淘汰 2
assert 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 变 MRU
c.put(3,3) # 淘汰 2
assert c.get(2) == -1 and c.get(3) == 3 and c.get(1) == 1
print("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. 可迁移解题能力#

  1. 需求正交分解:把”快速查找”和”顺序维护”拆成两个独立需求,各自选最优结构再组合——这是应对所有”设计题”的通用起手式。
  2. 哨兵技巧:用哑节点消除边界判断,把”特判一堆 if”转化为”统一路径”,在链表类问题中几乎总是适用。
  3. 双结构同步不变式:凡是”一个结构加速查询、另一个结构维护顺序”的组合(LFU、带过期时间的缓存、时间轮 + 索引),都必须显式维护两者的一致性并能在面试中说出不变式。
  4. 从 API 反推结构:当题目给出”O(1) 时间复杂度”这个硬约束时,它实际上是在指定数据结构——把约束翻译成结构要求,是设计题的核心解码能力。

四、今日重点与建议复习#

今日重点(按优先级)#

  1. 分层思维是今天所有题目的共同主线。协议层无状态 ≠ 应用层无状态(B2/S1);结果正确 ≠ 过程正确(B4);模型能力 ≠ 编排能力 ≠ 确定性系统能力(S2)。面试中能主动做层次划分,是中高级最明显的信号。
  2. “约束被移除”不等于”责任消失”,而是”责任上移”。MCP 移除 session 后,状态、鉴权、幂等、审计全部从协议层上移到应用层实现者身上——这是 B2、S1、B5 的同一条逻辑。
  3. 确定性重放的前提是非确定性结果固化(S2)。这句话是持久化执行类问题的高频得分点,也是最高频的失分点。
  4. 一致性是独立于成功率的维度(B4)。“78% 但用户投诉不降”这类题,答案永远是分分布而不是提均值。
  5. 最小代理 ≠ 最小权限(B5)。自主性、持久化、作用域三个额外维度是拉开差距的地方。

建议复习内容#

  • 概念对比卡片(建议做成 Anki 或手写):
    • 压缩 vs 摘要 vs 外部记忆载体 vs RAG vs 长期记忆
    • 协议层无状态 vs 应用层无状态 vs 服务无状态
    • 结果级评测 vs 轨迹级评测 vs 一致性;pass@k vs pass^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)#

  1. MCP 2026-07-28 规范发布公告 —— https://blog.modelcontextprotocol.io/posts/2026-07-28/
    已核验。无状态协议核心、MRTR、基于头的路由、可缓存列表结果、授权硬化、扩展框架、12 个月弃用策略的官方一手说明。
  2. 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 等),是准备面试细节的最佳材料。
  3. 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)#

  1. LeetCode 146. LRU Cache(官方题目页) —— 中文 https://leetcode.cn/problems/lru-cache/ | 英文 https://leetcode.com/problems/LRU-Cache/
    已核验(题号、难度 Medium、约束、示例、Acceptance Rate 48.2%)。

标准与规范(优先级 2,本轮未核验原文)#

  1. RFC 9207 OAuth 2.0 Authorization Server Issuer Identification —— https://www.rfc-editor.org/rfc/rfc9207
    未核验(本轮仅由 MCP 官方博客提及)。用于理解 iss 校验关闭的”授权服务器混淆”漏洞。
  2. OWASP GenAI Security:《Top 10 for Agentic Applications 2026》
    未核验(来自本地题库配置记录,本轮未访问原文)。涉及 B5 的风险分类,引用前请核对官方发布页。
  3. OpenTelemetry GenAI 语义约定 —— https://opentelemetry.io/docs/specs/semconv/gen-ai/
    未核验(本轮未访问原文;本地配置记录其 Agent 与 MCP 相关约定状态为 Development,以仓库实际状态为准)。

补充阅读(优先级 3-4)#

  1. 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中高
B3AI Agent 核心编排器-工作者、上下文隔离、结果契约、多 Agent中高
B4Agent 评测与可观测性轨迹级评测、Task Success Rate、pass@k、一致性、榜单污染中高
B5AI 安全与治理最小代理原则、混淆代理、非人类身份、代持令牌、审计链中高
S1有状态工具服务迁移到无状态协议核心协议迁移、状态外置、网关治理、灰度回滚、按工具限流高
S2长任务持久化执行与人工审批事件历史、确定性重放、幂等键、Saga、信号与定时器、版本化高
L1LeetCode / 哈希表 + 双向链表设计题、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 次阈值,未触发周期复盘。

2026-10-10 AI Agent 面试训练
https://vissssa.github.io/posts/ai-agent-interview-2026-10-10/
作者
Siegward
发布于
2026-10-10
许可协议
CC BY-NC-SA 4.0