端到端固化
B8(Day 71-80)这一段把 agent runner 从脚本推到「真服务 + 镜像」。
阶段: B8 · 真 API + 流式 + Docker(Day 71-80) 标签: #e2e #env-parity #eval-gate #fail-closed
今日导引(由浅入深)
B8(Day 71-80)这一段把 agent runner 从脚本推到「真服务 + 镜像」。 今天是这一段的收口:端到端固化 = 在容器内重跑 eval 回归,确认与本地一致,并设计 CI 触发 docker build 的链路。
这条「容器内通过率 == 本地通过率」的回归绿线,是部署可信度的根——防止经典的「本地能跑、容器挂」环境漂移。 完成它,B8 就从「写了 Dockerfile」升级到「证明了容器里跑出来的数和本地一样」, 明天才能干净地迈进 B9(云部署 + 韧性 + 可观测 + 独立红队)。
今天的「最小可判定产出」是容器内 docker run ... pnpm eval:agent 跑出的 29 任务通过率,与本地 V4-Flash 79.3% 比对一致。
1. 机理精读
环境一致性(env parity)是部署可信度的根。
同一份代码,本地 pnpm eval:agent 跑 79.3%,容器里跑出来若不是 79.3%,说明环境有漂移——
Node 版本、依赖解析、locale、文件路径、时区,任何一处不同都可能改变行为。
端到端固化就是把「容器内通过率 == 本地通过率」当成一条必须绿的回归线来守。 它比单元测试更高一层:单测证明函数对,env parity 证明整条部署链路在目标运行时里仍对。 这也是 Twelve-Factor App「dev/prod parity」原则的具体落地——尽量缩小开发与生产环境的差距。
fail-closed eval gate 是固化的执法者。
回归线靠 gate 强制。src/agent/eval/gate.ts 的设计哲学写在注释里:
"A gate must FAIL CLOSED: if a threshold is configured but the current metric is absent or non-finite (NaN/Infinity from a corrupt eval), that is a violation — never a silent green."
即——指标缺失 / 损坏不是「跳过」而是「违规」,宁可误拦不可放过。 这正是部署门禁该有的姿态:可疑即拦。 与之相对的 fail-open(出错就放行)是安全反模式——一份损坏的 eval 报告恰恰最该被拦,而不是被当成「没数据所以放过」。
CI 触发 docker build 的链路设计。
理想链路是:push → CI 跑测试 + eval → 构建镜像 → 容器内重跑 eval 回归 → gate 绿则放行部署。
当前本仓状态是代码件就绪、CI 自动触发未接:
Dockerfile.mcp、.dockerignore、resilience.ts、eval gate 都在仓库里且已测,
但 docker 构建 / run、TTFT、worklog 发布是待执行的手动 / 外部步骤。
诚实标注这条边界,正是 AISA 的可信度来源——不把「设计了链路」说成「接通了链路」。
与相邻概念的边界。 Day 77/78 解决「镜像怎么建、怎么瘦」;今天解决「镜像里跑出来的能力数 == 本地」。 前者是打包,后者是部署正确性验证。 再往后 B9 的 Cloud Run scale-to-zero 才谈「在哪跑、怎么弹、冷启动多少」—— 固化是部署的前置门,没有这条绿线,scale 出去的只是「能起但不知道对不对」的容器。
2. 推导 / 手算 / 代码走读
代码走读固化路径上的两个文件。
src/agent/eval/gate.ts — checkGate(current, baseline, thresholds):
maxCompletionDrop默认 0.05: completion 比 baseline 跌超 5pp 即违规。 但只在 baseline 存在时才检查(Number.isFinite(baseline.completionRate))——首次跑没 baseline,不误拦。- fail-closed 分支:
baseline 有值而 current 缺失 / 非有限(NaN/Infinity),直接 push 一条
completionRateviolation, detail 写「current completionRate absent or non-finite」——绝不静默放绿。 minKappa: judge 与人工标注的 Cohen's kappa 必须 ≥ 阈值(runner 默认配 0.6),低于即判 judge 失准。 配了阈值但 kappa 缺失同样 fail-closed。maxUnknownRate: judge 返回unknown的比例超阈即违规(runner 默认 0.25)。- 返回
{ pass: violations.length === 0, violations }——任何一条违规即不绿。
scripts/eval-gate.ts — pnpm eval:gate(无 key,只读已存报告):
latestReport():扫agent-evals/reports/,取最新*.json,抽出completionRate、unknownRate、judgeHumanKappa.kappa。- 无报告时优雅跳过:
exit(0)并提示「先用 key 跑pnpm eval:agent」——本地无报告不阻断。 - thresholds 写死:
{ maxCompletionDrop: 0.05, minKappa: 0.6, maxUnknownRate: 0.25 }。 - baseline 取
agent-evals/baseline.json(不存在则空对象{}→ 不检查 completion drop)。 checkGate不绿则console.error逐条违规 +process.exit(1)——CI 非零退出 = 拦合并。
走读结论:gate 决策逻辑(
gate.ts)纯函数已测,runner(eval-gate.ts)把 fs +process.exit包在外层。 fail-closed 行为成立。 诚实标注:agent-evals/baseline.json尚未定基线,所以当前 completion-drop 这条暂以空 baseline 跑(gated on 选定 baseline run)。
3. 今日实战
- 容器内重跑:
docker run ... pnpm eval:agent跑src/agent/eval/tasks.ts全 29 任务,比对本地数字。 - 守门:
pnpm eval:gate走src/agent/eval/gate.ts(fail-closed)+scripts/eval-gate.ts,确认无回归违规。 - 提交
README运行说明:docker build / run / eval 三步,让他人可复现。 - 设计(非接通)CI 触发 docker build 的链路草图,标注其为 B9 待接。
- 比对「容器内通过率 == 本地通过率」,差异即环境漂移,逐项排查 Node 版本 / 依赖 / locale。
4. 今日实测 / 产出
gate.ts+scripts/eval-gate.ts已构建 + 已测(fail-closed)。- 容器内 29 任务通过率 == 本地 的回归绿为 外部动作 / 手动 docker step——预期锚定本地 V4-Flash 79.3%(judge=pass)/ partial 0.900。
- CI 自动触发 docker build 未接(待云 / CI)。
- 本 block 里程碑(<300MB 镜像跑通 SSE + 容器内 29 任务复现)中:
- 代码件(
Dockerfile.mcp、.dockerignore、resilience.ts、eval gate)已就绪; - docker 构建 / run、TTFT、worklog 发布为待执行的手动 / 外部步骤,诚实标注。
- 代码件(
5. 常见误区 / 陷阱
- 「本地能跑」当部署证明: env parity 没验,容器里可能因 Node 版本 / 依赖解析 / locale 漂移而数变。必须容器内重跑对齐。
- gate 在指标缺失时静默放绿:
违背 fail-closed。
gate.ts把「current 缺失 / 非有限」判为 violation,不能图省事改成跳过。 - 用自评循环作 baseline:
被 source-agnostic 修复取代;
agent-evals/baseline.json选定后才接入 gate,且 baseline 不能来自自评。 - N=29 当足够: A/B 已证 N=29 不达统计 power,扩到 ~70 任务待办——固化绿不等于统计强。
6. 学习资源(每条带 YYYY-MM)
- Anthropic「Demystifying evals」(Anthropic, 2026-01)——eval-as-gate 与 fail-closed 思路。
- Twelve-Factor App — Dev/prod parity(原则文档,持续有效,2025 复核版)——env parity 的经典论述。
- GitHub Actions — building & pushing Docker images in CI(GitHub, 2025-12)——CI 触发 docker build 链路参考。
- 本仓代码:
src/agent/eval/gate.ts、scripts/eval-gate.ts、src/agent/eval/tasks.ts、src/agent/runtime/resilience.ts(2026-06,AICAP-180 B7/B8/B9)。
SOTA检查 (2026-06 更新)
- fail-closed eval gate 为当前最佳实践(
gate.ts已实现);容器内回归对齐为标准 CI 范式。 - 过时黑名单:在 CI 用自评循环作 baseline(应用 source-agnostic 的
groundTruthEval);指标缺失静默放绿;「本地能跑」当部署证明。 - 待办:
agent-evals/baseline.json选定后接入 gate;N=29 偏小,扩到 ~70 任务以获统计 power。 - 下次复查点:容器内 29 任务回归跑出后比对本地 79.3%;CI 自动触发 docker build 接通(B9);选定 baseline run 后接入 gate。
衔接
- 昨天:Day 79 — vibe-coding worklog(把真实数字写成公开作品集资产)
- 今天:容器内重跑 29 任务回归对齐本地、fail-closed gate 守门——给整个 B8 收口;docker / CI / TTFT 为待执行外部步骤
- 明天:Day 81 — Cloud Run scale-to-zero 部署模型(进入 B9:部署 + 韧性 + 可观测 + 独立红队)