核心摘要
AI Agent 从 POC 到生产需要的是一份晋级契约(Graduation Contract),而不是更精彩的 Demo。团队必须冻结完整发布单元,用代表性结果和关键不变量验证它,在不产生副作用的影子流量中观察真实分布,再分别灰度扩大用户暴露面与执行权限,并在放量前完成回滚演练。发布门禁只能根据版本化证据给出 advance、hold 或 rollback,不能根据单一平均分猜测“已经就绪”。
目录
核心要点
- POC 只证明在已记录条件下的有限可行性;生产就绪要证明系统能在代表性流量上受控重复。
- 可发布单元包括模型、Prompt、Tool、Policy、Retrieval、Memory、Runtime、UI 和 Evaluator。
- 用户暴露面和执行权限是两个独立灰度维度。即使只放量给少数用户,不受限的写权限仍可能造成很大影响。
- Outcome Check 与确定性不变量的优先级应高于单一 LLM Judge 或综合平均分。
- 回滚必须可执行、经过演练,并能在不破坏运行中任务的前提下阻止新副作用。
POC 能证明什么,不能证明什么
AI Agent POC 是一个可证伪实验:它验证特定系统和数据集下,一个明确工作流是否具备可行性。POC 不是缩小版生产环境。
区别在于,Demo 通常控制输入、操作人、数据、Tool、时序和重试;生产环境会出现模糊用户请求、过期状态、权限边界、依赖故障、并发、长尾输入、服务责任和真实副作用。
| 问题 | POC 证据 | 生产证据 |
|---|---|---|
| 工作流能否运行 | 选定示例能够完成 | 代表性与对抗性任务的结果测试通过 |
| 结果是否有价值 | Sponsor 接受一次 Demo | 用户相对现有基线完成目标任务 |
| 动作是否安全 | 操作人观察每一步 | Policy 独立授权身份、资源、参数与副作用 |
| 系统是否可靠 | 单次运行成功 | 重复试验、故障注入、恢复与 SLO 窗口通过 |
| 经济性是否成立 | 单次 Session 成本不高 | 每次成功任务成本包含重试和人工纠正 |
| 团队能否运营 | Builder 查看控制台日志 | Owner、Alert、Runbook、Rollback、Retention 与 Deletion 已验证 |
不要先问“怎样把这个 Agent 生产化”,而要先问“这个工作负载是否需要 Agent”。Anthropic 区分了路径固定的 Workflow 与由模型动态决定过程的 Agent,并建议只有在复杂度能够改善结果时才增加复杂度。OpenAI 也把 Agent 定位于确实需要模型决策和 Tool Use 的工作流。如果确定性代码、Retrieval 或有界 Workflow 已满足契约,减少自主性本身就是生产改进。
本文只负责晋级与放量决策。Checkpoint、Policy Decision、Effect Journal 与 Reconciliation 等运行时实现,请阅读 Agent Harness 实战;场景设计和 Judge 校准,请阅读 Agent Harness 评估。
先定义晋级契约
晋级契约描述 POC 获得生产流量或执行权限之前必须成立的条件。它应在团队针对最终 Demo 调参之前写完,避免事后悄悄移动成功标准。
契约至少包含:
workload:
id: support-refund-assistant
owner: support-platform
users: authenticated support agents
outcome: prepare an evidence-backed refund proposal
non_goals:
- approve refunds
- issue payments
authority:
initial: propose_only
maximum: bounded_write_after_human_approval
risk_tier: consequential
baselines:
comparison: current human-assisted workflow
dataset_revision: support-cases@sha256:8f21
release:
required_signoffs: [product, operations, security]
rollback_owner: support-oncall
fallback: human_only
测量工作负载,而不是只测模型
生产标准应把技术信号连接到用户和业务结果:
- **Outcome:**目标状态是否真实出现在源系统中?
- **Quality:**提案是否正确、有依据、完整,并能恰当表达不确定性?
- **Safety:**身份、租户、Policy、Approval 与 Prohibited Action 不变量是否保持?
- **Reliability:**Run 是否终止、可恢复,并避免重复或未知副作用?
- **Experience:**用户能否理解状态、限制、错误和升级路径?
- **Economics:**每次成功任务的成本与人工投入是多少?
使用相同定义记录现有工作流基线。如果不知道 Baseline、分母、置信区间、严重度分布,以及剩余错误的代价,“Agent 准确率 92%”就无法支持上线决策。
不要套用通用阈值。文案草拟助手与付款 Agent 的损失函数不同。对于跨租户访问等关键不变量,即使总体任务成功率很高,一次观察到的失败也可以阻断发布。
在流量进入前分配责任
生产 Owner 必须能回答:
- 谁接受剩余业务风险?
- 谁批准发布和每次权限扩张?
- 谁收到告警,并能停止新 Run?
- 谁对账运行中或
outcome_unknown的副作用? - 谁维护 Dataset、Policy、Tool 与 Runbook?
- 谁向用户说明限制和事故?
NIST AI RMF 将风险管理视为贯穿 AI 生命周期的活动。只有签字文件、没有持续 Owner,不构成生命周期治理。
冻结完整发布单元
Agent Release 是一组版本化组件构成的图,而不是一个模型名称。只比较模型,会遗漏 Prompt、Tool Description、Schema、Retrieval Data、Memory、Policy、Runtime 和用户界面引起的行为变化。
创建不可变 Release Manifest:
{
"release_id": "support-agent-2026-08-23.1",
"workload_contract": "sha256:1d08",
"model": "provider/model@snapshot",
"prompt_bundle": "sha256:4db2",
"tool_registry": "sha256:908a",
"policy_bundle": "sha256:ab31",
"retrieval_snapshot": "sha256:77ce",
"memory_schema": "v3",
"runtime_image": "sha256:0f19",
"ui_contract": "v5",
"eval_suite": "support-evals@sha256:5b20"
}
每次 Evaluation、Shadow Observation、Canary Decision、Trace 与 Incident 都要引用 release_id。任一组件变化,都要创建新 Candidate 并重新运行受影响门禁。否则团队批准的是一个系统,实际部署的却是另一个系统。
Manifest 也让回滚目标变得准确。如果事故来自 Tool Schema、Retrieval Snapshot 或 Policy Update,仅仅“切回旧模型”并不能恢复已批准版本。
建立生产证据包
生产证据包必须组合确定性、模型、人工、运营与安全证据。Anthropic 的 Agent Evaluation 指南区分 Task、重复 Trial、Grader、Transcript 和真实环境 Outcome,从而避免把一段流畅的最终回复误认为动作已经成功。
使用代表性案例和负例
版本化测试样本并保存其来源:
- 按真实频率抽样的常见历史任务;
- 低频但业务关键的任务;
- 模糊、格式异常、多语言和长上下文输入;
- 正确行为应是澄清、拒答或不执行的案例;
- 跨租户、Prompt Injection、数据外泄和越权 Tool 请求;
- 依赖超时、异常结果、部分故障、取消与重复投递;
- 已确认的生产事故,每个都转换为 Regression Test。
Synthetic Case 可以扩展覆盖,但不能代替真实分布或领域专家审阅。将探索“Agent 能做到什么”的 Capability Eval,与保护已批准行为的 Regression Eval 分开。
保留分母与风险切片
汇总通过率可能掩盖未测试或已经失败的关键切片。每个门禁都应保留 Task Source、Slice、Trial Count、Valid-trial Count、Environment-failure Count 与 Grader Revision。高影响、跨租户、拒答、多语言、长上下文和依赖故障切片应分别满足最低证据量;必需切片缺失或样本不足时只能 hold,不能把不确定性平均掉后继续晋级。
优先校验结果,而不是叙述
当环境存在可观察答案时,优先使用确定性 State Check:
- 目标 Ticket 是否真实存在?
- 系统是否没有发起付款?
- 拒绝后数据库是否保持不变?
- 相同 Idempotency Key 是否阻止重复副作用?
- Escalation 是否进入正确队列?
只有语气、完整性等需要解释的质量才使用 LLM Judge,并用人工标签校准。任何未校准 Judge 都不能覆盖失败的授权或 Outcome Invariant。
把运营能力纳入质量
证据包还需要:
| 领域 | 必要证据 |
|---|---|
| 负载 | 并发、队列、依赖限制与 Backpressure |
| 延迟 | 按任务切片统计端到端与分阶段分位数 |
| 成本 | 每次成功任务成本,包含重试与人工复核 |
| 恢复 | 重启、恢复、重复投递与未知结果对账 |
| 隐私 | 收集目的、脱敏、访问、留存与删除 |
| 安全 | 最小权限、注入、污染 Tool Result 与 Secret 处理 |
| 无障碍 | 状态、错误、确认、交接、键盘与辅助功能路径 |
| 运营 | Dashboard、Alert、Runbook、On-call 与 Rollback Drill |
AWS 公开的 Agent Evaluation Blueprint 展示了 Tool Use、Reasoning、Output、多次 Trial 和分阶段部署如何连接 Build-time 与 Production Evaluation。文中的阈值和案例结果只适用于该工作负载,应复用结构,不能复制数字。
沿两个独立维度灰度
安全放量同时控制暴露面和执行权限。暴露面表示谁会看到 Agent 输出;执行权限表示 Agent 能在外部系统中造成什么结果。
| 阶段 | 暴露面 | 执行权限 | 晋级证据 |
|---|---|---|---|
| sandbox | 合成与重放案例 | 模拟 Tool | 离线测试和故障注入通过 |
| shadow | 抽样生产请求,隐藏输出 | 无外部副作用 | 分布、延迟、成本、分歧与隐私检查 |
| internal | 已认证内部员工 | 只读或只提案 | 工作流效用和升级行为成立 |
| canary | 稳定的小范围用户组 | 白名单内可逆副作用 | SLO 窗口通过,无关键不变量失败 |
| expanded | 更广的合格用户组 | 单独扩大权限 | Outcome 稳定,支持容量已验证 |
| production | 已批准目标人群 | 仅契约允许的权限 | 持续监测与回归门禁 |
影子模式不是无风险镜像
Shadow Run 仍会处理真实数据并消耗依赖。它也需要合法目的、访问控制、Retention Limit、Rate Budget 和 Redaction。必须禁用或 Stub 所有产生副作用的 Tool,不能只依赖一句“不要写入”的 Prompt。比较 Agent 的 Proposed Action 与真实 Outcome,但不把隐藏输出展示给用户。
灰度分组必须稳定
在观察窗口内,同一用户、账号或工作流应持续命中同一个 Release。按请求随机分流可能把多轮 Session 分配给不同 Candidate、污染 Memory,并破坏 Outcome Attribution。
流量和权限要独立扩大。Canary 可以覆盖更多用户但仍保持 propose_only;有界写入应从低后果操作开始,同时配置明确 Limit、Confirmation 和快速 Containment。
开始前声明停止条件
每个阶段都需要三种决定:
advance:声明窗口内所有必要证据通过;hold:证据不完整,或可恢复的非关键门禁未通过;rollback:关键不变量失败,或 Candidate 超过预先定义的损失边界。
日历天数或流量百分比不是门禁。只有所需风险切片积累了足够观察量,而且团队理解缺失数据后,才能晋级。
实现确定性发布门禁
以下无第三方依赖的 Go 程序独立于 Agent 证明 Go/No-Go Policy。它拒绝畸形测量,要求每个声明的风险切片都有证据,并在质量平均值之前检查关键不变量。示例阈值只属于一个假设的客服工作流;真实团队必须根据后果、基线、流量和恢复能力定义自己的数值。
package main
import (
"fmt"
"math"
"sort"
)
type Decision string
const (
advance Decision = "advance"
hold Decision = "hold"
rollback Decision = "rollback"
)
type ReleaseManifest struct {
ReleaseID string
WorkloadDigest string
PromptDigest string
ToolRegistryDigest string
PolicyDigest string
EvalSuiteDigest string
}
type SliceEvidence struct {
Name string
ValidTrials int
SuccessRate float64
BaselineSuccessRate float64
}
type EvidenceWindow struct {
Slices []SliceEvidence
P95LatencyMS int
CostPerSuccess float64
CriticalFailures int
UnauthorizedEffects int
UnknownEffects int
Signoffs map[string]bool
}
type GatePolicy struct {
RequiredSlices []string
MinimumTrialsPerSlice int
MinimumSuccessRate float64
MaximumRegression float64
MaximumP95LatencyMS int
MaximumCostPerSuccess float64
RequiredSignoffs []string
}
type GateResult struct {
Decision Decision
Reasons []string
}
func finiteRate(value float64) bool {
return !math.IsNaN(value) && !math.IsInf(value, 0) &&
value >= 0 && value <= 1
}
func finiteNonNegative(value float64) bool {
return !math.IsNaN(value) && !math.IsInf(value, 0) && value >= 0
}
func complete(manifest ReleaseManifest) bool {
return manifest.ReleaseID != "" &&
manifest.WorkloadDigest != "" &&
manifest.PromptDigest != "" &&
manifest.ToolRegistryDigest != "" &&
manifest.PolicyDigest != "" &&
manifest.EvalSuiteDigest != ""
}
func evaluateRelease(
manifest ReleaseManifest,
window EvidenceWindow,
policy GatePolicy,
) GateResult {
if !complete(manifest) {
return GateResult{hold, []string{"incomplete_manifest"}}
}
if window.P95LatencyMS < 0 ||
!finiteNonNegative(window.CostPerSuccess) ||
policy.MinimumTrialsPerSlice < 1 ||
!finiteRate(policy.MinimumSuccessRate) ||
!finiteRate(policy.MaximumRegression) ||
policy.MaximumP95LatencyMS < 0 ||
!finiteNonNegative(policy.MaximumCostPerSuccess) {
return GateResult{hold, []string{"invalid_evidence"}}
}
if window.CriticalFailures > 0 ||
window.UnauthorizedEffects > 0 ||
window.UnknownEffects > 0 {
return GateResult{rollback, []string{"critical_invariant_failed"}}
}
byName := make(map[string]SliceEvidence)
for _, slice := range window.Slices {
if slice.Name == "" || slice.ValidTrials < 0 ||
!finiteRate(slice.SuccessRate) ||
!finiteRate(slice.BaselineSuccessRate) {
return GateResult{hold, []string{"invalid_slice_evidence"}}
}
if _, exists := byName[slice.Name]; exists {
return GateResult{hold, []string{"duplicate_slice"}}
}
byName[slice.Name] = slice
}
var reasons []string
for _, name := range policy.RequiredSlices {
slice, exists := byName[name]
if !exists {
reasons = append(reasons, "missing_slice:"+name)
continue
}
if slice.ValidTrials < policy.MinimumTrialsPerSlice {
reasons = append(reasons, "insufficient_trials:"+name)
}
if slice.SuccessRate < policy.MinimumSuccessRate {
reasons = append(reasons, "success_slo_missed:"+name)
}
if slice.BaselineSuccessRate-slice.SuccessRate > policy.MaximumRegression {
reasons = append(reasons, "baseline_regression:"+name)
}
}
if window.P95LatencyMS > policy.MaximumP95LatencyMS {
reasons = append(reasons, "latency_slo_missed")
}
if window.CostPerSuccess > policy.MaximumCostPerSuccess {
reasons = append(reasons, "cost_budget_exceeded")
}
for _, signoff := range policy.RequiredSignoffs {
if !window.Signoffs[signoff] {
reasons = append(reasons, "missing_signoff:"+signoff)
}
}
if len(reasons) > 0 {
sort.Strings(reasons)
return GateResult{hold, reasons}
}
return GateResult{advance, nil}
}
func fixture() (ReleaseManifest, GatePolicy, EvidenceWindow) {
manifest := ReleaseManifest{
ReleaseID: "support-agent-2026-08-23.1",
WorkloadDigest: "sha256:1d08",
PromptDigest: "sha256:4db2",
ToolRegistryDigest: "sha256:908a",
PolicyDigest: "sha256:ab31",
EvalSuiteDigest: "sha256:5b20",
}
policy := GatePolicy{
RequiredSlices: []string{"common", "cross_tenant"},
MinimumTrialsPerSlice: 100,
MinimumSuccessRate: 0.90,
MaximumRegression: 0.01,
MaximumP95LatencyMS: 4000,
MaximumCostPerSuccess: 0.25,
RequiredSignoffs: []string{"product", "operations", "security"},
}
window := EvidenceWindow{
Slices: []SliceEvidence{
{"common", 700, 0.94, 0.92},
{"cross_tenant", 120, 0.99, 0.99},
},
P95LatencyMS: 3200,
CostPerSuccess: 0.18,
Signoffs: map[string]bool{
"product": true, "operations": true, "security": true,
},
}
return manifest, policy, window
}
func main() {
manifest, policy, healthy := fixture()
unsafe := healthy
unsafe.UnauthorizedEffects = 1
sparse := healthy
sparse.Slices = healthy.Slices[:1]
fmt.Printf(
"healthy=%s unsafe=%s sparse=%s\n",
evaluateRelease(manifest, healthy, policy).Decision,
evaluateRelease(manifest, unsafe, policy).Decision,
evaluateRelease(manifest, sparse, policy).Decision,
)
}
预期输出:
healthy=advance unsafe=rollback sparse=hold
判断顺序很重要:畸形或缺失证据产生 hold;关键不变量在平均质量掩盖问题之前触发 rollback;只有完整的逐切片证据才能得到 advance。
上线后持续运营 Agent
生产环境是新的证据阶段,不是评估终点。Microsoft Agent Lifecycle 在发布后仍要求持续监测和用户反馈;NIST 则把 Measurement 与 Management 放在完整 AI 生命周期中。
记录稳定事件契约
记录:
{
"run_id": "run-882",
"release_id": "support-agent-2026-08-23.1",
"workload_id": "support-refund-assistant",
"cohort": "canary-a",
"authority": "propose_only",
"policy_decision": "approval_required",
"tool_result_class": "success",
"outcome": "human_approved",
"latency_ms": 2810,
"cost_microunits": 184000,
"redaction_profile": "support-v3"
}
默认不要记录原始 Credential、完整私密文档、不必要的 Tool Payload 或隐藏思维链。OpenTelemetry GenAI Semantic Conventions 已迁移到独立仓库并持续演进;应用应维护自己的稳定事件契约,再在 Telemetry Boundary 映射到当前规范版本。
保持兜底路径可用
Rollback 可以分级执行:
- 停止接收新的 Agent Run;
- 禁用写权限,同时保留读取或只提案服务;
- 将合格工作负载切回上一份 Manifest;
- 把流量切回人工或确定性 Workflow;
- 在重试前对账运行中和未知结果的副作用。
这些模式必须在 Canary 阶段实际演练。一个从未执行过的 Feature Flag 不能作为回滚证据。
把事故转换成受控变更
每个已确认故障都应产生:
- 与
run_id、release_id绑定的 Incident Record; - Severity 与受影响人群;
- 经过脱敏且可复现的 Scenario;
- 新增或修正后的 Evaluator;
- 包含修复的 Candidate Manifest;
- 重新放量前相同的分阶段门禁。
用户点踩、Judge Score、Latency Alert 和 Support Ticket 都只是分诊信号。只有经过标注和 Root Cause Analysis 后,它们才能成为发布证据。
验证故障与回滚路径
授予生产权限前,应主动注入:
| 故障 | 预期控制 |
|---|---|
| 模型超时或 Provider 故障 | 有界重试、Fallback 或显式未完成状态 |
| Tool Result 格式错误 | Schema 拒绝,不推断成功 |
| 重复投递 | Stable Idempotency Key,只产生一次外部副作用 |
| 派发后超时 | outcome_unknown,对账后再决定是否重试 |
| 跨租户资源 | Tool 执行前拒绝 |
| 审批过期或参数改变 | 拒绝并请求新的绑定审批 |
| Retrieval 或 Policy 过期 | 暂停发布,高风险动作 Fail Closed |
| Tool 或检索文本被污染 | 不改变 Policy 或 Authority |
| Loop 或 Retry Storm | Step、Time 和 Spend Budget 终止运行 |
| Telemetry 故障 | 进入已定义降级模式,不静默丢失必要审计证据 |
| Active Run 中回滚 | 停止新任务并对账现有副作用 |
| 人工队列过载 | 在不安全自动化前降低权限或流量 |
Agent 可观测性工程解释 Trace Contract;Agent Memory Persistence解释状态保留与恢复;Prompt Injection 防御解释对抗内容。本文只把它们的产物作为发布证据。
生产就绪检查清单
只有满足以下条件,Agent 才能晋级:
- Workload Contract、Baseline、Non-goal、Authority 与 Risk Tier 已批准。
- 完整 Release Manifest 不可变且可以部署。
- 代表性、负例、多轮与故障场景已版本化。
- 确定性 Outcome 和 Critical Invariant Grader 通过。
- Model Judge 已针对目标切片用人工标签校准。
- Capacity、Latency、Cost per Success、Privacy 与 Deletion 测试通过。
- Shadow Mode 禁用副作用,且隐私控制正常运行。
- Canary Assignment 稳定,暴露面和执行权限都有边界。
- Alert、Owner、Runbook、Kill Switch 与 Rollback Drill 已验证。
- Production Failure 会进入受控 Regression 与 Release Cycle。
常见问题
AI Agent POC 实际能证明什么?
POC 证明的是有边界技术可行性。它不证明生产分布上的可靠行为、授权动作、可接受的单位经济性、恢复能力、隐私合规、用户采用或支持责任。应把 POC 配置与失败保留为证据,而不是把成功 Demo 当作上线决策。
AI Agent 生产就绪门禁必须包含什么?
把 Workload Contract 和完整 Release Manifest 绑定到代表性结果测试、关键不变量、运营预算、隐私检查、分阶段观察、回滚准备度与责任人签署。缺少精确系统、Dataset、Policy、观察窗口和分母的分数,不是发布证据。
AI Agent 的影子模式与灰度发布有什么区别?
Shadow Mode 观察代表性生产请求,但隐藏输出并禁用副作用。Canary 服务一个有边界且稳定的 Cohort,并可能允许严格受限的真实副作用。前者以较低用户影响验证分布匹配,后者在可恢复范围内验证真实影响和运营响应。
可以套用通用通过率或固定灰度比例吗?
不可以。阈值应来自工作负载后果、当前基线、不确定性、请求量和恢复能力,并保留每个切片的分母与置信度。即使总体成功率很高,一次关键授权失败也可以阻断发布;低风险草拟工具则可以允许更多复核与纠正。
哪些情况必须回滚 AI Agent?
关键不变量失败、出现越权或重复副作用、结果无法对账、必要审计证据缺失,或预先声明的质量、可靠性、延迟、成本、隐私和升级边界被突破时,应立即回滚。所有触发器都要在生产流量进入前定义并演练。
总结
AI Agent 只有在特定、不可变的 Release 能够在代表性条件下证明受控价值时,才算从 POC 晋级。应在 Demo 前定义契约,冻结所有影响行为的组件,把 Outcome Test 与 Critical Invariant 结合,分开控制用户暴露面和执行权限,并只通过证据门禁逐级放量。生产仍是持续监测阶段,每次重大变更都要重新评估,且始终保留回滚与事故学习闭环。
相关资源
- AI Agent 开发与生产边界
- Agent Harness 实战
- Agent Harness 评估
- Agent 可观测性工程
- 企业 AI Agent 治理
- AI Agent
- Agent Harness
- 人机协同