S21-X18 · Agent 的协议、执行模式与评测口径:A2A、Twelve-Factor 与四类污染
Agent 项目最容易在两个地方翻车:协作——两个 Agent 靠自然语言传话,接口一变就全线崩;评测——同一份测试集被反复调参,分数不再代表能力。前者要协议,后者要口径。这篇把协议层(Agent2Agent)、授权层(OAuth2.0)、交付规范(Twelve-Factor)、生成式脚手架与五种执行模式、七个评测指标串成一条链。
1. Agent2Agent:协议先定"谁暴露什么"
A2A(Agent2Agent)的核心不是"让两个模型聊天",而是把 Agent 当服务描述:可发现的描述(agent card,声明能力与端点)、任务(task)的生命周期、产出物(artifact)。这三个词定下来,跨团队对接才有落脚点。
{
"name": "invoice-auditor",
"description": "校验发票字段与金额一致性",
"url": "https://agents.internal/invoice-auditor",
"version": "1.4.0",
"capabilities": { "streaming": true, "pushNotifications": false },
"skills": [
{ "id": "audit", "inputModes": ["text/plain", "application/json"],
"outputModes": ["application/json"] }
],
"authentication": { "schemes": ["Bearer"] }
}
| 概念 | 作用 | 不定清楚会怎样 |
|---|---|---|
| agent card | 能力与端点的声明 | 调用方只能靠文档口口相传,版本漂移即失效 |
| task | 一次有状态的工作单元 | 难以中断、恢复、追进度 |
| artifact | 任务的产出物 | 结果只能塞在文本里,难以结构化校验 |
| message | 交互的消息载体 | 上下文丢了就只能整段重发 |
| streaming / push | 长任务的通知方式 | 长任务只能轮询,成本高 |
任务状态机要显式定义,不要用自然语言描述"处理中":
submitted → working → (input-required → working)* → completed | failed | canceled
input-required 这一档最容易被省掉,省掉之后所有"需要人补一句"的场景都只能靠重跑。
2. OAuth2.0:Agent 的身份不是"用户的密码"
Agent 访问工具与数据时,权限模型通常是 OAuth2.0 的两种形态:client credentials(以 Agent 自身身份访问自有资源)与授权码/令牌交换(代表用户行事,需用户授权与 scope 收敛)。
| 形态 | 谁授权 | scope 粒度 | 适用 |
|---|---|---|---|
| client credentials | 平台自己 | 服务级 | Agent 访问自有 API、内部知识库 |
| 授权码 + PKCE | 终端用户 | 用户级 + 细粒度 scope | Agent 代用户读邮件、改日程 |
| 令牌交换 | 上游令牌换下游令牌 | 降权(scope 收窄) | 多服务串联,避免全权令牌扩散 |
| 服务账号 | 运维配置 | 粗 | 批处理任务 |
$ curl -s -X POST https://auth.internal/oauth2/token \
-d grant_type=client_credentials \
-d client_id=invoice-auditor \
-d client_secret="$SECRET" \
-d scope="invoice.read invoice.write"
三条纪律:令牌不落日志、scope 按最小集申请、调用前校验 scope 而非调完再报错。Agent 的自主循环会放大权限问题:一次误授权,循环里可能被调用上百次(典型量级)。
3. Twelve-Factor:哪些因子对 Agent 服务仍然成立
Twelve-Factor(12-Factor App)是服务化时代的交付规范。Agent 服务在"进程模型"和"状态"上偏离得最多,其余因子基本照用。
| 因子 | 在 Agent 服务里 | 是否仍成立 |
|---|---|---|
| 代码库 + 依赖显式声明 | 模型权重、提示词、工具 schema 都要进版本 | 成立,但要扩展到"非代码资产" |
| 配置与代码分离 | 模型名、温度、超时、预算都是配置 | 成立 |
| 后端服务视为附加资源 | 模型 API、向量库、沙箱都是可替换资源 | 成立 |
| 构建/发布/运行分离 | 提示词变更也是发布 | 成立,需纳入发布流程 |
| 进程无状态 | 会话、记忆必须外置 | 成立,但"外置"成本高 |
| 端口绑定 | 自托管 Agent 需暴露服务端口 | 成立 |
| 并发靠进程模型扩展 | 长任务要能中断与重入 | 部分成立,需补任务队列 |
| 可丢弃性 | 进程被杀不丢任务 | 要靠任务状态机落实 |
| 环境等价 | 开发/生产的模型版本必须一致 | 最容易破的一条 |
| 日志作为事件流 | 每次工具调用都是事件 | 成立 |
| 管理进程一次性 | 索引重建、评测跑批 | 成立 |
| 管理进程与常驻进程同源 | 评测脚本与线上代码同仓库 | 成立 |
最常破的是"环境等价":本地小模型调试、线上大模型,提示词两边表现完全不同。把模型版本写进配置并纳入发布审计,比事后排查便宜得多。
4. Services-as-Software 与生成式脚手架:GPT-Engineer 一类工具的边界
"服务即软件"(Services-as-Software)把能力做成可被调用的服务单元,而不是可下载的程序。这个视角对 Agent 很贴切:价值在编排已有服务,而不是重写它们。
生成式脚手架(GPT-Engineer 是早期代表,AutoGPT(Significant-Gravitas 组织维护)把"自主循环"走得更远)解决的是"从一句需求到可运行骨架"。产出特征是:
| 产出 | 可用性 | 需要补什么 |
|---|---|---|
| 目录结构与入口文件 | 高 | 依赖版本、构建脚本 |
| 数据模型与接口雏形 | 中 | 校验、错误码、幂等 |
| 测试骨架 | 低 | 断言、边界用例、数据构造 |
| 部署配置 | 低 | 资源限制、健康检查、密钥注入 |
把这类工具当"第一次提交的作者"而不是"交付者":骨架进仓库,然后按 Twelve-Factor 补齐配置、状态、可观测。跳过这一步,脚手架带来的速度会在第一次线上故障时全部还回去。
5. 五种执行模式:Single-Task、Self-Feeding、Multi-Level、Step-Back、Self-Reflection
| 模式 | 结构 | 解决的问题 | 代价 |
|---|---|---|---|
| Single-Task | 一问一答,无循环 | 简单确定性任务 | 复杂任务一次做不完 |
| Self-Feeding | 上一步输出喂给下一步,循环直到收敛 | 迭代式改进(写作、重构) | 会放大错误,需要停机条件 |
| Multi-Level | 分层:规划层 → 执行层 → 校验层 | 长任务分解、职责隔离 | 层间协议成本,调试链变长 |
| Step-Back | 先抽象出原则,再回到具体问题 | 细节淹没时的方向修正 | 多一次调用,可能答非所问 |
| Self-Reflection | 产出后自我批判并修订 | 格式错误、遗漏约束 | 容易自我肯定,需要外部校验 |
Self-Feeding 的停机条件(必须显式写):
1) 连续两轮 diff 为空
2) 达到最大轮次 max_rounds
3) 校验器通过
4) 预算耗尽
任一满足即停;没有停机条件的循环等于无限循环。
Step-Back 与 Self-Reflection 常被混用,方向不同:前者向上抽象(这条规则适用哪些情况),后者向内检查(这次输出违反哪条约束)。前者治跑偏,后者治格式与遗漏。
Multi-Level 的关键是层间协议结构化,别用自然语言传"我觉得应该……"。规划层输出任务列表(JSON),执行层逐条返回状态,校验层只判定不改写。
6. 评测口径:七个指标与四类污染的对应关系
评测最容易出的不是"分数低",而是"分数不可信"。七个指标各自防一种失效:
| 指标 | 定义 | 防的是哪种失效 |
|---|---|---|
| Outcome-based | 只看最终任务是否达成,不看中间步骤 | 步骤漂亮但结果错 |
| Per-test-case | 每个用例独立计分,不跨用例汇总 | 一个用例的运气拉平均分 |
| Cross-contamination | 测试用例之间是否互相泄漏信息 | 前一题的答案出现在后一题 |
| Cross-user | 不同用户/会话的数据是否隔离 | 上下文串味、隐私泄漏 |
| Relevance_Score | 检索或回答与问题的相关度 | 答得通顺但不切题 |
| Final_Score | 加权后的总分 | 单一指标被过度优化 |
| Delta-Based | 与基线版本的差值,而非绝对值 | 不同批次不可比 |
{
"run_id": "eval-20261109-03",
"model": "qwen3-4b",
"mode": "outcome_based",
"cases": [
{ "case_id": "c-017", "passed": true, "relevance": 0.86, "delta_vs_baseline": 0.04 },
{ "case_id": "c-018", "passed": false, "relevance": 0.41, "delta_vs_baseline": -0.22,
"failure": "missing_field", "artifact": "out/c-018.json" }
],
"final_score": 0.74,
"contamination_check": { "cross_contamination": 0, "cross_user_leak": 0 }
}
四类污染的落地做法:
1) Cross-contamination:每个用例在独立上下文里跑,禁用历史缓存;
2) Cross-user:评测数据按用户分片,禁止跨用户共享检索索引;
3) 记忆污染:评测前清空长期记忆,或用只读快照;
4) 提示词污染:被测提示词与评测脚本同版本,禁止评测中途改配置。
Delta-Based 最被低估:绝对分数会随测试集难度漂移,只有同一测试集、同一基线、同一模型版本下的差值才可比。所以每次评测要落 run_id、模型版本、提示词哈希、测试集版本四元组。
7. 排错:六个"跑通了但不可信"的现象
| 现象 | 原因 | 处理 |
|---|---|---|
| 分数突然大涨 | 测试集被用于调参 | 换留出集,重跑基线 |
| 同一 run 两次结果不同 | 温度不为 0、并发顺序不定 | 固定种子与执行顺序 |
| 用例答案互相引用 | 上下文共享了缓存 | 每用例独立上下文 |
| 多用户数据串味 | 索引未按用户分片 | 加命名空间或按用户隔离 |
| 任务卡死不返回 | Self-Feeding 缺停机条件 | 加轮次上限与预算闸门 |
| 协议升级后全量失败 | card 版本与实现不一致 | 版本号纳入契约测试 |
8. 边界:什么时候不该再加 Agent
| 场景 | 为什么 Agent 不合适 | 该用什么 |
|---|---|---|
| 固定流程、输入格式稳定 | 自主性带来不确定性,收益为负 | 确定性流水线 |
| 强合规审计 | 每步都要可复现 | 规则引擎 + 人工签核 |
| 延迟敏感(毫秒级) | 多轮调用满足不了 | 单次小模型或缓存 |
| 需要精确计算 | 模型不做算术保证 | 工具调用 + 代码执行 |
| 评测样本极少 | 区分不出模型差异 | 先攒数据再选型 |
一句话:协议定能不能对接,模式定跑不跑得完,口径定分数算不算数。
10. 自测
- A2A 里没有 agent card,对接会退化成什么状态?
- 服务凭证模式与授权码模式,scope 粒度差在哪里?
- Twelve-Factor 里哪一条在 Agent 服务上最容易被破坏?
- Self-Feeding 的停机条件至少要写几条,分别是哪几条?
- Step-Back 与 Self-Reflection 的方向差别是什么?
- 为什么 Delta-Based 分数比绝对分数更可信?