Files
Agentswarm/scripts/test-quality.py
T
Songhaoz666andClaude Opus 4.8 d487923646 benchmark: 落地决策层(τ/η/P)、质量(Q_quality)、通信遥测;关闭 #10 #23
四块互相交织的 benchmark 覆盖增量,统一提交:

1) 通信遥测(#23):orchestrator 路由 peer 消息时按 correlation_id 计请求/应答到
   SwarmRun.collaboration(内部状态,不进 Manager 事件流);collector 算 s_communication。
   治理计数由 run.approvals 派生(合规/总数)→ s_governance。

2) Q_quality 掩码归一(v2.1 裁定):metrics.quality_score 改为对 present 输入加权归一,
   非编码任务自动忽略 TestPassRate,全缺 → NaN(不伪造)。

3) 质量插桩 / Group B:新增 Pod 内代码测试沙箱(orchestrator/sandbox.py,环境清洗 +
   超时强杀 + 资源限额 + 路径越界校验,门控 ENABLE_QUALITY_EVAL)与 held-out fixture
   (benchmark/fixtures/);run 完成时用留出测试评分得 TestPassRate → Q_quality →
   collector 合成 reward。安全边界见 docs/integration/security-boundary.md §8.1。

4) 决策引擎 / Group A(#10,Option A score-at-pull):新增 orchestrator/decision_engine.py
   —— 信息素 τ(Redis 持久、(role,agent) 键控、冷启动 0.5、ρ 蒸发、夹紧、学习常开)+
   η 启发式评分 + ε-greedy 概率采样;每次 dispatch 产一条 DecisionTrace →
   SwarmRun.decisions;collector 算 tau/eta/p_decision。概率选择门控 ENABLE_ACO_DISPATCH
   (默认关,CI 用 ACO_SEED 固定)。

覆盖:单次 run 真实可算字段由 4 提升至最多 10/15(新增 communication/reward/tau/eta/
p_decision,外加 governance 有条件)。

测试:新增 test-sandbox / test-quality / test-decision-engine;扩充 collector/metrics 用例;
CI 纳入全部 benchmark 套件 + flag-on 的 ACO e2e。本地 11 项 gate 全绿。

诚实边界(未越界声称):
- Group A 为单边匹配(Option B 待 Group C);概率派发优于贪心未证;默认关闭。
- reward 的 CodeReview/UserAcceptance 未采集(掩码忽略);P_risk 为审批派生低估。
- s_gain/s_swarm/g_e/g_e_cost/benchmark 仍 NaN —— 需基线(#21/#13),本 PR 不动验收。

影响范围:Swarm(orchestrator + benchmark + docs + CI)。不改 Manager↔Swarm 事件契约
(遥测均为运行时内部状态);不影响 Client/计费/密钥/发布链路。新增 ENABLE_QUALITY_EVAL /
ENABLE_ACO_DISPATCH 两个开关,默认关闭。

Closes #10
Closes #23

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

113 lines
4.5 KiB
Python

"""Integration test for benchmark Group B: fixture grading -> Q_quality -> reward.
Boots the in-memory store, seeds a completed run whose tasks carry generated code, grades it
against the held-out `add_function` fixture in the sandbox, and asserts Q_quality and reward
become real. Hermetic, no model key. Run from agent_swarm_v6: python scripts/test-quality.py
"""
import asyncio
import json
import math
import os
import sys
from pathlib import Path
os.environ["REDIS_FAKE"] = "1"
os.environ["ENABLE_QUALITY_EVAL"] = "1" # gate ON for this test (executes code in the sandbox)
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 orchestrator.quality import evaluate_run_quality, collect_generated_files
from benchmark.collectors.run_collector import SwarmRunMetricsCollector
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
# A correct implementation + the swarm's OWN test (which must NOT be the grader).
IMPL = "def add(a, b):\n return a + b\n"
RESULT = {
"success": True,
"subtasks": [
{"status": "completed", "files": [{"path": "calc.py", "action": "write", "content": IMPL}]},
{"status": "completed", "files": [{"path": "test_calc.py", "action": "write",
"content": "from calc import add\ndef test_self():\n assert add(1, 1) == 2\n"}]},
],
"usage": {"model_cost_usd": 2.0},
}
async def add_completed_task(run, task_id, result):
t = await task_queue.create_task(task_id=task_id, description=task_id,
agent_role=task_id.split("-")[-1], depends_on=[], enqueue=False)
t.status = TaskStatus.COMPLETED
t.assigned_agent_id = "A"
t.retry_count = 0
t.result = json.dumps(result)
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": "Write add(a,b)"},
"orchestration_plan": {"budget": {"max_cost_usd": 10}},
"callback": {"url": "http://127.0.0.1:9/cb", "subscribed_events": []},
"metadata": {"manager_deployment_id": "m-q", "benchmark_fixture_id": "add_function"},
}
run, _ = await swarm_runtime.get_or_create_run(body=body, idempotency_key=None, correlation_id="cq")
impl_task = await add_completed_task(run, "t-implementation", RESULT)
tasks = [impl_task]
# file separation: impl vs the swarm's own tests
files = collect_generated_files(tasks)
check("collect splits impl vs agent tests",
[f.path for f in files["impl"]] == ["calc.py"] and len(files["agent_tests"]) == 1)
# grade against held-out fixture tests in the sandbox
quality = await evaluate_run_quality(run, tasks)
check("quality produced", quality is not None)
check("fixture TestPassRate = 100 (4/4 held-out)", quality and quality["test_pass_rate"] == 100.0)
check("Q_quality = 100 (masked: only test_pass present)", quality and quality["q_quality"] == 100.0)
check("agent self-test kept as separate signal", quality and quality["agent_test_pass_rate"] == 100.0)
check("code_review/user_acceptance remain uncollected (None)",
quality and quality["code_review_score"] is None and quality["user_acceptance"] is None)
# record + simulate a 30s run so V_speed is computable
await swarm_runtime.record_quality(run, quality)
run.created_at = run.updated_at - 30.0
await swarm_runtime.save_run(run)
collector = SwarmRunMetricsCollector(run.swarm_id)
m = await collector.collect()
check("reward is now REAL (not NaN)", not math.isnan(m.reward) and collector.coverage["reward"] is True)
# gain/p_decision/benchmark must still be NaN — Group B does not close them
check("gain still NaN (needs baselines)", math.isnan(m.s_gain) and collector.coverage["s_gain"] is False)
check("benchmark still NaN (aggregate)", math.isnan(m.benchmark) and collector.coverage["benchmark"] is False)
print()
if failures:
print(f"{len(failures)} quality check(s) FAILED: {failures}")
sys.exit(1)
print("all Group B quality checks passed")
if __name__ == "__main__":
asyncio.run(main())