四块互相交织的 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>
113 lines
4.5 KiB
Python
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())
|