Files
Agentswarm/benchmark/baselines/comparison.py
T
Songhaoz666andClaude Opus 4.8 baa67350e6 benchmark Group C:基线运行器 + 统一 BenchmarkRunRecord + 报告/回放(Closes #21)
在去中心化重构之上落地 benchmark 对比管线:5 个系统(single/strong/chain/sub_agent/swarm)
跑同一任务集、同一执行后端,产出统一 BenchmarkRunRecord → 评估器算 G_E/G_E,c → 报告 + 回放。

- benchmark/runners/:backend(Offline 确定性 / OpenAI 真实)+ base + 5 个 runner。各 runner
  用 held-out fixture 测试在 Group B 沙箱里评分得 TestPassRate(权威,非自评)。
- benchmark/tasksets/:统一任务集 + 加载器(coding-set-1,1 个 fixture)。
- benchmark/reports/、benchmark/replay/:G_E/G_E,c/coverage/confidence + 归档。
- benchmark/baselines/comparison.py:BenchmarkRunRecord 的 CodeReview/UserAcceptance 改为
  Optional(掩码归一,未采集即 None,规则 #9)。
- scripts/run-benchmark-suite.py harness + scripts/test-benchmark-runners.py。

与去中心化重构对齐:swarm runner 拓扑已**重指向去中心化流程**(种子→自选→自主分解→竞争→
同伴交叉评审→收敛,calls=6/review=1),非旧 Master「分解→派发→单评审」。仍用同一离线后端
建模以保证公平对比(驱动活体编排器会换后端→记录不可比;活体全流程由 test-workflow-e2e 验证)。

沙箱适配:runner 评分走 fail-closed 沙箱(#24),故 test + CI 步骤设 HEICODE_SANDBOX_ISOLATED=1
(仅 CI/隔离 Pod)。

影响范围:agent_swarm(benchmark 层 + 测试 + docs + CI)。不碰 orchestrator 编排逻辑、
不改 Manager↔Swarm 契约、不影响 Client/计费/密钥/审计/发布链路。

诚实边界:
- **离线后端只验证管线**:所有系统拿同一参考解 → quality 相同 → G_E=0、swarm_valid=False,
  刻意不显示蜂群优势(反造假)。真实 G_E>0 需 --backend openai + 足量冻结任务集 + 多次运行。
- 故 Closes #21(运行器 + 统一记录已落地并产出合规非 NaN 记录);Refs #20(仅 1/5 场景)、
  Refs #22(评估器/报告/回放已建,但 Quality 仅 TestPassRate,CodeReview/UserAcceptance 缺)、
  Refs #13(验收 EPIC,需真实 run 证明 Swarm>baselines,未满足)。

Closes #21
Refs #20
Refs #22
Refs #13

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:44:34 +08:00

134 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Baseline comparison adapter — Agent 蜂群指标量化与标准 v2.0.
Defines the shared `BenchmarkRunRecord` that BOTH the swarm and each baseline (Single / Chain /
Sub-Agent / Strong) emit per benchmark run, plus the evaluator that computes G_E and G_E,c from
two records. Pure/deterministic given records — the records themselves must be PRODUCED by the
respective programs (instrumentation, esp. Quality, is pending; see
docs/benchmark/baseline-record-schema.md). No record → no comparison (we never fabricate scores).
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
from ..metrics import (
quality_score, completion_score, speed_score, cost_score, robustness_score,
emergence_gain, swarm_cost, cost_normalized_gain, BASE_COEFFICIENTS,
)
SYSTEMS = {"swarm", "single", "chain", "sub", "strong"}
# Quality sub-inputs that may legitimately be absent (masked-renormalized in quality_score, rule #9).
_OPTIONAL_FIELDS = {"code_review_score", "user_acceptance"}
@dataclass
class BenchmarkRunRecord:
"""Unified per-run record (v2.0 §9 collection). Both swarm and baselines emit this.
CodeReview/UserAcceptance are Optional: a runner that can only measure TestPassRate (the
common case today — no reviewer/acceptance signal wired) leaves them None, and quality()
renormalizes over the present inputs. None ≠ 0 (never fabricate a score, rule #9).
"""
system: str # one of SYSTEMS
scenario: str # coding | refactoring | architecture | devops | bugfix
task_set_id: str # identifies the shared task set (fairness: same id across systems)
n_agent: int # agent count used by this system
completed_tasks: int
total_tasks: int
test_pass_rate: float # 0..100 (Quality input — held-out fixture tests)
budget_usd: float # planned cost
actual_cost_usd: float # measured model cost
model_tokens: int
target_time_s: float # planned/target time
actual_time_s: float # measured time
recovered_failures: int
total_failures: int
code_review_score: Optional[float] = None # 0..100, None when not collected
user_acceptance: Optional[float] = None # 0..100, None when not collected
@classmethod
def from_dict(cls, data: dict) -> "BenchmarkRunRecord":
required = [f for f in cls.__dataclass_fields__ if f not in _OPTIONAL_FIELDS]
missing = [f for f in required if f not in data]
if missing:
raise ValueError(f"benchmark record missing required fields: {missing}")
if data["system"] not in SYSTEMS:
raise ValueError(f"unknown system '{data['system']}' (expected one of {sorted(SYSTEMS)})")
return cls(**{f: data[f] for f in cls.__dataclass_fields__ if f in data})
# --- derived quantities (from a single record) ---
def quality(rec: BenchmarkRunRecord) -> float:
"""Q = 0.4·TestPass + 0.3·CodeReview + 0.3·UserAcceptance (v2.0 §4)."""
return quality_score(rec.test_pass_rate, rec.code_review_score, rec.user_acceptance)
def cost_efficiency(rec: BenchmarkRunRecord) -> float:
"""CostEfficiency = 100·Budget/ActualCost (v2.0 §4 E_cost口径)."""
return cost_score(rec.budget_usd, rec.actual_cost_usd)
def unified_metrics(rec: BenchmarkRunRecord) -> dict:
"""The protocol's unified collection (v2.0 §9): Completion / Quality / Cost / Time / Robustness."""
return {
"completion": completion_score(rec.completed_tasks, rec.total_tasks),
"quality": quality(rec),
"cost_efficiency": cost_efficiency(rec),
"speed": speed_score(rec.target_time_s, rec.actual_time_s),
"robustness": robustness_score(rec.recovered_failures, rec.total_failures),
}
# --- comparison (swarm vs one baseline) ---
def compare(swarm_rec: BenchmarkRunRecord, base_rec: BenchmarkRunRecord, *,
symmetric_cost: bool = True) -> dict:
"""Compute G_E and G_E,c for the swarm against one baseline record.
G_E = Q_swarm − Q_base
G_E,c = (Q_swarm / C_swarm) − (Q_base / C_base), C_swarm = N_agent·(CostEfficiency/100+0.5)
`symmetric_cost`:
- True (DEFAULT — Benchmark Owner ratified correction, 2026-06-09): `C_base` uses the SAME
formula as C_swarm with the baseline's own N_agent + CostEfficiency → both terms are
quality-per-cost (comparable, non-degenerate).
- False: literal v2.0 text — `C_base = 1.0` (scale-degenerate; kept for reference only).
See docs/benchmark/cost-normalized-gain.md and OWNER-NOTE-cost-normalized-gain.md.
"""
if swarm_rec.system != "swarm":
raise ValueError("swarm_rec.system must be 'swarm'")
if swarm_rec.task_set_id != base_rec.task_set_id:
raise ValueError("records must share the same task_set_id (fair comparison)")
q_swarm, q_base = quality(swarm_rec), quality(base_rec)
g_e = emergence_gain(q_swarm, q_base)
c_swarm = swarm_cost(swarm_rec.n_agent, cost_efficiency(swarm_rec))
c_base = swarm_cost(base_rec.n_agent, cost_efficiency(base_rec)) if symmetric_cost else 1.0
g_e_c = cost_normalized_gain(q_swarm, c_swarm, q_base, c_base=c_base)
return {
"base_system": base_rec.system,
"base_coefficient": BASE_COEFFICIENTS.get(base_rec.system),
"cost_mode": "symmetric" if symmetric_cost else "literal_v2.0",
"q_swarm": q_swarm,
"q_base": q_base,
"c_swarm": c_swarm,
"c_base": c_base,
"g_e": g_e,
"g_e_cost": g_e_c,
"raw_gain_positive": g_e > 0,
"cost_normalized_positive": g_e_c > 0,
}
def evaluate(swarm_rec: BenchmarkRunRecord, baseline_recs: list[BenchmarkRunRecord], *,
symmetric_cost: bool = True) -> dict:
"""Compare the swarm against all baselines. swarm_valid requires beating ALL on G_E and G_E,c."""
results = [compare(swarm_rec, b, symmetric_cost=symmetric_cost) for b in baseline_recs]
swarm_valid = bool(results) and all(r["raw_gain_positive"] and r["cost_normalized_positive"] for r in results)
return {
"scenario": swarm_rec.scenario,
"cost_mode": "symmetric" if symmetric_cost else "literal_v2.0",
"comparisons": results,
"swarm_valid": swarm_valid,
}