Files
Agentswarm/scripts/test-benchmark-capture.py
T
FastheiandClaude Opus 4.8 94ef894ace feat(benchmark): 阶段2 数据采集 — 真实 run 终态自动采集 SwarmMetrics 并落库(+可选导出)
把采集器接进真实运行,让数据自己累积,为后续【经验标定】O/系数/S_gain 攒真实
用户数据(这些量只能由真实运行事后标定,不能先验写死)。本阶段不编任何公式。

- orchestrator/main.py:run 终态(completed/failed)在终态事件后调 capture_run_metrics。
  只读 run 状态、try/except 包裹绝不失败 run;默认开,BENCHMARK_CAPTURE=0 可关。
- orchestrator/swarm_runtime.py:record_benchmark_metrics → 落 run.metadata['benchmark']。
- benchmark/collectors/capture.py:collect(run_collector) → 持久化 → 可选导出(to_thread)。
- benchmark/export/:MetricsExporter;默认 NoopExporter(无依赖/无凭据);
  BENCHMARK_EXPORT_TARGET=blob 启用 Azure Blob(连接串或 Workload/Managed Identity,
  凭据经环境注入,绝不写进代码),归档 <scenario>/<swarm_id>.json。

诚实:缺项指标 NaN→null,不伪造(规则#9)。

验证:新增 test-benchmark-capture.py + 现有 collector/selfcert + 契约冒烟(runtime/
merge/freeze)全 PASS。

影响范围:agent_swarm。运行时新增**只读**终态钩子(不改派发/执行/契约/计费/审计字段);
导出默认关,无密钥落地。Client/Manager/Agnet/CodeGW/发布链路不涉及。
依赖:benchmark/ 需在镜像内(quality.py 早已 import benchmark;由 #44 Dockerfile 修复覆盖)。

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

99 lines
3.8 KiB
Python

"""Test 阶段2 benchmark data capture: terminal run → SwarmMetrics persisted to run.metadata.
Hermetic (REDIS_FAKE), default NoopExporter (no storage dependency). Verifies:
- capture_run_metrics persists run.metadata['benchmark'] with metrics + coverage.
- real metrics are numbers; uncollected ones are null (NaN→null, never fabricated).
- default export is a no-op and does not fail the capture.
Run: python scripts/test-benchmark-capture.py
"""
import asyncio
import json
import os
import sys
from pathlib import Path
os.environ["REDIS_FAKE"] = "1"
os.environ.pop("BENCHMARK_EXPORT_TARGET", None) # ensure default Noop
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from orchestrator.redis_client import redis_client
from orchestrator import swarm_runtime as sr_mod
from orchestrator.swarm_runtime import swarm_runtime
from orchestrator.task_queue import task_queue, TaskStatus
from benchmark.collectors.capture import capture_run_metrics
failures = []
def check(name, cond):
print(("PASS" if cond else "FAIL"), "-", name)
if not cond:
failures.append(name)
async def _noop(self, *a, **k):
return None
async def add_task(run, task_id, *, status, agent, cost=0.0, retry=0, depends_on=None):
t = await task_queue.create_task(task_id=task_id, description=task_id,
agent_role=task_id.split("-")[-1], depends_on=depends_on or [],
enqueue=False)
t.status = status
t.assigned_agent_id = agent
t.retry_count = retry
t.result = json.dumps({"usage": {"model_cost_usd": cost}})
await task_queue._save_task(t)
await swarm_runtime.attach_task(run, t.task_id)
return t
async def main():
await redis_client.connect()
sr_mod.SwarmRuntime._post_callback = _noop
body = {
"mode": "swarm",
"requirement": {"objective": "capture test"},
"orchestration_plan": {"budget": {"max_cost_usd": 10}},
"callback": {"url": "http://127.0.0.1:9/cb", "subscribed_events": []},
"metadata": {"manager_deployment_id": "m-cap", "scenario": "coding"},
}
run, _ = await swarm_runtime.get_or_create_run(body=body, idempotency_key=None, correlation_id="c")
await add_task(run, "t-implementation", status=TaskStatus.COMPLETED, agent="A", cost=2.0)
await add_task(run, "t-testing", status=TaskStatus.COMPLETED, agent="B", cost=3.0)
payload = await capture_run_metrics(run)
# persisted onto the run
reloaded = await swarm_runtime.get_run(run.swarm_id)
bench = (reloaded.metadata or {}).get("benchmark")
check("run.metadata['benchmark'] persisted", isinstance(bench, dict))
check("payload scenario carried", bench.get("scenario") == "coding")
check("metrics dict present", isinstance(bench.get("metrics"), dict))
check("coverage dict present", isinstance(bench.get("coverage"), dict))
# s_completion real (2/2*100 = 100), serialized as a number
check("s_completion real (100)", bench["metrics"].get("s_completion") == 100.0
and bench["coverage"].get("s_completion") is True)
# uncollected metric (gain) → null, coverage False (no fabrication)
check("s_gain null + coverage False", bench["metrics"].get("s_gain") is None
and bench["coverage"].get("s_gain") is False)
# JSON-serializable (would raise on NaN-as-float being non-serializable only with allow_nan=False;
# we converted NaN→None, so strict dumps must succeed)
try:
json.dumps(payload, allow_nan=False)
check("payload strictly JSON-serializable (no NaN)", True)
except ValueError:
check("payload strictly JSON-serializable (no NaN)", False)
print()
if failures:
print(f"{len(failures)} capture check(s) FAILED: {failures}")
sys.exit(1)
print("all benchmark capture checks passed")
if __name__ == "__main__":
asyncio.run(main())