← 工程跃迁 / 第 16 章
架构定位

生产工程

用评测、可观测性、部署与证据证明系统在真实约束下可靠。

如何证明系统可靠,并完成部署、作品集和面试表达?

概念校准

验证与评测

它是什么
用测试、Schema、引用、状态和验收规则检查单次结果,并用数据集衡量系统表现。
它不是什么
不等于让模型对自己的输出再给一次意见;验证必须落到外部可检查证据。

可观测性

它是什么
通过结构化日志、Trace、指标和版本信息解释系统在一次运行中发生了什么。
它不是什么
不只是保存终端输出;没有关联 ID、状态转换和敏感信息边界的日志难以诊断。

Harness

它是什么
承载模型、工具、状态、权限、日志、预算、人工确认和恢复的运行控制环境。
它不是什么
它能限制风险并保留证据,但不能直接阻止模型产生幻觉。

评测、可观测性、部署与可靠性

构建评测集、Tracing、质量成本指标、部署、回滚和事故演练。

本章只做一件事形成可部署、可监控、可回滚的 Forge 候选版本。
开始前,Forge 已经具备已完成 Module 15 的“多平台适配与迁移”,其通过验收的 solution 是本章起点。
完成后,Forge 将能够形成可观察的线上版本,覆盖质量、延迟、成本、失败和回滚。
卡住时的最小恢复点只运行本地评测和结构化追踪,先证明失败可定位。
下载本章实验仓库Python 3.12 · pytest · Pydantic · SQLite · deterministic Mock
阶段能力链
01定义带稳定 ID、输入和验收条件的评测案例,并阻止重复案例02从逐案例结果计算准确率、引用覆盖率和工具成功率,并保留分母03记录带父子关系的 Span,并确保一个运行中的追踪 ID 保持一致04按运行聚合成本和延迟,并对超预算结果给出可发布的门禁结论05将评测、迁移和安全检查绑定到不可变发布清单,缺失门禁不得晋级06比较基线与金丝雀错误率和延迟,在明确阈值下自动建议回滚07记录检测、缓解、恢复和复盘时间点,并验证恢复目标与证据完整性
每一步只增加一种可验证能力;后一步建立在前一步已经通过的代码和测试上。

概念校准\n\n在开始操作代码前,先把本章涉及的概念放回正确的工程边界。\n\n
\n\n### 验证与评测 evaluation\n\n- 它是什么: Verification 用测试、Schema、引用、状态或验收规则检查一次结果;Evaluation 用数据集和指标衡量多次运行表现。\n- 它不是什么: 它不是让模型对自己的回答再给一次意见;通过与否必须落到外部可检查证据。\n- 与相邻概念的关系: Agent Loop 依赖 Verification 决定停止,RAG 需要分层评测,Planning 需要节点验收,Observability 提供运行记录。\n- 在 HeatStack Forge 中的位置: Forge 为单次运行保存合同测试与 evidence,再用固定场景集比较质量、延迟、成本、权限和恢复能力。\n- 典型误用与修正: 误用是把一个综合分数当成全部结论。修正方法是定义分层指标、失败样本和版本基线,并结合产品风险评审。\n\n### 可观测性 observability\n\n- 它是什么: 通过结构化日志、Trace、指标、状态转换和版本信息解释一次运行发生了什么,并支持定位失败与比较版本。\n- 它不是什么: 它不只是终端输出,也不是无限记录所有输入;缺少关联 ID、状态转换或敏感信息边界的日志既难排查又可能泄露数据。\n- 与相邻概念的关系: Harness 产生运行事件,Agent Loop 与工具调用形成 Trace,Evaluation 聚合指标,Memory 与恢复依赖版本记录。\n- 在 HeatStack Forge 中的位置: Forge 为每次任务生成 operation_id,记录阶段、工具、耗时、成本、错误类别、状态变化和证据路径,同时过滤敏感信息。\n- 典型误用与修正: 误用是等失败后再临时加日志。修正方法是在合同设计时定义事件与关联字段,并用故障注入确认能重建时间线。\n\n### Harness harness\n\n- 它是什么: 承载模型、工具、状态、权限、日志、预算、人工确认、隔离和恢复的运行控制环境。\n- 它不是什么: 它可以缩小风险、拒绝动作并保留证据,但不能保证模型输出永远正确,也不能代替领域验收。\n- 与相邻概念的关系: Agent Loop 在 Harness 中运行;Tool、Memory 和 MCP 连接受其策略约束;Observability 记录过程,Evaluation 判断行为。\n- 在 HeatStack Forge 中的位置: Forge Harness 管理工作目录、工具、确认队列、预算、Checkpoint、审计日志和回滚,把模型建议与真实副作用分开。\n- 典型误用与修正: 误用是把系统提示词当成安全边界。修正方法是把权限、路径、网络、密钥、预算和回滚落实在模型之外的控制中。\n\n

STAGE 01

定义带稳定 ID、输入和验收条件的评测案例,并阻止重复案例

先看一个具体问题

评测案例需要稳定标识,否则重复案例会破坏追踪

你正在构建一个评测数据集,用来保存多个评测案例。每个案例包含一个输入和一个期望输出,用于验证系统行为。

如果两个案例使用相同的 case_id,后续统计和追踪就会混淆:你无法区分哪个输入对应哪个结果,甚至可能意外覆盖已有案例。

当前代码中 EvalDataset 的 validate() 只检查了版本和案例列表是否为空,没有检查 case_id 是否唯一。

因此,当测试 test_duplicate_case_ids_are_rejected 构造两个 case_id 都为 “same” 的案例时,validate() 不会抛出异常,导致测试失败。

先做判断

在 EvalDataset.validate() 中,除了检查版本和案例列表非空,还需要增加什么检查才能让重复案例 ID 被拒绝?

  • 检查所有案例的 expected 字段是否非空
  • 检查所有 case_id 是否唯一
  • 检查 version 是否以 ‘v’ 开头
  • 检查 input 字段是否包含特定键

判断依据: 正确答案是检查所有 case_id 是否唯一。因为测试 test_duplicate_case_ids_are_rejected 期望当两个案例具有相同 ID 时抛出包含 “unique” 的 ValueError。其他检查虽然可能有用,但不是本测试要求的。

它是什么

评测数据集是一个带版本、由唯一案例组成的集合

EvalDataset 包含一个版本字符串和一个案例元组,每个案例有唯一的 case_id、输入和期望输出。

validate() 方法负责在数据集被使用前检查其完整性:版本和案例非空、案例 ID 唯一、每个案例的 expected 非空。

唯一 ID 是评测案例的稳定标识,确保每个案例可以被独立追踪和引用。

它不是什么

评测数据集不执行评测,也不计算指标

它只负责存储和验证案例定义,不运行模型或比较输出。

它不保证案例内容在业务上正确,只保证结构上满足约束。

它不是一个数据库,不会自动持久化或提供查询接口。

它与相邻概念的关系

EvalCase 与 EvalDataset 的关系

EvalCase 是单个评测案例,包含 case_id、input 和 expected。

EvalDataset 聚合多个 EvalCase,并添加版本信息。

validate() 方法遍历所有案例,检查 ID 唯一性和 expected 非空,确保数据集可用。

本阶段的边界决定: 当数据集包含重复 case_id 时,validate() 必须抛出 ValueError,因为重复 ID 会导致评测结果无法追踪。

STAGE 02

从逐案例结果计算准确率、引用覆盖率和工具成功率,并保留分母

先看一个具体问题

如何从多个案例结果中汇总出有意义的整体质量指标?

你已经有了 CaseResult 数据类,它记录单个案例的通过状态、引用要求和工具调用情况,但还没有一个函数能把这些案例结果汇总成整体质量指标。

如果直接手工计算准确率和引用覆盖率,不仅容易出错,而且当某个指标没有适用案例时(例如没有引用要求),分母为零会导致除零错误或错误的零分。

本阶段需要实现 summarize() 函数,它接收一个 CaseResult 元组,返回包含案例数、准确率、引用覆盖率和工具成功率的字典,并且正确处理分母为零的情况。

先做判断

在实现 summarize() 之前,请预测:当某个指标的分母为零(例如没有任何引用要求)时,该指标应该返回什么值?

  • 返回 0.0,因为没有成功案例
  • 返回 1.0,表示该指标不适用,视为成功
  • 抛出异常,因为无法计算
  • 返回 None,表示缺失

判断依据: 正确答案是返回 1.0。分母为零意味着该指标没有适用的案例,因此不应该惩罚系统;这种“空真”语义在质量指标中很常见,例如没有引用要求时引用覆盖率应视为完全满足。

它是什么

质量指标汇总是什么

质量指标汇总是把多个案例的离散结果(通过/失败、引用有效数、工具成功数)聚合成整体比率的过程,它保留了每个指标的分母(适用案例总数),从而反映系统在相关场景下的表现。

summarize() 函数接收一个 CaseResult 元组,返回一个字典,其中 accuracy 是正确案例的比例,citation_coverage 是有效引用占所需引用的比例,tool_success 是成功工具调用占尝试调用的比例。

它不是什么

质量指标汇总不是什么

它不是对单个案例的详细审查,也不是对模型输出的主观评价;它只基于 CaseResult 中已经记录的结构化字段进行算术聚合。

它不涉及追踪、预算或任何运行时上下文,也不负责判断案例是否应该被包含;这些决策由上游的评测流程完成。

它与相邻概念的关系

与其他概念的关系

CaseResult 是输入数据类,summarize() 消费它并产生指标字典;ratio() 是辅助函数,用于安全地计算比率,处理分母为零的情况。

这些指标为后续的可观测性面板和评测报告提供数据,但它们本身不产生日志或追踪信息。

本阶段的边界决定: 当分母为零时,指标应返回 1.0(空真),而不是 0.0 或抛出异常,因为零适用案例意味着没有失败,不应惩罚系统。

STAGE 03

记录带父子关系的 Span,并确保一个运行中的追踪 ID 保持一致

先看一个具体问题

一次运行中的 Span 必须共享同一个 Trace ID

当前 forge/reliability/ 目录中只有 metrics.py,还没有 tracing.py,因此测试文件 tests/test_stage.py 在导入 Spanvalidate_trace 时会直接失败。

你需要从零创建 forge/reliability/tracing.py,定义 Span 数据类和 validate_trace 函数,使两个测试通过。

test_valid_parent_child_trace 要求接受两个共享 trace_id="t1" 且父子关系正确的 Span;test_cross_trace_child_is_rejected 要求拒绝一个来自不同 trace 的子 Span。

如果只检查 Span ID 唯一性而忽略 trace ID 一致性,跨 trace 的子 Span 会被错误接受,导致追踪数据被污染。

先做判断

在实现 validate_trace 时,除了检查 Span ID 唯一性,为什么还必须检查所有 Span 的 trace_id 是否一致?

  • 因为 trace ID 是追踪的唯一标识,不一致会导致无法关联同一运行的所有 Span。
  • 因为 trace ID 必须与 Span ID 相同。
  • 因为 trace ID 只用于排序,不影响追踪正确性。
  • 因为 trace ID 可以随意设置,不需要检查。

判断依据: 正确选项是第一个。Trace ID 用于标识一次完整运行,所有属于该运行的 Span 必须共享同一个 trace ID,否则无法重建调用链。其他选项误解了 trace ID 的作用。

它是什么

Trace 是一棵由 Span 组成的树

一次运行对应一个 Trace,由唯一的 trace_id 标识;Trace 内的每个操作记录为一个 Span,Span 之间通过 parent_id 形成父子关系。

validate_trace 负责检查 Trace 的结构完整性:Span ID 必须唯一、所有 Span 的 trace_id 必须一致、每个 parent_id 必须指向已存在的 Span、持续时间不能为负。

它不是什么

Trace 验证不涉及指标或预算

validate_trace 只检查结构和 ID 一致性,不计算任何指标(如总时长、平均延迟),也不检查预算或资源消耗。

它不负责生成 Span,也不负责将 Span 发送到任何存储或可视化系统;它只是对已生成的 Span 集合进行静态校验。

它与相邻概念的关系

Span 与 Trace 的关系

一个 Trace 包含多个 Span,每个 Span 记录一个操作(如 runtool.call)及其持续时间。

根 Span 的 parent_idNone,其他 Span 的 parent_id 必须指向同一 Trace 内的某个 Span ID。

validate_trace 通过检查 trace_id 集合大小是否为 1 来强制所有 Span 属于同一 Trace,通过检查 parent_id 是否在 Span ID 集合中来保证父子关系有效。

本阶段的边界决定: 当发现跨 Trace 的子 Span 时,validate_trace 必须抛出 ValueError,因为接受它会导致追踪数据无法正确关联,后续分析会出错。

STAGE 04

按运行聚合成本和延迟,并对超预算结果给出可发布的门禁结论

先看一个具体问题

如何判断一次运行是否在成本与延迟预算内?

在发布前,我们需要一个明确的预算检查函数,它接收实际成本、实际延迟和预算对象,返回超预算的维度名称。

当前代码库中还没有 forge/reliability/budgets.py 文件,因此测试在导入时就会失败。

测试 test_over_budget_dimensions_are_named 期望当成本 1.5 超过预算 1.0 时返回 ('cost',),而 test_exact_budget_is_allowed 期望当成本 1.0 和延迟 1000 恰好等于预算时返回空元组。

你需要创建该文件,定义 Budget 数据类和 check_budget 函数,并确保比较逻辑正确。

先做判断

如果实际成本恰好等于预算上限,check_budget 应该返回什么?

  • 返回包含 ‘cost’ 的元组,因为达到上限就是超预算
  • 返回空元组,因为等于上限仍在允许范围内
  • 返回包含 ‘latency’ 的元组,因为延迟也可能等于上限
  • 抛出异常,因为等于上限是边界情况

判断依据: 预算上限是最大允许值,等于上限意味着没有超过,因此不应被视为违规。返回空元组表示所有维度都在预算内。

它是什么

预算检查是什么

预算检查是一个纯函数,它比较实际测量值与预算上限,并返回所有超预算维度的名称。

它只负责数值比较,不涉及发布门禁或回滚决策。

当实际值严格大于上限时才视为违规,等于上限是允许的。

它不是什么

预算检查不是什么

它不是发布门禁系统,不会自动阻止部署或触发回滚。

它不处理负数输入,负数测量值应被视为无效并抛出异常。

它不修改预算对象或测量值,只返回违规维度。

它与相邻概念的关系

与其他组件的关系

Budget 数据类定义预算上限,check_budget 使用这些上限进行比较。

测试文件 tests/test_stage.py 导入 Budgetcheck_budget,验证其行为。

该模块独立于 tracing 模块,但未来可能被发布流程调用。

本阶段的边界决定: 预算检查只比较数值,不涉及发布门禁或回滚;负数输入应抛出异常,等于上限视为允许。

STAGE 05

将评测、迁移和安全检查绑定到不可变发布清单,缺失门禁不得晋级

先看一个具体问题

发布候选版本缺少安全门禁却被晋级

当前 forge/reliability/deployment.py 文件尚未创建,因此 tests/test_stage.py 中的两个测试在导入阶段就会失败,报错 ModuleNotFoundError: No module named 'forge.reliability.deployment'

即使我们创建了文件并实现一个简单的 promote 函数,只要它只检查版本号存在就返回晋级字符串,那么 test_missing_security_gate_blocks_promotion 仍然会失败,因为缺少安全门禁的清单会被错误地标记为已晋级。

你需要实现一个不可变的 ReleaseManifest 数据类和一个 promote 函数,该函数必须验证发布身份(版本号非空且 artifact SHA256 长度为 64),并检查所有必需门禁(tests、evaluation、security、migration)都已通过,否则抛出包含缺失门禁名称的 ValueError

先做判断

在实现 promote 函数时,如果只检查 manifest.version 非空就返回晋级字符串,会发生什么?

  • 两个测试都会通过,因为版本号存在就足够晋级。
  • test_complete_release_is_promoted 会通过,但 test_missing_security_gate_blocks_promotion 会失败,因为缺少安全门禁的清单被错误晋级。
  • 两个测试都会失败,因为缺少安全门禁时应该抛出异常。
  • test_missing_security_gate_blocks_promotion 会通过,但 test_complete_release_is_promoted 会失败。

判断依据: 正确答案是第二个选项。只检查版本号存在无法阻止缺少安全门禁的清单晋级,因此第二个测试会失败。你需要比较 passed_gates 与必需门禁集合,并在缺失时抛出 ValueError

它是什么

发布晋级门禁是什么

发布晋级门禁是一个不可变的发布清单(ReleaseManifest)与一组必需门禁(REQUIRED_GATES)之间的集合比较过程。

promote 函数首先验证发布身份(版本号非空且 artifact SHA256 长度为 64),然后计算缺失门禁 = 必需门禁 - 已通过门禁,如果缺失集合非空则抛出包含缺失门禁名称的 ValueError,否则返回以 promoted: 开头的晋级字符串。

它不是什么

发布晋级门禁不是什么

发布晋级门禁不执行实际部署、回滚或任何基础设施操作;它只检查门禁集合是否完整。

它也不是对发布质量的全面评估,而是仅验证必需门禁是否全部通过,不关心门禁内部的具体指标或分数。

它与相邻概念的关系

与其他概念的关系

发布晋级门禁依赖于 Stage 04 的预算模块所建立的可靠性检查思想,但专注于发布前的门禁集合验证。

它与可观测性相关,因为晋级决策需要基于可观测的运行记录和测试结果,而不是模型的主观判断。

它与 Harness 的隔离和权限控制互补:Harness 负责运行时安全,而发布晋级门禁负责发布前的静态门禁检查。

本阶段的边界决定: 发布晋级只检查门禁集合,不执行实际部署或回滚;如果缺少任何必需门禁,必须抛出异常阻止晋级。

STAGE 06

比较基线与金丝雀错误率和延迟,在明确阈值下自动建议回滚

先看一个具体问题

何时应自动建议回滚金丝雀版本?

在部署新版本时,我们通常先让一小部分流量进入金丝雀实例,并观察其健康指标。

如果金丝雀的错误率或延迟显著高于基线,系统应自动建议回滚,而不是等待人工判断。

但小样本下的统计波动很大,仅凭少量请求就触发回滚会导致误报,浪费部署资源。

因此,我们需要实现一个函数,在满足最小样本量的前提下,比较基线与金丝雀的错误率和延迟,并给出回滚建议。

先做判断

在实现回滚决策函数时,以下哪种做法最合理?

  • 只要金丝雀错误率高于基线就立即回滚,不考虑样本量。
  • 先检查金丝雀请求数是否达到最小样本量,未达到则返回样本不足,达到后再比较错误率和延迟。
  • 只比较延迟,忽略错误率,因为延迟更能反映用户体验。
  • 无论指标如何,都让模型自行判断是否回滚。

判断依据: 正确做法是先检查最小样本量,避免小样本统计波动导致误回滚;然后再比较错误率和延迟是否超过阈值。

它是什么

回滚决策函数是什么

回滚决策函数是一个纯函数,输入基线健康指标、金丝雀健康指标和最小请求数,输出是否需要回滚以及原因元组。

它基于明确的阈值规则:错误率超过基线两倍且至少为2%,或延迟超过基线1.5倍时,建议回滚。

该函数不执行实际回滚,只提供决策依据,便于上层系统采取行动。

它不是什么

回滚决策函数不是什么

它不是实际执行回滚的操作,不会修改部署状态或发送通知。

它也不是一个统计检验工具,不会计算置信区间或进行假设检验,只是简单的阈值比较。

它不负责收集指标数据,输入的健康指标由外部系统提供。

它与相邻概念的关系

与其他组件的关系

该函数依赖于 Health 数据类,后者封装了请求数、错误数和 p95 延迟。

它被测试文件 tests/test_stage.py 中的两个测试用例直接调用,验证其行为。

在部署流程中,该函数位于金丝雀分析阶段,为后续的回滚操作提供决策。

本阶段的边界决定: 回滚决策只基于健康指标,不执行实际回滚或记录事故;当样本量不足时,返回 insufficient-sample 状态,避免误判。

STAGE 07

记录检测、缓解、恢复和复盘时间点,并验证恢复目标与证据完整性

先看一个具体问题

为什么有序时间线是恢复指标可信的前提?

在事故演练中,检测、缓解、恢复和复盘四个时间点必须按先后顺序记录,否则计算出的恢复时间毫无意义。

当前 forge/reliability/incident_drill.py 文件尚不存在,测试导入 DrillRecordverify_drill 时直接失败,因此需要从零创建该模块。

如果只计算 recovered_at - detected_at 而不检查时间戳顺序,像 detected_at=100, mitigated_at=80 这样的不可能序列也会被接受,导致恢复目标误判。

本阶段要求实现 verify_drill(),它必须验证时间线单调递增且证据非空,然后才计算恢复时间并判断是否达到目标。

先做判断

在实现 verify_drill() 时,如果时间戳顺序错误,函数应该怎么做?

  • 仍然计算恢复时间,但返回 objectiveMet=False
  • 抛出 ValueError,拒绝处理该记录
  • 自动调整时间戳顺序后再计算
  • 忽略顺序问题,只检查证据是否为空

判断依据: 正确做法是抛出 ValueError,因为时间线顺序是恢复指标有效性的前提,不能静默修正或忽略。

它是什么

事故演练验证是什么

事故演练验证是一个纯函数,接收 DrillRecord 和恢复目标秒数,返回包含恢复秒数和目标达成情况的字典。

它首先检查四个时间戳是否单调递增,然后检查证据元组非空,最后才计算恢复时间并比较目标。

该验证只依赖输入数据,不涉及实际部署或回滚操作,因此可以安全地用于演练复盘。

它不是什么

事故演练验证不是什么

它不是实际的事故响应系统,不会触发告警或执行回滚,只对演练记录进行静态检查。

它不负责生成时间戳或证据,这些数据必须由外部系统提供,验证只判断其合理性。

它不保证恢复目标一定达成,而是根据输入数据客观计算并返回布尔值。

它与相邻概念的关系

与其他组件的关系

DrillRecord 是数据载体,使用 dataclass(frozen=True) 确保不可变,避免验证过程中被意外修改。

verify_drill() 依赖 DrillRecord 的字段,但独立于任何存储或网络层,便于单元测试。

本模块与 rollback.py 等可靠性组件并列,共同构成事故处理工具集,但本阶段只关注验证逻辑。

本阶段的边界决定: 验证函数必须在计算任何指标之前拒绝无效输入,否则后续指标将建立在不可信的数据上。

完成本章

形成可观察的线上版本,覆盖质量、延迟、成本、失败和回滚。

FORGE / LOCAL CHECK

本地实验自检

  1. 未开始
  2. 2阅读中
  3. 3实验已下载
  4. 4测试结果已读取
  5. 5本地自检通过

verification.json 只在当前浏览器中解析,不会上传。这里验证的是实验合同,不是服务器认证或第三方背书。

概念校准与一周复习

三道题检查你是否掌握了本章边界、交付证据和恢复方法。答案只保存在当前浏览器。

  1. 1哪一项最能证明你真正完成了「评测、可观测性、部署与可靠性」?
  2. 2关于「验证与评测」,哪一种理解最准确?
  3. 3实验卡住时,哪个动作是本章建议的最小恢复点?