20407 字
102 分钟
2026-10-16 AI Agent 面试训练

2026-10-16 AI Agent 面试训练#

今日学习主题:连接与超时的分层防护、回归测试的非确定性治理、检索切片粒度与父子块、采样参数与可复现性边界、多 Agent 并行写冲突收敛、限流与熔断的粒度误配。

题型分布(依据 题库配置.md v4):非 Agent 基础 2 道(B01、B02)+ AI Agent 基础 2 道(B03、B04)+ Agent 场景 1 道(S01)+ 非 Agent 场景 1 道(S02)+ LeetCode 1 道(L01),共 7 道。

题目速览#

编号题目 ID题型一级类型二级主题难度考察方向
B01Q-20261016-B01基础(非 Agent)操作系统与网络持久连接、连接池与分层超时基础题(中高)连接复用机制、三类超时的防护对象、池容量推导
B02Q-20261016-B02基础(非 Agent)软件工程与平台工程测试策略、回归测试与 flaky 治理基础题(中高)非确定性来源、隔离策略、测试替身边界
B03Q-20261016-B03基础(Agent)RAG 与搜索文档解析、切片策略与父子块检索基础题(中高)切片粒度对检索与生成的双重影响、溯源锚点
B04Q-20261016-B04基础(Agent)AI 与大模型基础推理参数、采样策略与可复现性边界基础题(中高)参数作用阶段、seed 的真实保证、评测口径
S01Q-20261016-S01场景(Agent)AI Agent 核心 / 工具与互操作协议多 Agent 协作、写冲突与结果合并场景题(高)副作用所有权、乐观并发、幂等、重复劳动治理
S02Q-20261016-S02场景(非 Agent)分布式系统限流与熔断的粒度、位置与误配场景题(高)限流键选择、半开抖动、重试放大、降级取舍
L01Q-20261016-L01LeetCode树形动态规划 / 后序 DFS二叉树最大路径和medium后序遍历、单臂贡献与全局答案分离

B01 · 持久连接、连接池与分层超时#

题目 ID:Q-20261016-B01
一级类型:操作系统与网络
二级主题:持久连接、连接池与分层超时
难度:基础题(中高)

题目#

一个 Agent 服务在高并发调用外部工具 API 时,P99 延迟出现周期性抖动,并且偶发两类错误:Connection pool is full, discarding connection 与 Remote end closed connection without response。CPU 与内存都没有打满。

请解释 HTTP 持久连接与连接池的工作机制,说明 connect timeout / read timeout / idle timeout 三类超时分别防护什么故障,并说明连接池容量应该如何推导,而不是拍脑袋设成一个常数。

考察点#

  1. HTTP/1.1 默认使用持久连接,以及 Connection 头决定本次响应后连接是否保持;持久连接是逐跳语义,经过代理或负载均衡后不保证端到端保持。
  2. 三类超时的语义分层:各自防护的故障类型不同,不能合并成一个参数。
  3. 连接池容量与”并发数 × 单请求占用时长”的排队关系,以及池必须与下游容量联合推导。
  4. 半开连接(stale connection)的成因:本地 idle 回收周期长于对端 keep-alive 超时。
  5. 池的分桶维度:目标地址、协议版本、凭证与租户上下文不同的连接不可混用。

准确定义与适用边界#

持久连接:HTTP/1.1 默认允许在一条 TCP 连接上承载多个请求与响应,实现应当支持持久连接;接收方依据最近收到的消息的协议版本与 Connection 头判断连接是否保持,若出现 close 连接选项则当前响应后不再保持。

适用边界:持久连接是 hop-by-hop 的,中间设备可以随时关闭连接;对端单方面关闭是合法行为,客户端必须具备”取到已失效连接时丢弃并重建”的能力,而不是把这类错误当作下游故障上报。

连接池:在客户端侧缓存已建立的连接以便复用,避免每个请求重复 TCP 握手与 TLS 握手。

适用边界:池按”目标 + 协议 + 凭证/租户上下文”分桶,不是全局一个池。连接上若绑定了认证状态或租户上下文,跨上下文复用会造成越权或串号。

具体示例#

Agent 服务调用内部工具网关的并发上限是 200,单请求平均占用连接 800 ms。把池大小设成 200 看似”刚好匹配并发”,但一旦下游 P95 从 800 ms 涨到 2 s,维持同样吞吐需要的连接数是 200 × 2 / 0.8 = 500。此时池成为瓶颈,请求排队在”等待连接”上,表现为 P99 抖动而 CPU 空闲——这正是题目所述现象。

与相近概念的区别#

  • 连接池上限 vs 并发信号量:前者限制同时存在的连接数,后者限制同时在飞的请求数。HTTP/1.1 下一条连接同一时刻只能承载一个请求,两者近似 1:1;HTTP/2 多路复用下两者解耦,池大小由并发流上限决定。
  • read timeout vs 整体 deadline:read timeout 是”两次读到数据之间的最大间隔”,适合流式响应;整体 deadline 是整个请求的时间预算。模型流式输出与 SSE 不能用短 read timeout 判定死连接,否则正常的 token 间隔会被误杀。
  • 应用层 idle 回收 vs TCP keepalive 探测:前者是秒级的应用策略;后者是 TCP 层探测,默认周期远长于应用层,不能用来及时发现对端静默关闭。
  • 连接层失败 vs 请求层失败:连接建立前失败可安全重试;请求已被对端接收后的超时不可盲目重试,必须依赖幂等键。

在 AI Agent 系统中的典型应用#

  • Agent 一轮会话会触发数十次工具调用,且高度集中在少数几个 MCP 服务或内部网关上,连接复用直接决定 P99 与文件描述符占用。
  • 长耗时工具调用(例如改造为 MCP Tasks 后的轮询型调用)会长时间占用连接,必须与短调用分池隔离,否则慢调用会把池吸干,形成隐蔽的 head-of-line 阻塞。
  • 流式模型响应应采用”首字节超时 + 整体 deadline + 无 chunk 空闲超时”三层判定,而不是单一 read timeout。
  • 多租户 Agent 平台中,连接池必须按租户或凭证维度隔离,否则连接复用会成为跨租户的数据通道。

常见误区#

  1. 只暴露一个 timeout 参数,把连接建立、读取间隔与整体预算混为一谈。
  2. 池大小取 CPU 核数或随手设 10 / 100,不与下游容量和在飞时长联合推导。
  3. 客户端 idle 超时大于服务端 keep-alive 超时,稳定复现半开连接错误;正确做法是客户端 idle 略小于服务端,并对”取到连接后首次使用失败”做一次安全重试。
  4. 认为超时即未执行,对已接收的请求盲目重试,造成重复写。
  5. 忽略池的分桶维度,让不同租户或不同凭证的连接混用。

1-2 分钟面试回答#

HTTP/1.1 默认持久连接,客户端用连接池复用 TCP 与 TLS 握手成本。三类超时防护的对象不同:connect timeout 防网络不可达或对端 backlog 满,read timeout 防对端接受了请求却不返回,idle timeout 防对端静默关闭留下的半开连接——所以 idle 必须略小于服务端 keep-alive 超时,否则会稳定复现”对端无响应关闭”。池容量不能拍脑袋:HTTP/1.1 下一条连接同时只能承载一个请求,所需连接数约等于并发数乘以平均占用时长,下游变慢时需求会成比例上涨,因此池要与下游容量联合推导,并对长耗时调用单独分池,避免慢调用把池吸干。题目中 CPU 没打满却 P99 抖动,正是请求排队在获取连接上;修复方向是推导池容量、分层设超时、慢调用分池,同时按租户维度隔离连接。

面试官追问与回答要点#

追问:如果下游升级到 HTTP/2 多路复用,池容量推导和超时设计会怎么变?

回答要点:HTTP/2 单连接可并发多个流,池大小不再与并发请求数 1:1,而由并发流上限与在飞流数决定;池耗尽表现为流排队或收到 GOAWAY 而非”取不到连接”。超时的三层判定不变,但要区分连接级失败与流级失败:连接断开会影响其上所有流,需要整体重建;单个流超时只重试该流。此外流级并发上限本身可能成为新的瓶颈,需要单独观测。


B02 · 测试策略、回归测试与非确定性治理#

题目 ID:Q-20261016-B02
一级类型:软件工程与平台工程
二级主题:测试策略、回归测试与 flaky 治理
难度:基础题(中高)

题目#

一个服务的回归测试套件在 CI 上每天有 3 到 8 个用例随机失败,手工重跑就通过。团队加了”失败自动重跑三次”的插件后 CI 变绿,但线上缺陷率没有下降,反而有几次真实回归被当成抖动放过。

请解释非确定性测试(flaky test)的成因分类,说明为什么自动重跑不能解决问题,并说明测试替身(stub / mock / fake)的选择边界在哪里。

考察点#

  1. 非确定性的主要来源分类:隔离缺失、异步行为、远程服务、时间、资源泄漏。
  2. 隔离策略与代价:Fresh Fixture、清理策略、持久 Fresh Fixture(事务回滚)三者的取舍;共享可变 fixture 是顺序依赖型 flaky 的根因。
  3. 测试替身的语义边界与误用:mock 的行为断言会耦合实现细节,fake 存在与真实实现的行为漂移风险。
  4. 自动重跑与隔离(quarantine)的定位差异。
  5. 时间、随机性与并发的可控化手段:注入时钟、固定随机种子、worker 级隔离与唯一标识。

准确定义与适用边界#

非确定性测试:在被测代码、测试代码与环境都没有可见变化的情况下,有时通过、有时失败的测试。

适用边界:判定为 flaky 之前必须排除”被测代码本身存在并发缺陷”。一个稳定归因到竞态或死锁的失败不是 flaky,是产品缺陷。只有确认失败与执行顺序、时序或外部状态相关,且根因不在产品逻辑时,才归入非确定性。

隔离(quarantine):把已确认的 flaky 用例移出阻塞流水线,并作为技术债限期修复。它是止损手段,不是修复手段。

自动重跑(rerun):失败后按固定次数重试,任一次通过即判定通过。它的作用是降低噪声对流水线的干扰,代价是稀释失败信号。

具体示例#

一个用例断言”标签集合的第一个元素是 python”,而 tags 是一个 set。集合不保证跨进程的迭代顺序,断言”第一个元素”属于顺序依赖断言;改成成员断言后即稳定。这类缺陷的特点是:失败率随环境负载与数据插入历史变化,重跑大概率通过,因此极易被误判为”环境抖动”。

与相近概念的区别#

  • Fresh Fixture vs Shared Fixture:前者每测试重建,隔离性最好但成本高;后者共享,成本低但禁止可变。跨测试共享可变 fixture 是顺序依赖型 flaky 最主要的根因。
  • Stub vs Mock vs Fake:stub 提供固定应答,mock 断言”是否以某方式被调用”,fake 是可工作的简化实现。mock 断言调用次数与参数顺序会把测试绑定到实现细节,重构即红;fake 越复杂,与真实实现行为漂移的风险越高,最终”测的是替身”。
  • Quarantine vs Rerun:隔离是把用例移出阻塞链路并限期修复,失败信号仍然保留;重跑是让它继续留在链路里但抹掉失败,是掩盖而非修复。
  • 覆盖率 vs 有效性:覆盖率用于发现未被测到的代码,不能作为”测试好不好”的数值证明。追求覆盖率数字会催生大量只执行不断言的用例。

在 AI Agent 系统中的典型应用#

  • Agent 系统的回归测试里,外部模型调用是最典型的”远程服务 + 非确定性”复合来源。用录制回放(cassette)可以稳定协议层测试,但回放会把模型版本一起冻结,出现”测试全绿而线上质量漂移”。因此必须把协议契约测试(可冻结、断言请求结构与错误处理)与质量回归评测(不可冻结,用统计口径)分离。
  • 工具调用常经过队列与异步执行,断言必须用”轮询等待条件成立”而不是固定 sleep。
  • 涉及 TTL、会话过期、调度窗口的逻辑必须注入可控时钟,否则特定时间段会稳定失败。
  • 并发执行测试时,每个 worker 需要独立的数据库、文件路径与端口范围,并且不写进程级全局状态。

常见误区#

  1. 把自动重跑当作修复,甚至把重跑次数调到 5 次以上,使失败信号完全失效。
  2. 用 sleep 等待异步完成,在负载高的 CI 上间歇性失败。
  3. 对外部依赖使用过细粒度的 mock,断言调用次数与参数顺序,导致任何重构都红灯。
  4. 让 fake 实现越来越复杂,最终测试验证的是 fake 自身的正确性。
  5. 只统计通过率,不统计 flake 率,失去对套件可信度的观测;正确做法是把 flake 率当作与覆盖率同等级的一等指标。

1-2 分钟面试回答#

非确定性测试的来源基本可以归为五类:隔离缺失、异步行为、远程服务、时间、资源泄漏。其中最常见的是共享可变 fixture 造成的顺序依赖,以及用 sleep 等待异步。自动重跑不能解决问题,因为它抹掉的是失败信号而不是根因,代价是真实回归被当作抖动放过——题目里线上缺陷率不降正是这个原因。正确顺序是:先把 flaky 用例标记并隔离出阻塞流水线,再按来源修根因——共享 fixture 改成每测试重建或用事务回滚,异步改成等待条件,远程服务用契约级替身,时间用注入时钟,随机用固定种子。测试替身上,stub 给固定应答,mock 断言行为但会耦合实现细节,fake 提供真实语义但要警惕行为漂移;原则是替身尽量薄,且断言面向可观测行为而不是调用过程。最后要把 flake 率和隔离清单当作技术债来管理。

面试官追问与回答要点#

追问:如果排查后发现失败的根因确实在产品代码的竞态里,测试策略应该怎么处理?

回答要点:这不属于 flaky,必须优先修产品缺陷。测试侧要做的是把该场景固化为可确定性复现的并发测试——用可控的同步点(barrier、latch)或调度注入来构造交错,而不是靠 sleep 碰运气复现。修复合入后保留该用例作为回归,并观测其在 CI 上的稳定通过率。绝不能把它标记为 flaky 后隔离了事,那等于把一个真实缺陷永久移出检测范围。


B03 · 文档切片策略、父子块检索与溯源锚点#

题目 ID:Q-20261016-B03
一级类型:RAG 与搜索
二级主题:文档解析、切片策略与父子块检索
难度:基础题(中高)

题目#

一个企业知识库 RAG 系统的回答质量不稳定:同一个问题有时答案完整、有时缺关键前提;引用溯源经常指向”看起来无关”的段落;运营成本也偏高。

请解释切片策略如何同时影响检索端与生成端,并说明固定长度切片、结构化切片、重叠切片与父子块(small-to-big)各自的适用边界与代价。

考察点#

  1. 切片是检索单元与生成单元的耦合点:切得太小语义不完整,切得太大噪声与成本上升。
  2. 固定长度切片的语义破坏:切断标题与正文、表格行与表头、代码与注释、前提与结论。
  3. 重叠的真实收益与代价:只缓解边界丢失,代价是索引膨胀、重复召回与引用重复。
  4. 父子块如何解决”检索精度 vs 上下文完整”的冲突,以及它的生效前提。
  5. 切片与溯源的绑定关系:切片策略变更等价于索引重建,不能增量热更。

准确定义与适用边界#

切片:把文档划分为可独立检索、也可独立注入上下文的文本单元。

适用边界:粒度必须同时满足两个约束——细到向量能表达其单一语义,大到模型拿到后能据以作答。这两个约束方向相反,因此不存在全局最优的 chunk_size。

父子块(small-to-big / sentence-window):用小子块做向量索引与检索,命中后把所属的大父块回传给模型用于生成。

适用边界:父块必须有明确的结构边界(小节、条款、标题层级)。若父块边界模糊,回传会把无关内容一起带进上下文,反而抵消精度收益,并推高 token 成本。

结构化切片:按文档固有结构(标题层级、表格、代码块、列表项)切分。

适用边界:强依赖解析质量。扫描件、无结构纯文本、跨页表格会退化为固定长度切片,此时应显式降级并标记该文档的可信度。

具体示例#

一份退款政策文档:(1)7 天内可全额退款。(2)例外:已开通的增值服务不退。(3)例外:企业客户按合同执行。

按 200 字符硬切时,第 3 条的前提词”例外”可能落入与第 1 条不同的块。用户问”企业客户能全额退吗”,检索命中的是第 1 条,模型据此给出错误答案。改为按条款切片并在每个块上携带”所属小节标题 + 条款序号”元数据后,命中块自带例外上下文,答案与溯源同时修正。

与相近概念的区别#

  • 切片 vs 重排:切片决定”能不能被召回”,重排决定”召回后谁排在前面”。重排不补召回——切分把关键前提切走了,再好的重排也找不回来。
  • 重叠 vs 父子块:重叠是靠复制冗余来容忍边界丢失,会增加向量数量与重复召回;父子块不改变索引粒度,只在生成阶段回传更大范围,代价是单次注入 token 上升。
  • 切片 vs 文档解析:解析决定”文字有没有被正确提取”,切片决定”提取正确之后怎么分组”。表格被解析成乱序文本后,任何切片策略都救不回来。
  • 切片 vs 引用溯源:溯源要求每个块携带可定位的原文锚点(文档 ID、版本号、段落或页码范围);只存块内容不存锚点,就无法做到可验证引用。

在 AI Agent 系统中的典型应用#

  • 工具与 API 文档应以”单个接口”为原子切片单位。若模型只拿到半个接口签名,会拼出参数名或类型错误的调用。
  • 长文档问答中,父子块回传能显著降低”答案对但缺前提”的比例,这是切片层面对生成质量最直接的杠杆。
  • 知识更新必须按 doc_id + version 原子替换整篇文档的所有切片,否则新旧版本的切片混在同一索引里,相似度不可比,检索结果会出现自相矛盾的内容。
  • Agent 的引用输出应带文档版本号;用户点击引用时要能定位到原文位置,这要求切片阶段就写入锚点元数据。

常见误区#

  1. 全局使用一个 chunk_size,不区分 API 文档、财报表格、聊天记录与代码这几类结构完全不同的内容。
  2. 认为加大重叠能解决所有边界问题——它只缓解,并且带来重复召回与重复计费。
  3. 父子块回传时不做去重与顺序还原,模型看到乱序重复段落,反而更易 hallucinate。
  4. 切片策略变更后只重建部分文档,导致索引内粒度不一致、向量相似度不可比。
  5. 忽略解析质量,把多栏 PDF 与复杂表格的乱序文本当作已经解决的问题。

1-2 分钟面试回答#

切片是检索单元和生成单元的耦合点:切小了语义不完整,模型拿到的片段缺前提;切大了向量难以表达单一语义,噪声和成本都上升,所以不存在全局最优的 chunk_size。固定长度切片最容易切断”标题与正文""前提与结论""表格行与表头”这类强依赖,表现为答案时好时坏、溯源指向无关段落。重叠只能缓解边界丢失,代价是索引膨胀和重复召回。更有效的做法是结构化切片加父子块:用小子块做向量检索保证精度,命中后回传所属小节作为生成上下文保证完整,前提是父块必须有明确的结构边界。工程上还要注意两点:溯源锚点必须在切片阶段就写入元数据,否则无法做可验证引用;切片策略一旦变更,必须按文档版本原子重建该文档的全部切片,不能只更新一部分,否则新旧切片混在一起,相似度不可比。

面试官追问与回答要点#

追问:知识库每天增量更新约 5%,能否只重建变更文档的切片?

回答要点:可以,但有三个前提。第一,同一文档的所有切片必须整体替换,按 doc_id + version 原子生效,避免新旧切片共存。第二,嵌入模型版本必须一致;一旦升级嵌入模型,向量空间不可跨模型比较,必须全量重建,此时增量更新反而是错误的。第三,删除文档要真正删除其向量与元数据,只标记失效会让已删除内容仍可被召回。此外要观测”重建滞后”这一指标:从源文档更新到索引生效的时间差,它直接决定知识时效性问题的影响面。


B04 · 推理参数、采样策略与可复现性边界#

题目 ID:Q-20261016-B04
一级类型:AI 与大模型基础
二级主题:推理参数、采样策略与可复现性边界
难度:基础题(中高)

题目#

团队给 Agent 回归测试提了一个要求:同一个 prompt 必须每次得到完全相同的输出。工程同学把 temperature 设为 0、并加了固定 seed,但输出仍然偶发不一致,于是怀疑是 SDK 或网关的 bug。

请解释各推理参数分别作用于解码的哪个阶段,说明为什么确定性承诺是有边界的,以及这对 Agent 的评测口径意味着什么。

考察点#

  1. 参数的作用阶段:temperature 缩放 logits、top_p / top_k 截断候选集、penalty 修改分布、max_tokens / stop 终止生成、seed 影响采样随机数。
  2. 贪心解码(temperature=0)不等于跨版本确定性:批处理、内核与后端配置变化都会影响结果。
  3. seed 的真实保证级别是 best effort,需要配合 system_fingerprint 监控后端变更。
  4. temperature 与 top_p 同时调整的耦合问题及其对调参可解释性的破坏。
  5. 对评测的含义:单样本字符串比对是错误口径,应改用统计口径并锁定模型版本与指纹。

准确定义与适用边界#

采样:在每一步从模型输出的概率分布中选取下一个 token 的过程。

适用边界:采样参数只影响”怎么选”,不影响”模型知道什么”。用降温去修复知识缺失、检索召回错误或格式错误,都是用错了工具。

可复现性:在同一模型版本、同一后端配置、同一输入与同一组参数下,多次请求得到相同输出。

适用边界:以 OpenAI 官方文档为准,Chat Completions 默认是非确定的;提供 seed 参数与 system_fingerprint 响应字段是为了提供一定程度的控制,seed 的语义是”尽力而为地确定性采样,确定性不作保证”,并且官方明确建议通过 system_fingerprint 监控系统侧变更——该值变化意味着后端配置已变,输出可能不同。

具体示例#

一个工具类 Agent 用 temperature=0.2、top_p=0.9 生成调用参数。为追求稳定把 temperature 降到 0 之后,仍然观察到少量输入相同而输出不同的情况。正确处置不是继续调参,而是改变断言口径:把”输出字符串完全相等”改为”解析后的结构化参数经 schema 校验并且语义等价”,同时在评测中按 system_fingerprint 分组统计,把后端变更与代码变更区分开。

与相近概念的区别#

  • temperature vs top_p:temperature 改变分布形状(更尖锐或更平坦),top_p 截断尾部累积概率。两者都降低随机性但机制不同,同时大幅调整会让调参结论不可解释;工程上通常只动一个。
  • seed vs prompt 缓存:缓存保证的是”相同前缀的计算被复用”,与输出采样无关;命中缓存不保证输出相同。
  • 低温 vs 结构化输出约束:低温降低随机性,schema 约束保证格式。格式问题应当用约束与校验解决,而不是靠降温。
  • 确定性 vs 一致性:确定性指逐 token 相同,一致性指多次运行的任务成功率稳定。工程目标通常是一致性,而不是确定性。

在 AI Agent 系统中的典型应用#

  • 工具调用参数生成:低温 + schema 约束 + 解析校验 + 有预算的修复式重试,四层叠加,而不是单点依赖温度。
  • 多步规划:适度随机性有助于探索,但会让回放调试困难。做法是把每次调用的模型版本、指纹、完整参数与输入全部落盘,保证可回放。
  • 评测:golden dataset 上用多次运行统计任务成功率与轨迹一致性,而不是单次字符串比对;同时记录指纹,避免把模型侧变更误判为代码回归。
  • 缓存优化:把接近确定性的子任务(抽取、分类)与高随机性的创作子任务分离到不同调用,提高前缀缓存命中率。

常见误区#

  1. 认为 temperature=0 就是绝对确定。
  2. 同时大幅调整 temperature 与 top_p,得出无法归因的结论。
  3. 用采样参数去”修复”格式错误、幻觉或知识缺失。
  4. 回归测试用单样本字符串比对,把正常的采样波动当作回归缺陷,最终导致团队关闭该测试。
  5. 不记录 system_fingerprint 与模型版本,无法区分”模型变了”和”代码变了”。

1-2 分钟面试回答#

先分层看参数:temperature 缩放 logits 改变分布形状,top_p 和 top_k 截断候选集,penalty 修改已出现 token 的概率,max_tokens 与 stop 决定终止,seed 影响采样用的随机数。关键边界是:官方明确 Chat Completions 默认非确定,seed 提供的是 best effort 的确定性采样,不作保证,需要配合 system_fingerprint 监控后端配置变更;即使 temperature 设为 0,批处理与后端配置的变化仍可能带来差异。所以”必须逐字相同”这个要求本身定错了。正确的评测口径是把断言从字符串相等改成结构化语义等价,用多次运行统计成功率和轨迹一致性,并按指纹分组,把模型侧变更和代码侧变更分开。工程上格式问题靠 schema 约束加校验解决,不靠降温。

面试官追问与回答要点#

追问:如果业务方坚持”同一工单重复处理必须得到相同决策”,你会怎么落地?

回答要点:不追求 token 级相同,而是压缩决策的采样空间。第一层把决策结构化,用 schema 加枚举与阈值约束,让可选空间本身很小。第二层对连续量在规则层做量化(分档、取整、阈值化),避免边界抖动。第三层对高风险决策走规则引擎或确定性编排兜底,模型只做候选生成与解释。第四层保留决策记录用于幂等重放——重复处理时直接复用已记录的决策而不是重新推理。这样满足的是业务真正需要的”决策可复现与可审计”,而不是”文本逐字相同”。


S01 · 多 Agent 并行协作中的写冲突、重复劳动与结果合并#

题目 ID:Q-20261016-S01
题型:场景(Agent)
一级类型:AI Agent 核心 / 工具与互操作协议
二级主题:多 Agent 协作、副作用所有权、乐观并发与冲突收敛
难度:场景题(高)

背景#

一家企业的 IT 运维与客服自动化平台,用一个”工单主管 Agent”编排 4 个专用子 Agent:

  • 日志诊断 Agent:检索日志与监控指标,产出故障归因。
  • 配置变更 Agent:对 CMDB 与配置系统下发变更。
  • 账务 Agent:处理退款、账单调整。
  • 通知 Agent:向客户与内部 IM 推送进展。

为压缩 SLA,主管 Agent 把子任务并行派发给这些子 Agent,各子 Agent 独立调用工具并回传结果。

系统规模与目标#

  • 日均工单 3 万,峰值 200 个并发会话。
  • 单个工单 5 到 12 次工具调用,涉及 6 个外部系统:工单系统、CMDB、监控系统、配置管理、计费系统、IM。
  • 目标:P50 处理时长从 8 分钟降到 3 分钟以内,同时人工介入率不上升。

当前架构与数据流#

主管 Agent 拉取工单 → 生成子任务列表 → 并发派发 → 子 Agent 各自调用工具并直接写外部系统 → 子 Agent 回传自然语言结果 → 主管聚合后更新工单状态并回复客户。

已知现象#

  1. 上线两周内出现 7 起重复退款:日志诊断 Agent 与账务 Agent 各自独立判断”需要退款”,双双发起。
  2. 配置变更 Agent 与诊断 Agent 并发更新同一工单的状态字段,出现后写覆盖前写(lost update),工单状态与实际处置不一致。
  3. P50 时长没有下降反而上升,token 成本涨到约 3 倍:两个子 Agent 各自拉取了同一批日志与监控数据。
  4. 故障归因困难:审计日志只记录”某个 Agent 调用了某个接口”,无法定位到具体会话、规划分支与决策依据。

限制条件#

  • 计费系统与客户通知属于不可逆副作用,退款一旦发出无法自动撤销。
  • 6 个外部系统中只有 2 个提供幂等接口,其余只有普通写接口。
  • 合规要求每次账务变更可审计到”谁(人 / Agent / 会话)在什么依据下做的”。
  • 已上线,不能停机改造,需要灰度切换。

需要取舍的问题#

并行度与写冲突的取舍、单写者所有权与灵活性的取舍、乐观并发与悲观锁的取舍、共享上下文与隔离上下文的取舍、自动合并与人工升级的取舍。

考察点#

  1. 能否识别根因不在”模型不够聪明”,而在缺少副作用所有权与串行化机制。
  2. 能否把 Agent 的概率性能力与编排层的确定性能力清晰分层。
  3. 乐观并发(版本号 / CAS)在长任务场景下的适用性,以及为什么悲观分布式锁不可行。
  4. 重复劳动的治理:共享产物与任务指纹,而不是靠提示词约束。
  5. 冲突升级与审计归因:trace 维度设计,以及在无幂等接口的系统上如何补幂等。

3-5 分钟作答框架#

  1. 定性与止损:先按副作用分级,把不可逆操作(退款、客户通知)从并行路径中摘出来,改为串行审批,止血优先于优化。
  2. 归责分层:明确模型负责什么、编排层负责什么、外部系统负责什么。
  3. 所有权建模:按副作用域划分唯一写者,其他 Agent 只产出提案。
  4. 并发控制:读取带版本号,写入用 CAS,冲突走”重读—重规划”而非盲重试。
  5. 去重与降本:共享产物存储与任务指纹,消除重复拉取。
  6. 可观测与升级:trace 维度设计、冲突升级通道、评测指标。
  7. 灰度与回滚:按工单类型与风险等级灰度,保留单 Agent 串行模式作为回滚态。

参考回答:先给结论#

结论是:这不是模型能力问题,而是并发写缺少所有权与串行化的问题。多 Agent 并行带来的收益被三类成本抵消了——重复写(重复退款)、丢失更新、重复劳动(重复拉数据)。

正确的做法不是让子 Agent”更小心”,而是在架构上把三件事分开:谁有权写、写入如何串行化、结果如何去重。具体是:按副作用域设唯一写者,子 Agent 只产出带幂等键与预期前置状态的提案,由确定性的执行层串行执行;读取侧带版本号,写入侧用 CAS,冲突就重新读取并重新规划而不是盲目重试;把子 Agent 的中间产物放进共享产物存储,用任务指纹去重,消除重复拉取;最后把 trace 维度补全到”会话—规划分支—子 Agent—工具调用”,让审计能定位到决策依据。

同时必须承认一个取舍:并行度要下降。只读诊断可以充分并行,涉及副作用的操作必须串行或走审批。真正该优化的并行度是”只读阶段的并行”,而不是”所有阶段都并行”。

问题拆解#

拆解成五个子问题,逐个对应到可落地的机制:

  1. 副作用归属不清 → 多个 Agent 都能写同一个外部系统。
  2. 写入无串行化 → 并发 read-modify-write 造成丢失更新。
  3. 外部系统无幂等 → 重试与重复决策直接变成重复写。
  4. 中间产物不共享 → 重复拉取,成本与时延双升。
  5. 审计维度不足 → 无法归因,也无法复盘决策依据。

核心方案与关键执行流程#

第一层:阶段化编排(读阶段并行,写阶段串行)

把工单处理切成三个阶段:只读诊断阶段(日志诊断、指标查询、知识检索,充分并行)→ 决策与提案阶段(各子 Agent 基于共享产物产出提案,不写外部系统)→ 执行阶段(由执行层串行化落实提案,含幂等键与 CAS)。

这样保留了读阶段的并行收益,同时把副作用收敛到单一执行点。

第二层:副作用所有权(single-writer per side-effect domain)

按外部系统划分唯一写者:账务系统只有账务执行器可写,配置系统只有配置执行器可写。子 Agent 不直接写外部系统,只提交提案:

提案 = {
proposal_id, session_id, trace_id, planning_step_id,
target_system, operation, params,
idempotency_key, # 工单ID + 操作类型 + 意图指纹
expected_precondition, # 期望的前置状态,如 status == "diagnosed"
risk_level, # 用于决定是否需要人工审批
evidence_refs # 指向共享产物存储的证据引用
}

第三层:乐观并发(版本号 / CAS)

读取工单与资源时携带 version 或 ETag;执行写入时带 expected_version 做 CAS。失败不盲重试,而是回到”重读—重新生成提案”,并有重试预算。

为什么不用分布式锁:一次写操作的等待时间包含模型推理,可能几十秒;锁超时会导致两个持有者同时写,比不加锁更危险,因为团队会误以为已经互斥。

第四层:幂等补位

对无幂等接口的外部系统,在执行层维护幂等键 → 结果的映射表:同一幂等键重复提交直接返回首次结果,不重复调用。映射表需要有生存期与持久化,不能只放在内存。

第五层:共享产物存储与任务指纹

子 Agent 的中间产物(日志片段、指标结果、检索证据)写入共享产物存储,并返回引用。执行前用任务指纹(目标系统 + 参数哈希 + 时间窗)查重,命中则复用。这直接消除重复拉取,是 token 成本下降的主要来源。

第六层:可观测与审计

trace 维度固定为 trace_id → session_id → planning_step_id → sub_agent_id → tool_call_span,每次工具调用记录模型版本、指纹、输入摘要与输出摘要,保证可回放。审计日志落到”哪个会话、哪个规划分支、依据哪些产物、做了哪个写操作”。

技术选型、选择依据与备选方案#

决策点选择依据备选与代价
并发控制乐观并发(版本号 CAS)写操作间隔长、冲突率低且可接受重试;不引入锁的超时与死锁问题悲观分布式锁:长事务持锁不可用,锁超时会虚假互斥;数据库唯一约束:只能防重复插入,防不住更新丢失
副作用收敛单一执行器 + 提案队列写入点唯一,便于加幂等、审批与审计各 Agent 分散写 + 事后对账:对账滞后,退款不可逆,不可接受
并行策略读阶段并行、写阶段串行读操作无副作用,是并行的真正收益来源全阶段并行:冲突与重复写不可避免,需大量补偿
上下文策略子 Agent 上下文隔离 + 共享产物引用避免上下文污染与 token 膨胀全共享上下文:成本失控,且一个 Agent 的错误推理污染全部
幂等实现执行层幂等键映射表覆盖无幂等接口的外部系统要求所有上游改造:周期长,已上线系统不可行

三层能力归属(必须分清)#

层负责什么性质
模型(子 Agent)判断”是否需要退款”、生成提案与依据概率性,会错、会不一致
编排与执行层串行化、去重、CAS、幂等键、预算与重试、审批路由确定性,可测试、可回放
外部系统事务、约束、唯一索引、最终的事实状态确定性,是最终事实来源

关键判断:重复退款不是”模型判断错了”这么简单,而是把一个概率性输出直接接到了不可逆副作用上,中间缺少确定性闸门。

失败路径、边界条件与恢复策略#

失败场景处置说明
子 Agent 超时或崩溃提案带 expires_at,过期失效不执行提案不是命令,执行前必须校验新鲜度与前置状态
外部系统写入返回超时不盲目重试;用幂等键查询首次结果,或做 read-after-write 状态确认超时不等于未执行,这是重复退款的直接来源
CAS 冲突重读 → 重新生成提案 → 重试,受重试预算约束超出预算则升级人工,避免决策震荡
执行层自身崩溃提案队列持久化 + 执行状态机(pending / executing / done / failed),重启后按状态机恢复不能靠内存态
审批挂起超过 SLA按风险分级设定默认动作:退款类默认不执行并通知人工;配置类可回滚的允许默认执行默认动作取决于可逆性,不是统一超时跳过
部分完成(3 个写操作完成 1 个)预定义补偿动作(Saga),补偿本身也必须幂等不能假设外部系统都支持事务回滚
幂等映射表写入失败判定为 FAIL-CLOSED:不执行写操作宁可不执行,也不要失去去重保护
决策震荡(两次重规划结论相反)检测同一会话内同一决策点的结论翻转,触发人工升级只靠重试会掩盖真实的不确定性

安全、权限、成本、延迟与可靠性#

  • 权限:子 Agent 不应持有外部系统的写凭证。写权限收敛到执行器,执行器按 risk_level 校验提案是否被允许执行。授权边界必须置于模型之外——提示词里写”不要重复退款”不构成约束。
  • 审计:每次写操作落 session_id + sub_agent_id + planning_step_id + evidence_refs,满足”可审计到人 / Agent / 会话”。
  • 成本:重复拉取消除后 token 成本可显著下降;但父子提案与产物存储会引入额外开销,需要按工单归因成本并设置子 Agent 预算,超预算降级为单 Agent 串行。
  • 延迟:只读写并行保留,写阶段串行会增加少量时延;但由于消除了重复拉取与重复写后的对账,端到端 P50 仍应优于改造前。需要按阶段分别埋点,否则无法判断优化落在哪一段。
  • 可靠性:执行器是新的单点。需要有超时、限流与队列积压告警;执行器不可用时,工单应进入待执行队列而不是丢失。

评测指标、测试方法与上线观测#

离线指标:golden 工单集上的任务成功率、重复写率(目标 0)、丢失更新率(目标 0)、重复工具调用率、每工单 token 成本、人工升级率。

关键区分:单次成功率与多次运行一致性要分开统计。多 Agent 系统的不稳定性主要体现在轨迹不一致上——同样输入 5 次跑出 3 种不同的子任务分解——这比单次失败更危险,也更难从结果指标看出来。

测试方法:

  1. 并发注入测试:用同步点构造两个子 Agent 同时对同一工单 read-modify-write,验证 CAS 生效。
  2. 故障注入:外部系统写入返回超时,验证不会重复写。
  3. 回放测试:录制线上工单,按指纹分组回放,比较改造前后的重复拉取次数。

上线观测:按工单类型与风险等级灰度(先只读工单,再低风险写工单,最后退款类);保留单 Agent 串行模式作为回滚态。核心看板:每工单写操作次数分布(异常抬升即重复写信号)、CAS 冲突率、幂等命中率、阶段耗时拆分、升级后人工修改率(人工改得多说明自动决策质量差)。

三个逐渐深入的追问#

追问一:如果两个子 Agent 的写操作存在业务上的先后顺序,但这个顺序在运行前无法预先确定,怎么办?

回答要点:这说明依赖关系是运行时发现的,静态任务图不够用。两条路径:一是运行时发现依赖后重新调度,把后序操作重新入队并串行化;二是改为更粗的阶段化——先完整的只读诊断阶段,再统一的写阶段,牺牲一部分并行度换取可预测性。需要强调的是,让子 Agent 自己协商顺序是不可靠的,顺序约束必须由确定性的编排层表达。

追问二:乐观并发冲突后”重读—重新规划”会让模型看到不同的上下文,可能得出不同结论,造成决策震荡。怎么避免?

回答要点:这是 LLM 场景下乐观并发的特殊问题,传统 CAS 假设重试逻辑是确定性的,这里不是。对策有四条:一是把决策输入固定为快照,重规划时基于同一快照而不是重新读取的实时状态;二是决策与执行分离,决策结果结构化落盘,重试只重放执行不重做决策;三是设置重试预算(例如 2 次);四是检测同一决策点的结论翻转,翻转即升级人工而不是继续重试。

追问三:多 Agent 的成本怎么归因到单个工单和租户?

回答要点:在 trace 的每一层记录 token 用量,按 session_id 与 sub_agent_id 聚合到工单,再按租户聚合。关键是给每个子 Agent 设预算,超预算时降级为单 Agent 串行或升级人工,否则单个异常工单会放大成几十倍成本。另外要区分”输入 token”与”输出 token”,以及缓存命中部分单独统计,否则看不到优化空间。

常见误区#

  1. 认为多 Agent 并行一定更快。 忽略了写冲突、重复劳动与冲突恢复的代价;真正的并行收益在只读阶段。
  2. 用分布式锁保护长任务。 持锁时间包含模型推理,锁超时导致双写,而且团队会误以为已经互斥,比不加锁更危险。
  3. 把所有子 Agent 放进同一个共享上下文”方便协作”。 造成上下文污染、token 成本膨胀,且一个 Agent 的错误推理会污染全部。
  4. 在提示词里写”不要重复退款”就当作约束。 授权边界必须置于模型之外,靠执行层的幂等键与权限校验实现。

30-60 分钟小型实践#

用 Python 搭一个最小实验,不需要任何 Agent 框架:

  1. 建一个内存”工单”资源 {id, status, amount, version}。
  2. 起 8 个 worker,每个执行”读取工单 → 加 1 元 → 写回”,并发 200 次。统计最终值,观察丢失更新(最终值远小于期望值)。
  3. 加 version 字段,写入时 CAS,冲突计入 conflict_count 并重试(最多 3 次)。再次统计,验证最终值正确。
  4. 模拟”上游超时但已执行”:worker 写入成功但在返回前随机超时,然后重试。观察重复写;加入幂等键映射表后再观察。
  5. 输出一张表:无并发控制 / 仅 CAS / CAS + 幂等键 三种配置下的最终值、重复写次数、冲突重试次数。

做完这个实验,上面那套方案就不是概念而是可验证的结论。


S02 · 限流与熔断的粒度误配:共享出口 IP 被误伤与半开状态抖动#

题目 ID:Q-20261016-S02
题型:场景(非 Agent)
一级类型:分布式系统
二级主题:限流、熔断、重试放大与降级
难度:场景题(高)

本题核心方案不依赖模型或 Agent,是通用分布式系统问题。

业务背景#

一个 SaaS API 平台,对外提供订单与库存查询接口,客户以企业为主。

系统规模#

  • 日请求 2 亿,峰值 8 万 rps。
  • 200 多个租户,其中 20 家大型企业客户通过统一出口网关 / NAT 访问,即多个企业共享少量公网 IP。
  • 服务集群 300 个实例,依赖 3 个下游服务:用户中心、计费服务、库存服务。

当前架构与数据流#

客户端 → 边缘网关(按客户端 IP 做 100 rps 令牌桶限流)→ API 服务集群 → 下游依赖服务。客户端 SDK 默认对失败请求自动重试 3 次,无退避。

目标#

保障 SLA 99.95%,且不能因为稳定性措施误伤正常租户。

已知现象#

  1. 大促期间,一家大型企业客户出现大量 429,但该租户自身流量完全正常——它的出口 IP 上还承载着其他流量,共享配额被挤占。
  2. 库存服务慢化(P99 从 50 ms 涨到 2 s)后,熔断器进入半开—关闭—再熔断的反复抖动:半开放 1 个请求成功就整体关闭,随即被全量流量打挂,恢复时间被拉长到原来的 4 倍。
  3. 慢化期间客户端重试把流量放大到约 3 倍,加速了下游雪崩。

限制条件#

  • 无法强制所有客户升级 SDK,重试行为短期改不掉。
  • 下游依赖无法快速扩容。
  • 不允许为了稳定性把阈值调得过于宽松,否则失去保护意义。

需要取舍的问题#

限流粒度(IP vs 租户 vs 端点)、单机限流与分布式限流的取舍、快速失败与降级返回的取舍、熔断恢复节奏(快速恢复 vs 稳定恢复)、重试与限流的相互作用。

考察点#

  1. 能否识别根因是限流键选错(身份维度与网络维度混淆),而不是阈值太小。
  2. 分层限流的设计:粗粒度抗滥用与细粒度配额各在哪一层。
  3. 熔断恢复策略:半开探测流量的设计,以及慢调用为什么必须纳入熔断判定。
  4. 重试放大:多层重试的放大效应,以及服务端如何用 Retry-After 与退避引导客户端。
  5. 依赖不可用时的降级路径与可观测分桶。

3-5 分钟作答框架#

  1. 定性与止血:先确认 429 的分布——按 IP、租户、端点分桶看,判断是配额不足还是限流键错误。
  2. 改限流键:把”按来源 IP”改为”按租户 / API Key”,IP 限流降级为只防滥用的粗粒度兜底。
  3. 分层限流:边缘粗、网关细、服务实例级并发上限。
  4. 修熔断:判定加入慢调用比例,半开改为逐步放量。
  5. 治理重试:服务端给 Retry-After,客户端退避加抖动与重试预算,禁止多层嵌套重试。
  6. 补降级与观测:熔断后降级返回而非 5xx;按维度分桶观测 429 与熔断事件。

参考回答:先给结论#

结论是:误伤和抖动是同一个根因的两面——保护机制的粒度与被保护对象不匹配。

429 误伤不是阈值太小,而是限流键选错了。IP 是网络身份,租户是业务身份;二者在 NAT 场景下是多对一关系。把租户配额挂在 IP 上,等于让一个租户的额度被同出口的其他流量消耗,同时对真正的滥用者又过于宽松。修法是把限流键换成租户 / API Key,并把限流点移到认证之后;IP 限流保留在边缘,但只作为抗 DDoS 的粗粒度兜底,阈值放宽。

熔断抖动是恢复策略的问题。半开状态放 1 个请求成功就整体关闭,等于用单点采样判断整个依赖已恢复,样本量严重不足。正确做法是逐步放量:半开期间按时间或按成功次数分批提升通过比例,并同时考核慢调用比例而不只是错误率——“慢”往往是”挂”的前兆,只看错误率会在依赖已经半死不活时判定为健康。

重试放大需要服务端主动治理。既然短期内改不掉客户端行为,就在网关侧做重试折叠:同一请求的幂等重试在网关合并,并对超过配额的重试直接返回带 Retry-After 的 429 而不是转发到下游。

问题拆解与核心方案#

问题一:限流键错误

按 IP 限流只适用于”IP 与业务主体一一对应”的场景。企业客户走 NAT 时,一个 IP 背后是多个租户甚至多个企业,此时 IP 既不能代表租户(误伤),也不能代表攻击者(防不住)。

方案:限流点移到认证之后,键改为 tenant_id + endpoint_class。边缘保留 IP 限流但阈值大幅放宽,仅用于抗扫描与 DDoS。

问题二:单层限流无法覆盖多种风险

层键作用阈值特征
边缘源 IP抗滥用、抗 DDoS宽松,只拦极端流量
网关租户 + 端点类别配额与公平性按套餐分级,允许突发
服务实例进程级并发数自我保护按容量推导,与线程池一致

三层各司其职。只在边缘做限流,就无法做租户级公平;只在服务内做,就无法在流量进入系统前削减。

问题三:单机限流 vs 分布式限流

单机限流实现简单、无外部依赖,但在 300 个实例上无法精确控制租户全局配额,且实例扩缩容时配额随之漂移。

分布式限流(如 Redis 上的令牌桶)能精确控制全局配额,代价是引入外部依赖、每次请求多一次网络往返、以及计数精度与热键问题。

选择:网关层做分布式限流保证租户配额准确,服务实例层做本地并发上限保证自我保护。这样即使 Redis 抖动,实例层仍然有保护,不会直接裸奔。

问题四:熔断恢复抖动

改进三点:

  1. 判定指标:错误率 + 慢调用比例复合判定。慢调用比例单独设阈值(例如 P99 超过阈值的请求占比),因为慢化往往先于错误出现。
  2. 半开放量:从”放 1 个请求”改为”按批次逐步放量”——例如半开首阶段放行 5% 流量,连续 N 个成功窗口提升到 25%、50%、100%。失败则立即回到熔断并延长冷却期(指数退避)。
  3. 熔断维度:按依赖服务 + 端点维度熔断,而不是全局一个熔断器,避免一个慢端点拖垮整体。

问题五:重试放大

  • 服务端:429 与 503 必须带 Retry-After;对可识别的重复请求(相同幂等键)在网关折叠,不重复打到下游。
  • 客户端侧(能推动的部分):退避加抖动,避免重试同步;设置重试预算(例如总重试次数不超过总请求数的 10%);只在最外层重试一次,禁止每层都重试。
  • 关键原则:重试只能用于”确认未执行”的错误,对超时类错误必须配合幂等键。

失败路径与边界条件#

场景处置
Redis 不可用降级到网关本地静态配额;同时把下游熔断阈值收紧,用下游保护兜底。此时权衡的核心是下游弹性:下游有余量则放宽保可用性,下游已饱和则收紧保存活
限流热键单个超大租户的计数键集中在同一 Redis 分片;按租户分片或本地预分配额度 + 定期同步
时钟漂移基于时间窗口的限流依赖时钟一致;令牌桶按”上次填充时间 + 速率”计算,容忍一定漂移,比固定窗口计数更稳
客户端不遵守 Retry-After网关侧对同一客户端的超额重试直接 429,形成硬约束
熔断期间无降级数据对查询类接口返回带标记的陈旧数据(明确标注 stale),对写入类接口明确失败并给出可重试凭据
内部调用被限流误伤内部服务间调用走独立限流通道与信任标识,不与外部租户共用配额
突发流量打满令牌桶后长期饥饿配额分层:保底配额 + 突发配额 + 全局余量池,避免持续满负荷的租户饿死偶发租户

安全、成本、延迟与可靠性#

  • 安全:限流本身是一项抗滥用能力,但把 IP 当身份会让攻击者通过轮换 IP 规避;同时也要注意限流键泄漏(例如仅凭 URL 参数推断租户)不应成为越权通道。
  • 成本:分布式限流的每次 Redis 调用是真实成本;可以用本地预取额度(每隔几百毫秒同步一次)把 QPS 降下来,代价是配额精度略有偏差,这个偏差在大多数业务上可接受。
  • 延迟:限流判定必须在转发前完成,增加的 P99 应控制在个位数毫秒;熔断判定是本地状态机,不引入额外远程调用。
  • 可靠性:限流组件是保护性依赖,不能成为新的故障源。因此必须设计”依赖不可用时的降级限流”,并保证实例层本地保护永远在线。

评测指标、测试方法与上线观测#

  • 离线 / 压测:构造”同一出口 IP 上 5 个租户,其中 1 个突增 10 倍”的场景,对比按 IP 与按租户限流的误伤率(正常租户被拒比例)与漏放率(超额租户实际通过比例)。
  • 混沌测试:注入下游慢化(延迟 2 s 但无错误),验证仅看错误率的熔断器不会触发,而复合判定的熔断器会触发。
  • 上线观测看板:
    • 429 按 限流层 × 租户 × 端点 分桶,重点看是否有租户被持续误伤。
    • 重试放大系数 = 到达下游的请求数 / 客户端发起的请求数,正常应接近 1。
    • 熔断状态转换事件流与每次熔断的持续时长、恢复耗时。
    • 慢调用比例与错误率双曲线,观察谁先变化。

三个逐渐深入的追问#

追问一:如果某些企业客户必须在边缘完成限流(认证信息在更内层才可用),怎么办?

回答要点:两条路。一是把认证前置到边缘——用轻量令牌校验换取限流键,代价是边缘要能验证签名并承受密钥分发复杂度。二是给大客户独立入口:独立域名或专线,使其出口 IP 与租户一对一,从根上消除共享问题;边缘保留宽松 IP 限流只防滥用。选择依据是大客户数量与商业价值;数量少时独立入口更简单可靠。

追问二:Redis 不可用的时候,限流应该放宽还是收紧?

回答要点:这取决于下游当时的弹性,没有统一答案,但必须有预设策略而不是临场决定。如果下游容量充足、当前慢调用比例低,可以放宽到网关本地静态配额,优先保可用性;如果下游已处于饱和或已有熔断在生效,则收紧,优先保存活。关键设计是实例层本地保护始终在线,所以无论选哪个,系统都不会裸奔。同时这个降级事件必须告警并计入可靠性指标,不能静默发生。

追问三:限流与重试如何避免互相放大?

回答要点:核心是三点。第一,服务端必须返回 Retry-After,让客户端的重试时刻可预期,而不是”立即重试”。第二,重试只能有一层——客户端或网关,不能客户端重试 3 次、网关再重试、服务内再重试,那样放大是乘法而非加法。第三,引入重试预算:单位时间内的重试次数上限,超出即直接拒绝。补充一点容易被忽略的:被限流的请求通常尚未执行,是安全可重试的;而超时类请求可能已执行,必须配合幂等键才能重试——把这两类混为一谈会造成重复写。

常见误区#

  1. 把 IP 当作租户身份来限流。 在 NAT 与企业网关场景下必然误伤,同时防不住轮换 IP 的滥用者。
  2. 只设一个全局 QPS 阈值。 不同端点的成本差异巨大,一个重查询端点就能吃满全局配额,把轻量接口全部饿死。
  3. 多层各自重试。 放大效应是乘法,且每一层都认为自己在做正确的事。
  4. 熔断只看错误率。 依赖慢化时错误率可能仍然很低,但系统已经在排队堆积,等到错误率上升时已经雪崩。
  5. 半开状态放一个请求成功就全量恢复。 单点采样不足以证明依赖已恢复,是恢复抖动的直接原因。

30-60 分钟小型实践#

用 Python 实现一个最小可验证原型:

  1. 用内存令牌桶实现两种限流器:by_ip 与 by_tenant。
  2. 造数据:3 个租户共享 1 个 IP,其中租户 A 正常 20 rps、租户 B 正常 20 rps、租户 C 突增到 200 rps。
  3. 跑 60 秒,统计两种限流下 A 与 B 的被拒次数(误伤)与 C 的实际通过量(漏放)。
  4. 加一个熔断器:先只用错误率判定,注入”延迟 2 s、无错误”的慢化,观察它是否触发;再加慢调用比例判定,观察差异。
  5. 对比两种半开策略——“放 1 个请求即全量恢复”与”按 5% → 25% → 100% 逐步放量”,统计 5 分钟内的状态转换次数与总可用时长。

输出一张对比表,结论会比概念描述更有说服力。


L01 · LeetCode 124. 二叉树中的最大路径和#

题目 ID:Q-20261016-L01
题号:124
中文名:二叉树中的最大路径和
英文名:Binary Tree Maximum Path Sum
难度:Medium
官方链接(2026-10-16 已核验,中英文页面题号、标题、难度、示例与约束一致):

题目描述#

二叉树中的路径被定义为一条节点序列,序列中每对相邻节点之间都存在一条边;同一个节点在一条路径序列中至多出现一次。路径至少包含一个节点,且不一定经过根节点。路径和是路径中各节点值的总和。

给定二叉树的根节点 root,返回其最大路径和。

输入、输出与约束#

  • 输入:二叉树根节点 root。
  • 输出:一个整数,最大路径和。
  • 约束:
    • 节点数目范围 [1, 3 * 10^4]
    • -1000 <= Node.val <= 1000

示例#

示例 1:
输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3,路径和为 2 + 1 + 3 = 6
示例 2:
输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7,路径和为 15 + 20 + 7 = 42

主要考察的数据结构或算法模式#

树形动态规划 + 后序 DFS。核心模式是”子树向父节点贡献什么”与”在子树内部结算什么”这两件事必须分开——这是所有树上路径类问题的通用骨架。

渐进式提示#

提示一:如果路径经过某个节点 x,那么它在 x 这一层有两种形态:一种是 x 作为”拐点”,左右子树各挂一条链下来(路径在 x 处折成 Λ 形);另一种是 x 只是链上的一环,只能向父节点延伸一条链。这两种形态对上层的要求一样吗?

提示二:父节点能从子节点那里”接”什么?只能是一条单向向下的链,不能是 Λ 形——因为 Λ 形在父节点处会分叉,路径就不再是简单路径。所以子树的返回值应该定义为”从该子树根节点向下延伸的最大链和”,而不是”该子树内的最大路径和”。

提示三:既然子树的返回值不直接是答案,那全局最大值在哪里更新?注意链和可以是负数,当子树贡献为负时应该不接(视为 0),但不能让整棵树贡献为 0——因为路径至少要包含一个节点。

直观方案及其局限#

直观方案:枚举每个节点作为路径最高点(拐点),对每个节点计算”经过它的最大路径和” = 左链最大 + 右链最大 + 节点值,取全局最大。

局限在于如何高效求”从某节点向下的最大链和”。若对每个节点都单独向下遍历求链和,最坏情况(链状树)复杂度退化为 O(n²)。这提示我们:链和必须从底向上在一次遍历中算出。

推荐方案的推导过程#

定义后序 DFS 的返回值:

gain(node) = 从 node 出发、向下延伸到其某一侧子树的最大链和(必须包含 node)

递归计算:

left = max(0, gain(node.left)) # 左子树贡献为负则不接
right = max(0, gain(node.right)) # 右子树贡献为负则不接

在 node 处结算”以 node 为最高点的路径和”:

through_node = node.val + left + right

用 through_node 更新全局答案。而向上返回时只能选一侧:

gain(node) = node.val + max(left, right)

为什么这两者不同:作为拐点时左右都挂,作为链的一环时只能延续一侧,否则父节点再往上接就形成分叉,违反”路径”定义。

为什么 max(0, ...) 是对的:不接负贡献的子树等价于路径不延伸过去,这永远是更优的选择。但 gain 本身至少是 node.val(可能为负),不会被截断为 0——因为最终答案至少要包含一个节点,且父节点有权选择不接这个负的 gain(父节点处的 max(0, ...) 会处理)。

正确性说明#

归纳假设:对任意节点 node,gain(node) 等于从 node 向下延伸的最大链和。

归纳步骤:设左右子树的 gain 已正确。从 node 向下延伸的最优链,必然是 node 加上左子树的一条向下链、或右子树的一条向下链、或都不加(两侧贡献都为负时)。因此 gain(node) = node.val + max(0, gain(left), gain(right)),与递推式一致。

全局最优的覆盖性:任意一条简单路径都有唯一的最高点(LCA)x,且该路径在 x 处恰好由”左向下一链 + x + 右向下一链”组成(某一侧可以为空)。在遍历到 x 时,through_node 正好枚举了这一形态的最大值。因此所有路径都被枚举到,全局最大值即为所求。

复杂度#

  • 时间复杂度:O(n),每个节点恰好访问一次。
  • 空间复杂度:O(h),递归栈深度,h 为树高;最坏(链状树)为 O(n),平衡树为 O(log n)。

Python 参考代码#

from typing import Optional
class TreeNode:
def __init__(self, val: int = 0,
left: Optional["TreeNode"] = None,
right: Optional["TreeNode"] = None):
self.val = val
self.left = left
self.right = right
class Solution:
def maxPathSum(self, root: Optional[TreeNode]) -> int:
# 全局答案初始化为负无穷,不能初始化为 0
# 因为整棵树可能全为负数,此时答案是最大的那个负数
best = float("-inf")
def gain(node: Optional[TreeNode]) -> int:
"""返回从 node 向下延伸的最大链和(必须包含 node)。"""
nonlocal best
if node is None:
return 0
# 子树贡献为负则不接,等价于路径不延伸过去
left = max(0, gain(node.left))
right = max(0, gain(node.right))
# 在 node 处结算:以 node 为最高点的 Λ 形路径
best = max(best, node.val + left + right)
# 向上只能延续一侧,否则父节点处会分叉
return node.val + max(left, right)
gain(root)
return best
def build(values):
"""由层序遍历数组(含 None)构建二叉树,便于本地测试。"""
if not values or values[0] is None:
return None
nodes = [None if v is None else TreeNode(v) for v in values]
for i, node in enumerate(nodes):
if node is None:
continue
li, ri = 2 * i + 1, 2 * i + 2
if li < len(nodes):
node.left = nodes[li]
if ri < len(nodes):
node.right = nodes[ri]
return nodes[0]

关键边界情况#

  1. 全负数树:[-3],答案是 -3 而不是 0。这要求全局答案初始化为 -inf(或首节点值),并且 gain 不截断为 0。
  2. 单节点:[1] → 1。
  3. 根节点只挂一侧:[1,-2,3] → 答案是 4(路径 3 -> 1),注意根节点此时不是拐点而是链端。
  4. 负子树被正确舍弃:[-10,9,20,null,null,15,7] → 42,左子树的 9 被接上(9 > 0 所以确实接了),但 -10 这一侧在节点 20 处被 max(0, ...) 处理掉。
  5. 链状树:[1,2,3,4,...] 深度可达 3×10⁴,Python 默认递归深度约 1000 会栈溢出。生产代码需要显式提高递归限制或改写为迭代后序遍历——面试中应当主动指出这一点。

测试用例#

s = Solution()
# 1. 示例 1:左右都接
assert s.maxPathSum(build([1, 2, 3])) == 6
# 2. 示例 2:拐点在右子树,根节点被舍弃
assert s.maxPathSum(build([-10, 9, 20, None, None, 15, 7])) == 42
# 3. 单节点
assert s.maxPathSum(build([5])) == 5
# 4. 全负数:答案必须是最大的那个负数,不能是 0
assert s.maxPathSum(build([-3])) == -3
assert s.maxPathSum(build([-3, -5, -1])) == -1
# 5. 只接一侧
assert s.maxPathSum(build([1, -2, 3])) == 4
# 6. 复杂混合:最优路径 15 -> 20 -> 7 -> -10 -> ... 实际为 15+20+7=42
assert s.maxPathSum(build([-10, 9, 20, None, None, 15, 7])) == 42
print("all passed")

变体与追问#

变体:如果要求返回具体路径(节点值列表)而不只是最大和,怎么办?

回答要点:在 DFS 中同时维护每个节点”向下的最优链”指向哪一侧,以及全局最优路径的两个端点。递归时记录 best_path_left_node 与 best_path_right_node,结算 Λ 形时把两侧链的具体节点序列拼起来。本质是把”值”的 DP 扩展为”值 + 路径”的 DP,复杂度不变,但空间需要注意路径拼接的成本——用父指针回溯比每层复制列表更优。

可迁移的解题能力#

  1. “向上贡献”与”内部结算”分离:这是树形 DP 的通用骨架。凡是”子树能告诉父节点什么”与”子树内部的最优是什么”不一致的题目(最大路径和、直径、最大 BST 子树、打家劫舍 III),都适用这个模式。
  2. 负值截断的正确位置:max(0, ...) 加在”接子树”处而不是”返回值”处。加错位置会让全负数用例失败。
  3. 路径类问题的 LCA 视角:任意简单路径有唯一最高点,因此在最高点处枚举即可覆盖全部路径,不重复不遗漏。这个视角对树和图上很多路径题都成立。

与 AI Agent 工程的关系(仅自然联系处)#

  • 递归深度与栈溢出:对应 Agent 中深层递归的任务分解(子任务再拆子任务)。模型驱动的任务分解深度在运行前不可预知,因此编排层通常要用显式深度上限与迭代式执行,而不能依赖语言运行时的递归栈——这与本题链状树的栈溢出是同一类”深度不可控”问题。
  • “局部最优向上贡献” vs “全局最优内部结算”:对应多 Agent 系统中子 Agent 既要有对上级有价值的中间结论,又要在自己范围内完成独立结算;两者混为一谈会导致结果丢失(类似本题把 gain 直接当答案)。

延伸资料#

官方文档与标准规范(本轮已核验)#

  1. RFC 9112(HTTP/1.1)§9.3 Persistence —— 对应 B01。
    https://datatracker.ietf.org/doc/html/rfc9112
    关键原文:HTTP/1.1 defaults to the use of "persistent connections";接收方依据协议版本与 Connection 头判断连接是否保持;出现 close 连接选项则当前响应后不再保持。
    核验说明:本轮抓取到的页面正文在 §9.3 之后被截断,§9.5(Failures and Timeouts)与 §9.6(Tear-down)原文未取到,因此本文件未引用这两节的具体表述。

  2. OpenAI 官方文档:可复现输出与 seed 参数 —— 对应 B04。

  3. LeetCode 124 中英文官方题目页 —— 对应 L01,题号、标题、难度、示例与约束一致,本轮已核验。

高质量技术文章(本轮已核验)#

  1. Martin Fowler,《Eradicating Non-Determinism in Tests》,2011-04-14 —— 对应 B02。
    https://martinfowler.com/articles/nonDeterminism.html
    关键内容:非确定性测试的两大问题(无用 + 会感染整个套件);先隔离止损、再修根因;五类常见成因——lack of isolation、asynchronous behavior、remote services、time、resource leaks。

未在资料中引用、本轮未核验的项#

  • OpenTelemetry GenAI 语义约定的当前状态(配置 v4 记录为 Development 且无 tagged release,本轮未重新核验)。
  • 具体熔断库(如 Resilience4j)的默认参数与本文件所述策略的对应关系 —— 未核验,本文件只陈述策略原则,不引用具体实现默认值。
  • 各类 PDF / 表格解析库在结构化切片上的实际表现 —— 未核验,B03 仅陈述策略与失败模式。

当日重点与复习建议#

今日主线#

今天 7 道题有一条共同主线:保护机制与约束的粒度必须与被保护对象匹配。

  • B01:超时的粒度匹配故障类型(connect / read / idle 各防各的)。
  • B02:测试的隔离粒度匹配非确定性来源,重跑是掩盖不是修复。
  • B03:切片的粒度匹配”可检索”与”可作答”两个约束。
  • B04:确定性承诺的粒度匹配平台真实保证(best effort,不是保证)。
  • S01:写入所有权与串行化的粒度匹配副作用域。
  • S02:限流键与熔断维度的粒度匹配业务身份与依赖边界。
  • L01:树形 DP 中”向上贡献”与”内部结算”的粒度分离。

最值得优先巩固的三点#

  1. S01 的三层能力归属。 重复退款的根因是把概率性输出接到了不可逆副作用上,中间缺确定性闸门。这是中高级 Agent 岗位最核心的判断力,也是区分”会调 API”与”会做系统”的分水岭。
  2. S02 的限流键错误。 “IP 是网络身份、租户是业务身份”这一句能直接命中根因,比讨论阈值调优有效得多。
  3. B04 的确定性边界。 能把”逐 token 确定性”和”决策可复现”分开,并给出结构化约束 + 规则兜底的落地路径,是成熟度的体现。

建议复习日与转换角度#

题目 ID建议复习日转换角度
Q-20261016-B032026-10-23从”切片策略选型”转为”故障定位”:给定”答案缺前提”的线上指标上升,要求按解析 / 切片 / 召回 / 重排四层归因
Q-20261016-B042026-10-25从”参数语义”转为”评测落地”:给定回归测试 CI 频繁红灯,要求重设评测口径与分组维度
Q-20261016-S022026-10-22从”限流键选择”转为”容量规划”:给定租户增长预测,要求推导配额模型与扩容节奏
Q-20261016-S012026-10-27从”写冲突治理”转为”成本治理”:给定每工单 token 成本翻倍的指标,要求分层归因与预算设计

精选题复习状态#

  • Q-20261012-B02(Structured Output 与 Tool Calling 边界):建议首次复习日 2026-10-19,未到期。
  • Q-20261013-B03(混合检索融合与重排归因):建议首次复习日 2026-10-20,未到期。
  • 今日未安排复习题,两道精选题均未进入复习窗口。

归档与索引信息#

配置读取状态#

  • 配置文件:../题库配置.md,读取成功。
  • 实际版本:version 4(last_reviewed: 2026-10-12)。
  • 暂停类型:无。
  • 观察中的新技术:9 项,均未标记”允许试学”,本次未从中出题。
  • 本次题目结构:非 Agent 基础 2 + Agent 基础 2 + Agent 场景 1 + 非 Agent 场景 1 + LeetCode 1 = 7 道,符合 v4。
  • 题库配置.md 未被修改。

历史读取范围#

  1. 题目索引.md(33 条,覆盖 2026-10-10 至 2026-10-15)—— 已读。
  2. 精选/README.md(2 条精选题与复习节奏)—— 已读。
  3. 最近 30 天每日训练文件(2026-10-10、10-12、10-13、10-14、10-15 共 5 份)—— 读取文件结构与题目层级,未逐字读取全部正文。
  4. 周复盘文件:不存在(累计 6 次,未达 7 次阈值)。
  5. 更早的完整题目正文:未读取,索引信息已足够支撑去重判断。

去重结果#

最近 30 天强去重(2026-09-16 至 2026-10-16):

  • 题目标题:今日 7 题与窗口内 33 题无相同标题。
  • 一级类型与二级主题:B01(操作系统与网络 · 持久连接与分层超时)与 10-14 B01(CFS 配额)二级主题不同;B02(软件工程 · 测试策略与 flaky)与 10-14 B02(API 兼容性)不同;B03(RAG · 切片与父子块)与 10-13 B03(混合检索与重排)不同;B04(AI 基础 · 采样与可复现性)与 10-13 B04(微调与 LoRA)不同。
  • 核心考点:逐一比对无高度重合。
  • 场景约束与故障现象:S01(并行子 Agent 重复退款 + 丢失更新 + 重复拉取)与 S02(共享出口 IP 被 429 + 熔断半开抖动)均为首次出现的故障现象组合。

LeetCode 去重:题号 124 首次出现,未与 146、3、215、739、875 重复。算法模式为树形 DP + 后序 DFS,与前五次(哈希表 + 双向链表、滑动窗口、堆 + 快速选择、单调栈、二分答案)均不同。

31 至 90 天弱去重:窗口内暂无该区间题目(题库自 2026-10-10 起)。

超过 90 天:无。

关联说明#

  • Q-20261016-S01 与 Q-20261010-B03(编排器-工作者与子 Agent 上下文隔离)同属多 Agent 主题,但考察角度不同:B03 考察上下文隔离与污染控制,S01 考察共享状态的写所有权、并发收敛与幂等去重。两者在”结果回传契约”上有交集,已明确区分,不构成换皮重复。
  • Q-20261016-B03 与 Q-20261013-B03(混合检索)同属 RAG 一级类型,但分别处于检索链路的不同层:本次是索引构建层(切片),上次是召回融合层(重排)。归因顺序上互补而非重复。

本次新增覆盖#

一级类型本次二级主题累计覆盖次数
操作系统与网络持久连接、连接池与分层超时2
软件工程与平台工程测试策略、回归测试与 flaky 治理3
RAG 与搜索文档解析、切片策略与父子块检索2
AI 与大模型基础推理参数、采样策略与可复现性边界2

配置 v4 的 13 个一级类型仍全部至少覆盖一次。覆盖次数最少的方向现为 上下文工程与能力封装、RAG 与搜索、AI 与大模型基础、操作系统与网络(各 2 次左右),可作为后续轮换参考。

周期复盘状态#

  • 本次为第 6 次生成,未达 7 次阈值,未触发周期复盘。
  • 预计下一次运行(第 7 次)触发周复盘,届时需读取本周期全部训练文件、索引、精选文件与当前配置。

索引更新#

  • 本次训练文件写入成功后,已按题目 ID 幂等更新 ../题目索引.md(33 条 → 40 条,indexed_runs 更新为 6)。
  • 状态全部为 generated,不代表已作答或已掌握。
  • 本次未执行精选(未收到明确精选意图)。

本文件保存于本地 Obsidian 目录,未验证远端同步状态,不声称已同步。

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