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,131 @@
|
|
|
1
|
+
# Measurement and branch-control specification
|
|
2
|
+
|
|
3
|
+
This document turns the qualitative protocol in `SKILL.md` into an auditable reference policy. It does **not** claim that these constants are optimal or scientifically validated. They are initial defaults that must be calibrated against benchmark data.
|
|
4
|
+
|
|
5
|
+
## 1. Observable branch metrics
|
|
6
|
+
|
|
7
|
+
Each branch is represented by normalized values in `[0, 1]`:
|
|
8
|
+
|
|
9
|
+
- `evidence`: support from verified facts or observations
|
|
10
|
+
- `verification`: strength of independent checks
|
|
11
|
+
- `independence`: independence from other branches
|
|
12
|
+
- `information_gain`: expected value of further work on the branch
|
|
13
|
+
- `contradiction`: severity of conflict with verified facts
|
|
14
|
+
- `unresolved_assumptions`: remaining unsupported assumptions
|
|
15
|
+
- `normalized_cost`: relative compute/tool cost already consumed
|
|
16
|
+
- `shared_assumption_ratio`: overlap in critical assumptions with the current leader
|
|
17
|
+
- `semantic_similarity_to_leader`: redundancy signal for near-duplicate branches
|
|
18
|
+
|
|
19
|
+
These values are control metadata. They must not require exposing private chain-of-thought.
|
|
20
|
+
|
|
21
|
+
## 2. Correlation penalty
|
|
22
|
+
|
|
23
|
+
A branch should not receive full independence credit when it shares assumptions or is effectively a paraphrase of the leader.
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
correlation = max(shared_assumption_ratio, semantic_similarity_to_leader)
|
|
27
|
+
effective_independence = independence * (1 - correlation)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 3. Reference support score
|
|
31
|
+
|
|
32
|
+
The reference implementation uses:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
score = clamp01(
|
|
36
|
+
0.15
|
|
37
|
+
+ 0.25 * evidence
|
|
38
|
+
+ 0.25 * verification
|
|
39
|
+
+ 0.15 * effective_independence
|
|
40
|
+
+ 0.10 * information_gain
|
|
41
|
+
- 0.15 * contradiction
|
|
42
|
+
- 0.05 * unresolved_assumptions
|
|
43
|
+
- 0.05 * normalized_cost
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The weights are deliberately explicit so they can be benchmarked and changed. They are not hidden policy.
|
|
48
|
+
|
|
49
|
+
## 4. State transitions
|
|
50
|
+
|
|
51
|
+
Reference defaults:
|
|
52
|
+
|
|
53
|
+
- reject when `contradiction >= 0.85`
|
|
54
|
+
- move to dormant when `score < 0.35`
|
|
55
|
+
- otherwise keep active
|
|
56
|
+
|
|
57
|
+
A dormant branch is revived when at least one material change occurs relative to its previous measurement:
|
|
58
|
+
|
|
59
|
+
- evidence increases by `>= 0.15`, or
|
|
60
|
+
- verification increases by `>= 0.25`, or
|
|
61
|
+
- contradiction decreases by `>= 0.20`
|
|
62
|
+
|
|
63
|
+
A rejected branch is not automatically revived by the reference controller. A caller may explicitly reopen it if the supposedly verified fact that caused rejection is itself invalidated.
|
|
64
|
+
|
|
65
|
+
## 5. Dynamic search width
|
|
66
|
+
|
|
67
|
+
Uncertainty is estimated from the normalized Shannon entropy of surviving branch scores.
|
|
68
|
+
|
|
69
|
+
Reference width policy:
|
|
70
|
+
|
|
71
|
+
| uncertainty | branch range |
|
|
72
|
+
|---|---:|
|
|
73
|
+
| `< 0.25` | 2–3 |
|
|
74
|
+
| `0.25–0.55` | 4–6 |
|
|
75
|
+
| `>= 0.55` | 6–10 |
|
|
76
|
+
|
|
77
|
+
This is a compute-allocation recommendation, not a requirement to generate the maximum branch count.
|
|
78
|
+
|
|
79
|
+
## 6. Collapse criteria
|
|
80
|
+
|
|
81
|
+
The reference controller permits collapse only when all applicable checks pass:
|
|
82
|
+
|
|
83
|
+
1. leader score `>= 0.78`
|
|
84
|
+
2. leader verification `>= 0.75`
|
|
85
|
+
3. leader contradiction `<= 0.15`
|
|
86
|
+
4. if a runner-up exists, leader margin `>= 0.12`
|
|
87
|
+
5. no unresolved high-information alternative remains close enough to plausibly change the outcome
|
|
88
|
+
|
|
89
|
+
If these checks fail, the system should continue verification, keep multiple surviving alternatives, or report unresolved alternatives rather than manufacture certainty.
|
|
90
|
+
|
|
91
|
+
## 7. Diversity diagnostic
|
|
92
|
+
|
|
93
|
+
Given pairwise semantic similarities, a pair is treated as redundant when similarity is `>= 0.85` by default.
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
diversity_ratio = materially_distinct_pairs / all_measured_pairs
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This metric is intentionally simple. Embedding model choice, similarity calibration and domain effects must be reported by any experiment that uses it.
|
|
100
|
+
|
|
101
|
+
## 8. What must be calibrated
|
|
102
|
+
|
|
103
|
+
Benchmarking should test at least:
|
|
104
|
+
|
|
105
|
+
- score weights
|
|
106
|
+
- contradiction rejection threshold
|
|
107
|
+
- dormant threshold
|
|
108
|
+
- revival deltas
|
|
109
|
+
- collapse score and margin
|
|
110
|
+
- semantic duplicate threshold
|
|
111
|
+
- uncertainty-to-width mapping
|
|
112
|
+
|
|
113
|
+
Calibration must use a development set separate from final held-out evaluation.
|
|
114
|
+
|
|
115
|
+
## 9. Failure modes to measure
|
|
116
|
+
|
|
117
|
+
A serious evaluation should label and count:
|
|
118
|
+
|
|
119
|
+
- early lock-in
|
|
120
|
+
- duplicated branches
|
|
121
|
+
- shared-assumption failure
|
|
122
|
+
- false contradiction detection
|
|
123
|
+
- failure to revive a correct dormant branch
|
|
124
|
+
- excessive search after the answer is already established
|
|
125
|
+
- premature collapse
|
|
126
|
+
- tool-confirmation bias
|
|
127
|
+
- accuracy gain that is explained only by substantially higher compute
|
|
128
|
+
|
|
129
|
+
## 10. Reproducibility rule
|
|
130
|
+
|
|
131
|
+
Any reported improvement must include the baseline configuration and raw aggregate result files needed to reproduce the comparison. Do not publish only a selected percentage or a hand-picked example.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Host integration examples
|
|
2
|
+
|
|
3
|
+
These examples describe integration shapes without depending on a specific provider.
|
|
4
|
+
|
|
5
|
+
## Native skill host
|
|
6
|
+
|
|
7
|
+
Install the repository or copy `SKILL.md` into the host's skill directory. Preserve the YAML front matter and load the file as a persistent skill instruction.
|
|
8
|
+
|
|
9
|
+
## Persistent instruction host
|
|
10
|
+
|
|
11
|
+
Conceptually, the host should build the request as:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
persistent instructions:
|
|
15
|
+
<contents of SKILL.md>
|
|
16
|
+
|
|
17
|
+
task:
|
|
18
|
+
<user request>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Do not require the model to print private reasoning. The final answer may remain concise while the host records only observable aggregate telemetry.
|
|
22
|
+
|
|
23
|
+
## Controller-assisted host
|
|
24
|
+
|
|
25
|
+
A host-side agent loop may use the optional reference controller:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from reference.branch_controller import Branch, BranchMetrics, collapse_decision
|
|
29
|
+
|
|
30
|
+
branch = Branch(
|
|
31
|
+
branch_id="candidate-a",
|
|
32
|
+
metrics=BranchMetrics(
|
|
33
|
+
evidence=0.8,
|
|
34
|
+
verification=0.9,
|
|
35
|
+
independence=0.7,
|
|
36
|
+
information_gain=0.6,
|
|
37
|
+
contradiction=0.1,
|
|
38
|
+
unresolved_assumptions=0.2,
|
|
39
|
+
normalized_cost=0.3,
|
|
40
|
+
shared_assumption_ratio=0.1,
|
|
41
|
+
semantic_similarity_to_leader=0.2,
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The host supplies only measurements it can actually observe. Missing telemetry must not be guessed.
|
|
47
|
+
|
|
48
|
+
## Benchmark wrapper
|
|
49
|
+
|
|
50
|
+
For an A/B benchmark, keep everything fixed except the presence of the skill:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
baseline = normal persistent instructions + task
|
|
54
|
+
skill = normal persistent instructions + SKILL.md + same task
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Use the same model/version, tools, sampling settings and budget policy for both conditions. See [`../benchmark/README.md`](../benchmark/README.md).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Usage examples
|
|
2
|
+
|
|
3
|
+
The skill is designed to be invoked only when branching improves reliability.
|
|
4
|
+
|
|
5
|
+
## Scientific hypothesis
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Use the quantum-reasoning skill. Keep several materially different mechanisms alive, identify what evidence would falsify each one, use available tools to test them, and collapse only after cross-comparison.
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Mathematics
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
Use the quantum-reasoning skill. Explore independent solution strategies, test edge cases and counterexamples, verify the strongest path, and return only the final derivation and important uncertainty.
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Debugging
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
Use the quantum-reasoning skill. Maintain multiple root-cause hypotheses, design the cheapest discriminating test for each, eliminate contradicted causes, revive alternatives if new evidence conflicts with the leader, then give the most supported diagnosis.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Planning / architecture
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
Use the quantum-reasoning skill. Generate genuinely different architectures, compare assumptions and failure modes, allocate more analysis to unresolved high-impact choices, and synthesize the strongest design only after verification.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Expected behavior
|
|
30
|
+
|
|
31
|
+
The model should not print a large internal reasoning tree. It should use the branching protocol internally and return a concise result, the evidence that matters, remaining uncertainty, and genuinely unresolved alternatives when necessary.
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = { name: 'quantum-reasoning-skill', version: '1.0.0', description: 'Quantum-inspired reasoning skill for exploring multiple solution paths', skill: require('./SKILL.md') };
|
package/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"name":"quantum-reasoning-skill","version":"1.0.0","description":"Quantum-inspired reasoning skill for exploring multiple solution paths","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"keywords":["ai-agents","reasoning","quantum-inspired","multi-branch"],"author":"Furox-Art","license":"MIT","repository":{"type":"git","url":"git+https://github.com/Furox-Art/quantum-reasoning-skill.git"},"bugs":{"url":"https://github.com/Furox-Art/quantum-reasoning-skill/issues"},"homepage":"https://github.com/Furox-Art/quantum-reasoning-skill#readme"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Reference branch-control logic for the Quantum Reasoning Skill.
|
|
2
|
+
|
|
3
|
+
This module is intentionally provider-agnostic. It does not generate hidden reasoning
|
|
4
|
+
or call an LLM. It only manages branch telemetry and makes deterministic decisions
|
|
5
|
+
from explicit, externally supplied measurements.
|
|
6
|
+
|
|
7
|
+
The thresholds below are reference defaults, not scientifically validated constants.
|
|
8
|
+
They are meant to be calibrated with benchmark evidence.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, replace
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from math import log2
|
|
16
|
+
from typing import Iterable, Sequence
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BranchState(str, Enum):
|
|
20
|
+
ACTIVE = "active"
|
|
21
|
+
DORMANT = "dormant"
|
|
22
|
+
REJECTED = "rejected"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class BranchMetrics:
|
|
27
|
+
evidence: float
|
|
28
|
+
verification: float
|
|
29
|
+
independence: float
|
|
30
|
+
information_gain: float
|
|
31
|
+
contradiction: float
|
|
32
|
+
unresolved_assumptions: float
|
|
33
|
+
normalized_cost: float
|
|
34
|
+
shared_assumption_ratio: float = 0.0
|
|
35
|
+
semantic_similarity_to_leader: float = 0.0
|
|
36
|
+
|
|
37
|
+
def validate(self) -> None:
|
|
38
|
+
for name, value in self.__dict__.items():
|
|
39
|
+
if not 0.0 <= value <= 1.0:
|
|
40
|
+
raise ValueError(f"{name} must be in [0, 1], got {value!r}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class Branch:
|
|
45
|
+
branch_id: str
|
|
46
|
+
metrics: BranchMetrics
|
|
47
|
+
state: BranchState = BranchState.ACTIVE
|
|
48
|
+
previous_metrics: BranchMetrics | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class Thresholds:
|
|
53
|
+
collapse_score: float = 0.78
|
|
54
|
+
collapse_verification: float = 0.75
|
|
55
|
+
collapse_max_contradiction: float = 0.15
|
|
56
|
+
collapse_margin: float = 0.12
|
|
57
|
+
unresolved_near_leader_margin: float = 0.10
|
|
58
|
+
reject_contradiction: float = 0.85
|
|
59
|
+
dormant_score: float = 0.35
|
|
60
|
+
revive_evidence_delta: float = 0.15
|
|
61
|
+
revive_verification_delta: float = 0.25
|
|
62
|
+
revive_contradiction_drop: float = 0.20
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
DEFAULT_THRESHOLDS = Thresholds()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
WEIGHTS = {
|
|
69
|
+
"evidence": 0.25,
|
|
70
|
+
"verification": 0.25,
|
|
71
|
+
"independence": 0.15,
|
|
72
|
+
"information_gain": 0.10,
|
|
73
|
+
"contradiction": -0.15,
|
|
74
|
+
"unresolved_assumptions": -0.05,
|
|
75
|
+
"normalized_cost": -0.05,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _clamp01(value: float) -> float:
|
|
80
|
+
return max(0.0, min(1.0, value))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def effective_independence(metrics: BranchMetrics) -> float:
|
|
84
|
+
"""Penalize correlated branches and near-duplicates.
|
|
85
|
+
|
|
86
|
+
The effective independence is the declared independence reduced by the strongest
|
|
87
|
+
observed correlation signal: shared assumptions or semantic similarity.
|
|
88
|
+
"""
|
|
89
|
+
metrics.validate()
|
|
90
|
+
correlation = max(
|
|
91
|
+
metrics.shared_assumption_ratio,
|
|
92
|
+
metrics.semantic_similarity_to_leader,
|
|
93
|
+
)
|
|
94
|
+
return _clamp01(metrics.independence * (1.0 - correlation))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def branch_score(metrics: BranchMetrics) -> float:
|
|
98
|
+
"""Return a deterministic support score in [0, 1]."""
|
|
99
|
+
metrics.validate()
|
|
100
|
+
values = {
|
|
101
|
+
"evidence": metrics.evidence,
|
|
102
|
+
"verification": metrics.verification,
|
|
103
|
+
"independence": effective_independence(metrics),
|
|
104
|
+
"information_gain": metrics.information_gain,
|
|
105
|
+
"contradiction": metrics.contradiction,
|
|
106
|
+
"unresolved_assumptions": metrics.unresolved_assumptions,
|
|
107
|
+
"normalized_cost": metrics.normalized_cost,
|
|
108
|
+
}
|
|
109
|
+
raw = 0.15 + sum(WEIGHTS[name] * value for name, value in values.items())
|
|
110
|
+
return _clamp01(raw)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def rank_branches(branches: Iterable[Branch]) -> list[Branch]:
|
|
114
|
+
return sorted(
|
|
115
|
+
(branch for branch in branches if branch.state != BranchState.REJECTED),
|
|
116
|
+
key=lambda branch: branch_score(branch.metrics),
|
|
117
|
+
reverse=True,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def classify_branch(
|
|
122
|
+
branch: Branch,
|
|
123
|
+
thresholds: Thresholds = DEFAULT_THRESHOLDS,
|
|
124
|
+
) -> Branch:
|
|
125
|
+
"""Move an active branch to dormant/rejected when evidence warrants it."""
|
|
126
|
+
score = branch_score(branch.metrics)
|
|
127
|
+
if branch.metrics.contradiction >= thresholds.reject_contradiction:
|
|
128
|
+
return replace(branch, state=BranchState.REJECTED)
|
|
129
|
+
if score < thresholds.dormant_score:
|
|
130
|
+
return replace(branch, state=BranchState.DORMANT)
|
|
131
|
+
return replace(branch, state=BranchState.ACTIVE)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def should_revive(
|
|
135
|
+
branch: Branch,
|
|
136
|
+
thresholds: Thresholds = DEFAULT_THRESHOLDS,
|
|
137
|
+
) -> bool:
|
|
138
|
+
"""Return True when new evidence materially improves a dormant branch."""
|
|
139
|
+
if branch.state != BranchState.DORMANT or branch.previous_metrics is None:
|
|
140
|
+
return False
|
|
141
|
+
current = branch.metrics
|
|
142
|
+
previous = branch.previous_metrics
|
|
143
|
+
current.validate()
|
|
144
|
+
previous.validate()
|
|
145
|
+
return any(
|
|
146
|
+
(
|
|
147
|
+
current.evidence - previous.evidence >= thresholds.revive_evidence_delta,
|
|
148
|
+
current.verification - previous.verification
|
|
149
|
+
>= thresholds.revive_verification_delta,
|
|
150
|
+
previous.contradiction - current.contradiction
|
|
151
|
+
>= thresholds.revive_contradiction_drop,
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def update_branch_state(
|
|
157
|
+
branch: Branch,
|
|
158
|
+
thresholds: Thresholds = DEFAULT_THRESHOLDS,
|
|
159
|
+
) -> Branch:
|
|
160
|
+
if should_revive(branch, thresholds):
|
|
161
|
+
return replace(branch, state=BranchState.ACTIVE)
|
|
162
|
+
return classify_branch(branch, thresholds)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def normalized_entropy(probabilities: Sequence[float]) -> float:
|
|
166
|
+
"""Normalized Shannon entropy in [0, 1]."""
|
|
167
|
+
if not probabilities:
|
|
168
|
+
return 0.0
|
|
169
|
+
if any(value < 0 for value in probabilities):
|
|
170
|
+
raise ValueError("probabilities cannot contain negative values")
|
|
171
|
+
total = sum(probabilities)
|
|
172
|
+
if total <= 0:
|
|
173
|
+
return 0.0
|
|
174
|
+
normalized = [value / total for value in probabilities if value > 0]
|
|
175
|
+
if len(normalized) <= 1:
|
|
176
|
+
return 0.0
|
|
177
|
+
entropy = -sum(value * log2(value) for value in normalized)
|
|
178
|
+
return _clamp01(entropy / log2(len(normalized)))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def uncertainty_from_branches(branches: Sequence[Branch]) -> float:
|
|
182
|
+
"""Estimate uncertainty from the score distribution of surviving branches."""
|
|
183
|
+
surviving = [
|
|
184
|
+
branch_score(branch.metrics)
|
|
185
|
+
for branch in branches
|
|
186
|
+
if branch.state != BranchState.REJECTED
|
|
187
|
+
]
|
|
188
|
+
return normalized_entropy(surviving)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def recommended_width(uncertainty: float) -> tuple[int, int]:
|
|
192
|
+
"""Return a reference branch-count range for the next search step."""
|
|
193
|
+
if not 0.0 <= uncertainty <= 1.0:
|
|
194
|
+
raise ValueError("uncertainty must be in [0, 1]")
|
|
195
|
+
if uncertainty < 0.25:
|
|
196
|
+
return (2, 3)
|
|
197
|
+
if uncertainty < 0.55:
|
|
198
|
+
return (4, 6)
|
|
199
|
+
return (6, 10)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def collapse_decision(
|
|
203
|
+
branches: Sequence[Branch],
|
|
204
|
+
thresholds: Thresholds = DEFAULT_THRESHOLDS,
|
|
205
|
+
) -> tuple[bool, str, Branch | None]:
|
|
206
|
+
"""Determine whether the search can collapse to a single branch.
|
|
207
|
+
|
|
208
|
+
Returns ``(can_collapse, reason, leader)``. Collapse is blocked when a strong,
|
|
209
|
+
unresolved alternative remains close enough to the leader to matter.
|
|
210
|
+
"""
|
|
211
|
+
ranked = rank_branches(branches)
|
|
212
|
+
if not ranked:
|
|
213
|
+
return (False, "no surviving branch", None)
|
|
214
|
+
|
|
215
|
+
leader = ranked[0]
|
|
216
|
+
leader_score = branch_score(leader.metrics)
|
|
217
|
+
if leader_score < thresholds.collapse_score:
|
|
218
|
+
return (False, "leader score below collapse threshold", leader)
|
|
219
|
+
if leader.metrics.verification < thresholds.collapse_verification:
|
|
220
|
+
return (False, "leader verification below collapse threshold", leader)
|
|
221
|
+
if leader.metrics.contradiction > thresholds.collapse_max_contradiction:
|
|
222
|
+
return (False, "leader contradiction remains too high", leader)
|
|
223
|
+
|
|
224
|
+
if len(ranked) > 1:
|
|
225
|
+
runner_up = ranked[1]
|
|
226
|
+
runner_score = branch_score(runner_up.metrics)
|
|
227
|
+
if leader_score - runner_score < thresholds.collapse_margin:
|
|
228
|
+
return (False, "leader margin is too small", leader)
|
|
229
|
+
if (
|
|
230
|
+
runner_up.metrics.information_gain >= 0.60
|
|
231
|
+
and leader_score - runner_score
|
|
232
|
+
<= thresholds.unresolved_near_leader_margin
|
|
233
|
+
):
|
|
234
|
+
return (False, "high-information alternative remains unresolved", leader)
|
|
235
|
+
|
|
236
|
+
return (True, "collapse criteria satisfied", leader)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def diversity_ratio(
|
|
240
|
+
pairwise_similarities: Sequence[float],
|
|
241
|
+
duplicate_threshold: float = 0.85,
|
|
242
|
+
) -> float:
|
|
243
|
+
"""Fraction of pairwise comparisons that are materially distinct.
|
|
244
|
+
|
|
245
|
+
Similarity values must be in [0, 1]. A pair at or above the duplicate threshold
|
|
246
|
+
is treated as correlated/redundant for this diagnostic.
|
|
247
|
+
"""
|
|
248
|
+
if not pairwise_similarities:
|
|
249
|
+
return 1.0
|
|
250
|
+
if not 0.0 <= duplicate_threshold <= 1.0:
|
|
251
|
+
raise ValueError("duplicate_threshold must be in [0, 1]")
|
|
252
|
+
for value in pairwise_similarities:
|
|
253
|
+
if not 0.0 <= value <= 1.0:
|
|
254
|
+
raise ValueError("similarity values must be in [0, 1]")
|
|
255
|
+
distinct = sum(value < duplicate_threshold for value in pairwise_similarities)
|
|
256
|
+
return distinct / len(pairwise_similarities)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
9
|
+
SPEC = importlib.util.spec_from_file_location(
|
|
10
|
+
"benchmark_evaluate", ROOT / "benchmark" / "evaluate.py"
|
|
11
|
+
)
|
|
12
|
+
assert SPEC and SPEC.loader
|
|
13
|
+
evaluate = importlib.util.module_from_spec(SPEC)
|
|
14
|
+
SPEC.loader.exec_module(evaluate)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
CASES = [
|
|
18
|
+
{
|
|
19
|
+
"id": "c1",
|
|
20
|
+
"domain": "math",
|
|
21
|
+
"prompt": "1+1?",
|
|
22
|
+
"accepted_answers": ["2"],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "c2",
|
|
26
|
+
"domain": "logic",
|
|
27
|
+
"prompt": "yes?",
|
|
28
|
+
"accepted_answers": ["yes"],
|
|
29
|
+
},
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BenchmarkTests(unittest.TestCase):
|
|
34
|
+
def test_summary_computes_accuracy_and_telemetry(self):
|
|
35
|
+
results = [
|
|
36
|
+
{
|
|
37
|
+
"case_id": "c1",
|
|
38
|
+
"answer": "2",
|
|
39
|
+
"tokens": 100,
|
|
40
|
+
"tool_calls": 1,
|
|
41
|
+
"latency_ms": 50,
|
|
42
|
+
"branches_total": 4,
|
|
43
|
+
"branches_distinct": 3,
|
|
44
|
+
"revived_branches": 1,
|
|
45
|
+
"recovered_errors": 1,
|
|
46
|
+
"contradictions_found": 2,
|
|
47
|
+
"contradictions_resolved": 2,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"case_id": "c2",
|
|
51
|
+
"answer": "no",
|
|
52
|
+
"tokens": 200,
|
|
53
|
+
"tool_calls": 3,
|
|
54
|
+
"latency_ms": 150,
|
|
55
|
+
"branches_total": 2,
|
|
56
|
+
"branches_distinct": 1,
|
|
57
|
+
"revived_branches": 0,
|
|
58
|
+
"recovered_errors": 0,
|
|
59
|
+
"contradictions_found": 1,
|
|
60
|
+
"contradictions_resolved": 0,
|
|
61
|
+
},
|
|
62
|
+
]
|
|
63
|
+
summary = evaluate.summarize(CASES, results)
|
|
64
|
+
self.assertEqual(summary["accuracy"], 0.5)
|
|
65
|
+
self.assertEqual(summary["mean_tokens"], 150.0)
|
|
66
|
+
self.assertEqual(summary["mean_tool_calls"], 2.0)
|
|
67
|
+
self.assertEqual(summary["mean_latency_ms"], 100.0)
|
|
68
|
+
self.assertEqual(summary["branch_diversity_ratio"], 4 / 6)
|
|
69
|
+
self.assertEqual(summary["contradiction_resolution_rate"], 2 / 3)
|
|
70
|
+
|
|
71
|
+
def test_compare_reports_cost_and_accuracy_changes(self):
|
|
72
|
+
baseline = {
|
|
73
|
+
"accuracy": 0.5,
|
|
74
|
+
"mean_tokens": 100.0,
|
|
75
|
+
"mean_tool_calls": 1.0,
|
|
76
|
+
"mean_latency_ms": 100.0,
|
|
77
|
+
"branch_diversity_ratio": 0.4,
|
|
78
|
+
"contradiction_resolution_rate": 0.5,
|
|
79
|
+
}
|
|
80
|
+
skill = {
|
|
81
|
+
"accuracy": 0.75,
|
|
82
|
+
"mean_tokens": 150.0,
|
|
83
|
+
"mean_tool_calls": 2.0,
|
|
84
|
+
"mean_latency_ms": 120.0,
|
|
85
|
+
"branch_diversity_ratio": 0.7,
|
|
86
|
+
"contradiction_resolution_rate": 0.8,
|
|
87
|
+
}
|
|
88
|
+
delta = evaluate.compare(baseline, skill)
|
|
89
|
+
self.assertEqual(delta["accuracy_delta"], 0.25)
|
|
90
|
+
self.assertEqual(delta["tokens_percent_change"], 50.0)
|
|
91
|
+
self.assertEqual(delta["tool_calls_percent_change"], 100.0)
|
|
92
|
+
self.assertEqual(delta["latency_percent_change"], 20.0)
|
|
93
|
+
self.assertAlmostEqual(delta["branch_diversity_delta"], 0.3)
|
|
94
|
+
self.assertAlmostEqual(delta["contradiction_resolution_delta"], 0.3)
|
|
95
|
+
|
|
96
|
+
def test_missing_result_fields_fail(self):
|
|
97
|
+
with self.assertRaises(ValueError):
|
|
98
|
+
evaluate.summarize(
|
|
99
|
+
CASES,
|
|
100
|
+
[{"case_id": "c1", "answer": "2", "tokens": 1}],
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def test_unknown_case_id_fails(self):
|
|
104
|
+
with self.assertRaises(ValueError):
|
|
105
|
+
evaluate.summarize(
|
|
106
|
+
CASES,
|
|
107
|
+
[
|
|
108
|
+
{
|
|
109
|
+
"case_id": "missing",
|
|
110
|
+
"answer": "2",
|
|
111
|
+
"tokens": 1,
|
|
112
|
+
"tool_calls": 0,
|
|
113
|
+
"latency_ms": 1,
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
unittest.main()
|