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 | 题型 | 一级类型 | 二级主题 | 难度 | 考察方向 |
|---|---|---|---|---|---|---|
| B01 | Q-20261016-B01 | 基础(非 Agent) | 操作系统与网络 | 持久连接、连接池与分层超时 | 基础题(中高) | 连接复用机制、三类超时的防护对象、池容量推导 |
| B02 | Q-20261016-B02 | 基础(非 Agent) | 软件工程与平台工程 | 测试策略、回归测试与 flaky 治理 | 基础题(中高) | 非确定性来源、隔离策略、测试替身边界 |
| B03 | Q-20261016-B03 | 基础(Agent) | RAG 与搜索 | 文档解析、切片策略与父子块检索 | 基础题(中高) | 切片粒度对检索与生成的双重影响、溯源锚点 |
| B04 | Q-20261016-B04 | 基础(Agent) | AI 与大模型基础 | 推理参数、采样策略与可复现性边界 | 基础题(中高) | 参数作用阶段、seed 的真实保证、评测口径 |
| S01 | Q-20261016-S01 | 场景(Agent) | AI Agent 核心 / 工具与互操作协议 | 多 Agent 协作、写冲突与结果合并 | 场景题(高) | 副作用所有权、乐观并发、幂等、重复劳动治理 |
| S02 | Q-20261016-S02 | 场景(非 Agent) | 分布式系统 | 限流与熔断的粒度、位置与误配 | 场景题(高) | 限流键选择、半开抖动、重试放大、降级取舍 |
| L01 | Q-20261016-L01 | LeetCode | 树形动态规划 / 后序 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 三类超时分别防护什么故障,并说明连接池容量应该如何推导,而不是拍脑袋设成一个常数。
考察点
- HTTP/1.1 默认使用持久连接,以及
Connection头决定本次响应后连接是否保持;持久连接是逐跳语义,经过代理或负载均衡后不保证端到端保持。 - 三类超时的语义分层:各自防护的故障类型不同,不能合并成一个参数。
- 连接池容量与”并发数 × 单请求占用时长”的排队关系,以及池必须与下游容量联合推导。
- 半开连接(stale connection)的成因:本地 idle 回收周期长于对端 keep-alive 超时。
- 池的分桶维度:目标地址、协议版本、凭证与租户上下文不同的连接不可混用。
准确定义与适用边界
持久连接: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 平台中,连接池必须按租户或凭证维度隔离,否则连接复用会成为跨租户的数据通道。
常见误区
- 只暴露一个
timeout参数,把连接建立、读取间隔与整体预算混为一谈。 - 池大小取 CPU 核数或随手设 10 / 100,不与下游容量和在飞时长联合推导。
- 客户端 idle 超时大于服务端 keep-alive 超时,稳定复现半开连接错误;正确做法是客户端 idle 略小于服务端,并对”取到连接后首次使用失败”做一次安全重试。
- 认为超时即未执行,对已接收的请求盲目重试,造成重复写。
- 忽略池的分桶维度,让不同租户或不同凭证的连接混用。
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)的选择边界在哪里。
考察点
- 非确定性的主要来源分类:隔离缺失、异步行为、远程服务、时间、资源泄漏。
- 隔离策略与代价:Fresh Fixture、清理策略、持久 Fresh Fixture(事务回滚)三者的取舍;共享可变 fixture 是顺序依赖型 flaky 的根因。
- 测试替身的语义边界与误用:mock 的行为断言会耦合实现细节,fake 存在与真实实现的行为漂移风险。
- 自动重跑与隔离(quarantine)的定位差异。
- 时间、随机性与并发的可控化手段:注入时钟、固定随机种子、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 需要独立的数据库、文件路径与端口范围,并且不写进程级全局状态。
常见误区
- 把自动重跑当作修复,甚至把重跑次数调到 5 次以上,使失败信号完全失效。
- 用
sleep等待异步完成,在负载高的 CI 上间歇性失败。 - 对外部依赖使用过细粒度的 mock,断言调用次数与参数顺序,导致任何重构都红灯。
- 让 fake 实现越来越复杂,最终测试验证的是 fake 自身的正确性。
- 只统计通过率,不统计 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)各自的适用边界与代价。
考察点
- 切片是检索单元与生成单元的耦合点:切得太小语义不完整,切得太大噪声与成本上升。
- 固定长度切片的语义破坏:切断标题与正文、表格行与表头、代码与注释、前提与结论。
- 重叠的真实收益与代价:只缓解边界丢失,代价是索引膨胀、重复召回与引用重复。
- 父子块如何解决”检索精度 vs 上下文完整”的冲突,以及它的生效前提。
- 切片与溯源的绑定关系:切片策略变更等价于索引重建,不能增量热更。
准确定义与适用边界
切片:把文档划分为可独立检索、也可独立注入上下文的文本单元。
适用边界:粒度必须同时满足两个约束——细到向量能表达其单一语义,大到模型拿到后能据以作答。这两个约束方向相反,因此不存在全局最优的 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 的引用输出应带文档版本号;用户点击引用时要能定位到原文位置,这要求切片阶段就写入锚点元数据。
常见误区
- 全局使用一个 chunk_size,不区分 API 文档、财报表格、聊天记录与代码这几类结构完全不同的内容。
- 认为加大重叠能解决所有边界问题——它只缓解,并且带来重复召回与重复计费。
- 父子块回传时不做去重与顺序还原,模型看到乱序重复段落,反而更易 hallucinate。
- 切片策略变更后只重建部分文档,导致索引内粒度不一致、向量相似度不可比。
- 忽略解析质量,把多栏 PDF 与复杂表格的乱序文本当作已经解决的问题。
1-2 分钟面试回答
切片是检索单元和生成单元的耦合点:切小了语义不完整,模型拿到的片段缺前提;切大了向量难以表达单一语义,噪声和成本都上升,所以不存在全局最优的 chunk_size。固定长度切片最容易切断”标题与正文""前提与结论""表格行与表头”这类强依赖,表现为答案时好时坏、溯源指向无关段落。重叠只能缓解边界丢失,代价是索引膨胀和重复召回。更有效的做法是结构化切片加父子块:用小子块做向量检索保证精度,命中后回传所属小节作为生成上下文保证完整,前提是父块必须有明确的结构边界。工程上还要注意两点:溯源锚点必须在切片阶段就写入元数据,否则无法做可验证引用;切片策略一旦变更,必须按文档版本原子重建该文档的全部切片,不能只更新一部分,否则新旧切片混在一起,相似度不可比。
面试官追问与回答要点
追问:知识库每天增量更新约 5%,能否只重建变更文档的切片?
回答要点:可以,但有三个前提。第一,同一文档的所有切片必须整体替换,按 doc_id + version 原子生效,避免新旧切片共存。第二,嵌入模型版本必须一致;一旦升级嵌入模型,向量空间不可跨模型比较,必须全量重建,此时增量更新反而是错误的。第三,删除文档要真正删除其向量与元数据,只标记失效会让已删除内容仍可被召回。此外要观测”重建滞后”这一指标:从源文档更新到索引生效的时间差,它直接决定知识时效性问题的影响面。
B04 · 推理参数、采样策略与可复现性边界
题目 ID:Q-20261016-B04
一级类型:AI 与大模型基础
二级主题:推理参数、采样策略与可复现性边界
难度:基础题(中高)
题目
团队给 Agent 回归测试提了一个要求:同一个 prompt 必须每次得到完全相同的输出。工程同学把 temperature 设为 0、并加了固定 seed,但输出仍然偶发不一致,于是怀疑是 SDK 或网关的 bug。
请解释各推理参数分别作用于解码的哪个阶段,说明为什么确定性承诺是有边界的,以及这对 Agent 的评测口径意味着什么。
考察点
- 参数的作用阶段:temperature 缩放 logits、top_p / top_k 截断候选集、penalty 修改分布、max_tokens / stop 终止生成、seed 影响采样随机数。
- 贪心解码(temperature=0)不等于跨版本确定性:批处理、内核与后端配置变化都会影响结果。
seed的真实保证级别是 best effort,需要配合system_fingerprint监控后端变更。- temperature 与 top_p 同时调整的耦合问题及其对调参可解释性的破坏。
- 对评测的含义:单样本字符串比对是错误口径,应改用统计口径并锁定模型版本与指纹。
准确定义与适用边界
采样:在每一步从模型输出的概率分布中选取下一个 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 上用多次运行统计任务成功率与轨迹一致性,而不是单次字符串比对;同时记录指纹,避免把模型侧变更误判为代码回归。
- 缓存优化:把接近确定性的子任务(抽取、分类)与高随机性的创作子任务分离到不同调用,提高前缀缓存命中率。
常见误区
- 认为
temperature=0就是绝对确定。 - 同时大幅调整 temperature 与 top_p,得出无法归因的结论。
- 用采样参数去”修复”格式错误、幻觉或知识缺失。
- 回归测试用单样本字符串比对,把正常的采样波动当作回归缺陷,最终导致团队关闭该测试。
- 不记录
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 回传自然语言结果 → 主管聚合后更新工单状态并回复客户。
已知现象
- 上线两周内出现 7 起重复退款:日志诊断 Agent 与账务 Agent 各自独立判断”需要退款”,双双发起。
- 配置变更 Agent 与诊断 Agent 并发更新同一工单的状态字段,出现后写覆盖前写(lost update),工单状态与实际处置不一致。
- P50 时长没有下降反而上升,token 成本涨到约 3 倍:两个子 Agent 各自拉取了同一批日志与监控数据。
- 故障归因困难:审计日志只记录”某个 Agent 调用了某个接口”,无法定位到具体会话、规划分支与决策依据。
限制条件
- 计费系统与客户通知属于不可逆副作用,退款一旦发出无法自动撤销。
- 6 个外部系统中只有 2 个提供幂等接口,其余只有普通写接口。
- 合规要求每次账务变更可审计到”谁(人 / Agent / 会话)在什么依据下做的”。
- 已上线,不能停机改造,需要灰度切换。
需要取舍的问题
并行度与写冲突的取舍、单写者所有权与灵活性的取舍、乐观并发与悲观锁的取舍、共享上下文与隔离上下文的取舍、自动合并与人工升级的取舍。
考察点
- 能否识别根因不在”模型不够聪明”,而在缺少副作用所有权与串行化机制。
- 能否把 Agent 的概率性能力与编排层的确定性能力清晰分层。
- 乐观并发(版本号 / CAS)在长任务场景下的适用性,以及为什么悲观分布式锁不可行。
- 重复劳动的治理:共享产物与任务指纹,而不是靠提示词约束。
- 冲突升级与审计归因:trace 维度设计,以及在无幂等接口的系统上如何补幂等。
3-5 分钟作答框架
- 定性与止损:先按副作用分级,把不可逆操作(退款、客户通知)从并行路径中摘出来,改为串行审批,止血优先于优化。
- 归责分层:明确模型负责什么、编排层负责什么、外部系统负责什么。
- 所有权建模:按副作用域划分唯一写者,其他 Agent 只产出提案。
- 并发控制:读取带版本号,写入用 CAS,冲突走”重读—重规划”而非盲重试。
- 去重与降本:共享产物存储与任务指纹,消除重复拉取。
- 可观测与升级:trace 维度设计、冲突升级通道、评测指标。
- 灰度与回滚:按工单类型与风险等级灰度,保留单 Agent 串行模式作为回滚态。
参考回答:先给结论
结论是:这不是模型能力问题,而是并发写缺少所有权与串行化的问题。多 Agent 并行带来的收益被三类成本抵消了——重复写(重复退款)、丢失更新、重复劳动(重复拉数据)。
正确的做法不是让子 Agent”更小心”,而是在架构上把三件事分开:谁有权写、写入如何串行化、结果如何去重。具体是:按副作用域设唯一写者,子 Agent 只产出带幂等键与预期前置状态的提案,由确定性的执行层串行执行;读取侧带版本号,写入侧用 CAS,冲突就重新读取并重新规划而不是盲目重试;把子 Agent 的中间产物放进共享产物存储,用任务指纹去重,消除重复拉取;最后把 trace 维度补全到”会话—规划分支—子 Agent—工具调用”,让审计能定位到决策依据。
同时必须承认一个取舍:并行度要下降。只读诊断可以充分并行,涉及副作用的操作必须串行或走审批。真正该优化的并行度是”只读阶段的并行”,而不是”所有阶段都并行”。
问题拆解
拆解成五个子问题,逐个对应到可落地的机制:
- 副作用归属不清 → 多个 Agent 都能写同一个外部系统。
- 写入无串行化 → 并发 read-modify-write 造成丢失更新。
- 外部系统无幂等 → 重试与重复决策直接变成重复写。
- 中间产物不共享 → 重复拉取,成本与时延双升。
- 审计维度不足 → 无法归因,也无法复盘决策依据。
核心方案与关键执行流程
第一层:阶段化编排(读阶段并行,写阶段串行)
把工单处理切成三个阶段:只读诊断阶段(日志诊断、指标查询、知识检索,充分并行)→ 决策与提案阶段(各子 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 种不同的子任务分解——这比单次失败更危险,也更难从结果指标看出来。
测试方法:
- 并发注入测试:用同步点构造两个子 Agent 同时对同一工单 read-modify-write,验证 CAS 生效。
- 故障注入:外部系统写入返回超时,验证不会重复写。
- 回放测试:录制线上工单,按指纹分组回放,比较改造前后的重复拉取次数。
上线观测:按工单类型与风险等级灰度(先只读工单,再低风险写工单,最后退款类);保留单 Agent 串行模式作为回滚态。核心看板:每工单写操作次数分布(异常抬升即重复写信号)、CAS 冲突率、幂等命中率、阶段耗时拆分、升级后人工修改率(人工改得多说明自动决策质量差)。
三个逐渐深入的追问
追问一:如果两个子 Agent 的写操作存在业务上的先后顺序,但这个顺序在运行前无法预先确定,怎么办?
回答要点:这说明依赖关系是运行时发现的,静态任务图不够用。两条路径:一是运行时发现依赖后重新调度,把后序操作重新入队并串行化;二是改为更粗的阶段化——先完整的只读诊断阶段,再统一的写阶段,牺牲一部分并行度换取可预测性。需要强调的是,让子 Agent 自己协商顺序是不可靠的,顺序约束必须由确定性的编排层表达。
追问二:乐观并发冲突后”重读—重新规划”会让模型看到不同的上下文,可能得出不同结论,造成决策震荡。怎么避免?
回答要点:这是 LLM 场景下乐观并发的特殊问题,传统 CAS 假设重试逻辑是确定性的,这里不是。对策有四条:一是把决策输入固定为快照,重规划时基于同一快照而不是重新读取的实时状态;二是决策与执行分离,决策结果结构化落盘,重试只重放执行不重做决策;三是设置重试预算(例如 2 次);四是检测同一决策点的结论翻转,翻转即升级人工而不是继续重试。
追问三:多 Agent 的成本怎么归因到单个工单和租户?
回答要点:在 trace 的每一层记录 token 用量,按 session_id 与 sub_agent_id 聚合到工单,再按租户聚合。关键是给每个子 Agent 设预算,超预算时降级为单 Agent 串行或升级人工,否则单个异常工单会放大成几十倍成本。另外要区分”输入 token”与”输出 token”,以及缓存命中部分单独统计,否则看不到优化空间。
常见误区
- 认为多 Agent 并行一定更快。 忽略了写冲突、重复劳动与冲突恢复的代价;真正的并行收益在只读阶段。
- 用分布式锁保护长任务。 持锁时间包含模型推理,锁超时导致双写,而且团队会误以为已经互斥,比不加锁更危险。
- 把所有子 Agent 放进同一个共享上下文”方便协作”。 造成上下文污染、token 成本膨胀,且一个 Agent 的错误推理会污染全部。
- 在提示词里写”不要重复退款”就当作约束。 授权边界必须置于模型之外,靠执行层的幂等键与权限校验实现。
30-60 分钟小型实践
用 Python 搭一个最小实验,不需要任何 Agent 框架:
- 建一个内存”工单”资源
{id, status, amount, version}。 - 起 8 个 worker,每个执行”读取工单 → 加 1 元 → 写回”,并发 200 次。统计最终值,观察丢失更新(最终值远小于期望值)。
- 加
version字段,写入时 CAS,冲突计入conflict_count并重试(最多 3 次)。再次统计,验证最终值正确。 - 模拟”上游超时但已执行”:worker 写入成功但在返回前随机超时,然后重试。观察重复写;加入幂等键映射表后再观察。
- 输出一张表:无并发控制 / 仅 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%,且不能因为稳定性措施误伤正常租户。
已知现象
- 大促期间,一家大型企业客户出现大量 429,但该租户自身流量完全正常——它的出口 IP 上还承载着其他流量,共享配额被挤占。
- 库存服务慢化(P99 从 50 ms 涨到 2 s)后,熔断器进入半开—关闭—再熔断的反复抖动:半开放 1 个请求成功就整体关闭,随即被全量流量打挂,恢复时间被拉长到原来的 4 倍。
- 慢化期间客户端重试把流量放大到约 3 倍,加速了下游雪崩。
限制条件
- 无法强制所有客户升级 SDK,重试行为短期改不掉。
- 下游依赖无法快速扩容。
- 不允许为了稳定性把阈值调得过于宽松,否则失去保护意义。
需要取舍的问题
限流粒度(IP vs 租户 vs 端点)、单机限流与分布式限流的取舍、快速失败与降级返回的取舍、熔断恢复节奏(快速恢复 vs 稳定恢复)、重试与限流的相互作用。
考察点
- 能否识别根因是限流键选错(身份维度与网络维度混淆),而不是阈值太小。
- 分层限流的设计:粗粒度抗滥用与细粒度配额各在哪一层。
- 熔断恢复策略:半开探测流量的设计,以及慢调用为什么必须纳入熔断判定。
- 重试放大:多层重试的放大效应,以及服务端如何用
Retry-After与退避引导客户端。 - 依赖不可用时的降级路径与可观测分桶。
3-5 分钟作答框架
- 定性与止血:先确认 429 的分布——按 IP、租户、端点分桶看,判断是配额不足还是限流键错误。
- 改限流键:把”按来源 IP”改为”按租户 / API Key”,IP 限流降级为只防滥用的粗粒度兜底。
- 分层限流:边缘粗、网关细、服务实例级并发上限。
- 修熔断:判定加入慢调用比例,半开改为逐步放量。
- 治理重试:服务端给
Retry-After,客户端退避加抖动与重试预算,禁止多层嵌套重试。 - 补降级与观测:熔断后降级返回而非 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 抖动,实例层仍然有保护,不会直接裸奔。
问题四:熔断恢复抖动
改进三点:
- 判定指标:错误率 + 慢调用比例复合判定。慢调用比例单独设阈值(例如 P99 超过阈值的请求占比),因为慢化往往先于错误出现。
- 半开放量:从”放 1 个请求”改为”按批次逐步放量”——例如半开首阶段放行 5% 流量,连续 N 个成功窗口提升到 25%、50%、100%。失败则立即回到熔断并延长冷却期(指数退避)。
- 熔断维度:按依赖服务 + 端点维度熔断,而不是全局一个熔断器,避免一个慢端点拖垮整体。
问题五:重试放大
- 服务端: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。
- 熔断状态转换事件流与每次熔断的持续时长、恢复耗时。
- 慢调用比例与错误率双曲线,观察谁先变化。
- 429 按
三个逐渐深入的追问
追问一:如果某些企业客户必须在边缘完成限流(认证信息在更内层才可用),怎么办?
回答要点:两条路。一是把认证前置到边缘——用轻量令牌校验换取限流键,代价是边缘要能验证签名并承受密钥分发复杂度。二是给大客户独立入口:独立域名或专线,使其出口 IP 与租户一对一,从根上消除共享问题;边缘保留宽松 IP 限流只防滥用。选择依据是大客户数量与商业价值;数量少时独立入口更简单可靠。
追问二:Redis 不可用的时候,限流应该放宽还是收紧?
回答要点:这取决于下游当时的弹性,没有统一答案,但必须有预设策略而不是临场决定。如果下游容量充足、当前慢调用比例低,可以放宽到网关本地静态配额,优先保可用性;如果下游已处于饱和或已有熔断在生效,则收紧,优先保存活。关键设计是实例层本地保护始终在线,所以无论选哪个,系统都不会裸奔。同时这个降级事件必须告警并计入可靠性指标,不能静默发生。
追问三:限流与重试如何避免互相放大?
回答要点:核心是三点。第一,服务端必须返回 Retry-After,让客户端的重试时刻可预期,而不是”立即重试”。第二,重试只能有一层——客户端或网关,不能客户端重试 3 次、网关再重试、服务内再重试,那样放大是乘法而非加法。第三,引入重试预算:单位时间内的重试次数上限,超出即直接拒绝。补充一点容易被忽略的:被限流的请求通常尚未执行,是安全可重试的;而超时类请求可能已执行,必须配合幂等键才能重试——把这两类混为一谈会造成重复写。
常见误区
- 把 IP 当作租户身份来限流。 在 NAT 与企业网关场景下必然误伤,同时防不住轮换 IP 的滥用者。
- 只设一个全局 QPS 阈值。 不同端点的成本差异巨大,一个重查询端点就能吃满全局配额,把轻量接口全部饿死。
- 多层各自重试。 放大效应是乘法,且每一层都认为自己在做正确的事。
- 熔断只看错误率。 依赖慢化时错误率可能仍然很低,但系统已经在排队堆积,等到错误率上升时已经雪崩。
- 半开状态放一个请求成功就全量恢复。 单点采样不足以证明依赖已恢复,是恢复抖动的直接原因。
30-60 分钟小型实践
用 Python 实现一个最小可验证原型:
- 用内存令牌桶实现两种限流器:
by_ip与by_tenant。 - 造数据:3 个租户共享 1 个 IP,其中租户 A 正常 20 rps、租户 B 正常 20 rps、租户 C 突增到 200 rps。
- 跑 60 秒,统计两种限流下 A 与 B 的被拒次数(误伤)与 C 的实际通过量(漏放)。
- 加一个熔断器:先只用错误率判定,注入”延迟 2 s、无错误”的慢化,观察它是否触发;再加慢调用比例判定,观察差异。
- 对比两种半开策略——“放 1 个请求即全量恢复”与”按 5% → 25% → 100% 逐步放量”,统计 5 分钟内的状态转换次数与总可用时长。
输出一张对比表,结论会比概念描述更有说服力。
L01 · LeetCode 124. 二叉树中的最大路径和
题目 ID:Q-20261016-L01
题号:124
中文名:二叉树中的最大路径和
英文名:Binary Tree Maximum Path Sum
难度:Medium
官方链接(2026-10-16 已核验,中英文页面题号、标题、难度、示例与约束一致):
- 中文:https://leetcode.cn/problems/binary-tree-maximum-path-sum/
- 英文:https://leetcode.com/problems/binary-tree-maximum-path-sum/
题目描述
二叉树中的路径被定义为一条节点序列,序列中每对相邻节点之间都存在一条边;同一个节点在一条路径序列中至多出现一次。路径至少包含一个节点,且不一定经过根节点。路径和是路径中各节点值的总和。
给定二叉树的根节点 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]关键边界情况
- 全负数树:
[-3],答案是-3而不是 0。这要求全局答案初始化为-inf(或首节点值),并且gain不截断为 0。 - 单节点:
[1]→ 1。 - 根节点只挂一侧:
[1,-2,3]→ 答案是 4(路径3 -> 1),注意根节点此时不是拐点而是链端。 - 负子树被正确舍弃:
[-10,9,20,null,null,15,7]→ 42,左子树的 9 被接上(9 > 0 所以确实接了),但-10这一侧在节点 20 处被max(0, ...)处理掉。 - 链状树:
[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. 全负数:答案必须是最大的那个负数,不能是 0assert s.maxPathSum(build([-3])) == -3assert s.maxPathSum(build([-3, -5, -1])) == -1
# 5. 只接一侧assert s.maxPathSum(build([1, -2, 3])) == 4
# 6. 复杂混合:最优路径 15 -> 20 -> 7 -> -10 -> ... 实际为 15+20+7=42assert 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,复杂度不变,但空间需要注意路径拼接的成本——用父指针回溯比每层复制列表更优。
可迁移的解题能力
- “向上贡献”与”内部结算”分离:这是树形 DP 的通用骨架。凡是”子树能告诉父节点什么”与”子树内部的最优是什么”不一致的题目(最大路径和、直径、最大 BST 子树、打家劫舍 III),都适用这个模式。
- 负值截断的正确位置:
max(0, ...)加在”接子树”处而不是”返回值”处。加错位置会让全负数用例失败。 - 路径类问题的 LCA 视角:任意简单路径有唯一最高点,因此在最高点处枚举即可覆盖全部路径,不重复不遗漏。这个视角对树和图上很多路径题都成立。
与 AI Agent 工程的关系(仅自然联系处)
- 递归深度与栈溢出:对应 Agent 中深层递归的任务分解(子任务再拆子任务)。模型驱动的任务分解深度在运行前不可预知,因此编排层通常要用显式深度上限与迭代式执行,而不能依赖语言运行时的递归栈——这与本题链状树的栈溢出是同一类”深度不可控”问题。
- “局部最优向上贡献” vs “全局最优内部结算”:对应多 Agent 系统中子 Agent 既要有对上级有价值的中间结论,又要在自己范围内完成独立结算;两者混为一谈会导致结果丢失(类似本题把
gain直接当答案)。
延伸资料
官方文档与标准规范(本轮已核验)
-
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)原文未取到,因此本文件未引用这两节的具体表述。 -
OpenAI 官方文档:可复现输出与
seed参数 —— 对应 B04。- https://platform.openai.com/docs/guides/advanced-usage
- https://platform.openai.com/docs/api-reference/completions/create
关键原文:Chat Completions 默认非确定;seed的语义是our system will make a best effort to sample deterministically;Determinism is not guaranteed,需参照system_fingerprint监控后端变更;system_fingerprint表示模型运行的后端配置,可与seed配合使用以判断后端变更是否影响确定性。
-
LeetCode 124 中英文官方题目页 —— 对应 L01,题号、标题、难度、示例与约束一致,本轮已核验。
高质量技术文章(本轮已核验)
- 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 中”向上贡献”与”内部结算”的粒度分离。
最值得优先巩固的三点
- S01 的三层能力归属。 重复退款的根因是把概率性输出接到了不可逆副作用上,中间缺确定性闸门。这是中高级 Agent 岗位最核心的判断力,也是区分”会调 API”与”会做系统”的分水岭。
- S02 的限流键错误。 “IP 是网络身份、租户是业务身份”这一句能直接命中根因,比讨论阈值调优有效得多。
- B04 的确定性边界。 能把”逐 token 确定性”和”决策可复现”分开,并给出结构化约束 + 规则兜底的落地路径,是成熟度的体现。
建议复习日与转换角度
| 题目 ID | 建议复习日 | 转换角度 |
|---|---|---|
| Q-20261016-B03 | 2026-10-23 | 从”切片策略选型”转为”故障定位”:给定”答案缺前提”的线上指标上升,要求按解析 / 切片 / 召回 / 重排四层归因 |
| Q-20261016-B04 | 2026-10-25 | 从”参数语义”转为”评测落地”:给定回归测试 CI 频繁红灯,要求重设评测口径与分组维度 |
| Q-20261016-S02 | 2026-10-22 | 从”限流键选择”转为”容量规划”:给定租户增长预测,要求推导配额模型与扩容节奏 |
| Q-20261016-S01 | 2026-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未被修改。
历史读取范围
题目索引.md(33 条,覆盖 2026-10-10 至 2026-10-15)—— 已读。精选/README.md(2 条精选题与复习节奏)—— 已读。- 最近 30 天每日训练文件(2026-10-10、10-12、10-13、10-14、10-15 共 5 份)—— 读取文件结构与题目层级,未逐字读取全部正文。
- 周复盘文件:不存在(累计 6 次,未达 7 次阈值)。
- 更早的完整题目正文:未读取,索引信息已足够支撑去重判断。
去重结果
最近 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 目录,未验证远端同步状态,不声称已同步。