返回 AICAP-180
B8 · Day 80真 API + 流式 + Docker

端到端固化

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.dockerignoreresilience.ts、eval gate 都在仓库里且已测, 但 docker 构建 / run、TTFT、worklog 发布是待执行的手动 / 外部步骤。 诚实标注这条边界,正是 AISA 的可信度来源——不把「设计了链路」说成「接通了链路」。

与相邻概念的边界。 Day 77/78 解决「镜像怎么建、怎么瘦」;今天解决「镜像里跑出来的能力数 == 本地」。 前者是打包,后者是部署正确性验证。 再往后 B9 的 Cloud Run scale-to-zero 才谈「在哪跑、怎么弹、冷启动多少」—— 固化是部署的前置门,没有这条绿线,scale 出去的只是「能起但不知道对不对」的容器。

2. 推导 / 手算 / 代码走读

代码走读固化路径上的两个文件。

src/agent/eval/gate.tscheckGate(current, baseline, thresholds)

  1. maxCompletionDrop 默认 0.05: completion 比 baseline 跌超 5pp 即违规。 但只在 baseline 存在时才检查Number.isFinite(baseline.completionRate))——首次跑没 baseline,不误拦。
  2. fail-closed 分支: baseline 有值而 current 缺失 / 非有限(NaN/Infinity),直接 push 一条 completionRate violation, detail 写「current completionRate absent or non-finite」——绝不静默放绿
  3. minKappa: judge 与人工标注的 Cohen's kappa 必须 ≥ 阈值(runner 默认配 0.6),低于即判 judge 失准。 配了阈值但 kappa 缺失同样 fail-closed。
  4. maxUnknownRate: judge 返回 unknown 的比例超阈即违规(runner 默认 0.25)。
  5. 返回 { pass: violations.length === 0, violations }——任何一条违规即不绿。

scripts/eval-gate.tspnpm eval:gate无 key,只读已存报告):

  1. latestReport():扫 agent-evals/reports/,取最新 *.json,抽出 completionRateunknownRatejudgeHumanKappa.kappa
  2. 无报告时优雅跳过exit(0) 并提示「先用 key 跑 pnpm eval:agent」——本地无报告不阻断。
  3. thresholds 写死{ maxCompletionDrop: 0.05, minKappa: 0.6, maxUnknownRate: 0.25 }
  4. baseline 取 agent-evals/baseline.json(不存在则空对象 {} → 不检查 completion drop)。
  5. 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. 今日实战

  1. 容器内重跑:docker run ... pnpm eval:agentsrc/agent/eval/tasks.ts29 任务,比对本地数字。
  2. 守门:pnpm eval:gatesrc/agent/eval/gate.ts(fail-closed)+ scripts/eval-gate.ts,确认无回归违规。
  3. 提交 README 运行说明:docker build / run / eval 三步,让他人可复现。
  4. 设计(非接通)CI 触发 docker build 的链路草图,标注其为 B9 待接。
  5. 比对「容器内通过率 == 本地通过率」,差异即环境漂移,逐项排查 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.dockerignoreresilience.ts、eval gate)已就绪
    • docker 构建 / run、TTFT、worklog 发布为待执行的手动 / 外部步骤,诚实标注。

5. 常见误区 / 陷阱

  1. 「本地能跑」当部署证明: env parity 没验,容器里可能因 Node 版本 / 依赖解析 / locale 漂移而数变。必须容器内重跑对齐。
  2. gate 在指标缺失时静默放绿: 违背 fail-closed。gate.ts 把「current 缺失 / 非有限」判为 violation,不能图省事改成跳过。
  3. 用自评循环作 baseline: 被 source-agnostic 修复取代;agent-evals/baseline.json 选定后才接入 gate,且 baseline 不能来自自评。
  4. 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.tsscripts/eval-gate.tssrc/agent/eval/tasks.tssrc/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:部署 + 韧性 + 可观测 + 独立红队)