quantum-reasoning-skill 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/benchmark-result.yml +81 -0
- package/.github/ISSUE_TEMPLATE/bug-report.yml +38 -0
- package/.github/ISSUE_TEMPLATE/feature-request.yml +31 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- package/.github/workflows/npm-publish.yml +32 -0
- package/.github/workflows/publish-release.yml +79 -0
- package/.github/workflows/validate-skill.yml +154 -0
- package/CHANGELOG.md +51 -0
- package/CITATION.cff +17 -0
- package/CONTRIBUTING.md +88 -0
- package/LICENSE +21 -0
- package/README.md +133 -0
- package/SECURITY.md +17 -0
- package/SKILL.md +146 -0
- package/VERSION +1 -0
- package/benchmark/README.md +134 -0
- package/benchmark/cases.jsonl +6 -0
- package/benchmark/evaluate.py +257 -0
- package/benchmark/schemas/case.schema.json +20 -0
- package/benchmark/schemas/comparison.schema.json +64 -0
- package/benchmark/schemas/metadata.schema.json +53 -0
- package/benchmark/schemas/result.schema.json +25 -0
- package/benchmark/validate_submission.py +114 -0
- package/docs/COMPATIBILITY.md +44 -0
- package/docs/MEASUREMENT.md +131 -0
- package/examples/host-integration.md +57 -0
- package/examples/usage.md +31 -0
- package/index.js +1 -0
- package/package.json +1 -0
- package/reference/branch_controller.py +256 -0
- package/tests/test_benchmark.py +120 -0
- package/tests/test_reference.py +128 -0
- package/tests/test_submission_validation.py +78 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Evaluate baseline and quantum-reasoning runs from JSONL files.
|
|
2
|
+
|
|
3
|
+
The evaluator uses only the Python standard library. It does not call a model.
|
|
4
|
+
External runners can write one result row per case and this script will validate,
|
|
5
|
+
score, summarize, and compare those runs without inventing missing measurements.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import math
|
|
13
|
+
import statistics
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Iterable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
REQUIRED_RESULT_FIELDS = {
|
|
19
|
+
"case_id",
|
|
20
|
+
"answer",
|
|
21
|
+
"tokens",
|
|
22
|
+
"tool_calls",
|
|
23
|
+
"latency_ms",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
OPTIONAL_TELEMETRY_FIELDS = {
|
|
27
|
+
"branches_total",
|
|
28
|
+
"branches_distinct",
|
|
29
|
+
"revived_branches",
|
|
30
|
+
"recovered_errors",
|
|
31
|
+
"contradictions_found",
|
|
32
|
+
"contradictions_resolved",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def read_jsonl(path: Path) -> list[dict[str, Any]]:
|
|
37
|
+
rows: list[dict[str, Any]] = []
|
|
38
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
39
|
+
for line_number, raw in enumerate(handle, start=1):
|
|
40
|
+
line = raw.strip()
|
|
41
|
+
if not line:
|
|
42
|
+
continue
|
|
43
|
+
try:
|
|
44
|
+
value = json.loads(line)
|
|
45
|
+
except json.JSONDecodeError as exc:
|
|
46
|
+
raise ValueError(f"{path}:{line_number}: invalid JSON: {exc}") from exc
|
|
47
|
+
if not isinstance(value, dict):
|
|
48
|
+
raise ValueError(f"{path}:{line_number}: row must be a JSON object")
|
|
49
|
+
rows.append(value)
|
|
50
|
+
return rows
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def normalize_answer(value: Any) -> str:
|
|
54
|
+
return " ".join(str(value).strip().casefold().split())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def validate_case(case: dict[str, Any]) -> None:
|
|
58
|
+
for field in ("id", "domain", "prompt", "accepted_answers"):
|
|
59
|
+
if field not in case:
|
|
60
|
+
raise ValueError(f"case missing required field: {field}")
|
|
61
|
+
if not isinstance(case["accepted_answers"], list) or not case["accepted_answers"]:
|
|
62
|
+
raise ValueError(f"case {case['id']!r}: accepted_answers must be a non-empty list")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def validate_result(result: dict[str, Any]) -> None:
|
|
66
|
+
missing = REQUIRED_RESULT_FIELDS - set(result)
|
|
67
|
+
if missing:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"result {result.get('case_id', '<unknown>')!r} missing fields: "
|
|
70
|
+
+ ", ".join(sorted(missing))
|
|
71
|
+
)
|
|
72
|
+
for field in ("tokens", "tool_calls", "latency_ms"):
|
|
73
|
+
value = result[field]
|
|
74
|
+
if not isinstance(value, (int, float)) or value < 0:
|
|
75
|
+
raise ValueError(f"result {result['case_id']!r}: {field} must be non-negative")
|
|
76
|
+
for field in OPTIONAL_TELEMETRY_FIELDS:
|
|
77
|
+
if field in result:
|
|
78
|
+
value = result[field]
|
|
79
|
+
if not isinstance(value, (int, float)) or value < 0:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"result {result['case_id']!r}: {field} must be non-negative"
|
|
82
|
+
)
|
|
83
|
+
if "branches_distinct" in result and "branches_total" in result:
|
|
84
|
+
if result["branches_distinct"] > result["branches_total"]:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"result {result['case_id']!r}: branches_distinct cannot exceed branches_total"
|
|
87
|
+
)
|
|
88
|
+
if "contradictions_resolved" in result and "contradictions_found" in result:
|
|
89
|
+
if result["contradictions_resolved"] > result["contradictions_found"]:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"result {result['case_id']!r}: contradictions_resolved cannot exceed contradictions_found"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def answer_is_correct(case: dict[str, Any], answer: Any) -> bool:
|
|
96
|
+
normalized = normalize_answer(answer)
|
|
97
|
+
accepted = {normalize_answer(value) for value in case["accepted_answers"]}
|
|
98
|
+
return normalized in accepted
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def safe_mean(values: Iterable[float]) -> float | None:
|
|
102
|
+
materialized = list(values)
|
|
103
|
+
if not materialized:
|
|
104
|
+
return None
|
|
105
|
+
return statistics.fmean(materialized)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def safe_ratio(numerator: float, denominator: float) -> float | None:
|
|
109
|
+
if denominator == 0:
|
|
110
|
+
return None
|
|
111
|
+
return numerator / denominator
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def summarize(cases: list[dict[str, Any]], results: list[dict[str, Any]]) -> dict[str, Any]:
|
|
115
|
+
for case in cases:
|
|
116
|
+
validate_case(case)
|
|
117
|
+
for result in results:
|
|
118
|
+
validate_result(result)
|
|
119
|
+
|
|
120
|
+
case_by_id = {str(case["id"]): case for case in cases}
|
|
121
|
+
if len(case_by_id) != len(cases):
|
|
122
|
+
raise ValueError("case ids must be unique")
|
|
123
|
+
|
|
124
|
+
result_by_id: dict[str, dict[str, Any]] = {}
|
|
125
|
+
for result in results:
|
|
126
|
+
case_id = str(result["case_id"])
|
|
127
|
+
if case_id in result_by_id:
|
|
128
|
+
raise ValueError(f"duplicate result for case_id {case_id!r}")
|
|
129
|
+
if case_id not in case_by_id:
|
|
130
|
+
raise ValueError(f"result references unknown case_id {case_id!r}")
|
|
131
|
+
result_by_id[case_id] = result
|
|
132
|
+
|
|
133
|
+
missing_cases = sorted(set(case_by_id) - set(result_by_id))
|
|
134
|
+
evaluated = []
|
|
135
|
+
for case_id, result in result_by_id.items():
|
|
136
|
+
case = case_by_id[case_id]
|
|
137
|
+
evaluated.append((case, result, answer_is_correct(case, result["answer"])))
|
|
138
|
+
|
|
139
|
+
total = len(evaluated)
|
|
140
|
+
correct = sum(int(is_correct) for _, _, is_correct in evaluated)
|
|
141
|
+
|
|
142
|
+
branch_rows = [
|
|
143
|
+
result
|
|
144
|
+
for _, result, _ in evaluated
|
|
145
|
+
if "branches_total" in result and "branches_distinct" in result
|
|
146
|
+
]
|
|
147
|
+
contradiction_rows = [
|
|
148
|
+
result
|
|
149
|
+
for _, result, _ in evaluated
|
|
150
|
+
if "contradictions_found" in result and "contradictions_resolved" in result
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
total_branches = sum(row["branches_total"] for row in branch_rows)
|
|
154
|
+
distinct_branches = sum(row["branches_distinct"] for row in branch_rows)
|
|
155
|
+
contradictions_found = sum(row["contradictions_found"] for row in contradiction_rows)
|
|
156
|
+
contradictions_resolved = sum(
|
|
157
|
+
row["contradictions_resolved"] for row in contradiction_rows
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
"cases_defined": len(cases),
|
|
162
|
+
"cases_evaluated": total,
|
|
163
|
+
"missing_case_ids": missing_cases,
|
|
164
|
+
"accuracy": safe_ratio(correct, total),
|
|
165
|
+
"mean_tokens": safe_mean(float(result["tokens"]) for _, result, _ in evaluated),
|
|
166
|
+
"mean_tool_calls": safe_mean(
|
|
167
|
+
float(result["tool_calls"]) for _, result, _ in evaluated
|
|
168
|
+
),
|
|
169
|
+
"mean_latency_ms": safe_mean(
|
|
170
|
+
float(result["latency_ms"]) for _, result, _ in evaluated
|
|
171
|
+
),
|
|
172
|
+
"branch_diversity_ratio": safe_ratio(distinct_branches, total_branches),
|
|
173
|
+
"revival_rate_per_case": safe_mean(
|
|
174
|
+
float(result.get("revived_branches", 0)) for _, result, _ in evaluated
|
|
175
|
+
),
|
|
176
|
+
"error_recovery_rate_per_case": safe_mean(
|
|
177
|
+
float(result.get("recovered_errors", 0)) for _, result, _ in evaluated
|
|
178
|
+
),
|
|
179
|
+
"contradiction_resolution_rate": safe_ratio(
|
|
180
|
+
contradictions_resolved, contradictions_found
|
|
181
|
+
),
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def percent_change(new: float | None, old: float | None) -> float | None:
|
|
186
|
+
if new is None or old is None or old == 0:
|
|
187
|
+
return None
|
|
188
|
+
return ((new - old) / old) * 100.0
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def absolute_change(new: float | None, old: float | None) -> float | None:
|
|
192
|
+
if new is None or old is None:
|
|
193
|
+
return None
|
|
194
|
+
return new - old
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def compare(baseline: dict[str, Any], skill: dict[str, Any]) -> dict[str, Any]:
|
|
198
|
+
return {
|
|
199
|
+
"accuracy_delta": absolute_change(skill["accuracy"], baseline["accuracy"]),
|
|
200
|
+
"tokens_percent_change": percent_change(
|
|
201
|
+
skill["mean_tokens"], baseline["mean_tokens"]
|
|
202
|
+
),
|
|
203
|
+
"tool_calls_percent_change": percent_change(
|
|
204
|
+
skill["mean_tool_calls"], baseline["mean_tool_calls"]
|
|
205
|
+
),
|
|
206
|
+
"latency_percent_change": percent_change(
|
|
207
|
+
skill["mean_latency_ms"], baseline["mean_latency_ms"]
|
|
208
|
+
),
|
|
209
|
+
"branch_diversity_delta": absolute_change(
|
|
210
|
+
skill["branch_diversity_ratio"], baseline["branch_diversity_ratio"]
|
|
211
|
+
),
|
|
212
|
+
"contradiction_resolution_delta": absolute_change(
|
|
213
|
+
skill["contradiction_resolution_rate"],
|
|
214
|
+
baseline["contradiction_resolution_rate"],
|
|
215
|
+
),
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def ensure_finite(value: Any, label: str) -> None:
|
|
220
|
+
if isinstance(value, float) and not math.isfinite(value):
|
|
221
|
+
raise ValueError(f"{label} is not finite")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def main() -> int:
|
|
225
|
+
parser = argparse.ArgumentParser()
|
|
226
|
+
parser.add_argument("--cases", type=Path, required=True)
|
|
227
|
+
parser.add_argument("--skill", type=Path, required=True)
|
|
228
|
+
parser.add_argument("--baseline", type=Path)
|
|
229
|
+
parser.add_argument("--output", type=Path)
|
|
230
|
+
args = parser.parse_args()
|
|
231
|
+
|
|
232
|
+
cases = read_jsonl(args.cases)
|
|
233
|
+
skill_results = read_jsonl(args.skill)
|
|
234
|
+
skill_summary = summarize(cases, skill_results)
|
|
235
|
+
|
|
236
|
+
payload: dict[str, Any] = {"skill": skill_summary}
|
|
237
|
+
if args.baseline:
|
|
238
|
+
baseline_results = read_jsonl(args.baseline)
|
|
239
|
+
baseline_summary = summarize(cases, baseline_results)
|
|
240
|
+
payload["baseline"] = baseline_summary
|
|
241
|
+
payload["comparison"] = compare(baseline_summary, skill_summary)
|
|
242
|
+
|
|
243
|
+
for section_name, section in payload.items():
|
|
244
|
+
if isinstance(section, dict):
|
|
245
|
+
for key, value in section.items():
|
|
246
|
+
ensure_finite(value, f"{section_name}.{key}")
|
|
247
|
+
|
|
248
|
+
rendered = json.dumps(payload, indent=2, sort_keys=True)
|
|
249
|
+
print(rendered)
|
|
250
|
+
if args.output:
|
|
251
|
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
252
|
+
args.output.write_text(rendered + "\n", encoding="utf-8")
|
|
253
|
+
return 0
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
if __name__ == "__main__":
|
|
257
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/Furox-Art/quantum-reasoning-skill/blob/main/benchmark/schemas/case.schema.json",
|
|
4
|
+
"title": "Quantum Reasoning benchmark case",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": true,
|
|
7
|
+
"required": ["id", "domain", "prompt", "accepted_answers"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"id": {"type": "string", "minLength": 1},
|
|
10
|
+
"domain": {"type": "string", "minLength": 1},
|
|
11
|
+
"prompt": {"type": "string", "minLength": 1},
|
|
12
|
+
"accepted_answers": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"minItems": 1,
|
|
15
|
+
"items": {}
|
|
16
|
+
},
|
|
17
|
+
"source": {"type": ["string", "null"]},
|
|
18
|
+
"license": {"type": ["string", "null"]}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/Furox-Art/quantum-reasoning-skill/blob/main/benchmark/schemas/comparison.schema.json",
|
|
4
|
+
"title": "Quantum Reasoning benchmark comparison output",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["baseline", "skill", "comparison"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"baseline": {"$ref": "#/$defs/summary"},
|
|
10
|
+
"skill": {"$ref": "#/$defs/summary"},
|
|
11
|
+
"comparison": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"required": [
|
|
15
|
+
"accuracy_delta",
|
|
16
|
+
"tokens_percent_change",
|
|
17
|
+
"tool_calls_percent_change",
|
|
18
|
+
"latency_percent_change",
|
|
19
|
+
"branch_diversity_delta",
|
|
20
|
+
"contradiction_resolution_delta"
|
|
21
|
+
],
|
|
22
|
+
"properties": {
|
|
23
|
+
"accuracy_delta": {"type": ["number", "null"]},
|
|
24
|
+
"tokens_percent_change": {"type": ["number", "null"]},
|
|
25
|
+
"tool_calls_percent_change": {"type": ["number", "null"]},
|
|
26
|
+
"latency_percent_change": {"type": ["number", "null"]},
|
|
27
|
+
"branch_diversity_delta": {"type": ["number", "null"]},
|
|
28
|
+
"contradiction_resolution_delta": {"type": ["number", "null"]}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"$defs": {
|
|
33
|
+
"summary": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"additionalProperties": false,
|
|
36
|
+
"required": [
|
|
37
|
+
"cases_defined",
|
|
38
|
+
"cases_evaluated",
|
|
39
|
+
"missing_case_ids",
|
|
40
|
+
"accuracy",
|
|
41
|
+
"mean_tokens",
|
|
42
|
+
"mean_tool_calls",
|
|
43
|
+
"mean_latency_ms",
|
|
44
|
+
"branch_diversity_ratio",
|
|
45
|
+
"revival_rate_per_case",
|
|
46
|
+
"error_recovery_rate_per_case",
|
|
47
|
+
"contradiction_resolution_rate"
|
|
48
|
+
],
|
|
49
|
+
"properties": {
|
|
50
|
+
"cases_defined": {"type": "integer", "minimum": 0},
|
|
51
|
+
"cases_evaluated": {"type": "integer", "minimum": 0},
|
|
52
|
+
"missing_case_ids": {"type": "array", "items": {"type": "string"}},
|
|
53
|
+
"accuracy": {"type": ["number", "null"]},
|
|
54
|
+
"mean_tokens": {"type": ["number", "null"]},
|
|
55
|
+
"mean_tool_calls": {"type": ["number", "null"]},
|
|
56
|
+
"mean_latency_ms": {"type": ["number", "null"]},
|
|
57
|
+
"branch_diversity_ratio": {"type": ["number", "null"]},
|
|
58
|
+
"revival_rate_per_case": {"type": ["number", "null"]},
|
|
59
|
+
"error_recovery_rate_per_case": {"type": ["number", "null"]},
|
|
60
|
+
"contradiction_resolution_rate": {"type": ["number", "null"]}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/Furox-Art/quantum-reasoning-skill/blob/main/benchmark/schemas/metadata.schema.json",
|
|
4
|
+
"title": "Quantum Reasoning community benchmark metadata",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": true,
|
|
7
|
+
"required": [
|
|
8
|
+
"provider",
|
|
9
|
+
"model",
|
|
10
|
+
"run_date",
|
|
11
|
+
"skill_version",
|
|
12
|
+
"repetitions_per_case",
|
|
13
|
+
"baseline",
|
|
14
|
+
"skill",
|
|
15
|
+
"failures_recorded"
|
|
16
|
+
],
|
|
17
|
+
"properties": {
|
|
18
|
+
"provider": {"type": "string", "minLength": 1},
|
|
19
|
+
"model": {"type": "string", "minLength": 1},
|
|
20
|
+
"model_version": {"type": ["string", "null"]},
|
|
21
|
+
"run_date": {"type": "string", "format": "date"},
|
|
22
|
+
"skill_version": {"type": "string", "minLength": 1},
|
|
23
|
+
"skill_commit": {"type": ["string", "null"]},
|
|
24
|
+
"cases_source": {"type": ["string", "null"]},
|
|
25
|
+
"repetitions_per_case": {"type": "integer", "minimum": 1},
|
|
26
|
+
"random_seed": {"type": ["integer", "string", "null"]},
|
|
27
|
+
"hardware_runtime": {"type": ["string", "null"]},
|
|
28
|
+
"failures_recorded": {"const": true},
|
|
29
|
+
"baseline": {"$ref": "#/$defs/condition"},
|
|
30
|
+
"skill": {"$ref": "#/$defs/condition"},
|
|
31
|
+
"notes": {"type": ["string", "null"]}
|
|
32
|
+
},
|
|
33
|
+
"$defs": {
|
|
34
|
+
"condition": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"additionalProperties": true,
|
|
37
|
+
"required": [
|
|
38
|
+
"instruction_config",
|
|
39
|
+
"sampling",
|
|
40
|
+
"tool_availability",
|
|
41
|
+
"context_limit",
|
|
42
|
+
"output_limit"
|
|
43
|
+
],
|
|
44
|
+
"properties": {
|
|
45
|
+
"instruction_config": {"type": "string", "minLength": 1},
|
|
46
|
+
"sampling": {"type": "object"},
|
|
47
|
+
"tool_availability": {"type": ["array", "object", "string"]},
|
|
48
|
+
"context_limit": {"type": ["integer", "null"], "minimum": 1},
|
|
49
|
+
"output_limit": {"type": ["integer", "null"], "minimum": 1}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/Furox-Art/quantum-reasoning-skill/blob/main/benchmark/schemas/result.schema.json",
|
|
4
|
+
"title": "Quantum Reasoning benchmark result row",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": true,
|
|
7
|
+
"required": ["case_id", "answer", "tokens", "tool_calls", "latency_ms"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"case_id": {"type": "string", "minLength": 1},
|
|
10
|
+
"answer": {},
|
|
11
|
+
"tokens": {"type": "number", "minimum": 0},
|
|
12
|
+
"tool_calls": {"type": "number", "minimum": 0},
|
|
13
|
+
"latency_ms": {"type": "number", "minimum": 0},
|
|
14
|
+
"status": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["ok", "refusal", "timeout", "error"]
|
|
17
|
+
},
|
|
18
|
+
"branches_total": {"type": "number", "minimum": 0},
|
|
19
|
+
"branches_distinct": {"type": "number", "minimum": 0},
|
|
20
|
+
"revived_branches": {"type": "number", "minimum": 0},
|
|
21
|
+
"recovered_errors": {"type": "number", "minimum": 0},
|
|
22
|
+
"contradictions_found": {"type": "number", "minimum": 0},
|
|
23
|
+
"contradictions_resolved": {"type": "number", "minimum": 0}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Validate reproducible community benchmark result bundles."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import argparse, json, re
|
|
4
|
+
from datetime import date
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import evaluate
|
|
7
|
+
|
|
8
|
+
REQUIRED_FILES = {"metadata.json", "cases.jsonl", "baseline.jsonl", "skill.jsonl", "comparison.json", "README.md"}
|
|
9
|
+
COND_FIELDS = {"instruction_config", "sampling", "tool_availability", "context_limit", "output_limit"}
|
|
10
|
+
META_FIELDS = {"provider", "model", "run_date", "skill_version", "repetitions_per_case", "baseline", "skill", "failures_recorded"}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_json(path: Path):
|
|
14
|
+
try:
|
|
15
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
16
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
17
|
+
raise ValueError(f"{path}: invalid JSON: {exc}") from exc
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def validate_metadata(meta: dict) -> None:
|
|
21
|
+
if not isinstance(meta, dict):
|
|
22
|
+
raise ValueError("metadata.json must contain a JSON object")
|
|
23
|
+
missing = META_FIELDS - set(meta)
|
|
24
|
+
if missing:
|
|
25
|
+
raise ValueError(f"metadata.json missing fields: {sorted(missing)}")
|
|
26
|
+
for key in ("provider", "model", "skill_version"):
|
|
27
|
+
if not isinstance(meta[key], str) or not meta[key].strip():
|
|
28
|
+
raise ValueError(f"metadata.{key} must be a non-empty string")
|
|
29
|
+
try:
|
|
30
|
+
date.fromisoformat(meta["run_date"])
|
|
31
|
+
except Exception as exc:
|
|
32
|
+
raise ValueError("metadata.run_date must be YYYY-MM-DD") from exc
|
|
33
|
+
if not isinstance(meta["repetitions_per_case"], int) or meta["repetitions_per_case"] < 1:
|
|
34
|
+
raise ValueError("metadata.repetitions_per_case must be >= 1")
|
|
35
|
+
if meta["failures_recorded"] is not True:
|
|
36
|
+
raise ValueError("metadata.failures_recorded must be true")
|
|
37
|
+
for name in ("baseline", "skill"):
|
|
38
|
+
value = meta[name]
|
|
39
|
+
if not isinstance(value, dict):
|
|
40
|
+
raise ValueError(f"metadata.{name} must be an object")
|
|
41
|
+
missing_cond = COND_FIELDS - set(value)
|
|
42
|
+
if missing_cond:
|
|
43
|
+
raise ValueError(f"metadata.{name} missing fields: {sorted(missing_cond)}")
|
|
44
|
+
if not isinstance(value["instruction_config"], str) or not value["instruction_config"].strip():
|
|
45
|
+
raise ValueError(f"metadata.{name}.instruction_config must be non-empty")
|
|
46
|
+
if not isinstance(value["sampling"], dict):
|
|
47
|
+
raise ValueError(f"metadata.{name}.sampling must be an object")
|
|
48
|
+
for limit in ("context_limit", "output_limit"):
|
|
49
|
+
v = value[limit]
|
|
50
|
+
if v is not None and (not isinstance(v, int) or v < 1):
|
|
51
|
+
raise ValueError(f"metadata.{name}.{limit} must be null or >= 1")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _case_map(cases):
|
|
55
|
+
out = {}
|
|
56
|
+
for case in cases:
|
|
57
|
+
evaluate.validate_case(case)
|
|
58
|
+
cid = str(case["id"])
|
|
59
|
+
if cid in out:
|
|
60
|
+
raise ValueError(f"duplicate case id: {cid}")
|
|
61
|
+
out[cid] = case
|
|
62
|
+
return out
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def validate_bundle(bundle: Path) -> None:
|
|
66
|
+
missing = REQUIRED_FILES - {p.name for p in bundle.iterdir() if p.is_file()}
|
|
67
|
+
if missing:
|
|
68
|
+
raise ValueError(f"{bundle}: missing files: {sorted(missing)}")
|
|
69
|
+
validate_metadata(load_json(bundle / "metadata.json"))
|
|
70
|
+
cases = evaluate.read_jsonl(bundle / "cases.jsonl")
|
|
71
|
+
case_by_id = _case_map(cases)
|
|
72
|
+
baseline = evaluate.read_jsonl(bundle / "baseline.jsonl")
|
|
73
|
+
skill = evaluate.read_jsonl(bundle / "skill.jsonl")
|
|
74
|
+
for row in baseline + skill:
|
|
75
|
+
evaluate.validate_result(row)
|
|
76
|
+
for label, rows in (("baseline", baseline), ("skill", skill)):
|
|
77
|
+
ids = [str(r["case_id"]) for r in rows]
|
|
78
|
+
if len(ids) != len(set(ids)):
|
|
79
|
+
raise ValueError(f"{bundle}: duplicate {label} case_id")
|
|
80
|
+
if set(ids) != set(case_by_id):
|
|
81
|
+
raise ValueError(f"{bundle}: {label} must contain exactly one row for every case")
|
|
82
|
+
expected = {
|
|
83
|
+
"skill": evaluate.summarize(cases, skill),
|
|
84
|
+
"baseline": evaluate.summarize(cases, baseline),
|
|
85
|
+
}
|
|
86
|
+
expected["comparison"] = evaluate.compare(expected["baseline"], expected["skill"])
|
|
87
|
+
submitted = load_json(bundle / "comparison.json")
|
|
88
|
+
if submitted != expected:
|
|
89
|
+
raise ValueError(f"{bundle}: comparison.json does not match evaluator recomputation")
|
|
90
|
+
if not (bundle / "README.md").read_text(encoding="utf-8").strip():
|
|
91
|
+
raise ValueError(f"{bundle}: README.md must not be empty")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def main() -> int:
|
|
95
|
+
parser = argparse.ArgumentParser()
|
|
96
|
+
parser.add_argument("--root", type=Path, default=Path("benchmark/results/community"))
|
|
97
|
+
parser.add_argument("--allow-empty", action="store_true")
|
|
98
|
+
args = parser.parse_args()
|
|
99
|
+
root = args.root
|
|
100
|
+
if not root.exists():
|
|
101
|
+
if args.allow_empty:
|
|
102
|
+
return 0
|
|
103
|
+
raise SystemExit(f"community benchmark root not found: {root}")
|
|
104
|
+
bundles = sorted(p for p in root.iterdir() if p.is_dir())
|
|
105
|
+
if not bundles and not args.allow_empty:
|
|
106
|
+
raise SystemExit("no community benchmark bundles found")
|
|
107
|
+
for bundle in bundles:
|
|
108
|
+
validate_bundle(bundle)
|
|
109
|
+
print(f"validated {bundle}")
|
|
110
|
+
return 0
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Host compatibility
|
|
2
|
+
|
|
3
|
+
Quantum Reasoning is model-agnostic, but not every host exposes the same controls. Compatibility is therefore defined by capabilities rather than by provider branding.
|
|
4
|
+
|
|
5
|
+
## Compatibility levels
|
|
6
|
+
|
|
7
|
+
| Level | Required capabilities | What works |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Protocol-only | Host can persist or inject `SKILL.md` instructions | Multi-branch protocol, falsification rules, stopping/collapse policy |
|
|
10
|
+
| Measurable | Protocol-only plus aggregate run telemetry | Benchmark result schema, diversity/revival/contradiction measurements |
|
|
11
|
+
| Controller-assisted | Measurable plus a host-side loop that can call `reference/branch_controller.py` or equivalent logic | Deterministic scoring, state transitions, uncertainty-based width and collapse checks |
|
|
12
|
+
|
|
13
|
+
A host is incompatible only when it cannot reliably apply the skill instructions, truncates/replaces them in a way that changes the protocol, or prevents the requested task/tool access.
|
|
14
|
+
|
|
15
|
+
## Required host contract
|
|
16
|
+
|
|
17
|
+
A compatible integration should preserve these invariants:
|
|
18
|
+
|
|
19
|
+
1. `SKILL.md` is supplied as a persistent system/developer/skill instruction, not as untrusted task content.
|
|
20
|
+
2. The same task constraints and tool permissions apply to all candidate branches.
|
|
21
|
+
3. Tool observations are treated as evidence and are not fabricated.
|
|
22
|
+
4. Private chain-of-thought is not required or stored. Only aggregate control-plane telemetry may be emitted.
|
|
23
|
+
5. If numeric branch telemetry is unavailable, the host follows the qualitative protocol and omits unavailable measurements rather than inventing them.
|
|
24
|
+
6. Baseline-vs-skill benchmarks keep the model/version, tools, sampling and budget policy fixed.
|
|
25
|
+
|
|
26
|
+
## Integration patterns
|
|
27
|
+
|
|
28
|
+
### 1. Native skill directory
|
|
29
|
+
|
|
30
|
+
If a host supports a skill/plugin directory, install the repository so that `SKILL.md` is the skill entry point and keep its YAML front matter intact.
|
|
31
|
+
|
|
32
|
+
### 2. Persistent instruction wrapper
|
|
33
|
+
|
|
34
|
+
If the host has no native skill format, load the contents of `SKILL.md` into the persistent system/developer instruction layer before the user task. Do not prepend it to benchmark prompts only in the skill condition unless the baseline wrapper is otherwise identical.
|
|
35
|
+
|
|
36
|
+
### 3. Controller-assisted agent
|
|
37
|
+
|
|
38
|
+
A host with an explicit agent loop can map observable aggregate branch measurements to `BranchMetrics` and use `reference/branch_controller.py` for ranking, state changes and collapse decisions. The reference controller does not generate hidden reasoning or call an LLM.
|
|
39
|
+
|
|
40
|
+
See [`../examples/host-integration.md`](../examples/host-integration.md) for concrete generic examples.
|
|
41
|
+
|
|
42
|
+
## Provider/model claims
|
|
43
|
+
|
|
44
|
+
This repository intentionally does not label specific commercial models as "supported" without a reproducible third-party run. Community results may document a provider/model combination under `benchmark/results/community/`, but those results are measurements from the submitter, not a permanent compatibility guarantee.
|