AI Agent 的 Demo 越做越惊艳,可一接真实流量就翻车:改了提示词没回归、工具调用悄悄越权、效果好坏全靠人肉盯日志,交付节奏被一次次打回。

常规做法明显不够用——仅靠 Prompt 里写一句「禁止输出敏感信息」,模型根本不保证遵守;靠人工 review 每次改动,评审者看不到执行轨迹,只能凭直觉点头;出了问题再翻日志救火,等发现时事故已经发生。

本文分享一套可直接落地的 Harness Engineering 门禁实践:结构化留痕 + 离线评测打分 + 策略脚本阻断,配合分阶段灰度,让 Agent 从「能跑」变成「可控交付」。

一、门禁思维:Agent 交付要拦住三类风险

先明确一个前提:门禁不是为了卡人,而是为了把风险在合入前拦住:

  • 效果回退:一次提示词或模型版本调整,让历史好用的能力悄悄变差,没有基线就发现不了。
  • 行为越界:Agent 自主调用工具时读了不该读的数据、说了不该说的话,运行时才发现等于事故。
  • 不可回溯:出问题时拿不出「当时跑了什么、模型答了什么、调了哪个工具」,只能靠猜。
维度「能跑」阶段「可控交付」阶段
判定标准演示链路不报错指标达阈值且可回滚
失败处理上线后人工救火门禁自动阻断合入
责任归属出事再翻日志每次改动有证据留痕

问题:靠 Demo 和口头确认推进交付;治理:把三类风险翻译成可执行的检查项,让机器来判定放不放行。

核心结论: 门禁的本质是把「我觉得没问题」换成「数据说可以合」。

在这里插入图片描述

二、可观测留痕:让每次执行变成可查证据

要让门禁有据可依,先给每次执行落一套结构化证据:

  • 统一 Schema:固定字段记录 trace_id、span、状态、耗时与 token 消耗,跨版本才能横向比对。
  • 全链路 Span:计划、工具调用、最终回答各占一个 span,失败时能直接定位到是哪一环。
  • 原文入档:输入输出原文单独存储,脱敏后保留,供评测与事故复盘调用。

下面这段 Python 生成一份最小可用的执行轨迹,适配任何 Agent 框架的埋点后统一上报:

import json, time, uuid

def make_span(name, parent, status, latency_ms, tokens_in, tokens_out):
    return {
        "trace_id": uuid.uuid4().hex[:16],
        "span": name, "parent": parent, "status": status,
        "latency_ms": latency_ms,
        "tokens": {"in": tokens_in, "out": tokens_out},
        "ts": time.time(),
    }

spans = [
    make_span("plan", None, "ok", 820, 1200, 260),
    make_span("tool:search", "plan", "ok", 1450, 400, 0),
    make_span("answer", "tool:search", "ok", 960, 2100, 480),
]
print(json.dumps(spans, ensure_ascii=False, indent=2))

强埋点 → 可回放 → 有证据:没有轨迹的 Agent,任何门禁都是空转。

三、离线评测:用黄金集给每次改动打分

改动该不该合,不能凭手感,要交给固定评测集算分:

  • 黄金集:从真实工单里挑 30~100 条问答对,标注期望答案,版本之间复用同一份。
  • 门槛分:先跑一版拿到基线分,此后任何改动低于基线即阻断,防止一点点回退。
  • 多维打分:除准确率外,把延迟、敏感词命中、工具调用正确率一并纳入门槛。

下面这段 Python 是可直接跑的最小编分器,替换成你的 runner 即可接入 CI:

GOLDEN = [
    {"q": "退款多久到账?", "expect": "3-5 个工作日"},
    {"q": "发票能重开吗?", "expect": "当月可申请一次"},
]

def score(runner, threshold=0.9):
    hits = sum(runner(item["q"]) == item["expect"] for item in GOLDEN)
    acc = hits / len(GOLDEN)
    return {"acc": round(acc, 3), "pass": acc >= threshold}

print(score(lambda q: "3-5 个工作日" if "退款" in q else ""))
# {'acc': 0.5, 'pass': False}
指标含义建议门槛
回答准确率与黄金集期望答案一致≥ 基线分
P95 延迟95 分位端到端耗时≤ 4000 ms
越权调用率触发未授权工具的比例= 0

核心结论: 评测集是唯一的度量衡,没有它,所谓优化只是换了个方向波动。

在这里插入图片描述

四、策略阻断:把护栏写成可执行脚本

分数达标只是一半,另一半是把红线规则固化成检查脚本:

  • 钩子前置:把检查挂在 pre-commit 或 CI 首个 job,问题在合入前暴露,成本最低。
  • 失败即阻断:任何一项返回非零就终止流水线,不给「先合了再补」留口子。
  • 规则即代码:敏感词、延迟上限、调用白名单全部写进脚本,评审时 diff 可见。

下面是一份可直接放进仓库 .gates/ 目录的门禁脚本,适配 Linux + Python 环境:

#!/usr/bin/env bash
# 交付门禁:CI 或 pre-commit 调用,任一失败即退出非零
set -euo pipefail

python eval/golden_set.py --threshold 0.9      # 评测分达标
python lint/agent_trace.py --max-p95 4000      # P95 延迟上限 4s
python policy/leak_check.py --deny "身份证号"   # 敏感输出拦截

echo "GATE PASSED"

脚本化红线 → 机器判定 → 零口头放行:规则一旦写成代码,就没有「这次特殊」。

五、灰度放行:分阶段收紧爆炸半径

静态检查全绿也别急着全量,放行要按阶段逐级加压:

  • 影子流量:新版本只旁路记录不返回用户,先比对新旧回答差异,零用户风险。
  • 小流量实测:切 5% 真实流量,盯评测分、延迟与人工投诉三条曲线。
  • 一键回滚:开关与版本号绑定,任一指标越界秒级切回上一版本,不留中间态。
阶段流量放行条件回滚动作
影子0%新旧回答一致率 ≥ 95%丢弃新版本
小流量5%指标无劣化持续 24h开关切回
全量100%无告警持续 72h保留热备版本

核心结论: 灰度不是拖延上线,而是把「不可逆的大事故」拆成「可逆的小实验」。

在这里插入图片描述

六、两周落地:按四步把门禁装进流水线

门禁不必一步到位,两周即可跑出最小闭环:

  1. 第一周·留痕:接入统一 trace Schema,跑通一条链路的全 span 上报,导出 JSON 可查。
  2. 第二周前半·评测:从工单里筛出 30 条黄金集,跑出基线分并写入仓库。
  3. 第二周后半·阻断:把评测、延迟、敏感词三个检查挂进 CI,设为合入必过项。
  4. 随后两周·灰度:新版本先影子再 5% 流量,验证回滚开关真的能在秒级生效。

落地时最容易踩的坑,是把门禁做成「事后补作业」:先有证据,再有分数,最后才有阻断,顺序颠倒只会让团队绕开门禁。每道门都要能独立回答一个问题——出了事查得到吗、改了动更差吗、越了权拦得住吗。

核心结论: 顺序不能乱:留痕 → 评测 → 阻断 → 灰度,四步各解决一个不可控。

结语

Harness Engineering 的价值不在框架有多新,而在于把 Agent 交付从「玄学」变成「工程」:结构化留痕让事故可回溯,离线评测让效果可度量,策略脚本让红线可执行,灰度放行让风险可回滚。这套门禁不绑定任何特定 Agent 框架,脚本与表格都能直接抄进你自己的仓库。

先把最小闭环跑起来——一条 trace、一份黄金集、三个检查项,你会发现 Agent 的「敢上线」不是靠胆量,而是靠门禁。

能跑是运气,可控才是工程;门禁装上,Agent 才算真正交付。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐