15444 字
77 分钟
2026-10-21 AI Agent 面试训练

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

今日学习主题#

隐含假设的显式化:版本、单位、保留集与独占权。

今天五道题表面分属五个领域,内核是同一件事:系统里大量”默认应该没问题”的假设——版本范围意味着兼容、字符数可以折算 token、压缩不会丢关键约束、拿着锁就是唯一执行者、连通性一眼就能看出来——在工程上都必须在机制层面被显式表达,否则它们的失效是静默的:不报错、不告警,直到造成不可逆后果才被发现。

编号题目名称题型一级类型难度一句话考察点
B01语义化版本、锁文件与可复现构建非 Agent 基础软件工程与平台工程基础题(中高)版本范围是意图声明不是保证,锁文件与契约测试才是保证
B02Token、分词与 Embedding 表示Agent 基础AI 与大模型基础基础题(中高)token 不是字符,embedding 不是语义,单位错配让成本与检索同时失真
S01长会话上下文超限与压缩导致的质量退化Agent 场景上下文工程与能力封装场景题(高)压缩的取舍不在”压不压”,在”谁决定保留什么”
S02定时任务重复执行与漏执行非 Agent 场景分布式系统场景题(高)持有租约不等于拥有执行权,fencing 与对账才是兜底
L01547. 省份数量LeetCode并查集medium把隐式等价关系显式化:路径压缩 + 按秩合并

题型分布#

  • 非 Agent 基础定义题 1 道(B01)
  • AI Agent / LLM 基础定义题 1 道(B02)
  • AI Agent 真实研发场景题 1 道(S01)
  • 非 AI Agent 真实研发场景题 1 道(S02)
  • LeetCode 1 道(L01)

B01 · 非 Agent 基础 · 软件工程与平台工程#

题目 ID:Q-20261021-B01
一级类型:软件工程与平台工程
二级主题:依赖与版本管理(语义化版本、版本范围、锁文件与可复现构建)
难度:基础题(中高)

题目#

某核心服务依赖一个内部 SDK,依赖声明写作 ^1.4.0。CI 每次构建都重新解析依赖,仓库里没有提交锁文件。上周五构建一切正常;本周一同一份代码重新构建,依赖被解析到 SDK 1.9.0,服务启动后调用 create_client(tenant, api_key) 抛出 TypeError——SDK 从 1.6.0 起把第二个参数改成了 credentials 对象。回滚到 1.5.2 后恢复。

团队要求你给出整改方案。请回答:

  1. ^1.4.0 这个声明在语义上到底承诺了什么、没有承诺什么?
  2. 为什么同样的代码两次构建会得到不同的依赖树?
  3. 整改要靠哪几层机制,而不是靠”让大家小心一点”?

先思考#

  • 先把”版本号”和”依赖范围”分开看:1.9.0 是一个版本,^1.4.0 是一个集合。这个集合的上界是多少?
  • 再想:即便 SDK 方严格遵守规范,谁来判断某个改动属于”不兼容”?这个判断的可靠性如何?
  • 最后想:如果明天 SDK 发布 1.9.1,你的构建结果会不会又变?什么机制能让”同一份输入永远得到同一份输出”?

参考答案#

结论:^1.4.0 只表达”我期望 1.x 系列都能用”,它是一个范围声明而不是兼容性保证;真正保证可复现与可兼容的是锁文件、传递依赖固定与契约测试三件事,版本号本身只是人写的意图标签。

定义:语义化版本 2.0.0(SemVer)规定版本格式为 X.Y.Z(主版本号.次版本号.修订号),递增规则为:主版本号——做了不兼容的 API 修改;次版本号——做了向下兼容的功能性新增(公共 API 被标记弃用时也必须递增);修订号——做了向下兼容的问题修正。规范原文还明确:“使用语义化版本控制的软件必须(MUST)定义公共 API”、“标记版本号的软件发行后,禁止(MUST NOT)改变该版本软件的内容”。

适用边界:SemVer 只对已经明确定义的公共 API 负责。规范自己也承认:“现实世界是复杂的,我们除了提高警觉外能做的不多。” 也就是说,版本号是发布方的意图声明,不是消费方的安全证明。它也不能表达”运行时行为细微变化”、“性能退化”、“默认配置变更”这类事实上破坏消费者但未必被判定为 API 不兼容的改动。

简洁示例:^1.4.0 等价于 >=1.4.0 <2.0.0,因此 1.9.0 完全落在范围内,1.6.0 的签名变更是 SDK 方对规范的违背(应升到 2.0.0)。但消费方无法在构建时验证这一点——^ 不会替你读 changelog。反例是 ~1.4.0,它等价于 >=1.4.0 <1.5.0,只允许修订号递增;如果消费者的真实需求是”锁死次版本”,^ 从一开始就用错了。

与相近概念的区别:

  • SemVer vs CalVer:前者传达兼容性意图,后者只传达发布时间,不能用于推导兼容性。
  • 版本范围 vs 锁文件:范围描述”可接受的解集合”,锁文件记录”本次实际选中的完整解”,包含全部传递依赖。两者不是替代关系,而是”约束 + 快照”。
  • 锁文件 vs 可复现构建:锁文件固定依赖树;可复现构建进一步要求工具链、编译参数与源码哈希一致,使同样输入产出字节级一致的产物。前者是后者的必要不充分条件。
  • 0.x 与 1.x:规范规定”主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变。这样的公共 API 不应该被视为稳定版”。因此 ^0.4.0 在多数生态中等价于 >=0.4.0 <0.5.0,与 ^1.4.0 的 <2.0.0 完全不同——这是高频踩坑点。

典型应用(通用工程):锁文件提交进仓库并在 CI 中以冻结模式安装;依赖升级走独立的自动化 PR(解析新版本 → 跑全量测试与契约测试 → 人工评审);对外发布的库维护公共 API 清单并对破坏性改动走主版本;用依赖审计与 SBOM 回答”线上跑的到底是什么版本”。

面试现场回答(1-2 分钟):
“^1.4.0 的语义是 >=1.4.0 <2.0.0,它表达的是使用方的期望,不是发布方的保证。SDK 在 1.6.0 改函数签名,按 SemVer 应该升主版本,这是 SDK 方违背规范;但我们自己的问题是没有锁文件——CI 每次重新解析,依赖树随时间漂移,同一份代码在不同时间构建出不同产物,这本身就不可接受。整改分四层:第一,提交锁文件、CI 用冻结安装模式,保证构建可复现;第二,依赖升级走独立 PR 并跑契约测试,不混在业务改动里;第三,对内部 SDK 建立公共 API 契约测试,发布前由流水线自动判定是否构成破坏性变更,而不是靠人工判断版本号;第四,服务启动与灰度阶段做兼容性验证与快速回滚通道,让”万一漏网”变成可发现、可回退而不是线上事故。版本号只是标签,真正的保证来自锁文件、自动化判定和回滚能力。”

面试官追问:“如果依赖的是一个 0.x 的内部库,上面的策略要不要改?“
回答要点:要改。按 SemVer,0.y.z 处于初始开发阶段,公共 API 不应视为稳定,次版本变更就可能不兼容。因此 ^0.4.0 只覆盖 0.4.x,任何 0.5.0 都要当作主版本升级对待;对 0.x 依赖应固定精确版本或采用”每次升级都强制评审 + 契约测试”的强流程,并且推动提供方尽快发布 1.0.0 或改用明确的内部版本约定。

关键点#

  • ^1.4.0 = >=1.4.0 <2.0.0,~1.4.0 = >=1.4.0 <1.5.0;两者混淆是这类事故最常见的直接原因。
  • SemVer 的前提是”存在明确定义的公共 API”;没有 API 清单,版本号无意义。
  • 规范规定已发布版本内容不可变,任何修改必须以新版本发行;因此”重新发布同一个版本号”是不允许的,这也是锁文件能生效的基础。
  • 版本优先级比较时忽略构建元数据(规范原文:“当判断版本的优先层级时,版本编译信息可(SHOULD)被忽略”),1.0.0+build.1 与 1.0.0+build.2 同级。
  • 弃用需要至少一个含弃用信息的次版本过渡,才能在新主版本移除——这是”可平滑迁移”的规范要求。

常见误区#

  1. 把 ^ 当成”锁定”:^ 明确允许次版本与修订号递增,它是范围不是固定值。真正锁定要写精确版本或用锁文件。
  2. 认为提交锁文件就能防止不兼容升级:锁文件只固定当前解析结果,升级锁文件的那一刻仍需验证;而且不完整记录传递依赖的”锁”是无效的——你的依赖的依赖同样会漂移。
  3. 认为遵守 SemVer 就不需要契约测试:版本号是人工判断的结果,规范自己都要求”提高警觉”。自动化兼容性判定(API 签名 diff、契约测试、消费者驱动测试)才是机器可验证的保证。

延伸学习#

  • Semantic Versioning 2.0.0 官方规范(中文):https://semver.org/lang/zh-CN/ —— 已核验,本文引用的规则条目与原文一致。
  • Reproducible Builds 项目:https://reproducible-builds.org/ —— 本轮未核验具体页面,仅作为”锁文件 → 可复现构建”的延伸方向。

B02 · Agent 基础 · AI 与大模型基础#

题目 ID:Q-20261021-B02
一级类型:AI 与大模型基础
二级主题:Token、分词(BPE / 子词)与 Embedding 向量表示
难度:基础题(中高)

题目#

某 Agent 平台有两处”看起来合理”的设计:

  1. 成本与上下文预算按”字符数 ÷ 4”估算 token,接近预算时按字符截断上下文。
  2. 工具检索用 embedding 余弦相似度,全局阈值 0.75:查询与工具描述的相似度超过 0.75 就命中该工具。

上线后出现两类问题:

  • 中文与多语言用户的实际 token 消耗是估算值的 2 到 3 倍,账单严重超预期;截断后模型偶尔输出乱码。
  • 工具检索频繁把”发送邮件”和”发送短信”同时判为高相似,命中错误工具;同一阈值换了一个 embedding 模型后命中率断崖式下跌。

请解释这两类问题背后的共同根因,并给出工程上正确的做法。

先思考#

  • token 是模型的输入单位,但它和字符、词之间是什么关系?这个关系对中文和英文一样吗?
  • “相似度 0.75” 到底是谁和谁的相似度?这个数字的含义依赖什么前提?
  • 如果这两件事都要”可预测”,你需要在系统里引入什么确定性的东西来替代经验值?

参考答案#

结论:两类问题的共同根因是单位错配——把 token 当成字符的线性函数、把 embedding 的几何邻近当成语义等价。token 是分词器的输出单位,embedding 是模型学习到的几何表示,两者都不是人类直觉中的”字”或”意思”。工程上必须用真实分词器计数、在 token 边界操作,并把相似度阈值当作需要标定的模型相关参数。

定义:

  • Token:模型处理文本的单位。官方定义原文为:“Tokens are the units that OpenAI models use to process text. A token can represent a character, part of a word, a whole word, or punctuation. Spaces also affect how text is divided into tokens.” 即一个 token 可以是一个字符、词的一部分、整个词或标点,空格也参与切分。
  • 子词分词 / BPE:Sennrich、Haddow、Birch 在 2015 年提出用子词单元编码稀有词与未知词,实现开放词汇翻译;其直觉是”各类词可以借助比词更小的单位来翻译”——名字靠字符拷贝或音译、复合词靠组合翻译、同源词与借词靠音韵与形态变换。该工作比较了字符 n-gram 与基于字节对编码(BPE)压缩算法的分词方式。
  • Embedding:把文本映射到稠密向量空间,使得在该模型看来相关的文本在几何上接近。它编码的是模型的表示空间结构,不是形式语义。

适用边界:

  • 官方给出的经验值只针对英文:“1 token is approximately 4 characters”、“1 token is approximately three-quarters of a word”、“100 tokens are approximately 75 words”;且明确限定:“These are estimates, not exact counts. Sentence and paragraph lengths vary, and other languages can have different relationships between characters, words, and tokens.”
  • 同一段文本的 token 数取决于模型、编码与语言:“The same text can produce different token counts depending on the model, its encoding, and the language.” 拼写、大小写与周围文本也会改变切分(“A word can be divided into different tokens depending on its spelling, capitalization, and surrounding text.”)。
  • Embedding 相似度分数只在同一个模型、同一个版本下具有可比性,不能跨模型迁移阈值。

简洁示例:英文里常见词往往整体成 token(“apple” 可能是一个 token),而中文、日文或含大量专有名词、代码、UUID 的文本常常被切成更细的片段,一个汉字可能对应一到多个 token。因此”字符数 ÷ 4”在中文场景下系统性低估。工具描述侧,“发送邮件”与”发送短信”共享”发送”这个动作与大量上下文词,向量空间中本就接近——0.75 这个全局阈值无法区分”动作相同”与”工具相同”。

与相近概念的区别:

  • Token vs 字符 vs 词:token 是分词器输出,字符是编码单位,词是语言学单位;三者之间没有跨语言的固定换算。
  • 上下文窗口 vs 可用预算:标称窗口是容量上界,实际可用预算还要扣掉系统提示、工具定义、历史消息与输出预留。
  • 余弦相似度 vs 点积:对 L2 归一化后的向量,余弦相似度是点积的单调函数,两者排序等价;未归一化时点积受向量模长影响,排序会变。这是”换模型/换归一化后阈值失效”的数学来源之一。
  • Embedding 相似 vs 语义等价:前者是几何邻近,后者需要逻辑判断。“发送邮件”与”发送短信”相似但不等价,去重、权限判定这类问题不能交给相似度。
  • 稀疏检索 vs 密集检索:稀疏(如 BM25)吃词面匹配,密集吃语义邻近;二者失效模式不同,常需混合而非互相替代。

典型应用:

  • 在 Agent 系统中的应用:① 上下文预算必须按真实分词器计数并预留输出空间,压缩/截断的触发点应基于 token 实测值而非字符估算;② 成本归因要按请求、按工具、按子任务统计真实 token,而不是用字符折算做单位经济学;③ 工具检索的相似度阈值必须按任务标定并版本化,模型升级要回归;④ 语义缓存与去重键不能只依赖相似度,必须叠加确定性判据(租户、权限、参数指纹);⑤ 截断必须在 token 边界且避免切断多字节字符与结构化块(JSON、代码)。

面试现场回答(1-2 分钟):
“这两类问题是一个根因:把经验值当成了物理定律。token 不是字符,它是分词器的输出单位,官方给的四字符换算是英文估算,而且同一段文本的 token 数取决于模型、编码和语言——中文场景系统性低估,按字符截断还会在多字节字符中间切断,输出乱码就是这么来的。正确做法是接入与模型匹配的分词器做真实计数,预算按 token 管理,截断在 token 边界并保留结构化块完整。embedding 那边,相似度和语义等价是两件事:‘发送邮件’和’发送短信’在向量空间里本来就接近,因为它们共享’发送’这个动作和上下文。一个全局的 0.75 阈值既没有标定依据,也跨模型不可迁移——余弦分数的分布随模型和归一化方式变化。工程上应该用标注数据按任务画 PR 曲线选阈值,把阈值当成随模型版本走的配置并做回归;同时工具命中不能只靠相似度,要叠加确定性判据,比如参数 schema 校验、权限校验,以及高风险工具的显式确认。一句话:token 与 embedding 都是模型侧的单位,凡是影响正确性、成本和安全的判断,都要有确定性的兜底。”

面试官追问:“embedding 相似度阈值到底该怎么定,能不能给一个通用值?“
回答要点:不能。阈值是任务相关、模型相关的经验参数。做法:① 构造带标注的查询-工具正样本集(含易混淆负样本,如”发邮件/发短信/发通知”);② 按分数画 PR 曲线,按业务对误命中和漏命中的代价不对称来选择工作点——误调工具通常比漏召回严重得多,阈值应偏向高精度;③ 阈值与 embedding 模型版本一起登记、一起回归;④ 对高风险或高混淆的工具,不靠阈值,改用显式路由规则或二次确认;⑤ 上线后监控低置信区间的分布漂移,作为模型或工具描述变更的早期信号。

关键点#

  • 官方经验值(4 字符 / 0.75 词 / 100 tokens ≈ 75 words)只适用于英文,且明确标注为 estimates 而非 exact counts。
  • token 数由模型 + 编码 + 语言共同决定,不可跨模型假设 token ID 或切分一致。
  • 大小写、前导空格与周围文本都会改变切分——这意味着”字符串层面的拼接顺序”会影响成本。
  • 余弦相似度对归一化向量与点积单调等价;未归一化时不等价,且不同模型的分数分布不可比。
  • 影响正确性、权限、成本结算的判断必须有确定性兜底(真实计数、schema 校验、权限判定、人工确认),不能只靠相似度。

常见误区#

  1. 用”字符数 ÷ 4”做多语言成本与预算:这是英文经验值,直接套用到中文、代码、UUID 上会系统性低估,预算与账单同时失真。
  2. 按字符截断上下文:截断位置可能落在多字节字符或 JSON/代码结构中间,既产生乱码又破坏结构,而且越靠近窗口上限越容易触发——成本省下来了,质量悄悄掉了。
  3. 把相似度阈值当跨模型常量:阈值与模型、归一化方式和任务分布绑定;换模型不回归阈值,命中率断崖是必然结果而非异常。

延伸学习#

  • Rico Sennrich, Barry Haddow, Alexandra Birch:《Neural Machine Translation of Rare Words with Subword Units》,arXiv:1508.07909(2015-08-31 提交,v5 为 2016-06-10,ACL 2016 接收)——子词单元与 BPE 分词的原始论文,已核验。
  • OpenAI 帮助中心《Understanding and counting tokens》:https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them ——已核验,本文引用的经验值与限定条件与原文一致。

S01 · Agent 场景 · 上下文工程与能力封装#

题目 ID:Q-20261021-S01
一级类型:上下文工程与能力封装(同时触及 AI Agent 核心、AI 安全与治理)
二级主题:上下文超限、压缩策略与保留集设计;压缩触发的质量退化
场景题类型:上下文超限与信息压缩
难度:场景题(高)

题目#

业务背景:企业内部 IT 运维 Agent。用户报障后开一个会话,Agent 自主完成诊断与处置:查询 CMDB、拉取监控、执行只读脚本、读取日志、必要时执行重启或回滚。

系统规模:日活约 1200 个会话;单会话平均 40 到 80 轮工具调用;单次工具返回 2k 到 8k token;模型上下文窗口 200k token;P50 会话累计 token 约 180k。

当前架构:单 Agent 循环。当累计 token 接近窗口上限(95%)时触发一次性压缩:把整条消息历史交给模型做摘要,提示词是”请总结以上对话的关键信息”,然后用摘要重启上下文继续执行。

目标:压缩后不丢失关键约束、不重复执行已完成的写操作、不产生错误引用。

限制条件:不能无限扩容上下文(成本与延迟都不可接受);不能让会话中断重来(用户体验硬要求);写操作(重启、回滚、变更)不可逆,重复执行有真实业务代价。

故障现象(压缩发生后):

  1. Agent 重复执行了本工单里已经完成的一次服务重启;
  2. Agent 忘记了”本工单只允许在变更窗口内操作、且禁止操作生产数据库”这条约束,执行了一次越权的 DB 变更;
  3. Agent 在最终报告里引用了一个已被压缩掉的工单号,输出了错误单号。

需要取舍的问题:压缩的激进程度(省多少 token)与保留集完整性(丢多少关键信息)之间如何取舍?更进一步:哪些信息根本不应该进入”由模型决定是否保留”的范畴?

先思考#

  • 三类故障看上去都是”压缩丢了东西”,但它们丢失的东西性质一样吗?约束、已完成的操作、标识符——这三类分别该由谁保管?
  • “压缩提示词写得更详细一点”能解决问题吗?如果模型这次没漏,你怎么知道下次不漏?
  • 如果必须压缩,能否让被压缩的那部分信息在需要时还能取回来,而不是永久消失?

参考答案#

结论:压缩不是一个”信息变小”的问题,而是”保留集由谁决定”的问题。凡是决定正确性、安全性与可审计性的信息(硬约束、已完成的副作用、业务标识符),都不能交给概率性摘要去决定是否保留,必须外置到确定性载体,并在每次请求时确定性地重新注入。压缩只能作用于”可以丢、丢了还能取回”的部分(主要是工具原始输出)。

问题拆解:三类故障对应三种不同性质的信息,需要分别处置:

  • 约束(变更窗口、禁止清单、权限)属于策略,必须是确定性的、每次重新注入的,根本不该待在对话历史里;
  • 已完成的副作用(重启已执行)属于状态,必须持久化到外部记录,并由幂等键 + 写闸门兜底,而不是靠模型”记住”;
  • 标识符(工单号、资源 ID、变更单号)属于结构化事实,必须进入结构化状态文件,而不是留在摘要的自然语言里。
    共同错误在于:把三者都塞进了”让模型总结一下”这一个概率性通道。

核心方案:分层上下文 + 保留集外置 + 分级压缩。

  1. 不可压缩层(pinned):系统策略与硬约束、当前工单的结构化状态卡片(工单号、资源清单、变更窗口、禁止操作)、已完成操作日志摘要(结构化,非自然语言)。这一层由服务端在每次请求组装时确定性地注入,压缩永不触碰。
  2. 可回收层(raw tool outputs):历史工具的原始返回。压缩时优先清理这一部分——按 Anthropic 的表述,这是”最安全、最轻量的压缩形式”(tool result clearing):工具在很久以前被调用过,为什么还要看到原始结果?
  3. 可重取层(re-fetchable):保留轻量引用(文件路径、查询 ID、日志游标),需要时用工具重新拉取,而不是把内容常驻上下文。
  4. 摘要层:仅对上述 2、3 层做摘要,且摘要产物写入结构化状态文件而非只留在消息里。

关键执行流程:

  • 每次请求组装时:注入 pinned 层 → 追加近期消息(最近 N 轮原始消息)→ 追加压缩摘要 → 追加当前轮输入。
  • 预算管理:按真实分词器计数(见 B02),并为输出预留空间;在 70% 到 80% 处就开始分级清理,而不是拖到 95% 一次性抢救。
  • 触发压缩:只清理/摘要可回收层;pinned 层原样保留。
  • 压缩产物:写入结构化状态文件(progress file / NOTES 模式),包含”已完成操作及结果""未解决问题""关键标识符""下一步计划”四个固定字段。
  • 压缩后:以”压缩后的上下文 + 结构化状态 + 最近访问的关键文件/资源”重启继续。

技术选型与依据:

  • 工具结果清理:最安全、性价比最高,优先做。
  • 结构化笔记 / agentic memory:把笔记持久化到上下文窗口之外,后续按需拉回;适合”有明确里程碑的迭代式工作”,运维工单正属此类。
  • 子 Agent 隔离:把深度检索/长日志分析交给子 Agent,主 Agent 只拿 1k 到 2k token 的精炼摘要;适合”可并行探索”的部分。
  • 对话压缩(compaction):用于维持”需要大量来回对话”的连续性,但必须配合前三者,不能作为唯一手段。
    选型依据来自任务特征:本场景是”长会话 + 有明确里程碑 + 包含不可逆写操作”,因此以结构化笔记为主、工具结果清理为基础、子 Agent 处理深挖、压缩仅作兜底。

方案取舍:

  • 惰性压缩 vs 主动压缩:拖到 95% 才压缩,一次性信息损失最大且最容易丢约束;提前分级清理会增加少量压缩调用成本,但把损失摊薄——本场景选后者。
  • 摘要保真度 vs token 节省:先最大化召回(确保摘要提示词不漏项),再迭代提升精度(剔除冗余);过度激进的压缩会丢掉那些”当时看不重要、后来才显关键”的细节。
  • 常驻 vs 可重取:把日志全文常驻保证速度,改成存引用按需拉取则省 token 但增加延迟与工具失败风险;折中是”近期常驻 + 历史存引用”。
  • 单 Agent 长会话 vs 多 Agent 分工:多 Agent 能隔离上下文污染,但引入结果回传契约、上下文重复与冲突协调成本;运维工单的写操作需要单一所有权,宜保持单写者 + 子 Agent 只读深挖。

失败路径、边界条件、超时、重试、幂等与恢复:

  • 压缩调用失败或超时:压缩本身是一次模型调用,必须设超时与重试预算;失败时降级为纯工具结果清理(不需要模型,纯确定性裁剪),保证会话能继续,而不是卡死。
  • 压缩产物损坏/缺字段:对结构化状态文件做 schema 校验,校验失败则回退到上一版快照;状态文件本身版本化并保留历史版本。
  • 重复执行:所有写操作必须带幂等键(工单号 + 操作类型 + 目标资源 + 意图指纹),服务端按幂等键去重;同时引入写闸门——pinned 层的”已完成操作”记录决定是否放行,而不是问模型”我是不是已经做过了”。
  • 越权执行:权限与变更窗口判定放在模型之外的策略执行点;工具入参经校验后才允许调用;默认 FAIL-CLOSED(无法判定时拒绝)。
  • 恢复:会话级别持久化 checkpoint(消息指针 + 状态文件版本 + 工具调用记录),支持从任意 checkpoint 恢复;写操作中途失败不回滚业务,只记录并转为人工接管。
  • 边界条件:极长单条工具返回(超过剩余预算)要在写入前裁剪或转为文件引用;压缩后仍然存在超预算会话要有终止与人工接管通道。

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

  • 安全:约束与权限属于确定性层,模型输出不得作为授权依据;高风险操作保留人工确认;所有写操作留审计记录(谁、什么时间、依据哪个工单、幂等键)。
  • 成本:分级清理把”高价值 token”留在窗口内、把”低价值 token”挪到可重取层,比无脑扩窗口更省;压缩调用本身也有成本,要按会话频率设预算。
  • 延迟:压缩是阻塞用户的额外一次模型调用,应在后台预做(接近阈值时提前异步压缩),并避免在主链路上串行等待。
  • 可靠性:压缩是概率性组件,其失败必须可降级;系统的正确性不能依赖压缩一定成功。

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

  • 核心指标:压缩后任务完成率(对比压缩前基线)、越权操作率(应为 0,按工具 × 权限维度断言)、重复写操作率、错误引用率、压缩后首轮响应延迟。
  • 离线:构造”长会话回归集”(不同工具返回规模、不同写操作序列),对压缩策略做 A/B;对 pinned 层做注入-抽取测试:压缩后断言关键约束、已完成操作与标识符 100% 仍在上下文中。
  • 线上:按会话记录压缩次数、每次压缩的输入输出 token、压缩前后累计 token;对”压缩后 N 轮内再次触发同类写操作”打点告警;对越权与重复写设零容忍告警。

三层能力归属:

  • 模型本身的概率性能力:生成摘要、判断哪些细节重要、在压缩后继续推理——可错、不稳定、不可作为安全依据。
  • Agent 编排 / 状态 / 工作流能力:决定何时压缩、压缩哪一层、如何重新组装上下文、读写结构化状态文件、触发人工接管。
  • 传统软件系统的确定性能力:权限与变更窗口判定、幂等键去重、写闸门、状态文件 schema 校验与版本化、审计日志、超时与重试预算、告警。

面试官追问 1:“压缩后模型仍然可能’忘记’约束,你怎么验证它一定不会?“
回答要点:不验证模型,改为消除对模型的依赖。把约束放进 pinned 层由服务端每次确定性注入,并在工具网关侧做策略校验(不满足变更窗口或命中禁止清单直接拒绝,FAIL-CLOSED)。验证方式是断言式的:对回归集每一条,压缩后检查请求体里是否存在该约束、网关在越权样本上是否 100% 拒绝。这类指标必须是 100%,不能是”99.3% 的情况下记得”。

面试官追问 2:“能不能干脆不压缩,改用更长的上下文窗口?“
回答要点:窗口是容量上界不是可用预算;即使容量够,也存在”上下文腐烂”——随着上下文 token 数增加,模型从中准确回忆信息的能力下降,且这种退化是性能梯度而非硬悬崖,静默且难归因。另外成本与 prefill 延迟随长度上升,长上下文还会放大位置偏差(中部信息被忽略)。所以正确顺序是:先做上下文工程把 token 用对,再谈要不要扩窗口;即便扩窗口,工具结果清理与结构化状态依然是必要的。

关键点#

  • 压缩的核心决策是保留集的归属:约束 → 确定性策略层;已完成副作用 → 持久化状态 + 幂等键 + 写闸门;标识符 → 结构化状态文件。三者都不应由摘要决定。
  • 最安全最轻量的压缩是清理历史工具结果;压缩应分级、提前、可降级,而不是在 95% 处一次性抢救。
  • 上下文是有限且边际收益递减的资源;退化表现为渐变而非断崖,必须靠指标而非直觉发现。
  • 重复执行的防线是幂等键 + 写闸门,越权的防线是模型之外的策略执行点且默认 FAIL-CLOSED。
  • 评测主指标是压缩后的任务成功率 / 越权率 / 重复写率,不是”摘要写得好不好”。

常见误区#

  1. 把优化压缩提示词当成解法:提示词只能提高召回概率,不能提供保证;只要”关键约束是否存在”取决于一次模型调用的结果,就永远存在非零的丢失概率,而越权是不可接受后果。
  2. 认为压缩后重新注入系统提示就够了:如果约束最初是用户或上级 Agent 在对话中提出的(例如”这个工单别碰生产库”),它根本不在系统提示里,重新注入也救不回来。正确做法是把这类动态约束在进入对话时就结构化落盘并提升到 pinned 层。
  3. 用摘要质量评分作为验收标准:摘要流畅度与任务正确性弱相关;真正会咬人的是”漏掉一条已完成的写操作”和”引用错单号”,这些必须用断言式指标覆盖。
  4. 把压缩放在主链路同步阻塞:压缩失败或超时会让会话卡死;应当预做、异步、可降级为纯裁剪。

30 到 60 分钟的小型实践:用任意 LLM API 搭一个最小循环:准备一份 5 轮的工具调用日志(含一条”禁止操作生产库”的约束、一次”重启已执行”的写操作、一个工单号),构造三种上下文组装策略——(a) 只做一次性摘要,(b) 摘要 + 结构化状态文件,(c) 摘要 + pinned 约束 + 工具结果清理;让模型在”压缩后”回答三个问题(是否允许操作生产库、是否已重启过、工单号是多少),统计各策略 10 次运行的正确率。再把写操作换成带幂等键的本地函数,验证重复触发只生效一次。

延伸学习#

  • Anthropic 工程博客《Effective context engineering for AI agents》(2025-09-29):https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents ——已核验,本文关于 compaction、tool result clearing、结构化笔记与子 Agent 的引述与原文一致。
  • 上下文腐烂与长上下文位置偏差的原始论文:Nelson F. Liu 等,arXiv:2307.03172(Lost in the Middle),2023-07-06 提交,v3 为 2023-11-20——沿用 2026-10-19 的核验结论。

S02 · 非 Agent 场景 · 分布式系统#

题目 ID:Q-20261021-S02
一级类型:分布式系统(同时触及数据库与存储)
二级主题:分布式锁与租约、fencing token、幂等与对账修复
难度:场景题(高)

题目#

业务背景:每天 02:00 跑一次的资金对账任务:扫描交易流水,与渠道方账单比对,产出差异单并推送给下游财务系统。

系统规模:单次执行 8 到 25 分钟;扫描约 400 万条记录;写入 3 张结果表;下游每天只应收到一份对账结果。

当前架构:部署在 Kubernetes 上的服务有 3 个副本;外部调度器在 02:00 向服务发起一次 HTTP 触发(打到任一副本);被触发的副本执行 SET lock:daily_reconcile <random> NX PX 600000 抢锁,拿到锁才执行,执行完 DEL 释放。

目标:同一业务日只产出一份权威对账结果;且不漏跑。

限制条件:可以使用已有的 PostgreSQL(不允许引入新的重型协调组件作为唯一依赖);调度器本身可能重复触发(at-least-once);副本随时可能被驱逐或重启。

故障现象:

  1. 某天对账跑了两次,结果表出现两条记录,下游收到重复差异单;
  2. 另一天完全没跑,直到第二天人工核对时才发现。

需要取舍的问题:为了消除重复执行,要不要把锁的 TTL 调到远大于最长执行时间?“互斥”应该由锁来保证,还是由被写入的数据来保证?漏执行能不能靠同一套机制解决?

先思考#

  • 先问一句:你用这把锁是为了效率(避免重复劳动)还是为了正确性(不能重复写)?这两种目的的可靠实现代价完全不同。
  • 如果拿锁的进程在持锁期间被暂停(GC、页缺失、CPU 争抢、网络延迟),暂停时长超过 TTL,会发生什么?“在写之前再检查一次锁是否还在”能解决吗?
  • 重复执行和漏执行,是同一个问题的两面,还是两个独立问题?

参考答案#

结论:把互斥寄托在租约上,等于假设”客户端在租约期内一定能完成、且一定能感知租约过期”——这个假设在异步系统里不成立。正确方案是三层:租约只做效率优化(减少重复劳动),由被保护资源侧校验的 fencing token 保证正确性,业务日唯一约束 + 幂等保证只产出一份权威结果,独立的对账扫描兜住漏执行。把 TTL 调到远大于执行时间只是把概率降低,没有消除问题,还会拖长故障恢复时间。

问题拆解:两类故障是两个独立失效模式,不能靠同一机制解决。

  • 重复执行:并发安全 / 互斥问题 → 需要 fencing + 幂等。
  • 漏执行:活性问题 → 需要一个”应该跑但没跑”的外部观测者,锁机制天然检测不到”根本没人来抢锁”。

核心方案:

  1. 以业务日为单位的执行记录表(reconcile_run,唯一键 business_date),用一次数据库事务抢占:INSERT ... ON CONFLICT (business_date) DO NOTHING RETURNING id。抢到的那一行自带单调递增的自增 ID(或显式版本号),它天然就是 fencing token——由数据库这个”被保护资源所在的一致存储”生成,不需要额外的发号器。
  2. 写结果时携带 fencing token:所有结果写入与下游推送都以 run_id 为外键/幂等键;存储侧拒绝 token 回退的写入(run_id < 已接受的最大 run_id 则拒绝)。
  3. 保留 Redis 租约,但仅用于减少无谓的重复启动(效率),并配合续约心跳;它不再承担正确性。
  4. 独立的对账扫描器(reconciliation loop):每 15 分钟检查”是否存在应该已执行但未执行/未完成的业务日”,缺失则补触发并告警。
  5. 执行状态机:pending → running → succeeded / failed,带 started_at、heartbeat_at、finished_at;超时未心跳的运行被标记为 stale,由扫描器决定是否接管——接管仍需重新抢占同一业务日的行,走同一套 fencing 逻辑。

关键执行流程:

  1. 调度器触发(可能重复)→ 任一副本收到请求。
  2. 先抢 Redis 租约(短 TTL,携带随机值);抢不到直接返回(效率层,避免惊群)。
  3. 抢到后尝试插入 reconcile_run(business_date);DO NOTHING 无返回行 → 说明本业务日已被抢占,直接退出。
  4. 抢占成功后后台续约 Redis 租约,并周期性更新 heartbeat_at。
  5. 分批扫描与写入:每批以 (business_date, batch_no) 幂等 upsert,结果行带 run_id。
  6. 完成后标记 succeeded,写入结果摘要与行数校验值。
  7. 扫描器独立核查:业务日是否存在 succeeded 记录、结果行数是否在预期区间、下游是否已收到一次推送。

技术选型与依据:

  • 为什么把协调放到数据库:本任务每天一次、争用极低,已有 PostgreSQL 提供事务与唯一约束,用 INSERT ... ON CONFLICT DO NOTHING 的原子性换取正确性,比新增 ZooKeeper/etcd 更省运维,且 fencing token 与被保护数据在同一个一致域内生成与校验——这正是 fencing 的关键要求。
  • 为什么不用 Redlock 那类算法:Martin Kleppmann 的核心批评是它有”大量关于时序的假设”并且缺乏生成 fencing token 的能力(原文:“it does not have any facility for generating fencing tokens”);其结论是它”neither fish nor fowl”——做效率锁太重,做正确性锁不够安全。本场景的正确性必须由资源侧保证。
  • 为什么保留 Redis 单实例锁:Kleppmann 的建议是,如果锁只用于 best-effort 的效率优化,用单实例 Redis 的条件设置与按值删除即可,并在代码里明确写清这些锁只是近似的。这与本方案的分工一致。
  • 备选方案:ZooKeeper / etcd(可用 zxid、revision 或有序节点作为 fencing token);直接用云厂商的托管调度 + 任务去重;或把任务改为事件驱动 + 流处理。取舍在于运维成本与一致性域是否统一。

方案取舍:

  • 正确性锁 vs 效率锁:正确性必须由被保护资源侧(DB 约束 + fencing)保证;效率锁可以弱。混为一谈是这类事故的根源。
  • TTL 长 vs 短:TTL 长降低过期概率但延长故障接管时间;TTL 短接管快但更容易出现”两个工作者重叠”。正确做法不是调 TTL,而是让重叠变安全(fencing)。
  • 抢占式接管 vs 等待原工作者:长任务应支持续约心跳与 checkpoint 续跑;接管前必须确认原工作者已停,或依赖 fencing 让旧写自动失效。
  • DB 协调 vs 外部协调服务:DB 方案省组件但增加 DB 压力与耦合;对于每天一次的任务,这个代价可以忽略。

失败路径、边界条件、超时、重试、幂等与恢复:

  • 租约过期但旧进程仍在写:fencing token 让资源侧拒绝旧 token 的写入;旧进程收到拒绝后必须主动中止并转人工,不能重试覆盖。
  • 进程被 SIGKILL / Pod 被驱逐:heartbeat_at 停止更新,扫描器标记为 stale;新工作者按业务日抢占(已被占则退出),或补触发。
  • DB 主从切换 / 事务超时:抢占语句必须设 statement_timeout 与 lock_timeout;超时视为”未抢占”并重试,重试靠唯一约束保证幂等——重试是安全的,因为抢占是幂等的。
  • 时钟跳变:不依赖墙上时钟判断过期,改用数据库侧的 now() 与心跳时间戳,避免各节点时钟不一致导致误判。
  • 下游重复推送:下游调用带 Idempotency-Key: business_date + run_id,下游侧按幂等键去重;推送失败按指数退避重试并设重试预算。
  • 结果不完整:完成前写入行数校验值,扫描器核对行数区间;不完整的成功记录要能识别为可疑并告警。

性能、可用性与验证指标:

  • 性能:单实例 8 到 25 分钟的任务对 DB 的额外压力只有几次单行事务,可忽略;分批 upsert 控制单事务规模,避免长事务与复制延迟放大。
  • 可用性:调度器、抢锁、抢占、扫描器四者相互独立,任一失效不导致”永久不跑”;扫描器是活性的最后防线。
  • 验证指标:每日结果记录数恒为 1(唯一约束 + 监控断言)、漏跑检测时长(从应跑到告警 ≤ 15 分钟)、下游重复推送次数恒为 0、重复执行但被 fencing 拒绝的次数(应可见且趋近 0)、任务时长分布与超时率。
  • 测试方法:故障注入——在任务执行中 kill -STOP 进程超过 TTL、强制驱逐 Pod、让 DB 事务超时、让调度器重复触发三次;断言结果表始终一行且下游只收到一次。

面试官追问 1:“执行时间从 25 分钟涨到 3 小时,方案要改什么?“
回答要点:TTL 不能简单拉长。改为:① 租约续约(心跳线程独立,间隔远小于 TTL);② 长任务拆分为可续跑批次,写 checkpoint(已完成批次号),重启从 checkpoint 继续;③ fencing token 仍然必须,且因为时间窗变长,重叠概率反而上升;④ stale 判定基于心跳超时而非预估时长;⑤ 考虑是否需要把扫描窗口也从”每天一次”改为”每小时检查业务日状态”。

面试官追问 2:“为什么不在 Kubernetes 层面用单副本 CronJob 解决?“
回答要点:可以减少重复,但不能消除——Pod 被驱逐、节点故障、Job 重试策略都可能产生第二次执行;而且单点故障会造成漏执行,与本题第二类故障同源。CronJob 是有用的效率层,但正确性仍要由”业务日唯一约束 + 幂等键”保证,活性仍要由”扫描器 + 告警”保证。换言之,把执行收敛到一个副本只是把概率降低,不是把问题消除。

关键点#

  • 锁的两种目的必须分开:效率(省一次重复劳动,失败代价低)与正确性(并发写会破坏状态,失败代价高)。混用会导致”用了很重的锁却仍然不安全”。
  • 持有租约 ≠ 拥有执行权。客户端可能因 GC、页缺失、CPU 争抢、网络延迟被暂停任意时长;“写前再检查一次锁”无效,因为暂停可能就发生在检查与写之间。
  • Fencing token 必须由被保护资源侧校验,且由锁服务单调递增地发放;随机值只解决”误删别人的锁”,不解决”自己过期后继续写”。
  • 漏执行不能靠锁检测——需要独立的对账扫描与告警,这是活性问题的唯一可靠观测点。
  • 幂等是最后一道防线:业务日唯一约束 + 幂等键使重复触发在语义上退化为一次。

常见误区#

  1. 认为拿到锁就独占:锁过期后原持有者仍会继续写,两个工作者会同时认为自己持有锁。这是把互斥寄托在时序假设上的典型错误。
  2. 用”把 TTL 调到远大于执行时间”解决:只是降低概率且未消除;一旦执行时间意外超过 TTL 就会失效,同时 TTL 过长会让故障接管窗口被拖长,活性变差。
  3. 用随机值做锁释放判定就认为安全:随机值(compare-and-delete)只防止误删他人持有的锁,对”自己已过期仍在写”这一核心失效模式毫无作用。

延伸学习#

  • Martin Kleppmann:《How to do distributed locking》(2016-02-08):https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html ——已核验,本文关于 fencing token、客户端暂停、Redis 使用 gettimeofday 与 “neither fish nor fowl” 的引述与原文一致。
  • Mike Burrows:《The Chubby lock service for loosely-coupled distributed systems》(OSDI 2006)——锁的效率与正确性两种用途的出处,沿上文引用,本轮未单独核验原文。

L01 · LeetCode · 并查集#

题目 ID:Q-20261021-L01
LeetCode 题号:547
中文名:省份数量
英文名:Number of Provinces
难度:medium
官方链接:https://leetcode.com/problems/number-of-provinces/ | https://leetcode.cn/problems/number-of-provinces/ (两页题目、示例与约束一致,已核验;英文页显示通过率约 71.2%,该数字会随时间变化)

题目#

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。省份是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给定一个 n x n 的矩阵 isConnected,isConnected[i][j] = 1 表示第 i 个城市与第 j 个城市直接相连,为 0 表示不直接相连。返回矩阵中省份的数量。

输入:n x n 的 0/1 邻接矩阵 isConnected。
输出:整数,省份数量。
约束:

  • 1 <= n <= 200
  • n == isConnected.length,n == isConnected[i].length
  • isConnected[i][j] 为 0 或 1
  • isConnected[i][i] == 1
  • isConnected[i][j] == isConnected[j][i](矩阵对称)

示例:

  • 示例 1:输入 isConnected = [[1,1,0],[1,1,0],[0,0,1]],输出 2。
  • 示例 2:输入 isConnected = [[1,0,0],[0,1,0],[0,0,1]],输出 3。

先思考#

  • “属于同一个省份”是一种什么性质的关系?自反、对称、传递——能不能用一种数据结构把”同组”这件事直接维护起来,而不是每次从头遍历?
  • 矩阵是对称的、对角线恒为 1。你是否需要对每个元素都做一次合并?哪些边是冗余的?
  • 如果每次合并都把一棵大树挂到小树下,查找会退化成什么样?有没有两种几乎零成本的优化能把代价压到接近常数?

参考答案#

结论:这是一道标准的**并查集(Union-Find)**题。初始化 n 个单元素集合,把所有 isConnected[i][j] == 1 的边(i < j)对应的集合合并,最后统计”根等于自身”的节点数量,即为连通分量数,也就是省份数。

题目描述与输入输出:见上文。

解题思路:

  1. 直观方案:DFS / BFS 标记。从未访问的城市出发做一次遍历,标记可达的全部城市,每启动一次遍历省份数加一。时间 O(n²)(需要检查矩阵每个元素),空间 O(n)。
  2. 直观方案的局限:它必须显式遍历整个矩阵才能判断连通;当边是逐步到来、或需要频繁回答”这两个点是否同组”时,每次都要重新搜索,无法增量维护。
  3. 推荐方案的推导:省份关系是一个等价关系(自反、对称、传递),而并查集正是为维护等价类而设计的结构——它只回答两件事:find(x) 我属于哪一类,union(a, b) 把两类合成一类。于是:
    • 初始化:parent[i] = i,共 n 个集合。
    • 合并:遍历上三角(j 从 i+1 开始),遇到 isConnected[i][j] == 1 就 union(i, j)。只扫上三角的依据是矩阵对称([i][j] 与 [j][i] 是同一条边),对角线恒为 1 属于自环无需处理。
    • 统计:find(i) == i 的节点是各集合的代表元,其数量即集合数。
  4. 两个优化:
    • 路径压缩:find 时把访问路径上的节点直接挂到根下,用迭代写法时可以做”折半压缩”(parent[x] = parent[parent[x]]),避免递归深度问题。
    • 按秩合并:union 时把秩小的根挂到秩大的根下,只有两者秩相等时才把新根的秩加一。
      二者结合后,单次操作的摊还代价是反 Ackermann 级别的 α(n),在工程上视为常数。

正确性说明:初始时每个城市自成一个集合,集合数等于 n。算法遍历上三角中每一条”直接相连”的边并执行合并;由于矩阵对称,上三角已覆盖全部无向边,不遗漏也不重复处理实质信息。合并操作保持”两个集合的元素恰在存在路径相连时同属一个集合”这一不变量:若 i 与 j 直接相连则合并,其传递闭包由后续合并逐步完成。算法结束时,每个集合恰好是矩阵对应的一个连通分量(省份),每个连通分量有且仅有一个根(find(i) == i),因此根的数量等于省份数量。对角线 isConnected[i][i] == 1 只产生自环,不影响结果。

时间复杂度:矩阵上三角遍历 O(n²) 次,每次 union 最多两次 find,单次摊还 O(α(n)),总时间 O(n² · α(n))。
空间复杂度:parent 与 rank 数组各 O(n),总计 O(n)(不把输入矩阵计入)。

可运行的 Python 代码:

from typing import List
class Solution:
def findCircleNum(self, isConnected: List[List[int]]) -> int:
n = len(isConnected)
parent = list(range(n))
rank = [0] * n
def find(x: int) -> int:
# 迭代式路径压缩(折半),避免长链退化与递归深度问题
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
def union(a: int, b: int) -> None:
ra, rb = find(a), find(b)
if ra == rb:
return
if rank[ra] < rank[rb]:
ra, rb = rb, ra
parent[rb] = ra
if rank[ra] == rank[rb]:
rank[ra] += 1
for i in range(n):
for j in range(i + 1, n):
if isConnected[i][j]:
union(i, j)
return sum(1 for i in range(n) if find(i) == i)

边界情况:

  • n == 1:矩阵为 [[1]],答案为 1(约束保证对角线为 1)。
  • 全不连通(单位矩阵):答案为 n。
  • 全连通(全 1 矩阵):答案为 1。
  • 链状连通([0]-[1]-[2]):合并顺序不影响结果,答案为 1。
  • 已经是根的节点再次 find:直接返回自身,无副作用。
  • 只扫上三角:避免把每条边处理两次,也跳过自环。

测试用例(均在本机执行通过):

输入期望输出说明
[[1,1,0],[1,1,0],[0,0,1]]2官方示例 1
[[1,0,0],[0,1,0],[0,0,1]]3官方示例 2,全不连通
[[1]]1最小规模
[[1,1,1],[1,1,1],[1,1,1]]1全连通
[[1,1,0],[1,1,1],[0,1,1]]1链状连通,需经传递闭包
[[1,0,1],[0,1,0],[1,0,1]]2非连续下标同组
[[1,0,0,1],[0,1,1,0],[0,1,1,1],[1,0,1,1]]1跨块间接相连

关键点#

  • 省份关系是等价关系,并查集是它的原生数据结构;判据是”根的数量”而非”是否遍历完”。
  • 矩阵对称 → 只扫上三角;对角线恒为 1 → 自环无需处理。
  • 路径压缩 + 按秩合并缺一不可,二者共同把摊还代价压到 O(α(n))。
  • find 用迭代写法更安全:n 虽小,但这种写法可直接迁移到大规模场景。
  • 面试官可能的变体:如果输入不是邻接矩阵而是稀疏边表(城市数量巨大、边很少),并查集仍是 O(E · α(V)),与 BFS 的 O(V + E) 同阶;并查集的真正优势在于增量场景——边陆续到来时无需重跑,且能随时 O(α(n)) 回答两点是否同组。另一个常见追问是”如何输出每个省份的城市列表”:按 find(i) 分组即可。

常见误区#

  1. 只扫全部元素但漏掉对称性处理:不会算错答案,但会做一倍冗余合并;更常见的是反过来——只扫下三角却写错循环边界,导致漏边。
  2. 把 find(i) == i 当成”统计未访问节点”:在路径压缩未完全执行时,某些节点的父指针可能仍指向中间节点,直接数 parent[i] == i 会多算——必须调用 find 完成压缩后再判断。
  3. 认为并查集一定优于 DFS:在”一次性、静态、邻接矩阵”的输入下,两者同阶,DFS 代码更短。并查集的价值在于增量与动态查询,选型的依据应是输入形态与查询模式,而不是”并查集更高级”。

延伸学习#

  • LeetCode 547 官方题页(中英文)——已核验,链接见本题开头。
  • 可迁移的解题能力:把隐式关系显式化为可增量维护的数据结构。这条能力在 Agent 工程里直接对应”实体归一与多源身份合并”——同一工单在不同系统里的别名、同一用户在 CRM 与工单系统的不同 ID、检索结果里指向同一文档的多条记录,都可以用”逐步 union + 按需 find 代表元”的方式收敛,而不是每次全量比对。

延伸资料#

主题资料类型状态
语义化版本Semantic Versioning 2.0.0(semver.org/lang/zh-CN/)标准规范已核验
子词分词 / BPESennrich, Haddow, Birch, arXiv:1508.07909(ACL 2016)原始论文已核验
Token 计数OpenAI《Understanding and counting tokens》帮助中心官方文档已核验
上下文工程与压缩Anthropic《Effective context engineering for AI agents》(2025-09-29)官方工程博客已核验
长上下文位置偏差Liu 等,Lost in the Middle,arXiv:2307.03172原始论文沿用 2026-10-19 核验结论
分布式锁与 fencingKleppmann《How to do distributed locking》(2016-02-08)可信技术文章 / 作者为《DDIA》作者已核验
锁的两种用途Burrows, Chubby lock service(OSDI 2006)原始论文未核验原文,仅作引用出处
可复现构建reproducible-builds.org可信技术组织资料未核验

当日重点#

  1. 版本与依赖:^ 是范围不是保证;锁文件固定”本次实际解”,契约测试与自动化兼容性判定把意图变成保证;0.x 的次版本变更要当作主版本对待。
  2. 单位错配:token 是分词器输出,不是字符;embedding 相似是几何邻近,不是语义等价。凡影响成本、正确性、权限的判断都要有确定性兜底。
  3. 压缩的保留集归属:约束进确定性策略层,已完成副作用进持久化状态 + 幂等键 + 写闸门,标识符进结构化状态文件;压缩只应作用于可回收的工具原始输出,且必须可降级。
  4. 租约不是独占权:效率锁与正确性锁分开;fencing token 由被保护资源侧校验;漏执行靠对账扫描而不是靠锁。
  5. 等价关系显式化:并查集是”隐式连通性”的显式数据结构,路径压缩 + 按秩合并是摊还优化的通用手法。

建议复习日:B01 2026-10-28(角度:从”依赖声明”转为”依赖漂移的线上归因”);B02 2026-10-28(角度:从”单位定义”转为”成本与检索的联合调优”);S01 2026-10-30(角度:从”压缩策略”转为”压缩后的越权与重复写审计”)。

本次运行信息#

  • 配置读取状态:成功。题库配置.md 版本 6(last_maintained: 2026-10-19),本次未修改该文件。无暂停类型;「观察中的新技术」8 项均未标记「允许试学」,未从中出题。实际配置版本:6。
  • 历史读取:读取 题目索引.md(50 条记录,indexed_runs: 8)与 精选/README.md(4 条精选);未逐字读取更早的训练文件正文。
  • 去重窗口:30 天,覆盖 2026-09-21 至 2026-10-21。窗口内已有 8 次训练记录。
  • 去重结果:
    • B01 与既有题目无重复。窗口内「软件工程与平台工程」已有的二级主题为 API 与接口设计、零停机 schema 变更与灰度发布、测试策略与 flaky 治理;本次新增”依赖与版本管理”。
    • B02 与既有题目无重复。窗口内「AI 与大模型基础」已有微调/LoRA/量化/蒸馏、推理参数与可复现性、Attention 与 KV Cache;本次新增”Token、分词与 Embedding 表示”,避免了与上下文容量边界题的语义重叠。
    • S01 与既有题目无重复。窗口内场景题已用 17 类,本次取「上下文超限与信息压缩」,为首次以该场景类型出题;与 Q-20261010-B01(压缩/摘要/草稿本的定义边界)角度不同,本题为线上故障定位与方案设计。
    • S02 与既有题目无重复。窗口内非 Agent 场景已用缓存雪崩、零停机 schema、读写分离、限流熔断、消费组再平衡、滚动发布优雅终止、mTLS 证书轮换;本次新增”分布式锁与定时任务”,核心失效模式(租约失效后继续写)与既有题目不同。
    • L01 LeetCode 547(并查集)为窗口内首次出现。已用题号:146、3、215、739、875、124、207、142、547;已用算法模式:哈希表 + 双向链表、滑动窗口、堆与快速选择、单调栈、二分答案、树形 DP、图的拓扑排序、快慢指针、并查集。另按 30 天去重口径规避 322(曾生成后被删除未登记)。
  • LeetCode 代码验证:参考代码已在本机执行,7 个测试用例全部通过(含官方 2 例、最小规模、全连通、链状连通、非连续下标同组、跨块间接相连)。
  • 本次参考的历史题目或文件:题目索引.md、精选/README.md、题库配置.md(v6)、README.md。
  • 到期提示:精选题 Q-20261012-B02(建议复习 2026-10-19)与 Q-20261013-B03(建议复习 2026-10-20)已过建议复习日但尚未记录复习完成;Q-20261015-B03 建议复习日 2026-10-23,Q-20261015-S01 建议复习日 2026-10-24。本次未自动执行任何精选或复习动作。
  • 精选状态:本次未收到明确的精选意图,未执行精选。
  • 同步状态:仅本地写入成功,未对远端同步做独立验证。
2026-10-21 AI Agent 面试训练
https://vissssa.github.io/posts/ai-agent-interview-2026-10-21/
作者
Siegward
发布于
2026-10-21
许可协议
CC BY-NC-SA 4.0