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
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""Replay a hash-bound snapshot of local reranker outputs.
|
|
2
|
+
|
|
3
|
+
This adapter measures replay runtime, not model inference latency. Model timing
|
|
4
|
+
belongs to the owner-side score snapshot retained as separate evidence.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
import math
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Mapping
|
|
13
|
+
|
|
14
|
+
from .contracts import ContractError, canonical_json
|
|
15
|
+
|
|
16
|
+
RECORDED_SCORE_SCHEMA = "sphynx.recorded.rerank.scores.v1"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def replay_rerank_scores(request: Mapping[str, Any], score_path: str | Path,
|
|
20
|
+
expected_sha256: str) -> dict[str, Any]:
|
|
21
|
+
"""Return recorded scores only for an exactly matching label-free request."""
|
|
22
|
+
payload = Path(score_path).read_bytes()
|
|
23
|
+
if hashlib.sha256(payload).hexdigest() != expected_sha256.removeprefix("sha256:"):
|
|
24
|
+
raise ContractError("recorded model score snapshot hash differs")
|
|
25
|
+
try:
|
|
26
|
+
artifact = json.loads(payload)
|
|
27
|
+
except (ValueError, UnicodeError) as exc:
|
|
28
|
+
raise ContractError("invalid recorded model score snapshot") from exc
|
|
29
|
+
if not isinstance(artifact, dict) or artifact.get("schema_version") != RECORDED_SCORE_SCHEMA:
|
|
30
|
+
raise ContractError("unsupported recorded model score snapshot")
|
|
31
|
+
if request.get("schema_version") != "sphynx.rag.rerank.request.v1":
|
|
32
|
+
raise ContractError("unsupported recorded-score request")
|
|
33
|
+
rows = artifact.get("cases")
|
|
34
|
+
if not isinstance(rows, list) or any(not isinstance(row, dict) for row in rows):
|
|
35
|
+
raise ContractError("recorded model cases are invalid")
|
|
36
|
+
identifiers = [row.get("case_id") for row in rows]
|
|
37
|
+
if len(set(identifiers)) != len(identifiers):
|
|
38
|
+
raise ContractError("recorded model cases contain duplicate IDs")
|
|
39
|
+
matches = [row for row in rows if row.get("case_id") == request.get("case_id")]
|
|
40
|
+
if len(matches) != 1:
|
|
41
|
+
raise ContractError("recorded model case not found")
|
|
42
|
+
row = matches[0]
|
|
43
|
+
request_digest = "sha256:" + hashlib.sha256(canonical_json(dict(request)).encode("utf-8")).hexdigest()
|
|
44
|
+
if row.get("request_sha256") != request_digest:
|
|
45
|
+
raise ContractError("recorded model request identity differs")
|
|
46
|
+
scores = row.get("scores")
|
|
47
|
+
if not isinstance(scores, list) or len(scores) != len(request.get("candidates", [])):
|
|
48
|
+
raise ContractError("recorded model score coverage differs")
|
|
49
|
+
expected_ids = [candidate["candidate_id"] for candidate in request["candidates"]]
|
|
50
|
+
if [item.get("candidate_id") for item in scores] != expected_ids:
|
|
51
|
+
raise ContractError("recorded model score order or IDs differ")
|
|
52
|
+
for item in scores:
|
|
53
|
+
value = item.get("ranking_score")
|
|
54
|
+
if set(item) != {"candidate_id", "ranking_score"} or isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
|
|
55
|
+
raise ContractError("recorded model scores must be finite numbers")
|
|
56
|
+
return {
|
|
57
|
+
"schema_version": "sphynx.rag.rerank.response.v1",
|
|
58
|
+
"query_id": request["query_id"], "pool_digest": request["pool_digest"],
|
|
59
|
+
"scores": scores,
|
|
60
|
+
}
|
|
61
|
+
# The strict additive interface retains the historical v1 replay API unchanged.
|
|
62
|
+
BOUND_RECORDED_SCORE_SCHEMA = "sphynx.recorded.rerank.scores.v2"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _strict_json(payload: bytes) -> dict[str, Any]:
|
|
66
|
+
def unique(pairs):
|
|
67
|
+
result = {}
|
|
68
|
+
for key, value in pairs:
|
|
69
|
+
if key in result:
|
|
70
|
+
raise ContractError("duplicate recorded JSON field")
|
|
71
|
+
result[key] = value
|
|
72
|
+
return result
|
|
73
|
+
def invalid(value):
|
|
74
|
+
raise ContractError("nonfinite recorded JSON number")
|
|
75
|
+
try:
|
|
76
|
+
value = json.loads(payload, object_pairs_hook=unique, parse_constant=invalid)
|
|
77
|
+
except ContractError:
|
|
78
|
+
raise
|
|
79
|
+
except (ValueError, UnicodeError) as exc:
|
|
80
|
+
raise ContractError("invalid recorded score JSON") from exc
|
|
81
|
+
if not isinstance(value, dict):
|
|
82
|
+
raise ContractError("recorded artifact must be an object")
|
|
83
|
+
return value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _read_bound(path, digest):
|
|
87
|
+
from .integration_contracts import require_digest
|
|
88
|
+
require_digest(digest, "recording_sha256")
|
|
89
|
+
payload = Path(path).read_bytes()
|
|
90
|
+
if "sha256:" + hashlib.sha256(payload).hexdigest() != digest:
|
|
91
|
+
raise ContractError("recorded snapshot hash differs")
|
|
92
|
+
return _strict_json(payload)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _recorded_rows(requests):
|
|
96
|
+
from .model_adapters import request_pairs
|
|
97
|
+
result = {}
|
|
98
|
+
for request in requests:
|
|
99
|
+
# Detach the entire JSON request, including candidate lists and metadata.
|
|
100
|
+
request = json.loads(canonical_json(dict(request)))
|
|
101
|
+
request_pairs(request)
|
|
102
|
+
case_id = request["case_id"]
|
|
103
|
+
if case_id in result:
|
|
104
|
+
raise ContractError("duplicate requested recorded case IDs")
|
|
105
|
+
result[case_id] = request
|
|
106
|
+
if not result:
|
|
107
|
+
raise ContractError("recorded cohort must be nonempty")
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _validated_scores(scores, request):
|
|
112
|
+
if not isinstance(scores, list) or len(scores) != len(request["candidates"]):
|
|
113
|
+
raise ContractError("recorded score coverage differs")
|
|
114
|
+
expected = [item["candidate_id"] for item in request["candidates"]]
|
|
115
|
+
if any(not isinstance(item, dict) or set(item) != {"candidate_id", "ranking_score"} for item in scores):
|
|
116
|
+
raise ContractError("invalid recorded score fields")
|
|
117
|
+
if [item["candidate_id"] for item in scores] != expected:
|
|
118
|
+
raise ContractError("recorded candidate order differs")
|
|
119
|
+
for item in scores:
|
|
120
|
+
value = item["ranking_score"]
|
|
121
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
|
|
122
|
+
raise ContractError("recorded scores must be finite scalar utilities")
|
|
123
|
+
return [dict(item) for item in scores]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class DatasetBoundRecordedScorer:
|
|
127
|
+
"""Strict complete-cohort replay; no model/provider is imported or executed.
|
|
128
|
+
|
|
129
|
+
Construction pins exact score bytes and every ordered label-free request.
|
|
130
|
+
Each call rechecks bytes against that pin, including after construction.
|
|
131
|
+
Original inference timing remains provenance; replay timing is lookup only.
|
|
132
|
+
"""
|
|
133
|
+
def __init__(self, score_path, expected_sha256, *, identity, requests, expected_model):
|
|
134
|
+
from .integration_contracts import ComparisonIdentity, value_digest, CandidateConfiguration
|
|
135
|
+
if not isinstance(identity, ComparisonIdentity):
|
|
136
|
+
raise ContractError("recorded scorer requires ComparisonIdentity")
|
|
137
|
+
if identity.track != "rerank":
|
|
138
|
+
raise ContractError("recorded scorer requires the rerank track")
|
|
139
|
+
self.path = Path(score_path)
|
|
140
|
+
self.digest = expected_sha256
|
|
141
|
+
self.identity = identity
|
|
142
|
+
self._requests = _recorded_rows(requests)
|
|
143
|
+
self._request_digests = {case_id: value_digest(request) for case_id, request in self._requests.items()}
|
|
144
|
+
self._expected_model = dict(expected_model)
|
|
145
|
+
artifact = self._load()
|
|
146
|
+
self.configuration = CandidateConfiguration("recorded-" + artifact["model"]["id"] + "-v2", "sphynx.recorded.rerank.v2",
|
|
147
|
+
"sha256:" + hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
|
|
148
|
+
{"recording_sha256": expected_sha256, "identity": identity.to_dict(), "model": artifact["model"], "provenance": artifact["provenance"], "request_digests": list(self._request_digests.values()), "timing_scope": "recorded_lookup_only_not_model_inference"}, "recorded")
|
|
149
|
+
|
|
150
|
+
def _load(self):
|
|
151
|
+
from .integration_contracts import value_digest
|
|
152
|
+
artifact = _read_bound(self.path, self.digest)
|
|
153
|
+
if set(artifact) != {"schema_version", "identity", "model", "provenance", "cases"} or artifact["schema_version"] != BOUND_RECORDED_SCORE_SCHEMA:
|
|
154
|
+
raise ContractError("unsupported strict recorded score schema")
|
|
155
|
+
if artifact["identity"] != self.identity.to_dict():
|
|
156
|
+
raise ContractError("recorded dataset/cohort/reference/policy identity differs")
|
|
157
|
+
if artifact["model"] != self._expected_model:
|
|
158
|
+
raise ContractError("recorded model/checkpoint/tokenizer/truncation identity differs")
|
|
159
|
+
_validate_model_identity(artifact["model"])
|
|
160
|
+
provenance = artifact["provenance"]
|
|
161
|
+
fields = {"source_recording_sha256", "source_pool_sha256", "source_schema", "runtime", "timing_scope"}
|
|
162
|
+
mapped = isinstance(provenance, dict) and "request_order_mapping" in provenance
|
|
163
|
+
if mapped:
|
|
164
|
+
fields.add("request_order_mapping")
|
|
165
|
+
if not isinstance(provenance, dict) or set(provenance) != fields:
|
|
166
|
+
raise ContractError("recorded provenance fields differ")
|
|
167
|
+
if mapped:
|
|
168
|
+
from .public_rerank import PUBLIC_POOL_RELEASES
|
|
169
|
+
if (provenance["request_order_mapping"] != "verified_source_vector_to_first_stage_rank_by_exact_candidate_id"
|
|
170
|
+
or provenance["source_schema"] != "expanded-reranker.model-scores.v2"
|
|
171
|
+
or provenance["source_pool_sha256"] not in {"sha256:" + release.source_sha256 for release in PUBLIC_POOL_RELEASES}
|
|
172
|
+
or self.identity.dataset_digest != provenance["source_pool_sha256"]):
|
|
173
|
+
raise ContractError("invalid recorded release-specific ordering provenance")
|
|
174
|
+
from .integration_contracts import require_digest
|
|
175
|
+
require_digest(provenance["source_recording_sha256"], "source_recording_sha256")
|
|
176
|
+
if provenance["source_pool_sha256"] is not None:
|
|
177
|
+
require_digest(provenance["source_pool_sha256"], "source_pool_sha256")
|
|
178
|
+
if not isinstance(provenance["source_schema"], str) or not isinstance(provenance["timing_scope"], str) or not isinstance(provenance["runtime"], dict):
|
|
179
|
+
raise ContractError("invalid recording timing/runtime provenance")
|
|
180
|
+
rows = artifact["cases"]
|
|
181
|
+
if not isinstance(rows, list) or any(not isinstance(row, dict) for row in rows):
|
|
182
|
+
raise ContractError("invalid recorded cohort rows")
|
|
183
|
+
if [row.get("case_id") for row in rows] != list(self._requests):
|
|
184
|
+
raise ContractError("recorded cohort coverage or order differs")
|
|
185
|
+
for row, request in zip(rows, self._requests.values()):
|
|
186
|
+
if set(row) != {"case_id", "request_sha256", "scores", "model_inference_ms"} or row["request_sha256"] != value_digest(request):
|
|
187
|
+
raise ContractError("recorded request identity differs")
|
|
188
|
+
_validated_scores(row["scores"], request)
|
|
189
|
+
latency = row["model_inference_ms"]
|
|
190
|
+
if latency is not None and (isinstance(latency, bool) or not isinstance(latency, (int, float)) or not math.isfinite(latency) or latency < 0):
|
|
191
|
+
raise ContractError("invalid recorded inference latency")
|
|
192
|
+
return artifact
|
|
193
|
+
|
|
194
|
+
def __call__(self, request):
|
|
195
|
+
from .integration_contracts import value_digest
|
|
196
|
+
case_id = request.get("case_id")
|
|
197
|
+
if case_id not in self._requests or value_digest(request) != self._request_digests[case_id]:
|
|
198
|
+
raise ContractError("recorded request identity differs")
|
|
199
|
+
# Exact bytes were fully validated during construction. Rechecking the
|
|
200
|
+
# pinned bytes is sufficient; repeating cohort validation adds no proof.
|
|
201
|
+
artifact = _read_bound(self.path, self.digest)
|
|
202
|
+
row = next(row for row in artifact["cases"] if row["case_id"] == case_id)
|
|
203
|
+
return {"schema_version": "sphynx.rag.rerank.response.v1", "query_id": request["query_id"], "pool_digest": request["pool_digest"], "scores": _validated_scores(row["scores"], request)}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _validate_model_identity(model):
|
|
207
|
+
from .model_adapters import get_model_spec
|
|
208
|
+
from .integration_contracts import require_digest
|
|
209
|
+
fields = {"id", "checkpoint", "revision", "max_length", "batch_size", "tokenizer_revision", "truncation", "activation", "snapshot_digest", "onnx_sha256"}
|
|
210
|
+
if not isinstance(model, dict) or set(model) != fields:
|
|
211
|
+
raise ContractError("recorded model identity fields differ")
|
|
212
|
+
spec = get_model_spec(model["id"])
|
|
213
|
+
for name in ("checkpoint", "revision", "max_length", "batch_size"):
|
|
214
|
+
if model[name] != getattr(spec, name) or (name in ("max_length", "batch_size") and type(model[name]) is not int):
|
|
215
|
+
raise ContractError("recorded model differs from frozen parameters")
|
|
216
|
+
if model["tokenizer_revision"] != spec.revision or model["truncation"] != "longest_first" or model["activation"] not in ("identity", "legacy_cross_encoder_default"):
|
|
217
|
+
raise ContractError("recorded tokenizer/truncation/activation identity differs")
|
|
218
|
+
for name in ("snapshot_digest", "onnx_sha256"):
|
|
219
|
+
if model[name] is not None:
|
|
220
|
+
require_digest(model[name], name)
|
|
221
|
+
if spec.backend == "onnx_int8" and model["onnx_sha256"] is None:
|
|
222
|
+
raise ContractError("recorded INT8 graph digest required")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def import_recorded_scores(score_path, expected_sha256, *, identity, requests, expected_model, source_pool_path=None, source_pool_sha256=None, source_task_ids=None, source_request_order="source"):
|
|
226
|
+
"""Return a strict v2 artifact from an exact owner-held completed recording.
|
|
227
|
+
|
|
228
|
+
Supports the existing SDK v1 and expanded-reranker.model-scores.v2. Expanded
|
|
229
|
+
inputs require exact original pool bytes and explicit task-ID mapping. No
|
|
230
|
+
join on text or guessed IDs is performed; every query and ordered passage is
|
|
231
|
+
checked. Caller saves the result outside source control and pins its bytes.
|
|
232
|
+
``snapshot_digest=None`` preserves unknown historical snapshot provenance.
|
|
233
|
+
``source_request_order='first_stage_rank'`` is available only for registered
|
|
234
|
+
revised public pools. It verifies the original score-vector order first,
|
|
235
|
+
then maps unchanged values by exact candidate ID to the dense request order.
|
|
236
|
+
"""
|
|
237
|
+
from .integration_contracts import ComparisonIdentity, value_digest
|
|
238
|
+
if not isinstance(identity, ComparisonIdentity) or identity.track != "rerank":
|
|
239
|
+
raise ContractError("import requires rerank ComparisonIdentity")
|
|
240
|
+
if source_request_order not in ("source", "first_stage_rank"):
|
|
241
|
+
raise ContractError("unsupported recorded source request order")
|
|
242
|
+
model = dict(expected_model)
|
|
243
|
+
_validate_model_identity(model)
|
|
244
|
+
artifact = _read_bound(score_path, expected_sha256)
|
|
245
|
+
requests = _recorded_rows(requests)
|
|
246
|
+
source_model = artifact.get("model")
|
|
247
|
+
if not isinstance(source_model, dict):
|
|
248
|
+
raise ContractError("source recording model identity missing")
|
|
249
|
+
for name in ("id", "checkpoint", "revision", "max_length", "batch_size"):
|
|
250
|
+
if source_model.get(name) != model[name]:
|
|
251
|
+
raise ContractError("source recorded model identity differs")
|
|
252
|
+
if model["snapshot_digest"] != source_model.get("snapshot_digest"):
|
|
253
|
+
raise ContractError("source snapshot digest is absent or differs; historical unknowns must remain null")
|
|
254
|
+
if model["onnx_sha256"] is not None and source_model.get("onnx_sha256", "").removeprefix("sha256:") != model["onnx_sha256"].removeprefix("sha256:"):
|
|
255
|
+
raise ContractError("source recorded ONNX graph identity differs")
|
|
256
|
+
cases = []
|
|
257
|
+
if artifact.get("schema_version") == RECORDED_SCORE_SCHEMA:
|
|
258
|
+
if source_request_order != "source":
|
|
259
|
+
raise ContractError("legacy recording requires unchanged source order")
|
|
260
|
+
if model["activation"] != "legacy_cross_encoder_default":
|
|
261
|
+
raise ContractError("SDK v1 recording requires its historical default activation identity")
|
|
262
|
+
if artifact.get("dataset_digest") != identity.dataset_digest:
|
|
263
|
+
raise ContractError("source recorded dataset identity differs")
|
|
264
|
+
rows = artifact.get("cases")
|
|
265
|
+
if not isinstance(rows, list) or any(not isinstance(row, dict) for row in rows):
|
|
266
|
+
raise ContractError("invalid source recording rows")
|
|
267
|
+
if [row.get("case_id") for row in rows] != list(requests) or artifact.get("case_count") != len(rows):
|
|
268
|
+
raise ContractError("source recording cohort coverage differs")
|
|
269
|
+
for row, request in zip(rows, requests.values()):
|
|
270
|
+
if row.get("request_sha256") != value_digest(request):
|
|
271
|
+
raise ContractError("source recording request identity differs")
|
|
272
|
+
cases.append({"case_id": request["case_id"], "request_sha256": value_digest(request), "scores": _validated_scores(row.get("scores"), request), "model_inference_ms": row.get("model_inference_ms")})
|
|
273
|
+
pool_digest = None
|
|
274
|
+
scope = artifact.get("timing_scope")
|
|
275
|
+
elif artifact.get("schema_version") == "expanded-reranker.model-scores.v2":
|
|
276
|
+
if model["activation"] != "identity":
|
|
277
|
+
raise ContractError("expanded source recording requires raw identity activation")
|
|
278
|
+
if source_pool_path is None or source_pool_sha256 is None or source_task_ids is None:
|
|
279
|
+
raise ContractError("expanded recordings require pinned source pool and explicit task-ID mapping")
|
|
280
|
+
pools = _read_bound(source_pool_path, source_pool_sha256)
|
|
281
|
+
if source_request_order == "first_stage_rank":
|
|
282
|
+
from .public_rerank import PUBLIC_POOL_RELEASES, load_public_pool_release
|
|
283
|
+
release = next((item for item in PUBLIC_POOL_RELEASES
|
|
284
|
+
if item.source_sha256 == source_pool_sha256.removeprefix("sha256:")), None)
|
|
285
|
+
if release is None or identity.dataset_digest != "sha256:" + release.source_sha256:
|
|
286
|
+
raise ContractError("dense request ordering requires an exact registered public release")
|
|
287
|
+
load_public_pool_release(source_pool_path, release_id=release.release_id)
|
|
288
|
+
if artifact.get("pools_sha256") != source_pool_sha256.removeprefix("sha256:"):
|
|
289
|
+
raise ContractError("source recording pool identity differs")
|
|
290
|
+
if set(source_task_ids) != set(requests) or len(set(source_task_ids.values())) != len(requests):
|
|
291
|
+
raise ContractError("source task mapping coverage differs")
|
|
292
|
+
def unique_rows(rows, field):
|
|
293
|
+
if not isinstance(rows, list) or any(not isinstance(row, dict) or not isinstance(row.get(field), str) for row in rows):
|
|
294
|
+
raise ContractError("source rows invalid")
|
|
295
|
+
result = {row[field]: row for row in rows}
|
|
296
|
+
if len(result) != len(rows):
|
|
297
|
+
raise ContractError("source rows contain duplicate IDs")
|
|
298
|
+
return result
|
|
299
|
+
pool_rows = unique_rows(pools.get("queries"), "task_id")
|
|
300
|
+
score_rows = unique_rows(artifact.get("queries"), "task_id")
|
|
301
|
+
for case_id, request in requests.items():
|
|
302
|
+
task_id = source_task_ids[case_id]
|
|
303
|
+
if task_id not in pool_rows or task_id not in score_rows:
|
|
304
|
+
raise ContractError("source recorded cohort incomplete")
|
|
305
|
+
pool, row = pool_rows[task_id], score_rows[task_id]
|
|
306
|
+
source_candidates = pool.get("candidates", [])
|
|
307
|
+
ordered_candidates = sorted(source_candidates, key=lambda item: item["first_stage_rank"]) if source_request_order == "first_stage_rank" else source_candidates
|
|
308
|
+
if pool.get("query") != request["query"] or [{"candidate_id": p.get("candidate_id"), "input": p.get("input")} for p in ordered_candidates] != [{"candidate_id": p["candidate_id"], "input": p["input"]} for p in request["candidates"]]:
|
|
309
|
+
raise ContractError("source pool query/passages differ from request")
|
|
310
|
+
if source_request_order == "first_stage_rank" and any(
|
|
311
|
+
candidate["first_stage_rank"] != requested["dense_rank"] or
|
|
312
|
+
candidate["first_stage_score"] != requested["dense_score"]
|
|
313
|
+
for candidate, requested in zip(ordered_candidates, request["candidates"])
|
|
314
|
+
):
|
|
315
|
+
raise ContractError("source dense ranks or scores differ from request")
|
|
316
|
+
split = pool.get("split")
|
|
317
|
+
completion = artifact.get("completion", {}).get(split, {})
|
|
318
|
+
split_count = sum(p.get("split") == split for p in pool_rows.values())
|
|
319
|
+
actual_scored = sum(key in score_rows for key, p in pool_rows.items() if p.get("split") == split)
|
|
320
|
+
if completion.get("complete") is not True or completion.get("pool_query_count") != split_count or completion.get("scored_query_count") != actual_scored or actual_scored != split_count:
|
|
321
|
+
raise ContractError("source recording split remains prescreen-only or incomplete")
|
|
322
|
+
ids, values = row.get("candidate_ids"), row.get("scores")
|
|
323
|
+
if not isinstance(ids, list) or not isinstance(values, list) or len(ids) != len(values):
|
|
324
|
+
raise ContractError("source recording candidate coverage differs")
|
|
325
|
+
if source_request_order == "first_stage_rank":
|
|
326
|
+
if ids != [candidate["candidate_id"] for candidate in source_candidates]:
|
|
327
|
+
raise ContractError("recorded original score-vector order differs from source pool")
|
|
328
|
+
by_id = dict(zip(ids, values))
|
|
329
|
+
ids = [candidate["candidate_id"] for candidate in request["candidates"]]
|
|
330
|
+
values = [by_id[cid] for cid in ids]
|
|
331
|
+
scores = [{"candidate_id": cid, "ranking_score": value} for cid, value in zip(ids, values)]
|
|
332
|
+
cases.append({"case_id": case_id, "request_sha256": value_digest(request), "scores": _validated_scores(scores, request), "model_inference_ms": row.get("latency_ms")})
|
|
333
|
+
pool_digest = source_pool_sha256
|
|
334
|
+
scope = "source_sequential_per_query_inference_including_tokenization_excluding_model_load; historical_warmup_unknown"
|
|
335
|
+
else:
|
|
336
|
+
raise ContractError("unsupported source recorded schema")
|
|
337
|
+
output = {"schema_version": BOUND_RECORDED_SCORE_SCHEMA, "identity": identity.to_dict(), "model": model,
|
|
338
|
+
"provenance": {"source_recording_sha256": expected_sha256, "source_pool_sha256": pool_digest, "source_schema": artifact["schema_version"], "runtime": artifact.get("runtime", {}), "timing_scope": scope}, "cases": cases}
|
|
339
|
+
if source_request_order != "source":
|
|
340
|
+
output["provenance"]["request_order_mapping"] = "verified_source_vector_to_first_stage_rank_by_exact_candidate_id"
|
|
341
|
+
# Validate all output values without needing a saved temporary artifact.
|
|
342
|
+
for row in cases:
|
|
343
|
+
latency = row["model_inference_ms"]
|
|
344
|
+
if latency is not None and (isinstance(latency, bool) or not isinstance(latency, (int, float)) or not math.isfinite(latency) or latency < 0):
|
|
345
|
+
raise ContractError("invalid source recorded inference latency")
|
|
346
|
+
if not isinstance(scope, str) or not isinstance(output["provenance"]["runtime"], dict):
|
|
347
|
+
raise ContractError("source timing/runtime provenance missing")
|
|
348
|
+
return output
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def create_recorded_scorer(score_path, expected_sha256, *, identity, requests, expected_model):
|
|
352
|
+
return DatasetBoundRecordedScorer(score_path, expected_sha256, identity=identity, requests=requests, expected_model=expected_model)
|
sphynx/records.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Stable evidence and grading identity contracts for later SDK stages.
|
|
2
|
+
|
|
3
|
+
These are immutable value types only. Recording, grading, and persistence are
|
|
4
|
+
implemented by separate subsystems, which must preserve these identities.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
from .contracts import ContractError, _id, _text, _tuple_of_ids
|
|
13
|
+
|
|
14
|
+
EVIDENCE_SCHEMA_VERSION = "sphynx.evidence.v1"
|
|
15
|
+
EVALUATION_SCHEMA_VERSION = "sphynx.evaluation.v1"
|
|
16
|
+
METRIC_DEFINITION_SCHEMA_VERSION = "sphynx.metric-definition.v1"
|
|
17
|
+
|
|
18
|
+
_DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$")
|
|
19
|
+
_COVERAGE_STATES = frozenset(
|
|
20
|
+
{"available", "not_captured", "truncated", "redacted", "corrupt", "expired", "unsupported_schema"}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class CaptureIdentity:
|
|
26
|
+
"""One attempt at one case; its run and capture IDs must stay distinct."""
|
|
27
|
+
|
|
28
|
+
run_id: str
|
|
29
|
+
capture_id: str
|
|
30
|
+
case_id: str
|
|
31
|
+
trial_index: int
|
|
32
|
+
attempt_index: int
|
|
33
|
+
pool_digest: str
|
|
34
|
+
trust_tier: str = "local_unverified"
|
|
35
|
+
schema_version: str = EVIDENCE_SCHEMA_VERSION
|
|
36
|
+
|
|
37
|
+
def __post_init__(self) -> None:
|
|
38
|
+
for field in ("run_id", "capture_id", "case_id"):
|
|
39
|
+
_id(getattr(self, field), field)
|
|
40
|
+
if self.run_id == self.capture_id:
|
|
41
|
+
raise ContractError("run_id and capture_id must be distinct")
|
|
42
|
+
for field in ("trial_index", "attempt_index"):
|
|
43
|
+
value = getattr(self, field)
|
|
44
|
+
if type(value) is not int or value < 1:
|
|
45
|
+
raise ContractError(f"{field} must be a positive integer")
|
|
46
|
+
if not isinstance(self.pool_digest, str) or not _DIGEST.fullmatch(self.pool_digest):
|
|
47
|
+
raise ContractError("pool_digest must be a sha256: lowercase-hex digest")
|
|
48
|
+
if self.trust_tier != "local_unverified":
|
|
49
|
+
raise ContractError("this SDK stage supports only local_unverified trust tier")
|
|
50
|
+
if self.schema_version != EVIDENCE_SCHEMA_VERSION:
|
|
51
|
+
raise ContractError(f"capture schema_version must be {EVIDENCE_SCHEMA_VERSION}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class EvaluationIdentity:
|
|
56
|
+
"""A grading pass over retained evidence; regrading creates a new ID."""
|
|
57
|
+
|
|
58
|
+
run_id: str
|
|
59
|
+
evaluation_id: str
|
|
60
|
+
grader_release: str
|
|
61
|
+
metric_release: str
|
|
62
|
+
schema_version: str = EVALUATION_SCHEMA_VERSION
|
|
63
|
+
|
|
64
|
+
def __post_init__(self) -> None:
|
|
65
|
+
_id(self.run_id, "run_id")
|
|
66
|
+
_id(self.evaluation_id, "evaluation_id")
|
|
67
|
+
if self.run_id == self.evaluation_id:
|
|
68
|
+
raise ContractError("run_id and evaluation_id must be distinct")
|
|
69
|
+
_id(self.grader_release, "grader_release")
|
|
70
|
+
_id(self.metric_release, "metric_release")
|
|
71
|
+
if self.schema_version != EVALUATION_SCHEMA_VERSION:
|
|
72
|
+
raise ContractError(f"evaluation schema_version must be {EVALUATION_SCHEMA_VERSION}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class EvidenceCapability:
|
|
77
|
+
"""Observed availability, distinct from a metric result or a zero value."""
|
|
78
|
+
|
|
79
|
+
capability_id: str
|
|
80
|
+
capability_schema_version: str
|
|
81
|
+
state: str
|
|
82
|
+
observed_count: int
|
|
83
|
+
reason: str | None = None
|
|
84
|
+
|
|
85
|
+
def __post_init__(self) -> None:
|
|
86
|
+
_id(self.capability_id, "capability_id")
|
|
87
|
+
_id(self.capability_schema_version, "capability_schema_version")
|
|
88
|
+
if self.state not in _COVERAGE_STATES:
|
|
89
|
+
raise ContractError(f"state must be one of {', '.join(sorted(_COVERAGE_STATES))}")
|
|
90
|
+
if type(self.observed_count) is not int or self.observed_count < 0:
|
|
91
|
+
raise ContractError("observed_count must be a nonnegative integer")
|
|
92
|
+
if self.reason is not None:
|
|
93
|
+
_text(self.reason, "reason")
|
|
94
|
+
if self.state != "available" and self.reason is None:
|
|
95
|
+
raise ContractError("unavailable evidence requires a reason")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass(frozen=True)
|
|
99
|
+
class MetricDefinition:
|
|
100
|
+
"""Versioned metric declaration; computation belongs to the grader."""
|
|
101
|
+
|
|
102
|
+
metric_id: str
|
|
103
|
+
release: str
|
|
104
|
+
unit: str
|
|
105
|
+
direction: str
|
|
106
|
+
required_capabilities: tuple[str, ...]
|
|
107
|
+
primary: bool = False
|
|
108
|
+
schema_version: str = METRIC_DEFINITION_SCHEMA_VERSION
|
|
109
|
+
|
|
110
|
+
def __post_init__(self) -> None:
|
|
111
|
+
_id(self.metric_id, "metric_id")
|
|
112
|
+
_id(self.release, "release")
|
|
113
|
+
_id(self.unit, "unit")
|
|
114
|
+
if self.direction not in ("higher", "lower"):
|
|
115
|
+
raise ContractError("direction must be higher or lower")
|
|
116
|
+
object.__setattr__(
|
|
117
|
+
self,
|
|
118
|
+
"required_capabilities",
|
|
119
|
+
_tuple_of_ids(self.required_capabilities, "required_capabilities"),
|
|
120
|
+
)
|
|
121
|
+
if type(self.primary) is not bool:
|
|
122
|
+
raise ContractError("primary must be a boolean")
|
|
123
|
+
if self.schema_version != METRIC_DEFINITION_SCHEMA_VERSION:
|
|
124
|
+
raise ContractError(f"metric definition schema_version must be {METRIC_DEFINITION_SCHEMA_VERSION}")
|