知识库Agent生产工程生产工程Agent 评估与可观测性Agent 评估与可观测性
04 · 生产工程生产工程
Roadmap 04进阶Markdown72 min

Agent 评估与可观测性Agent 评估与可观测性

从任务验收、轨迹评估到 LLM-as-judge 校准,建立可复现、可诊断、可发布的 Agent 质量体系。从任务验收、轨迹评估到 LLM-as-judge 校准,建立可复现、可诊断、可发布的 Agent 质量体系。

#EvalEval#ObservabilityObservability#OpenTelemetryOpenTelemetry#SLOSLO更新于 2026-08-16

专题导读

对普通 REST 服务,HTTP 200 常能说明协议层调用成功;对 Agent,它只说明“入口返回了响应”。一次 Agent 运行可能经历规划、检索、模型调用、工具执行、人工审批和重试,最终文本看似合理,数据库却没有产生正确状态,甚至越过了授权边界。因此评估对象必须从“单次模型输出”升级为“任务及其完整执行轨迹”。

面试时不要只罗列 BLEU、准确率或 token 数。更有区分度的回答是:先定义任务契约与可判定结果,再建立离线数据集;线上以 trace 串联一次用户意图,以可恢复的 run 表示业务执行,以 span 记录外部可观察步骤;最后用确定性断言、人工标注和经过校准的 LLM-as-judge 组成分层证据。评估结论必须能按任务类型、租户、模型、提示词、工具和知识库版本切片,否则总体均值很容易掩盖回归。

知识地图

任务契约 → 数据集与切片 → 离线回放 → 确定性检查/人工标注/Judge → 发布门禁 → 线上 Trace → SLI/SLO → 告警与事故归因 → 失败样本回流

层次 核心对象 典型指标 主要问题
业务结果 task/run 任务成功率、错误拒绝率、人工接管率 用户目标是否在约束内完成
轨迹过程 trace/span/step 工具选择正确率、循环次数、越权拦截率 是如何成功或失败的
模型调用 model invocation token、首 token、结构化输出通过率 模型质量、延迟和成本
平台依赖 retrieval/tool/queue P95/P99、超时率、重试率、积压 失败来自哪里
治理发布 dataset/version/gate 回归差异、置信区间、错误预算消耗 新版本是否可以扩量

面试题

Q1:如何定义 Agent 的“任务成功率”?为什么不能用 HTTP 成功率代替?

核心回答: 任务成功率是“满足任务验收条件且没有违反安全、预算和时限约束的已完成任务数 / 可判定任务数”。必须先规定分母、终态和排除项;HTTP 成功率只能反映入口协议,不代表业务副作用、答案质量或合规性成功。

例如“查询订单”可用返回订单号与数据库真实状态一致来验收;“取消订单”还要验证目标订单属于当前主体、状态确实转换、事件已可靠发出且没有重复退款。建议把终态明确为 SUCCEEDEDFAILEDREJECTEDCANCELLEDNEEDS_REVIEW,不要把仍在运行或无法判定的样本悄悄计入成功。

分母也要固定口径。若用户主动取消不算系统失败,应单独报告;若安全策略正确拒绝恶意请求,它是“正确拒绝”而不是任务失败。生产报表至少按任务类型、版本和风险等级分桶,并同时给样本量与置信区间。否则大量简单问答会抬高总体成功率,掩盖少量高价值写操作的失败。

面试追问: 一个 run 生成了正确答案,但超过预算,算成功吗?通常应标为“质量成功、约束失败”,总任务契约不通过,同时保留分维度结果。

常见错误回答: “模型没有报错就是成功”;“让用户点赞就是成功”。点赞受响应率和选择偏差影响,只能作为弱反馈。

Q2:Trace、Run、Step、Span 分别是什么关系?

核心回答: OpenTelemetry 的正式追踪模型是一个 trace 由多个 span 构成,span 表示一次有开始、结束和上下文的操作。runstep 通常是 Agent 业务模型:run 是一次可持久化、可恢复的任务执行,step 是状态机中的逻辑步骤;它们可以映射为 span 或 span 属性,但不能宣称是 OpenTelemetry 的固定层级。

一种实用建模是:用户请求创建 traceIdrunId;编排、模型、检索、工具、审批、队列生产与消费分别创建 span;业务数据库保存 run/step 状态,并把 traceId 作为关联字段。同步调用通过 trace context 传播父子关系,异步队列通过消息头传播上下文或建立 span link。重试通常创建新的 attempt span,而不是覆盖旧 span,这样才能看见失败历史。

span 应保存低基数、可聚合属性,例如任务类型、模型版本、提示模板版本、工具名、状态码和错误类别。userId、完整 prompt、文档正文等高敏或高基数数据不应直接成为 metric label;必要时使用受控日志、摘要、哈希引用或短期加密存储。可参考 OpenTelemetry Trace 概念 和其语义约定,但 GenAI 约定会演进,生产中应锁定采用的版本。

面试追问: run 跨越数小时、多个队列消费者,是否一定要保持单个超长 root span?不一定。业务 run 必须连续;遥测可按执行阶段拆 trace,再用稳定 runId 与 span link 关联,避免后端对超长 trace 的限制。

常见错误回答: “trace 就是一次 run,step 就一定是 span”。二者经常对应,但生命周期、采样和持久化语义不同。

Q3:一个可诊断的 Agent Span 至少记录什么?

核心回答: 至少记录关联标识、版本、时间、结果、资源消耗和错误分类,同时遵循数据最小化。字段要能回答“哪一版在什么条件下做了什么,耗时和成本是多少,为什么失败”。

建议字段包括:traceId/runId/stepId/attempt;任务类型和风险等级;模型供应商的逻辑名与版本;提示模板、工具 schema、检索索引和路由规则版本;开始/结束时间;输入/输出 token;缓存命中;工具状态;重试原因;错误类别;预算消耗;审批结果。模型与工具的原始输入输出默认不采集,只有经数据分类、脱敏、采样和访问控制后才进入专门存储。

错误分类要稳定,例如 MODEL_TIMEOUTTOOL_5XXSCHEMA_INVALIDAUTHZ_DENIEDBUDGET_EXHAUSTED,不要把供应商原始字符串直接作为指标标签。异常堆栈进入日志,metrics 只保留受控枚举。还要记录版本而非只记录“模型 A”,否则发布后无法重放。

面试追问: 是否记录模型的隐藏推理链?不应要求或依赖隐藏推理。记录外部可审计的计划摘要、工具参数、策略决策和结果证据即可。

常见错误回答: “把所有 prompt 和 response 全量写日志,事故时最好查”。这会把可观测平台变成敏感数据副本。

Q4:离线评测集应如何构建,才能避免“测了但没用”?

核心回答: 评测集应来自明确任务分布,并包含正常、边界、对抗、历史事故和长尾样本;每个样本要有输入、环境快照、验收器、允许行为和版本信息。随机抽几十个漂亮例子不是可复现评测。

数据集至少分四层:黄金集用于高置信回归;历史失败集防止事故复发;合成边界集扩大组合覆盖;线上代表性采样用于发现分布漂移。涉及 RAG 时要固定或版本化语料快照,涉及工具时用录制回放或可控 sandbox,避免第三方状态变化让分数失真。

训练、调参和最终验收集必须隔离。若团队反复查看测试失败并修改 prompt,测试集已经参与开发,应另留 holdout。数据中应保留任务类型、语言、长度、权限、租户规模等切片标签,并审核是否遗漏关键用户群体。NIST 的 AI RMF 强调在设计、开发、使用和评估全过程管理可信性风险;这意味着数据集也需要版本、责任人、变更记录和适用边界。

面试追问: 合成数据能替代生产样本吗?不能。它适合扩展已知边界,但可能复制生成模型偏差,也难以真实覆盖用户分布。

常见错误回答: “每次随机生成 100 个问题测一下”。随机变化会让版本间差异无法归因。

Q5:为什么优先使用确定性评估?如何评估工具调用轨迹?

核心回答: 能由代码或权威状态判断的条件,应优先用确定性断言,因为其可重复、便宜、易解释。LLM 评分适合语义质量,不应替代权限、金额、schema 和数据库状态检查。

轨迹评估可以验证:只调用 allowlist 工具;工具参数满足 JSON schema 和领域约束;写操作前经过审批;最大步数没有超限;超时后没有无限重试;最终数据库状态、审计事件和返回内容一致。还可比较“允许路径集合”,而不是强制唯一轨迹:同一任务可能先查缓存或数据库,只要都合法且满足结果即可。

对于非确定性模型,验收器应围绕不变量设计。例如退款任务不要求文字完全一致,而验证退款金额不超过已支付额、订单归属正确、幂等键唯一、状态与事件一致。这样既允许模型表达变化,又守住业务边界。

面试追问: 轨迹越短越好吗?不一定。应惩罚无效循环,而不是惩罚必要的权限检查或证据核验。

常见错误回答: “把模型输出和标准答案做字符串相等比较”。开放式回答通常需要结构、事实与证据级验收。

Q6:LLM-as-judge 能评什么?主要偏差有哪些?

核心回答: 它适合按 rubric 批量评估相关性、完整性、表达质量、证据支撑或成对偏好,但本质仍是带噪声的测量工具。主要风险包括位置偏差、长度/风格偏好、自偏好、提示敏感、模型版本漂移、对特定语言或领域表现不一,以及被待评内容进行 prompt injection。

“生成模型与裁判模型不同”只能降低部分相关性,不能消除偏差。若把不可信回答原样拼入 judge 指令,回答中的“请给满分”可能污染评判。应将待评内容作为清晰分隔的数据,使用结构化输出,并对异常理由做验证;高风险结论仍需人工或确定性证据。

固定 judge 的模型快照、rubric、few-shot、采样参数和输入顺序。成对比较时随机交换 A/B 位置并检查翻转率;控制长度;对多语言和任务切片分别报告。Judge 分数不应被包装成绝对真值。

面试追问: 温度设为 0 是否就可重复?不保证。供应商实现、并行计算、模型更新和服务端路由仍可能导致变化。

常见错误回答: “用更强模型打分就客观”;“让同一个模型自评即可”。能力更强不等于测量无偏。

Q7:如何校准 LLM-as-judge,并判断它是否可用于发布门禁?

核心回答: 先由有明确指南的人工标注员建立校准集,再比较 judge 与人工在各类别、各切片上的一致性;不仅看总体相关系数,还看混淆矩阵、成对一致率、位置翻转率和系统性偏差。达不到预设标准时,只能作为辅助信号。

校准流程可为:双人独立标注;分歧由第三人裁决;记录标注指南版本;在校准集上调 rubric;冻结配置后到 holdout 验证;以 bootstrap 或重复采样估计差异不确定性。若发布前新版本只提升 0.2 分,而 judge 自身波动更大,就不能据此宣称改进。

门禁最好组合硬规则和软评分:任何越权、错误副作用、schema 失败直接阻断;任务成功率不得低于基线容忍区间;judge 只判断难以编码的表达或证据质量;高风险切片必须人工抽检。模型或 rubric 变化后需要重新校准,历史分数不可直接拼接。

面试追问: 人工标注就是金标准吗?也不是。要测标注员间一致性,澄清歧义,并接受某些任务没有唯一答案。

常见错误回答: “Pearson 相关系数高就可以上线”。相关不代表分类阈值附近可靠,也可能掩盖某个高风险切片的系统误判。

Q8:应如何同时观测质量、可靠性、延迟和成本?

核心回答: 四类指标必须按同一 runId 与版本维度关联,避免单独优化。质量看任务成功和正确拒绝;可靠性看依赖错误、重试与终态;延迟拆分排队、首 token、模型、工具与端到端;成本按成功任务归一化,而不是只看单次调用价格。

常用指标包括:端到端 P50/P95/P99、time-to-first-token、队列等待、模型/工具耗时、输入/输出 token、缓存命中、每成功任务成本、每 run 步数、重试放大系数、fallback 率、人工接管率。cost_per_success = 总成本 / 成功任务数 往往比平均调用成本更真实,因为廉价但低成功率的路线会产生重试和人工成本。

百分位数不能相加:各阶段 P95 之和不等于端到端 P95,应从 run 级样本直接计算。metrics 标签控制低基数;高基数关联放 trace/log。告警围绕用户影响和错误预算,而非“token 使用量变高”这种缺少上下文的单点阈值。

面试追问: 流式输出很快,为什么用户仍抱怨慢?首 token 快不代表任务完成快;工具执行或最终提交可能仍很慢。

常见错误回答: “平均延迟 2 秒,所以 SLA 没问题”。长尾和任务类型分布通常更关键。

Q9:怎样为 Agent 设计 SLI、SLO 与告警?

核心回答: SLI 必须从用户可感知结果出发,SLO 给出统计窗口和目标,错误预算用于决定发布与可靠性投入。Agent 至少需要“合格任务比例”和“在时限内完成比例”,必要时再增加高风险动作正确拦截等安全目标。

示例:28 天窗口内,具有确定验收器的交互式查询中,99.0% 在 8 秒内完成且结果合格;异步任务中,99.5% 在 10 分钟内到达终态。分母必须说明是否排除用户取消、上游计划维护和不可判定样本。安全违规通常不能简单用宽松错误预算抵消,应作为独立阻断条件。

告警优先使用错误预算消耗率,在短窗口快速燃烧与长窗口持续燃烧时报警,而不是每次供应商 5xx 都叫醒人。Google SRE 的 SLO 实施指南给出了从 SLI 到错误预算的工程方法;目标不是追求 100%,而是用用户需求约束可靠性投入。

面试追问: 质量评估可能延迟几小时,如何做实时 SLO?实时使用代理指标和确定性结果,延迟质量标签回填后修正;两者分别命名,不混为一个实时真值。

常见错误回答: “可用性 99.9% 就足够”。入口可用但任务错误,对用户仍是失败。

Q10:如何设计采样、日志脱敏和数据保留?

核心回答: 默认最小采集,按风险分级做 head/tail sampling,并把内容与遥测元数据分离。错误、高风险写操作、审批和罕见路径可提高采样率;普通成功流量低比例采样。采样策略本身要版本化并在指标中校正。

内容记录前先做数据分类:禁止密钥与认证令牌;PII 尽量删除或不可逆处理;工具参数只保留允许字段和摘要;原文需要复现时进入隔离、加密、短保留期的 evidence store,并由 trace 保存引用。访问应审计,测试环境不能直接复制生产 prompt。

纯 head sampling 可能漏掉后续失败;tail sampling 能依据最终状态保留错误 trace,但需要 collector 缓冲和额外资源。metrics 一般不采样,但标签必须限基数。删除请求还要覆盖日志、对象存储、离线数据集和向量化副本,而非只删业务库。

面试追问: 哈希 userId 就一定匿名吗?不是。稳定哈希仍可关联行为,低熵标识可能被枚举,需要密钥化散列、访问控制和保留策略。

常见错误回答: “内部日志不用脱敏”;“采 1% 就能代表所有错误”。

Q11:如何把评测接入版本发布与回滚?

核心回答: 每个结果必须绑定模型、prompt、工具 schema、路由、检索索引和评测集版本;离线门禁通过后,采用 shadow/canary 渐进放量,并预先定义自动停止和回滚条件。一次只改变少数变量,才能归因。

发布流程可分为:静态 schema 与安全检查;黄金集和事故集回归;成本/延迟预算检查;影子流量比较但禁止真实副作用;小流量 canary;按任务切片观察;逐级扩量。新旧版本使用一致验收器,报告绝对值、差值、样本量和不确定性。若模型供应商版本不可锁定,至少记录返回的模型标识并维护探针集检测漂移。

回滚不仅是改模型别名。若提示与工具 schema、索引版本或状态机同时变更,需要兼容旧 run;数据库和消息格式要支持向后兼容。安全违规、错误副作用和错误预算快速消耗应立即停止扩量,不等待总体均值显著下降。

面试追问: shadow 流量能评估写工具吗?只能执行到授权/参数验证或进入 sandbox,不能把同一个生产副作用执行两次。

常见错误回答: “A/B 测试胜率高就全量”。还需看统计功效、高风险切片、成本和 SLO。

Q12:系统设计题——设计一套可回放的 Agent 评估与可观测平台

核心回答: 入口生成 runId 并传播 trace context;编排器持久化 run/step;遥测 SDK 上报 span/metric,日志与敏感 evidence 分库存储;评测服务消费终态事件,运行确定性验收器、judge 和抽样人工审核;结果按完整版本向量入仓并驱动看板与发布门禁。

关键数据表可包括 runstep_attemptevaluation_caseevaluation_resultartifact_versionhuman_review。终态通过 outbox 发布,消费者幂等写结果。回放环境固定提示、索引快照和工具录制;真实写工具替换为 sandbox adapter。指标聚合不携带高基数内容,点击异常点再通过 runId 进入受控 trace/evidence 查询。

容量上先估算:若稳定观测窗口内达到 200 run/s,每 run 平均 12 spans,则约 2,400 spans/s;若每 span 序列化后平均 1.5 KiB,原始入口约 3,600 KiB/s(约 3.52 MiB/s),未计副本、索引膨胀和 tail-sampling 缓冲。保留策略应区分 metrics、普通成功 trace、错误 trace 和敏感 evidence。Judge 任务走独立低优先级队列,设置每日 token/金额预算,不能反压在线编排。

可用性上,遥测出口失败不应阻塞主业务;本地有界缓冲满时丢弃低优先级成功 span,并记录 dropped telemetry 指标。评测服务故障可延迟质量回填,但发布门禁应 fail closed:没有所需证据就不扩量。

面试追问: 如何证明一次回放与历史运行可比较?核对版本向量、输入摘要、工具录制、随机参数和验收器版本;无法固定的外部依赖应标记为不可严格复现。

常见错误回答: “把所有数据放 Elasticsearch,再做一个大盘”。缺少任务契约、版本关系、脱敏、回放和门禁,只是日志平台。

Q13:事故排查题——任务成功率下降但模型错误率不变,怎么查?

核心回答: 先确认指标口径和版本变更,再按 trace 分解失败阶段,不预设是模型问题。查看任务类型/租户/版本切片,比较排队、检索、工具、审批、schema、预算和最终验收失败率;模型 2xx 不代表输出可用。

排查顺序可为:确认告警窗口、分母与迟到标签;定位最早异常版本;比较新旧 run 的状态转移;从失败类别抽样 trace;检查工具 429/超时、索引发布、权限策略、输出 schema、重试放大和预算耗尽;用同一输入在固定环境回放。若只有 P99 变差,平均值与模型错误率可能都不变,但队列积压使任务超出时限。

处置上先停止扩量或切回已验证版本;保护高风险写操作;必要时降级为只读或人工接管。事故后把代表性 run 脱敏加入回归集,新增能更早发现该模式的 SLI,并修正文档化的运行手册。

面试追问: Judge 分数突然下降应先回滚吗?先检查 judge 模型/rubric 是否漂移;若同时出现确定性失败或用户影响,则按影响处置,不能让 judge 成为单点真相。

常见错误回答: “模型接口没报错,所以是用户问题”;“先把超时调大”。调大超时可能放大排队和成本。

Java 21 完整示例:按任务终态计算成功率与 SLO

下面示例只使用 Java 21 标准库。它明确区分可判定任务、正确拒绝、质量失败和超时,并按任务类型聚合;真实平台可把外部遥测系统抽象为 TelemetrySink 接口。示例容量为内存中保留当前窗口,时间复杂度为 O(n),空间复杂度为 O(k),其中 k 是任务类型数量;生产系统应改为流式聚合,避免保存全部 run。

JAVA
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public final class AgentEvaluationExample {
    enum Outcome {
        SUCCEEDED, FAILED, CORRECTLY_REJECTED, INCORRECTLY_REJECTED,
        CANCELLED_BY_USER, NEEDS_REVIEW
    }

    record RunResult(
            String runId,
            String taskType,
            String version,
            Instant startedAt,
            Instant finishedAt,
            Outcome outcome,
            boolean withinPolicy,
            long inputTokens,
            long outputTokens,
            long costMicros) {
        RunResult {
            Objects.requireNonNull(runId);
            Objects.requireNonNull(taskType);
            Objects.requireNonNull(version);
            Objects.requireNonNull(startedAt);
            Objects.requireNonNull(finishedAt);
            Objects.requireNonNull(outcome);
            if (finishedAt.isBefore(startedAt) || inputTokens < 0 || outputTokens < 0 || costMicros < 0) {
                throw new IllegalArgumentException("invalid run result");
            }
        }

        Duration latency() {
            return Duration.between(startedAt, finishedAt);
        }
    }

    record SloReport(
            long eligible,
            long good,
            long withinLatency,
            double taskSuccessRate,
            double latencySli,
            double costPerSuccessMicros) {}

    interface TelemetrySink {
        void publish(String taskType, String version, SloReport report);
    }

    static Map<String, SloReport> evaluate(
            List<RunResult> runs, Duration latencyObjective, TelemetrySink sink) {
        record Acc(long eligible, long good, long fast, long costMicros) {
            Acc add(RunResult run) {
                // 成功率只统计可判定任务;成本统计当前窗口内所有已执行 run。
                boolean isEligible = run.outcome() != Outcome.CANCELLED_BY_USER
                        && run.outcome() != Outcome.NEEDS_REVIEW;
                boolean isGood = isEligible && run.withinPolicy()
                        && (run.outcome() == Outcome.SUCCEEDED
                        || run.outcome() == Outcome.CORRECTLY_REJECTED);
                boolean isFast = isGood
                        && run.latency().compareTo(latencyObjective) <= 0;
                return new Acc(
                        eligible + (isEligible ? 1 : 0),
                        good + (isGood ? 1 : 0),
                        fast + (isFast ? 1 : 0),
                        costMicros + run.costMicros());
            }
        }

        Map<String, Acc> accumulators = new HashMap<>();
        for (RunResult run : runs) {
            String key = run.taskType() + "|" + run.version();
            accumulators.compute(key, (ignored, current) ->
                    (current == null ? new Acc(0, 0, 0, 0) : current).add(run));
        }

        Map<String, SloReport> reports = new HashMap<>();
        accumulators.forEach((key, acc) -> {
            double successRate = acc.eligible() == 0 ? Double.NaN
                    : (double) acc.good() / acc.eligible();
            double latencySli = acc.eligible() == 0 ? Double.NaN
                    : (double) acc.fast() / acc.eligible();
            double costPerSuccess = acc.good() == 0 ? Double.POSITIVE_INFINITY
                    : (double) acc.costMicros() / acc.good();
            SloReport report = new SloReport(
                    acc.eligible(), acc.good(), acc.fast(),
                    successRate, latencySli, costPerSuccess);
            reports.put(key, report);

            String[] dimensions = key.split("\\|", 2);
            sink.publish(dimensions[0], dimensions[1], report);
        });
        return Map.copyOf(reports);
    }

    public static void main(String[] args) {
        Instant base = Instant.parse("2026-08-16T10:00:00Z");
        List<RunResult> runs = new ArrayList<>(List.of(
                new RunResult("r-1", "order-query", "prompt-v7", base,
                        base.plusSeconds(2), Outcome.SUCCEEDED, true, 800, 120, 2_400),
                new RunResult("r-2", "order-query", "prompt-v7", base,
                        base.plusSeconds(12), Outcome.SUCCEEDED, true, 900, 160, 2_700),
                new RunResult("r-3", "order-query", "prompt-v7", base,
                        base.plusSeconds(1), Outcome.INCORRECTLY_REJECTED, true, 300, 30, 900),
                new RunResult("r-4", "refund", "prompt-v7", base,
                        base.plusSeconds(3), Outcome.CORRECTLY_REJECTED, true, 500, 60, 1_500),
                new RunResult("r-5", "refund", "prompt-v7", base,
                        base.plusSeconds(4), Outcome.CANCELLED_BY_USER, true, 200, 20, 600)
        ));

        TelemetrySink console = (taskType, version, report) ->
                System.out.printf("%s %s -> %s%n", taskType, version, report);
        evaluate(runs, Duration.ofSeconds(8), console);
    }
}

这里的 latencySli 分母仍是全部可判定任务,因此质量失败不会因“响应很快”而成为好事件;costPerSuccess 能暴露低价路线因失败和重试造成的真实浪费。生产中还应补充窗口、置信区间、P95/P99、迟到事件修正和数据保留策略。

速记总结

  • HTTP 成功不等于任务成功;先写任务契约、终态、分母和约束。
  • OpenTelemetry 中 trace 由 spans 构成;run/step 是 Agent 业务实体,可通过属性和 link 关联。
  • 能确定判断的权限、schema、金额、数据库状态优先使用代码断言。
  • LLM-as-judge 有位置、长度、自偏好、提示敏感和漂移等偏差,必须经人工校准并报告不确定性。
  • 同时看质量、可靠性、延迟和成本;重点关注每成功任务成本与端到端长尾。
  • 版本向量至少包含模型、prompt、工具、索引、路由和验收器。
  • 可观测数据同样受最小化、脱敏、访问控制和保留期约束。
  • 离线门禁之后仍需 shadow/canary、错误预算和可回滚设计。

参考资料