sable-sphynx 0.7.0.dev0__py3-none-any.whl
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.
- sable_sphynx-0.7.0.dev0.dist-info/METADATA +152 -0
- sable_sphynx-0.7.0.dev0.dist-info/RECORD +174 -0
- sable_sphynx-0.7.0.dev0.dist-info/WHEEL +5 -0
- sable_sphynx-0.7.0.dev0.dist-info/top_level.txt +1 -0
- sphynx/__init__.py +469 -0
- sphynx/__init__.pyi +376 -0
- sphynx/_analytical_client.py +77 -0
- sphynx/_analytical_values.py +252 -0
- sphynx/_audit_client.py +173 -0
- sphynx/_comparison_adapters.py +113 -0
- sphynx/_componentized_full_rag.py +34 -0
- sphynx/_composed_pipeline.py +109 -0
- sphynx/_full_rag_grading.py +218 -0
- sphynx/_full_rag_trace.py +221 -0
- sphynx/_local_eval.py +268 -0
- sphynx/_pipeline_core.py +243 -0
- sphynx/_pr_client.py +237 -0
- sphynx/_rag_diagnostics_calibration.py +285 -0
- sphynx/_rag_diagnostics_catalog.py +39 -0
- sphynx/_rag_diagnostics_common.py +26 -0
- sphynx/_rag_diagnostics_contracts.py +186 -0
- sphynx/_rag_diagnostics_engine.py +243 -0
- sphynx/_rag_diagnostics_importers.py +242 -0
- sphynx/_rag_diagnostics_ranking.py +81 -0
- sphynx/_rag_diagnostics_semantic.py +325 -0
- sphynx/_rag_ranking.py +149 -0
- sphynx/_speech_benchmark_scoring.py +140 -0
- sphynx/_speech_benchmark_sources.py +275 -0
- sphynx/_speech_complete_scoring.py +218 -0
- sphynx/_speech_complete_source.py +118 -0
- sphynx/_speech_kido.py +294 -0
- sphynx/_speech_scoring.py +77 -0
- sphynx/_track_capture.py +213 -0
- sphynx/analytical_views.py +515 -0
- sphynx/assessment_contracts.py +378 -0
- sphynx/assessment_selection.py +57 -0
- sphynx/audit_access.py +245 -0
- sphynx/audit_contracts.py +204 -0
- sphynx/audit_requests.py +180 -0
- sphynx/authoring.py +188 -0
- sphynx/benchmarks.py +568 -0
- sphynx/candidates.py +62 -0
- sphynx/catalog.py +272 -0
- sphynx/chunking.py +372 -0
- sphynx/clickhouse_reader.py +169 -0
- sphynx/client.py +486 -0
- sphynx/comparison_core.py +149 -0
- sphynx/comparison_views.py +223 -0
- sphynx/components.py +1661 -0
- sphynx/contracts.py +514 -0
- sphynx/customer_rerank.py +299 -0
- sphynx/customer_retrieval.py +146 -0
- sphynx/data/benchmark-releases.v1.json +164 -0
- sphynx/data/public-benchmark-release.v1.json +354 -0
- sphynx/data/rag-diagnostics-synthetic.v1.json +6982 -0
- sphynx/data/rag-paper-references.v1.json +176 -0
- sphynx/data/rag-paper-schemas.v1.json +1069 -0
- sphynx/data/rag-rerank-v1.json +507 -0
- sphynx/dataset_catalog_data.py +1214 -0
- sphynx/dataset_readiness.py +138 -0
- sphynx/dataset_v3.py +999 -0
- sphynx/demo.py +130 -0
- sphynx/demo_candidates/__init__.py +1 -0
- sphynx/demo_candidates/dense.py +11 -0
- sphynx/demo_candidates/lexical.py +21 -0
- sphynx/diagnostics.py +207 -0
- sphynx/documentation.py +737 -0
- sphynx/documentation_candidate.py +939 -0
- sphynx/evals.py +538 -0
- sphynx/evaluation_protocols.py +124 -0
- sphynx/evaluator_bindings.py +370 -0
- sphynx/evidence.py +352 -0
- sphynx/execution_capabilities.py +109 -0
- sphynx/fast_search.py +285 -0
- sphynx/full_rag.py +341 -0
- sphynx/hosted/__init__.py +18 -0
- sphynx/hosted/benchmark_intake.py +218 -0
- sphynx/hosted/contracts.py +361 -0
- sphynx/hosted/model_admission.py +130 -0
- sphynx/hosted/model_artifacts.py +298 -0
- sphynx/hosted/model_contracts.py +63 -0
- sphynx/hosted/model_gcs.py +181 -0
- sphynx/hosted/model_history.py +84 -0
- sphynx/hosted/model_session.py +233 -0
- sphynx/hosted/public_suite.py +74 -0
- sphynx/hosted/registry.py +97 -0
- sphynx/hosted/revisions.py +126 -0
- sphynx/hosted/sandbox.py +105 -0
- sphynx/hosted/submission.py +287 -0
- sphynx/hosted_workflow.py +451 -0
- sphynx/http_transport.py +86 -0
- sphynx/integrated_rag.py +352 -0
- sphynx/integrated_workflow.py +552 -0
- sphynx/integration_contracts.py +474 -0
- sphynx/invocation.py +791 -0
- sphynx/invocation_candidates.py +392 -0
- sphynx/invocation_catalog_data.py +40 -0
- sphynx/learned_catalog_data.py +30 -0
- sphynx/learned_rerank.py +267 -0
- sphynx/legacy_full_rag.py +67 -0
- sphynx/lifecycle.py +885 -0
- sphynx/lifecycle_workflow.py +490 -0
- sphynx/metric_views.py +474 -0
- sphynx/metrics.py +282 -0
- sphynx/model_adapters.py +438 -0
- sphynx/model_catalog_data.py +30 -0
- sphynx/official_benchmarks.py +385 -0
- sphynx/packs.py +258 -0
- sphynx/pipeline.py +313 -0
- sphynx/pipeline_workflow.py +551 -0
- sphynx/pr_evaluations.py +560 -0
- sphynx/progress.py +89 -0
- sphynx/public_bundle.py +173 -0
- sphynx/public_eval.py +62 -0
- sphynx/public_rerank.py +355 -0
- sphynx/py.typed +0 -0
- sphynx/pytest_plugin.py +62 -0
- sphynx/question_robustness.py +437 -0
- sphynx/rag.py +329 -0
- sphynx/rag_diagnostics.py +309 -0
- sphynx/rag_papers/__init__.py +29 -0
- sphynx/rag_papers/__main__.py +45 -0
- sphynx/rag_papers/candidates.py +19 -0
- sphynx/rag_papers/contracts.py +282 -0
- sphynx/rag_papers/datasets.py +291 -0
- sphynx/rag_papers/experiments.py +116 -0
- sphynx/rag_papers/judges.py +308 -0
- sphynx/rag_papers/registry.py +33 -0
- sphynx/rag_papers/runner.py +176 -0
- sphynx/rag_papers/sable.py +135 -0
- sphynx/rag_papers/schemas.py +12 -0
- sphynx/rag_papers/scoring.py +249 -0
- sphynx/rag_papers/semantic.py +288 -0
- sphynx/rag_papers/trec.py +80 -0
- sphynx/recorded_scores.py +352 -0
- sphynx/records.py +124 -0
- sphynx/reflex_traces.py +193 -0
- sphynx/report_rendering.py +71 -0
- sphynx/rerank_family_contracts.py +150 -0
- sphynx/rerank_workflow.py +424 -0
- sphynx/research_candidate.py +190 -0
- sphynx/results.py +852 -0
- sphynx/retrieval.py +750 -0
- sphynx/retrieval_candidates.py +295 -0
- sphynx/retrieval_catalog_data.py +19 -0
- sphynx/retrieval_recordings.py +125 -0
- sphynx/reviewer_audit.py +266 -0
- sphynx/routing_policies.py +375 -0
- sphynx/runner.py +544 -0
- sphynx/sable_rerank.py +489 -0
- sphynx/score_reliability.py +393 -0
- sphynx/speech_benchmarks.py +204 -0
- sphynx/speech_campaigns.py +378 -0
- sphynx/speech_complete.py +236 -0
- sphynx/speech_contracts.py +225 -0
- sphynx/speech_evidence.py +429 -0
- sphynx/speech_listening.py +115 -0
- sphynx/speech_multilingual.py +71 -0
- sphynx/speech_tts.py +479 -0
- sphynx/sql/001_sync_v1.sql +30 -0
- sphynx/stage_contracts.py +199 -0
- sphynx/statistical_catalog_data.py +53 -0
- sphynx/statistical_rerank.py +232 -0
- sphynx/sync.py +797 -0
- sphynx/workbench.py +378 -0
- sphynx/workbench_catalog_data.py +13 -0
- sphynx/workbench_tracks.py +78 -0
- sphynx/workflow_async.py +197 -0
- sphynx/workflow_contracts.py +594 -0
- sphynx/workflow_decisions.py +211 -0
- sphynx/workflow_defaults.py +16 -0
- sphynx/workflow_extensions.py +212 -0
- sphynx/workflow_registry.py +513 -0
- sphynx/workflows.py +792 -0
sphynx/benchmarks.py
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
"""Immutable benchmark/label metadata contracts; no execution or data downloads.
|
|
2
|
+
|
|
3
|
+
Eligibility validates declared metadata, not receipt bytes or human approval.
|
|
4
|
+
The only default release is the existing public illustrative synthetic fixture.
|
|
5
|
+
Legacy evaluation records and comparison digests are not changed by this layer.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import asdict, dataclass, fields
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import re
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from .evals import EvaluationContractError, canonical_contract_json
|
|
18
|
+
|
|
19
|
+
LABEL_SCHEMA = "sphynx.label-set-release.v1"
|
|
20
|
+
BENCHMARK_SCHEMA = "sphynx.benchmark-release.v1"
|
|
21
|
+
CATALOG_SCHEMA = "sphynx.benchmark-catalog.v1"
|
|
22
|
+
ELIGIBILITY_SCHEMA = "sphynx.benchmark-eligibility.v1"
|
|
23
|
+
PROJECTION_VERSION = "sphynx.synthetic-rerank.metadata-projections.v1"
|
|
24
|
+
DEFAULT_BENCHMARK = "synthetic.rerank.fixture@2.0.0"
|
|
25
|
+
DEFAULT_LABELS = "synthetic.rerank.labels@2.0.0"
|
|
26
|
+
LEGACY_BANK = "help-center-evidence-mini@2.0.0"
|
|
27
|
+
_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:@/-]{0,255}\Z")
|
|
28
|
+
_DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z")
|
|
29
|
+
_AUTHORITY = {"synthetic_fixture", "native_public_qrels", "owner_reviewed", "human_adjudicated", "machine_provisional"}
|
|
30
|
+
_PROFILES = {"routing_actions", "passage_qrels", "required_evidence", "passage_qrels_and_evidence_groups", "answer_references", "query_intent"}
|
|
31
|
+
_METRICS = {
|
|
32
|
+
"rag.invoke.metric.v1": ("routing_actions", "sphynx.rag.invoke.v1"),
|
|
33
|
+
"rag.rerank.metric.v1": ("passage_qrels_and_evidence_groups", "sphynx.rag.rerank.v1"),
|
|
34
|
+
"rag.rerank.beir.metric.v1": ("passage_qrels", "sphynx.rag.rerank.v1"),
|
|
35
|
+
"rag.retrieve.metric.v1": ("required_evidence", "sphynx.rag.retrieve.v1"),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BenchmarkContractError(ValueError):
|
|
40
|
+
"""Stable payload-free diagnostic; metadata errors never expose data bytes."""
|
|
41
|
+
def __init__(self, code):
|
|
42
|
+
self.code = code
|
|
43
|
+
super().__init__(code)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _require(condition, code):
|
|
47
|
+
if not condition:
|
|
48
|
+
raise BenchmarkContractError(code)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _id(value):
|
|
52
|
+
_require(type(value) is str and _ID.fullmatch(value), "invalid_identifier")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _digest(value):
|
|
56
|
+
_require(type(value) is str and _DIGEST.fullmatch(value), "invalid_digest")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _choice(value, options):
|
|
60
|
+
_require(type(value) is str and value in options, "unsupported_option")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _count(value, maximum=2**53 - 1):
|
|
64
|
+
_require(type(value) is int and 0 <= value <= maximum, "invalid_count")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _tuples(values, kind, *, maximum=512):
|
|
68
|
+
_require(type(values) is tuple and len(values) <= maximum and all(isinstance(v, kind) for v in values), "invalid_collection")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _ids(values):
|
|
72
|
+
_tuples(values, str)
|
|
73
|
+
for value in values:
|
|
74
|
+
_id(value)
|
|
75
|
+
_require(len(set(values)) == len(values), "duplicate_identifier")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def record_digest(value):
|
|
79
|
+
try:
|
|
80
|
+
return "sha256:" + hashlib.sha256(canonical_contract_json(value).encode()).hexdigest()
|
|
81
|
+
except EvaluationContractError:
|
|
82
|
+
raise BenchmarkContractError("invalid_canonical_metadata") from None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class _Record:
|
|
86
|
+
def to_dict(self):
|
|
87
|
+
return json.loads(canonical_contract_json(asdict(self)))
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def _values(cls, value):
|
|
91
|
+
_require(type(value) is dict and set(value) == {field.name for field in fields(cls)}, "invalid_record_shape")
|
|
92
|
+
return dict(value)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(frozen=True)
|
|
96
|
+
class AssetRef(_Record):
|
|
97
|
+
ref: str
|
|
98
|
+
version: str
|
|
99
|
+
digest: str
|
|
100
|
+
|
|
101
|
+
def __post_init__(self):
|
|
102
|
+
_id(self.ref); _id(self.version); _digest(self.digest)
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def from_dict(cls, value):
|
|
106
|
+
return cls(**cls._values(value))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _asset(value, *, optional=False):
|
|
110
|
+
_require(isinstance(value, AssetRef) or optional and value is None, "invalid_asset_reference")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass(frozen=True)
|
|
114
|
+
class ReviewReceipt(_Record):
|
|
115
|
+
receipt: AssetRef
|
|
116
|
+
reviewer_id: str
|
|
117
|
+
reviewed_at_utc: str
|
|
118
|
+
case_ids: tuple[str, ...]
|
|
119
|
+
kind: str
|
|
120
|
+
policy_revision: str
|
|
121
|
+
rubric_revision: str
|
|
122
|
+
|
|
123
|
+
def __post_init__(self):
|
|
124
|
+
_asset(self.receipt); _id(self.reviewer_id); _ids(self.case_ids)
|
|
125
|
+
_require(bool(self.case_ids), "empty_review_scope")
|
|
126
|
+
_id(self.policy_revision); _id(self.rubric_revision)
|
|
127
|
+
_choice(self.kind, {"review", "adjudication"})
|
|
128
|
+
try:
|
|
129
|
+
_require(type(self.reviewed_at_utc) is str and re.fullmatch(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?Z", self.reviewed_at_utc), "invalid_review_timestamp")
|
|
130
|
+
datetime.fromisoformat(self.reviewed_at_utc.replace("Z", "+00:00"))
|
|
131
|
+
except ValueError:
|
|
132
|
+
raise BenchmarkContractError("invalid_review_timestamp") from None
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def from_dict(cls, value):
|
|
136
|
+
value = cls._values(value)
|
|
137
|
+
_require(type(value["case_ids"]) is list, "invalid_collection")
|
|
138
|
+
value.update(receipt=AssetRef.from_dict(value["receipt"]), case_ids=tuple(value["case_ids"]))
|
|
139
|
+
return cls(**value)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass(frozen=True)
|
|
143
|
+
class LabelCase(_Record):
|
|
144
|
+
case_id: str
|
|
145
|
+
input_digest: str
|
|
146
|
+
status: str
|
|
147
|
+
|
|
148
|
+
def __post_init__(self):
|
|
149
|
+
_id(self.case_id); _digest(self.input_digest)
|
|
150
|
+
_choice(self.status, {"judged", "unjudged", "blocked"})
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def from_dict(cls, value):
|
|
154
|
+
return cls(**cls._values(value))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass(frozen=True)
|
|
158
|
+
class LabelLineage(_Record):
|
|
159
|
+
parent_ref: str
|
|
160
|
+
parent_digest: str
|
|
161
|
+
relationship: str
|
|
162
|
+
reason: str
|
|
163
|
+
changed_case_count: int
|
|
164
|
+
evidence: AssetRef | None
|
|
165
|
+
|
|
166
|
+
def __post_init__(self):
|
|
167
|
+
_id(self.parent_ref); _digest(self.parent_digest)
|
|
168
|
+
_choice(self.relationship, {"revision", "subset"})
|
|
169
|
+
_choice(self.reason, {"labels_corrected", "policy_changed", "rubric_changed", "subset_selected"})
|
|
170
|
+
_count(self.changed_case_count); _asset(self.evidence, optional=True)
|
|
171
|
+
_require(self.changed_case_count == 0 or self.evidence is not None, "lineage_evidence_required")
|
|
172
|
+
_require(self.reason != "subset_selected" or self.changed_case_count == 0, "changed_labels_need_revision_reason")
|
|
173
|
+
_require(self.reason != "subset_selected" or self.relationship == "subset", "lineage_subset_reason_mismatch")
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def from_dict(cls, value):
|
|
177
|
+
value = cls._values(value)
|
|
178
|
+
value["evidence"] = None if value["evidence"] is None else AssetRef.from_dict(value["evidence"])
|
|
179
|
+
return cls(**value)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass(frozen=True)
|
|
183
|
+
class LabelSetRelease(_Record):
|
|
184
|
+
schema_version: str
|
|
185
|
+
ref: str
|
|
186
|
+
version: str
|
|
187
|
+
input: AssetRef
|
|
188
|
+
corpus: AssetRef | None
|
|
189
|
+
reference: AssetRef
|
|
190
|
+
label_profile: str
|
|
191
|
+
policy_revision: str
|
|
192
|
+
rubric_revision: str
|
|
193
|
+
authority: str
|
|
194
|
+
missingness: str
|
|
195
|
+
cases: tuple[LabelCase, ...]
|
|
196
|
+
review_receipts: tuple[ReviewReceipt, ...]
|
|
197
|
+
lineage: tuple[LabelLineage, ...]
|
|
198
|
+
|
|
199
|
+
def __post_init__(self):
|
|
200
|
+
_require(self.schema_version == LABEL_SCHEMA, "unsupported_label_schema")
|
|
201
|
+
_id(self.ref); _id(self.version); _asset(self.input); _asset(self.corpus, optional=True); _asset(self.reference)
|
|
202
|
+
_choice(self.label_profile, _PROFILES); _id(self.policy_revision); _id(self.rubric_revision)
|
|
203
|
+
_choice(self.authority, _AUTHORITY)
|
|
204
|
+
_choice(self.missingness, {"complete_required", "native_qrels_unjudged", "development_assume_zero"})
|
|
205
|
+
_tuples(self.cases, LabelCase); _tuples(self.review_receipts, ReviewReceipt); _tuples(self.lineage, LabelLineage, maximum=16)
|
|
206
|
+
ids = tuple(case.case_id for case in self.cases)
|
|
207
|
+
_ids(ids); _require(bool(ids), "empty_label_release")
|
|
208
|
+
_require(len({r.receipt.ref for r in self.review_receipts}) == len(self.review_receipts), "duplicate_review_receipt")
|
|
209
|
+
_require(len({parent.parent_ref for parent in self.lineage}) == len(self.lineage), "duplicate_lineage_parent")
|
|
210
|
+
_require(self.authority != "native_public_qrels" or self.label_profile == "passage_qrels", "authority_profile_mismatch")
|
|
211
|
+
_require(self.missingness != "native_qrels_unjudged" or self.authority == "native_public_qrels", "missingness_authority_mismatch")
|
|
212
|
+
_require(self.missingness != "development_assume_zero" or self.authority == "machine_provisional", "missingness_authority_mismatch")
|
|
213
|
+
_require(self.authority in {"owner_reviewed", "human_adjudicated"} or not self.review_receipts, "unexpected_human_receipts")
|
|
214
|
+
for receipt in self.review_receipts:
|
|
215
|
+
_require(set(receipt.case_ids) <= set(ids), "review_scope_mismatch")
|
|
216
|
+
_require(receipt.policy_revision == self.policy_revision and receipt.rubric_revision == self.rubric_revision, "review_policy_mismatch")
|
|
217
|
+
self.digest
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def digest(self):
|
|
221
|
+
return record_digest(self.to_dict())
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def asset_ref(self):
|
|
225
|
+
return AssetRef(self.ref, self.version, self.digest)
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def from_dict(cls, value):
|
|
229
|
+
value = cls._values(value)
|
|
230
|
+
for key in ("input", "reference"):
|
|
231
|
+
value[key] = AssetRef.from_dict(value[key])
|
|
232
|
+
value["corpus"] = None if value["corpus"] is None else AssetRef.from_dict(value["corpus"])
|
|
233
|
+
for key, kind in (("cases", LabelCase), ("review_receipts", ReviewReceipt), ("lineage", LabelLineage)):
|
|
234
|
+
_require(type(value[key]) is list, "invalid_collection")
|
|
235
|
+
value[key] = tuple(kind.from_dict(item) for item in value[key])
|
|
236
|
+
return cls(**value)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@dataclass(frozen=True)
|
|
240
|
+
class BenchmarkCase(_Record):
|
|
241
|
+
case_id: str
|
|
242
|
+
input_digest: str
|
|
243
|
+
company: str
|
|
244
|
+
split: str
|
|
245
|
+
input_form: str
|
|
246
|
+
form_status: str
|
|
247
|
+
form_receipt: AssetRef | None
|
|
248
|
+
length_words: int
|
|
249
|
+
|
|
250
|
+
def __post_init__(self):
|
|
251
|
+
_id(self.case_id); _digest(self.input_digest); _id(self.company); _id(self.split)
|
|
252
|
+
_require(self.split != "all", "reserved_split_name")
|
|
253
|
+
_choice(self.input_form, {"single_sentence", "paragraph", "not_applicable", "unclassified"})
|
|
254
|
+
_choice(self.form_status, {"reviewed", "authored", "unclassified"})
|
|
255
|
+
_asset(self.form_receipt, optional=True); _count(self.length_words, 1_000_000)
|
|
256
|
+
_require((self.form_status == "reviewed") == (self.form_receipt is not None), "form_receipt_mismatch")
|
|
257
|
+
_require((self.input_form == "unclassified") == (self.form_status == "unclassified"), "form_classification_mismatch")
|
|
258
|
+
|
|
259
|
+
@classmethod
|
|
260
|
+
def from_dict(cls, value):
|
|
261
|
+
value = cls._values(value)
|
|
262
|
+
value["form_receipt"] = None if value["form_receipt"] is None else AssetRef.from_dict(value["form_receipt"])
|
|
263
|
+
return cls(**value)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@dataclass(frozen=True)
|
|
267
|
+
class BenchmarkRelease(_Record):
|
|
268
|
+
schema_version: str
|
|
269
|
+
ref: str
|
|
270
|
+
version: str
|
|
271
|
+
evaluation_id: str
|
|
272
|
+
protocol_revision: str
|
|
273
|
+
interface_version: str
|
|
274
|
+
scope: str
|
|
275
|
+
input: AssetRef
|
|
276
|
+
corpus: AssetRef | None
|
|
277
|
+
label_set: AssetRef
|
|
278
|
+
required_label_profile: str
|
|
279
|
+
metric_profile: str
|
|
280
|
+
required_input_form: str
|
|
281
|
+
intended_use: str
|
|
282
|
+
cases: tuple[BenchmarkCase, ...]
|
|
283
|
+
|
|
284
|
+
def __post_init__(self):
|
|
285
|
+
_require(self.schema_version == BENCHMARK_SCHEMA, "unsupported_benchmark_schema")
|
|
286
|
+
for value in (self.ref, self.version, self.evaluation_id, self.protocol_revision, self.interface_version):
|
|
287
|
+
_id(value)
|
|
288
|
+
_choice(self.scope, {"component", "pipeline", "complete_system"})
|
|
289
|
+
_asset(self.input); _asset(self.corpus, optional=True); _asset(self.label_set)
|
|
290
|
+
_choice(self.required_input_form, {"any", "single_sentence", "paragraph"})
|
|
291
|
+
_choice(self.intended_use, {"illustrative", "development", "benchmark", "protected"})
|
|
292
|
+
_require(type(self.metric_profile) is str and self.metric_profile in _METRICS and _METRICS[self.metric_profile] == (self.required_label_profile, self.interface_version), "metric_label_interface_mismatch")
|
|
293
|
+
_tuples(self.cases, BenchmarkCase); _ids(tuple(case.case_id for case in self.cases))
|
|
294
|
+
_require(bool(self.cases), "empty_benchmark_release")
|
|
295
|
+
self.digest
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def digest(self):
|
|
299
|
+
return record_digest(self.to_dict())
|
|
300
|
+
|
|
301
|
+
@property
|
|
302
|
+
def asset_ref(self):
|
|
303
|
+
return AssetRef(self.ref, self.version, self.digest)
|
|
304
|
+
|
|
305
|
+
@classmethod
|
|
306
|
+
def from_dict(cls, value):
|
|
307
|
+
value = cls._values(value)
|
|
308
|
+
for key in ("input", "label_set"):
|
|
309
|
+
value[key] = AssetRef.from_dict(value[key])
|
|
310
|
+
value["corpus"] = None if value["corpus"] is None else AssetRef.from_dict(value["corpus"])
|
|
311
|
+
_require(type(value["cases"]) is list, "invalid_collection")
|
|
312
|
+
value["cases"] = tuple(BenchmarkCase.from_dict(item) for item in value["cases"])
|
|
313
|
+
return cls(**value)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@dataclass(frozen=True)
|
|
317
|
+
class SliceParameters(_Record):
|
|
318
|
+
companies: tuple[str, ...] = ()
|
|
319
|
+
split: str = "all"
|
|
320
|
+
length_words: tuple[int, int] | None = None
|
|
321
|
+
|
|
322
|
+
def __post_init__(self):
|
|
323
|
+
_ids(self.companies); _id(self.split)
|
|
324
|
+
if self.length_words is not None:
|
|
325
|
+
_require(type(self.length_words) is tuple and len(self.length_words) == 2, "invalid_length_range")
|
|
326
|
+
for value in self.length_words:
|
|
327
|
+
_count(value, 1_000_000)
|
|
328
|
+
_require(self.length_words[0] <= self.length_words[1], "invalid_length_range")
|
|
329
|
+
|
|
330
|
+
def to_dict(self):
|
|
331
|
+
return {"companies": list(self.companies), "split": self.split,
|
|
332
|
+
"length_words": None if self.length_words is None else dict(zip(("minimum", "maximum"), self.length_words))}
|
|
333
|
+
|
|
334
|
+
@classmethod
|
|
335
|
+
def from_dict(cls, value):
|
|
336
|
+
_require(type(value) is dict and set(value) <= {"companies", "split", "length_words"}, "invalid_slice_parameters")
|
|
337
|
+
companies, length = value.get("companies", []), value.get("length_words")
|
|
338
|
+
_require(type(companies) is list, "invalid_collection")
|
|
339
|
+
if length is not None:
|
|
340
|
+
_require(type(length) is dict and set(length) == {"minimum", "maximum"}, "invalid_length_range")
|
|
341
|
+
length = (length["minimum"], length["maximum"])
|
|
342
|
+
return cls(tuple(companies), value.get("split", "all"), length)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@dataclass(frozen=True)
|
|
346
|
+
class BenchmarkEligibility:
|
|
347
|
+
"""A detached compatibility report, not approval, scoring or execution."""
|
|
348
|
+
_json: str
|
|
349
|
+
|
|
350
|
+
def __post_init__(self):
|
|
351
|
+
try:
|
|
352
|
+
value = json.loads(self._json)
|
|
353
|
+
except (ValueError, TypeError):
|
|
354
|
+
raise BenchmarkContractError("invalid_eligibility_record") from None
|
|
355
|
+
required = {"schema_version", "benchmark", "label_set", "evaluation_id", "protocol_revision", "interface_version", "scope", "metric_profile", "authority", "missingness", "intended_use", "purpose", "metadata_only", "review_receipts_verified", "semantic_authority_validated", "selected_case_ids", "selected_case_count", "judged_case_count", "unjudged_case_count", "blocked_case_count", "form_blocked_case_count", "status", "blockers", "slice"}
|
|
356
|
+
_require(type(value) is dict and set(value) == required and value["schema_version"] == ELIGIBILITY_SCHEMA, "invalid_eligibility_record")
|
|
357
|
+
_require(value["metadata_only"] is True and value["review_receipts_verified"] is False and value["semantic_authority_validated"] is False, "invalid_eligibility_trust_claim")
|
|
358
|
+
AssetRef.from_dict(value["benchmark"]); AssetRef.from_dict(value["label_set"])
|
|
359
|
+
for key in ("evaluation_id", "protocol_revision", "interface_version", "metric_profile"):
|
|
360
|
+
_id(value[key])
|
|
361
|
+
_choice(value["scope"], {"component", "pipeline", "complete_system"})
|
|
362
|
+
_choice(value["authority"], _AUTHORITY)
|
|
363
|
+
_choice(value["missingness"], {"complete_required", "native_qrels_unjudged", "development_assume_zero"})
|
|
364
|
+
_choice(value["intended_use"], {"illustrative", "development", "benchmark", "protected"})
|
|
365
|
+
_choice(value["purpose"], {"development_instrumentation", "evaluation"})
|
|
366
|
+
_choice(value["status"], {"eligible", "blocked"})
|
|
367
|
+
_require(type(value["selected_case_ids"]) is list and type(value["blockers"]) is list, "invalid_eligibility_record")
|
|
368
|
+
_ids(tuple(value["selected_case_ids"])); _ids(tuple(value["blockers"]))
|
|
369
|
+
for key in ("selected_case_count", "judged_case_count", "unjudged_case_count", "blocked_case_count", "form_blocked_case_count"):
|
|
370
|
+
_count(value[key], 512)
|
|
371
|
+
_require(0 < value["selected_case_count"] == len(value["selected_case_ids"]) and all(value[key] <= value["selected_case_count"] for key in ("judged_case_count", "unjudged_case_count", "blocked_case_count", "form_blocked_case_count")) and value["judged_case_count"] + value["unjudged_case_count"] <= value["selected_case_count"], "invalid_eligibility_counts")
|
|
372
|
+
_require((value["status"] == "eligible") == (not value["blockers"]) and value["blockers"] == sorted(value["blockers"]), "invalid_eligibility_status")
|
|
373
|
+
SliceParameters.from_dict(value["slice"])
|
|
374
|
+
_require(canonical_contract_json(value) == self._json, "invalid_canonical_metadata")
|
|
375
|
+
|
|
376
|
+
def to_dict(self):
|
|
377
|
+
return json.loads(self._json)
|
|
378
|
+
|
|
379
|
+
@property
|
|
380
|
+
def eligible(self):
|
|
381
|
+
return self.to_dict()["status"] == "eligible"
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
@dataclass(frozen=True)
|
|
385
|
+
class BenchmarkRegistry:
|
|
386
|
+
label_sets: tuple[LabelSetRelease, ...]
|
|
387
|
+
benchmarks: tuple[BenchmarkRelease, ...]
|
|
388
|
+
|
|
389
|
+
def __post_init__(self):
|
|
390
|
+
self.validate()
|
|
391
|
+
|
|
392
|
+
def validate(self):
|
|
393
|
+
_tuples(self.label_sets, LabelSetRelease); _tuples(self.benchmarks, BenchmarkRelease)
|
|
394
|
+
labels = {record.ref: record for record in self.label_sets}
|
|
395
|
+
_require(len(labels) == len(self.label_sets), "duplicate_label_release")
|
|
396
|
+
_require(len({record.ref for record in self.benchmarks}) == len(self.benchmarks), "duplicate_benchmark_release")
|
|
397
|
+
visiting, visited = set(), set()
|
|
398
|
+
def visit(ref):
|
|
399
|
+
_require(ref not in visiting, "lineage_cycle")
|
|
400
|
+
if ref in visited:
|
|
401
|
+
return
|
|
402
|
+
visiting.add(ref)
|
|
403
|
+
current = labels[ref]
|
|
404
|
+
current_cases = {case.case_id: case.input_digest for case in current.cases}
|
|
405
|
+
for link in current.lineage:
|
|
406
|
+
_require(link.parent_ref in labels, "lineage_parent_missing")
|
|
407
|
+
parent = labels[link.parent_ref]
|
|
408
|
+
visit(parent.ref)
|
|
409
|
+
_require(link.parent_digest == parent.digest, "lineage_parent_digest_mismatch")
|
|
410
|
+
_require(current.label_profile == parent.label_profile, "lineage_label_profile_mismatch")
|
|
411
|
+
_require(current.corpus == parent.corpus, "lineage_corpus_mismatch")
|
|
412
|
+
parent_cases = {case.case_id: case.input_digest for case in parent.cases}
|
|
413
|
+
if link.relationship == "revision":
|
|
414
|
+
_require(current.input == parent.input and current_cases == parent_cases, "lineage_input_mismatch")
|
|
415
|
+
else:
|
|
416
|
+
_require(all(parent_cases.get(key) == value for key, value in current_cases.items()), "lineage_subset_mismatch")
|
|
417
|
+
_require(link.changed_case_count <= len(current_cases), "lineage_changed_count_invalid")
|
|
418
|
+
_require(current.policy_revision == parent.policy_revision or link.reason == "policy_changed", "lineage_policy_reason_required")
|
|
419
|
+
_require(current.rubric_revision == parent.rubric_revision or link.reason in {"rubric_changed", "policy_changed"}, "lineage_rubric_reason_required")
|
|
420
|
+
visiting.remove(ref); visited.add(ref)
|
|
421
|
+
for ref in labels:
|
|
422
|
+
visit(ref)
|
|
423
|
+
protocols = {}
|
|
424
|
+
for benchmark in self.benchmarks:
|
|
425
|
+
_require(benchmark.label_set.ref in labels, "label_release_missing")
|
|
426
|
+
label = labels[benchmark.label_set.ref]
|
|
427
|
+
_require(benchmark.label_set == label.asset_ref, "label_release_digest_mismatch")
|
|
428
|
+
_require((benchmark.input, benchmark.corpus) == (label.input, label.corpus), "benchmark_input_binding_mismatch")
|
|
429
|
+
_require({c.case_id: c.input_digest for c in benchmark.cases} == {c.case_id: c.input_digest for c in label.cases}, "benchmark_case_binding_mismatch")
|
|
430
|
+
_require(benchmark.required_label_profile == label.label_profile, "benchmark_label_profile_mismatch")
|
|
431
|
+
_require(label.authority != "synthetic_fixture" or benchmark.intended_use == "illustrative", "synthetic_authority_not_benchmark")
|
|
432
|
+
_require(label.authority != "machine_provisional" or benchmark.intended_use == "development", "provisional_authority_not_benchmark")
|
|
433
|
+
_require(benchmark.intended_use != "protected" or label.authority in {"owner_reviewed", "human_adjudicated"}, "protected_authority_required")
|
|
434
|
+
key = (benchmark.evaluation_id, benchmark.protocol_revision)
|
|
435
|
+
boundary = (benchmark.interface_version, benchmark.scope, benchmark.required_label_profile, benchmark.metric_profile, benchmark.required_input_form)
|
|
436
|
+
_require(key not in protocols or protocols[key] == boundary, "protocol_boundary_mismatch")
|
|
437
|
+
protocols[key] = boundary
|
|
438
|
+
return self
|
|
439
|
+
|
|
440
|
+
def to_dict(self):
|
|
441
|
+
return {"schema_version": CATALOG_SCHEMA, "label_sets": [record.to_dict() for record in self.label_sets],
|
|
442
|
+
"benchmarks": [record.to_dict() for record in self.benchmarks]}
|
|
443
|
+
|
|
444
|
+
def resolve(self, benchmark_ref, parameters=None):
|
|
445
|
+
benchmark = next((b for b in self.benchmarks if b.ref == benchmark_ref), None)
|
|
446
|
+
_require(benchmark is not None, "benchmark_not_registered")
|
|
447
|
+
parameters = SliceParameters() if parameters is None else parameters
|
|
448
|
+
if type(parameters) is dict:
|
|
449
|
+
parameters = SliceParameters.from_dict(parameters)
|
|
450
|
+
_require(isinstance(parameters, SliceParameters), "invalid_slice_parameters")
|
|
451
|
+
_require(set(parameters.companies) <= {case.company for case in benchmark.cases}, "company_not_supported")
|
|
452
|
+
_require(parameters.split == "all" or parameters.split in {case.split for case in benchmark.cases}, "split_not_supported")
|
|
453
|
+
selected = [case for case in benchmark.cases if
|
|
454
|
+
(not parameters.companies or case.company in parameters.companies)
|
|
455
|
+
and (parameters.split == "all" or case.split == parameters.split)
|
|
456
|
+
and (parameters.length_words is None or parameters.length_words[0] <= case.length_words <= parameters.length_words[1])]
|
|
457
|
+
_require(bool(selected), "empty_benchmark_slice")
|
|
458
|
+
_require(not parameters.companies or {case.company for case in selected} == set(parameters.companies), "unsupported_joint_slice")
|
|
459
|
+
label = next(record for record in self.label_sets if record.ref == benchmark.label_set.ref)
|
|
460
|
+
labels = {case.case_id: case for case in label.cases}
|
|
461
|
+
reviewed = {key for receipt in label.review_receipts for key in receipt.case_ids
|
|
462
|
+
if label.authority != "human_adjudicated" or receipt.kind == "adjudication"}
|
|
463
|
+
blocked, forms, blockers = set(), set(), set()
|
|
464
|
+
judged = unjudged = 0
|
|
465
|
+
for case in selected:
|
|
466
|
+
status = labels[case.case_id].status
|
|
467
|
+
judged += status == "judged"; unjudged += status == "unjudged"
|
|
468
|
+
if status == "blocked":
|
|
469
|
+
blocked.add(case.case_id); blockers.add("blocked_reference_cases")
|
|
470
|
+
if status == "unjudged" and label.missingness == "complete_required":
|
|
471
|
+
blockers.add("required_labels_missing")
|
|
472
|
+
if status == "judged" and label.authority in {"owner_reviewed", "human_adjudicated"} and case.case_id not in reviewed:
|
|
473
|
+
blocked.add(case.case_id); blockers.add("review_coverage_incomplete")
|
|
474
|
+
if benchmark.required_input_form != "any" and (case.input_form != benchmark.required_input_form
|
|
475
|
+
or case.form_status != "reviewed" and benchmark.intended_use != "illustrative"):
|
|
476
|
+
forms.add(case.case_id); blockers.add("reviewed_input_form_required")
|
|
477
|
+
value = {"schema_version": ELIGIBILITY_SCHEMA, "benchmark": benchmark.asset_ref.to_dict(),
|
|
478
|
+
"label_set": label.asset_ref.to_dict(), "evaluation_id": benchmark.evaluation_id,
|
|
479
|
+
"protocol_revision": benchmark.protocol_revision, "interface_version": benchmark.interface_version,
|
|
480
|
+
"scope": benchmark.scope, "metric_profile": benchmark.metric_profile, "authority": label.authority,
|
|
481
|
+
"missingness": label.missingness,
|
|
482
|
+
"intended_use": benchmark.intended_use,
|
|
483
|
+
"purpose": "development_instrumentation" if label.authority == "synthetic_fixture" else "evaluation",
|
|
484
|
+
"metadata_only": True, "review_receipts_verified": False, "semantic_authority_validated": False,
|
|
485
|
+
"selected_case_ids": [case.case_id for case in selected], "selected_case_count": len(selected),
|
|
486
|
+
"judged_case_count": judged, "unjudged_case_count": unjudged, "blocked_case_count": len(blocked),
|
|
487
|
+
"form_blocked_case_count": len(forms), "status": "blocked" if blockers else "eligible",
|
|
488
|
+
"blockers": sorted(blockers), "slice": parameters.to_dict()}
|
|
489
|
+
return BenchmarkEligibility(canonical_contract_json(value))
|
|
490
|
+
|
|
491
|
+
def validate_legacy(self, resolved_record):
|
|
492
|
+
"""Gate a trusted resolved legacy invocation without rewriting its bytes."""
|
|
493
|
+
try:
|
|
494
|
+
from .evals import resolve_eval
|
|
495
|
+
definition, parameters = resolved_record["definition"], resolved_record["parameters"]
|
|
496
|
+
expected = resolve_eval("rag.rerank.v1", parameters).to_dict()
|
|
497
|
+
_require(definition == expected["definition"] and parameters == expected["parameters"]
|
|
498
|
+
and resolved_record["artifacts"] == expected["artifacts"],
|
|
499
|
+
"legacy_benchmark_binding_mismatch")
|
|
500
|
+
except (KeyError, TypeError, EvaluationContractError):
|
|
501
|
+
raise BenchmarkContractError("legacy_benchmark_binding_mismatch") from None
|
|
502
|
+
eligibility = self.resolve(DEFAULT_BENCHMARK)
|
|
503
|
+
_require(eligibility.eligible, "legacy_benchmark_not_eligible")
|
|
504
|
+
return eligibility
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def synthetic_projection_metadata():
|
|
508
|
+
"""Derived digests only; do not claim independently archived input/label assets."""
|
|
509
|
+
from .contracts import build_candidate_request, canonical_json
|
|
510
|
+
from .rag import FIXTURE_SHA256, load_rag_rerank_pack
|
|
511
|
+
pack = load_rag_rerank_pack()
|
|
512
|
+
requests = [build_candidate_request(case).to_dict() for case in pack.cases]
|
|
513
|
+
def projection_digest(value):
|
|
514
|
+
return "sha256:" + hashlib.sha256(canonical_json(value).encode()).hexdigest()
|
|
515
|
+
inputs = {"schema_version": "sphynx.synthetic-rerank.input-projection.v1", "cases": requests}
|
|
516
|
+
corpus = {"schema_version": "sphynx.synthetic-rerank.corpus-projection.v1", "cases": [
|
|
517
|
+
{"case_id": request["case_id"], "versions": request["versions"], "candidates": [
|
|
518
|
+
{"candidate_id": candidate["candidate_id"], "input": candidate["input"]} for candidate in request["candidates"]]}
|
|
519
|
+
for request in requests]}
|
|
520
|
+
references = {"schema_version": "sphynx.synthetic-rerank.reference-projection.v1", "cases": [
|
|
521
|
+
asdict(pack.reference_for(case.case_id)) for case in pack.cases]}
|
|
522
|
+
source = AssetRef(LEGACY_BANK, "2.0.0", "sha256:" + FIXTURE_SHA256)
|
|
523
|
+
input_ref = AssetRef("synthetic.rerank.input-projection@2.0.0", "2.0.0", projection_digest(inputs))
|
|
524
|
+
corpus_ref = AssetRef("synthetic.rerank.corpus-projection@2.0.0", "2.0.0", projection_digest(corpus))
|
|
525
|
+
reference_ref = AssetRef("synthetic.rerank.reference-projection@2.0.0", "2.0.0", projection_digest(references))
|
|
526
|
+
cases = tuple(BenchmarkCase(case.case_id, projection_digest(request), "synthetic", case.split,
|
|
527
|
+
"unclassified", "unclassified", None, len(case.query.split())) for case, request in zip(pack.cases, requests))
|
|
528
|
+
return source, input_ref, corpus_ref, reference_ref, cases
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def default_registry():
|
|
532
|
+
"""Validate bundled metadata against the actual existing fixture before use."""
|
|
533
|
+
path = Path(__file__).resolve().parent / "data" / "benchmark-releases.v1.json"
|
|
534
|
+
try:
|
|
535
|
+
def unique(pairs):
|
|
536
|
+
value = {}
|
|
537
|
+
for key, item in pairs:
|
|
538
|
+
_require(key not in value, "duplicate_metadata_field")
|
|
539
|
+
value[key] = item
|
|
540
|
+
return value
|
|
541
|
+
raw = json.loads(path.read_bytes(), object_pairs_hook=unique,
|
|
542
|
+
parse_constant=lambda _: (_ for _ in ()).throw(BenchmarkContractError("invalid_canonical_metadata")))
|
|
543
|
+
_require(type(raw) is dict and set(raw) == {"schema_version", "source", "projection_version", "metadata_only", "independent_assets_archived", "label_sets", "benchmarks"}, "invalid_catalog_shape")
|
|
544
|
+
_require(raw["schema_version"] == CATALOG_SCHEMA and raw["projection_version"] == PROJECTION_VERSION
|
|
545
|
+
and raw["metadata_only"] is True and raw["independent_assets_archived"] is False,
|
|
546
|
+
"invalid_catalog_provenance")
|
|
547
|
+
source, inputs, corpus, references, cases = synthetic_projection_metadata()
|
|
548
|
+
_require(raw["source"] == source.to_dict(), "fixture_source_mismatch")
|
|
549
|
+
_require(type(raw["label_sets"]) is list and type(raw["benchmarks"]) is list
|
|
550
|
+
and len(raw["label_sets"]) == len(raw["benchmarks"]) == 1, "unexpected_default_catalog")
|
|
551
|
+
registry = BenchmarkRegistry(tuple(LabelSetRelease.from_dict(item) for item in raw["label_sets"]),
|
|
552
|
+
tuple(BenchmarkRelease.from_dict(item) for item in raw["benchmarks"]))
|
|
553
|
+
label, benchmark = registry.label_sets[0], registry.benchmarks[0]
|
|
554
|
+
_require((label.ref, label.input, label.corpus, label.reference) == (DEFAULT_LABELS, inputs, corpus, references), "fixture_projection_mismatch")
|
|
555
|
+
_require(benchmark.ref == DEFAULT_BENCHMARK and benchmark.cases == cases and benchmark.evaluation_id == "rag.rerank.v1"
|
|
556
|
+
and benchmark.protocol_revision == "rag.rerank.v1" and benchmark.scope == "component"
|
|
557
|
+
and benchmark.version == label.version == "2.0.0"
|
|
558
|
+
and benchmark.interface_version == "sphynx.rag.rerank.v1" and benchmark.metric_profile == "rag.rerank.metric.v1"
|
|
559
|
+
and benchmark.required_label_profile == label.label_profile == "passage_qrels_and_evidence_groups"
|
|
560
|
+
and label.policy_revision == "rag.rerank.synthetic-label-policy.v1" and label.rubric_revision == "rag.rerank.metric.v1"
|
|
561
|
+
and benchmark.required_input_form == "any" and benchmark.intended_use == "illustrative"
|
|
562
|
+
and label.authority == "synthetic_fixture" and label.missingness == "complete_required"
|
|
563
|
+
and all(case.status == "judged" for case in label.cases), "fixture_membership_mismatch")
|
|
564
|
+
return registry
|
|
565
|
+
except (OSError, ValueError, TypeError, RecursionError) as exc:
|
|
566
|
+
if isinstance(exc, BenchmarkContractError):
|
|
567
|
+
raise
|
|
568
|
+
raise BenchmarkContractError("invalid_benchmark_catalog") from None
|
sphynx/candidates.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Small adapters for candidate scripts using the existing subprocess protocol."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from contextlib import redirect_stdout
|
|
7
|
+
from typing import Callable
|
|
8
|
+
|
|
9
|
+
from .contracts import (
|
|
10
|
+
RESPONSE_SCHEMA_VERSION,
|
|
11
|
+
ContractError,
|
|
12
|
+
canonical_json,
|
|
13
|
+
parse_candidate_request,
|
|
14
|
+
validate_candidate_response,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def serve_reranker(score: Callable[[str, str, float], float]) -> None:
|
|
19
|
+
"""Consume one v1 request and write one fully validated v1 response.
|
|
20
|
+
|
|
21
|
+
Call this from a candidate script's ``if __name__ == "__main__"`` block.
|
|
22
|
+
``score(query, passage, supplied_score)`` runs once per frozen passage and
|
|
23
|
+
must return a finite Python int or float (booleans are rejected). Callback
|
|
24
|
+
prints go to stderr. On invalid input/output or callback failure this raises
|
|
25
|
+
``SystemExit(1)`` after a payload-free diagnostic on stderr and writes no
|
|
26
|
+
response. Timeout, environment, stream limits, and execution remain owned
|
|
27
|
+
by the subprocess runner. This adapter does not provide OS isolation.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
if not callable(score):
|
|
32
|
+
raise ContractError("score must be callable")
|
|
33
|
+
request = parse_candidate_request(getattr(sys.stdin, "buffer", sys.stdin).read())
|
|
34
|
+
except Exception as exc:
|
|
35
|
+
message = str(exc) if isinstance(exc, ContractError) else f"could not read request ({type(exc).__name__})"
|
|
36
|
+
print(f"serve_reranker: {message}", file=sys.stderr)
|
|
37
|
+
raise SystemExit(1) from None
|
|
38
|
+
|
|
39
|
+
scores = []
|
|
40
|
+
for index, item in enumerate(request.candidates):
|
|
41
|
+
try:
|
|
42
|
+
with redirect_stdout(sys.stderr):
|
|
43
|
+
number = score(request.query, item.input, item.dense_score)
|
|
44
|
+
except BaseException as exc:
|
|
45
|
+
# Exception text can contain the query/passage or caller secrets.
|
|
46
|
+
print(f"serve_reranker: scoring callback failed at passage {index + 1} ({type(exc).__name__})", file=sys.stderr)
|
|
47
|
+
raise SystemExit(1) from None
|
|
48
|
+
scores.append({"candidate_id": item.candidate_id, "ranking_score": number})
|
|
49
|
+
try:
|
|
50
|
+
response = validate_candidate_response({
|
|
51
|
+
"schema_version": RESPONSE_SCHEMA_VERSION,
|
|
52
|
+
"query_id": request.query_id,
|
|
53
|
+
"pool_digest": request.pool_digest,
|
|
54
|
+
"scores": scores,
|
|
55
|
+
}, request)
|
|
56
|
+
payload = canonical_json(response.to_dict()) + "\n"
|
|
57
|
+
except Exception as exc:
|
|
58
|
+
message = str(exc) if isinstance(exc, ContractError) else f"invalid response ({type(exc).__name__})"
|
|
59
|
+
print(f"serve_reranker: {message}", file=sys.stderr)
|
|
60
|
+
raise SystemExit(1) from None
|
|
61
|
+
sys.stdout.write(payload)
|
|
62
|
+
sys.stdout.flush()
|