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,424 @@
|
|
|
1
|
+
"""Native adapter for the installed illustrative frozen-pool reranking suite.
|
|
2
|
+
|
|
3
|
+
RAG schemas, fixed case membership, local policy and metric layouts belong in
|
|
4
|
+
this adapter. Shared orchestration uses WorkflowBackend and native references.
|
|
5
|
+
The adapter preserves local_unverified evidence; admission is not a sandbox or
|
|
6
|
+
verification of candidate source bytes.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from dataclasses import asdict
|
|
12
|
+
from importlib import resources
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from .assessment_contracts import ASSESSMENT_VIEW_SCHEMA, Assessment, CaseView
|
|
17
|
+
from .contracts import (
|
|
18
|
+
PACK_ID, REQUEST_SCHEMA_VERSION, CandidateSpec, CaseReferences, EvidenceGroup,
|
|
19
|
+
ReferenceCandidate, build_candidate_request, canonical_json, validate_candidate_request,
|
|
20
|
+
)
|
|
21
|
+
from .evals import ResolvedInvocation, require_legacy_rerank, resolve_eval
|
|
22
|
+
from .evidence import RunRef, read_run_manifest, sha256_bytes
|
|
23
|
+
from .metrics import GRADER_RELEASE, METRIC_RELEASE, metric_definitions
|
|
24
|
+
from .packs import check_candidate, get_pack
|
|
25
|
+
from .rag import FIXTURE_SHA256, load_rag_rerank_pack, validate_dataset_v2
|
|
26
|
+
from .results import Comparison, EvaluationRef, ResultError, Scorecard
|
|
27
|
+
from .results import compare as native_compare
|
|
28
|
+
from .results import grade as native_grade
|
|
29
|
+
from .results import report as native_report
|
|
30
|
+
from .runner import run as native_run
|
|
31
|
+
from .workflow_contracts import (
|
|
32
|
+
AdmissionBinding, BackendDescriptor, Criterion, Dataset, DatasetDefinition,
|
|
33
|
+
EvaluatorDefinition, MetricSpec, PayloadSchema, PlanBinding,
|
|
34
|
+
ReferenceBinding, ResolvedBinding, SuiteDefinition, WorkflowContractError,
|
|
35
|
+
contract_digest,
|
|
36
|
+
)
|
|
37
|
+
from .workflow_registry import WorkflowRegistry
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
RERANK_BACKEND_ID = "sphynx.rerank.local"
|
|
41
|
+
RERANK_REFERENCE_SCHEMA = "sphynx.rag.rerank.case-references.v1"
|
|
42
|
+
_AGGREGATION_POLICY_ID = "sphynx.rerank.aggregate.v1"
|
|
43
|
+
_COMPARISON_POLICY_ID = "sphynx.rerank.paired-comparison.v1"
|
|
44
|
+
_DESCRIPTOR = BackendDescriptor(
|
|
45
|
+
backend_id=RERANK_BACKEND_ID, version="v1",
|
|
46
|
+
implementation_id="sphynx.rag.rerank.native-adapter.v1",
|
|
47
|
+
capabilities=("sphynx.case-inputs.v1", "sphynx.local-subprocess.v1",
|
|
48
|
+
"sphynx.saved-assessment.v1", "sphynx.native-report.v1",
|
|
49
|
+
"sphynx.paired-comparison.v1"),
|
|
50
|
+
supported_scopes=("component",), supported_modes=("local",), suite_ids=(PACK_ID,),
|
|
51
|
+
metadata={"native_pack_id": PACK_ID, "trust_tier": "local_unverified",
|
|
52
|
+
"illustrative": True, "candidate_source_bytes_verified": False},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _candidate_identity(candidate: CandidateSpec) -> dict[str, str]:
|
|
57
|
+
# The native v1 identity pins argv metadata, not executable/source bytes.
|
|
58
|
+
return {"name": candidate.name, "version": candidate.version,
|
|
59
|
+
"interface_version": candidate.interface_version,
|
|
60
|
+
"argv_sha256": sha256_bytes(canonical_json(list(candidate.argv)).encode("utf-8"))}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _case_view(case: Any) -> CaseView:
|
|
64
|
+
request = build_candidate_request(case, PACK_ID)
|
|
65
|
+
return CaseView(
|
|
66
|
+
case.case_id, REQUEST_SCHEMA_VERSION, request.to_dict(),
|
|
67
|
+
metadata={"dataset_id": case.dataset_id, "dataset_version": case.dataset_version,
|
|
68
|
+
"pool_digest": case.pool_digest, "split": case.split,
|
|
69
|
+
"split_group_id": case.split_group_id, "cluster_id": case.cluster_id},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class RerankBackend:
|
|
74
|
+
"""Delegate the common flow to unchanged native reranking operations."""
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def descriptor(self) -> BackendDescriptor:
|
|
78
|
+
return _DESCRIPTOR
|
|
79
|
+
|
|
80
|
+
def describe(self, suite_id: str) -> SuiteDefinition:
|
|
81
|
+
"""Read declared metadata only; do not load cases or grading labels."""
|
|
82
|
+
if suite_id != PACK_ID:
|
|
83
|
+
raise WorkflowContractError(f"no native reranking adapter for suite {suite_id!r}")
|
|
84
|
+
invocation = resolve_eval(suite_id)
|
|
85
|
+
descriptor = get_pack(suite_id)
|
|
86
|
+
artifact = invocation.artifacts[0]
|
|
87
|
+
dataset = DatasetDefinition(
|
|
88
|
+
dataset_id=str(artifact["ref"]).rsplit("@", 1)[0],
|
|
89
|
+
version=artifact["version"], provider_id="sphynx.rerank.frozen-pool.v1",
|
|
90
|
+
input_schema_id=REQUEST_SCHEMA_VERSION, reference_schema_id=RERANK_REFERENCE_SCHEMA,
|
|
91
|
+
selection={"membership": "all_installed_cases", "order": "native_pack_order"},
|
|
92
|
+
identity={"artifact_ref": artifact["ref"], "sha256": artifact["digest"]},
|
|
93
|
+
)
|
|
94
|
+
return SuiteDefinition(
|
|
95
|
+
suite_id=suite_id, version=descriptor.pack_release,
|
|
96
|
+
backend_id=self.descriptor.backend_id, title=descriptor.title,
|
|
97
|
+
criterion_ids=invocation.definition.metric_ids,
|
|
98
|
+
evaluator_ids=(GRADER_RELEASE,), metric_ids=invocation.definition.metric_ids,
|
|
99
|
+
required_capabilities=("sphynx.local-subprocess.v1", "sphynx.saved-assessment.v1"),
|
|
100
|
+
datasets=(dataset,),
|
|
101
|
+
metadata={"native_definition_digest": invocation.definition.definition_digest,
|
|
102
|
+
"native_metric_criterion_mapping": True,
|
|
103
|
+
"native_execution_policy": asdict(descriptor.execution_policy),
|
|
104
|
+
"case_count": descriptor.case_count, "illustrative": True,
|
|
105
|
+
"trust_tier": "local_unverified"},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _suite(self, suite: SuiteDefinition) -> None:
|
|
109
|
+
if not isinstance(suite, SuiteDefinition):
|
|
110
|
+
raise WorkflowContractError("native reranking suite must be SuiteDefinition")
|
|
111
|
+
expected = self.describe(suite.suite_id)
|
|
112
|
+
if suite.definition_digest != expected.definition_digest:
|
|
113
|
+
raise WorkflowContractError("suite definition differs from the native reranking adapter")
|
|
114
|
+
|
|
115
|
+
def _plan(self, plan: PlanBinding) -> None:
|
|
116
|
+
if not isinstance(plan, PlanBinding):
|
|
117
|
+
raise WorkflowContractError("native reranking plan must be PlanBinding")
|
|
118
|
+
self._suite(plan.suite)
|
|
119
|
+
if plan.scope not in self.descriptor.supported_scopes or plan.mode not in self.descriptor.supported_modes:
|
|
120
|
+
raise WorkflowContractError("this suite supports only scope='component', mode='local'")
|
|
121
|
+
if not isinstance(plan.candidate, CandidateSpec):
|
|
122
|
+
raise WorkflowContractError("native reranking candidate must be CandidateSpec")
|
|
123
|
+
check = check_candidate(plan.candidate, plan.suite.suite_id)
|
|
124
|
+
if not check.compatible or not check.pack_runnable:
|
|
125
|
+
raise WorkflowContractError("; ".join(check.reasons) or "native suite cannot execute")
|
|
126
|
+
if plan.runtime:
|
|
127
|
+
raise WorkflowContractError("native reranking does not accept runtime overrides")
|
|
128
|
+
|
|
129
|
+
def cases(self, suite: SuiteDefinition) -> tuple[CaseView, ...]:
|
|
130
|
+
self._suite(suite)
|
|
131
|
+
return tuple(_case_view(case) for case in load_rag_rerank_pack().cases)
|
|
132
|
+
|
|
133
|
+
def resolve(self, plan: PlanBinding) -> ResolvedBinding:
|
|
134
|
+
self._plan(plan)
|
|
135
|
+
invocation = resolve_eval(plan.suite.suite_id, plan.parameters)
|
|
136
|
+
return ResolvedBinding(
|
|
137
|
+
identity={"backend": self.descriptor.to_dict(),
|
|
138
|
+
"suite_id": plan.suite.suite_id,
|
|
139
|
+
"suite_definition_digest": plan.suite.definition_digest,
|
|
140
|
+
"scope": plan.scope, "mode": plan.mode, "runtime": dict(plan.runtime),
|
|
141
|
+
"candidate": _candidate_identity(plan.candidate),
|
|
142
|
+
"native_invocation": invocation.to_dict(),
|
|
143
|
+
"candidate_source_bytes_verified": False},
|
|
144
|
+
native_ref=invocation,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def _resolution(self, plan: PlanBinding, resolved: ResolvedBinding) -> None:
|
|
148
|
+
if not isinstance(resolved, ResolvedBinding) or not isinstance(resolved.native_ref, ResolvedInvocation):
|
|
149
|
+
raise WorkflowContractError("native reranking requires a native resolved invocation")
|
|
150
|
+
expected = self.resolve(plan)
|
|
151
|
+
if (resolved.resolution_digest != expected.resolution_digest
|
|
152
|
+
or resolved.native_ref.invocation_digest != expected.native_ref.invocation_digest):
|
|
153
|
+
raise WorkflowContractError("resolved suite or candidate identity changed; resolve the plan again")
|
|
154
|
+
|
|
155
|
+
def _facts(self, plan: PlanBinding, resolved: ResolvedBinding) -> dict[str, Any]:
|
|
156
|
+
self._resolution(plan, resolved)
|
|
157
|
+
admitted = require_legacy_rerank(plan.suite.suite_id, plan.parameters)
|
|
158
|
+
if admitted.invocation_digest != resolved.native_ref.invocation_digest:
|
|
159
|
+
raise WorkflowContractError("native admission differs from the resolved invocation")
|
|
160
|
+
# A cached successful load cannot establish the identity of current bytes.
|
|
161
|
+
payload = resources.files("sphynx").joinpath("data", "rag-rerank-v1.json").read_bytes()
|
|
162
|
+
digest = sha256_bytes(payload)
|
|
163
|
+
if digest != "sha256:" + FIXTURE_SHA256:
|
|
164
|
+
raise WorkflowContractError("installed suite bytes differ from the pinned release")
|
|
165
|
+
pack = load_rag_rerank_pack()
|
|
166
|
+
if pack.dataset_digest != digest:
|
|
167
|
+
raise WorkflowContractError("loaded suite identity differs from verified asset bytes")
|
|
168
|
+
verified_cases, _, _ = validate_dataset_v2(json.loads(payload))
|
|
169
|
+
if pack.cases != verified_cases:
|
|
170
|
+
raise WorkflowContractError("loaded case membership differs from verified suite bytes")
|
|
171
|
+
descriptor = get_pack(plan.suite.suite_id)
|
|
172
|
+
if descriptor != pack.descriptor:
|
|
173
|
+
raise WorkflowContractError("suite descriptor differs from the loaded native pack")
|
|
174
|
+
return {"dataset_digest": digest, "descriptor_digest": contract_digest(asdict(descriptor)),
|
|
175
|
+
"case_ids": [case.case_id for case in pack.cases]}
|
|
176
|
+
|
|
177
|
+
def admit(self, plan: PlanBinding, resolved: ResolvedBinding) -> AdmissionBinding:
|
|
178
|
+
return AdmissionBinding(
|
|
179
|
+
resolution_digest=resolved.resolution_digest, facts=self._facts(plan, resolved),
|
|
180
|
+
trust_tier="local_unverified", artifact_bytes_verified=True,
|
|
181
|
+
candidate_source_bytes_verified=False,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def recheck(self, plan: PlanBinding, resolved: ResolvedBinding, admission: AdmissionBinding) -> None:
|
|
185
|
+
if not isinstance(admission, AdmissionBinding):
|
|
186
|
+
raise WorkflowContractError("native reranking admission must be AdmissionBinding")
|
|
187
|
+
expected = self.admit(plan, resolved)
|
|
188
|
+
if contract_digest(admission.to_dict()) != contract_digest(expected.to_dict()):
|
|
189
|
+
raise WorkflowContractError("admitted suite has changed; resolve and admit a new plan")
|
|
190
|
+
|
|
191
|
+
def execute(self, plan: PlanBinding, resolved: ResolvedBinding, admission: AdmissionBinding,
|
|
192
|
+
output_dir: str | Path | None) -> RunRef:
|
|
193
|
+
self.recheck(plan, resolved, admission)
|
|
194
|
+
if output_dir is None:
|
|
195
|
+
raise WorkflowContractError("native reranking requires an output directory")
|
|
196
|
+
return native_run(plan.candidate, plan.suite.suite_id, output_dir,
|
|
197
|
+
parameters=plan.parameters if plan.parameters else None)
|
|
198
|
+
|
|
199
|
+
def accepts_reference(self, native_ref: Any, kind: str) -> bool:
|
|
200
|
+
if kind == "run":
|
|
201
|
+
return isinstance(native_ref, RunRef) and native_ref.pack_id == PACK_ID
|
|
202
|
+
if kind == "grading":
|
|
203
|
+
if not isinstance(native_ref, EvaluationRef):
|
|
204
|
+
return False
|
|
205
|
+
if native_ref.pack_id == PACK_ID:
|
|
206
|
+
return True
|
|
207
|
+
# A wholly unreadable native run cannot supply a pack or grading
|
|
208
|
+
# path. Keep its explicit error diagnostic for an already-bound
|
|
209
|
+
# backend; this does not recognize foreign packs or successful data.
|
|
210
|
+
return (native_ref.pack_id is None and native_ref.path is None
|
|
211
|
+
and native_ref.status in {"corrupt", "incompatible", "unsupported_schema", "grader_error"})
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
def _reference(self, native_ref: Any, kind: str) -> None:
|
|
215
|
+
if not self.accepts_reference(native_ref, kind):
|
|
216
|
+
raise WorkflowContractError(f"unsupported native reranking {kind} reference")
|
|
217
|
+
|
|
218
|
+
def reference(self, native_ref: Any, kind: str) -> ReferenceBinding:
|
|
219
|
+
self._reference(native_ref, kind)
|
|
220
|
+
if kind == "run":
|
|
221
|
+
manifest = read_run_manifest(native_ref)
|
|
222
|
+
return ReferenceBinding(
|
|
223
|
+
record_id=native_ref.run_id, run_id=native_ref.run_id,
|
|
224
|
+
status=manifest["status"], trust_tier="local_unverified",
|
|
225
|
+
metadata={"native_pack_id": native_ref.pack_id, "illustrative": True},
|
|
226
|
+
)
|
|
227
|
+
return ReferenceBinding(
|
|
228
|
+
record_id=native_ref.evaluation_id, run_id=native_ref.run_id,
|
|
229
|
+
status=native_ref.status, trust_tier="local_unverified",
|
|
230
|
+
metadata={"native_pack_id": native_ref.pack_id, "reason": native_ref.reason,
|
|
231
|
+
"illustrative": True},
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
def assess(self, native_run: Any) -> EvaluationRef:
|
|
235
|
+
self._reference(native_run, "run")
|
|
236
|
+
return native_grade(native_run)
|
|
237
|
+
|
|
238
|
+
def report(self, native_grading: Any) -> Scorecard:
|
|
239
|
+
self._reference(native_grading, "grading")
|
|
240
|
+
if native_grading.path is None:
|
|
241
|
+
raise WorkflowContractError(native_grading.reason or "no readable saved grading record")
|
|
242
|
+
return native_report(native_grading)
|
|
243
|
+
|
|
244
|
+
def assessments(self, native_grading: Any, case_id: str | None = None) -> tuple[Assessment, ...]:
|
|
245
|
+
self._reference(native_grading, "grading")
|
|
246
|
+
if native_grading.path is None:
|
|
247
|
+
raise WorkflowContractError(native_grading.reason or "no readable saved grading record")
|
|
248
|
+
path = native_grading.path / "judgments.jsonl"
|
|
249
|
+
try:
|
|
250
|
+
raw = path.read_bytes()
|
|
251
|
+
except OSError as exc:
|
|
252
|
+
raise ResultError(f"cannot read saved assessments: {exc}") from exc
|
|
253
|
+
data = self.report(native_grading).to_dict()
|
|
254
|
+
if path.read_bytes() != raw:
|
|
255
|
+
raise WorkflowContractError("saved assessments changed during verification; read the report again")
|
|
256
|
+
rows = [json.loads(line) for line in raw.decode("utf-8").splitlines()]
|
|
257
|
+
if case_id is not None and case_id not in {row["case_id"] for row in rows}:
|
|
258
|
+
raise WorkflowContractError(f"case {case_id!r} is not in this report")
|
|
259
|
+
assessments = []
|
|
260
|
+
for row in rows:
|
|
261
|
+
if case_id is not None and row["case_id"] != case_id:
|
|
262
|
+
continue
|
|
263
|
+
for metric_id, value in row["values"].items():
|
|
264
|
+
reason = row["undefined_reasons"].get(metric_id) or row["reason"]
|
|
265
|
+
if value is not None:
|
|
266
|
+
availability = "available"
|
|
267
|
+
elif row["status"] == "grader_error":
|
|
268
|
+
availability = "error"
|
|
269
|
+
elif row["status"] in {"corrupt", "incompatible", "unsupported_schema"}:
|
|
270
|
+
availability = "unavailable"
|
|
271
|
+
elif reason in {"unsupported_query", "zero_ideal_dcg"}:
|
|
272
|
+
availability = "undefined"
|
|
273
|
+
else:
|
|
274
|
+
availability = "missing"
|
|
275
|
+
assessments.append(Assessment(
|
|
276
|
+
criterion_id=metric_id, evaluator_id=data["releases"]["grader_release"],
|
|
277
|
+
case_id=row["case_id"], kind="score", availability=availability,
|
|
278
|
+
value=value, reason=reason,
|
|
279
|
+
evidence_ids=(row["capture_id"],) if row["capture_id"] else (),
|
|
280
|
+
run_id=row["run_id"], grading_id=row["evaluation_id"],
|
|
281
|
+
trial=row["trial_index"], attempt=1,
|
|
282
|
+
))
|
|
283
|
+
return tuple(assessments)
|
|
284
|
+
|
|
285
|
+
def compare(self, baseline_grading: Any, candidate_grading: Any) -> Comparison:
|
|
286
|
+
self._reference(baseline_grading, "grading")
|
|
287
|
+
self._reference(candidate_grading, "grading")
|
|
288
|
+
return native_compare(baseline_grading, candidate_grading)
|
|
289
|
+
|
|
290
|
+
def compare_view(self, baseline_grading, candidate_grading):
|
|
291
|
+
from ._comparison_adapters import _rerank_view, _sources
|
|
292
|
+
native = self.compare(baseline_grading, candidate_grading).to_dict()
|
|
293
|
+
reports = [self.report(ref).to_dict() for ref in (baseline_grading, candidate_grading)]
|
|
294
|
+
conditions = [{key: report[key] for key in ("identity", "releases", "candidate")}
|
|
295
|
+
for report in reports]
|
|
296
|
+
return _rerank_view(_sources(self, baseline_grading, candidate_grading, conditions=conditions),
|
|
297
|
+
native, {item.metric_id: _metric_spec(item) for item in metric_definitions()})
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _validate_native_request(value: Any) -> None:
|
|
301
|
+
"""Wrap the strict typed native parser without changing validator return policy."""
|
|
302
|
+
validate_candidate_request(value)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _validate_native_references(value: Any) -> None:
|
|
306
|
+
"""Validate the reference structure without loading or authorizing native labels.
|
|
307
|
+
|
|
308
|
+
Exact case/cohort/label binding remains the native grader's responsibility.
|
|
309
|
+
The public default provider never supplies a reference loader or grant.
|
|
310
|
+
"""
|
|
311
|
+
if type(value) is not dict or set(value) != {"case_id", "label_authority", "candidates", "evidence_groups"}:
|
|
312
|
+
raise WorkflowContractError("native references require the exact CaseReferences fields")
|
|
313
|
+
if type(value["candidates"]) is not list or not value["candidates"]:
|
|
314
|
+
raise WorkflowContractError("native references require a nonempty candidate array")
|
|
315
|
+
if type(value["evidence_groups"]) is not list:
|
|
316
|
+
raise WorkflowContractError("native references require an evidence-group array")
|
|
317
|
+
candidate_fields = {"candidate_id", "relevance_grade", "evidence_group_ids"}
|
|
318
|
+
group_fields = {"evidence_group_id", "description", "required", "acceptable_candidate_ids"}
|
|
319
|
+
if any(type(item) is not dict or set(item) != candidate_fields for item in value["candidates"]):
|
|
320
|
+
raise WorkflowContractError("native reference candidates have unsupported fields")
|
|
321
|
+
if any(type(item) is not dict or set(item) != group_fields for item in value["evidence_groups"]):
|
|
322
|
+
raise WorkflowContractError("native evidence groups have unsupported fields")
|
|
323
|
+
references = CaseReferences(
|
|
324
|
+
value["case_id"], value["label_authority"],
|
|
325
|
+
tuple(ReferenceCandidate(**item) for item in value["candidates"]),
|
|
326
|
+
tuple(EvidenceGroup(**item) for item in value["evidence_groups"]),
|
|
327
|
+
)
|
|
328
|
+
candidates = {item.candidate_id: item for item in references.candidates}
|
|
329
|
+
groups = {item.evidence_group_id: item for item in references.evidence_groups}
|
|
330
|
+
if any(not set(item.evidence_group_ids).issubset(groups) for item in references.candidates):
|
|
331
|
+
raise WorkflowContractError("native reference candidate names an unknown evidence group")
|
|
332
|
+
if any(not set(item.acceptable_candidate_ids).issubset(candidates) for item in references.evidence_groups):
|
|
333
|
+
raise WorkflowContractError("native evidence group names an unknown candidate")
|
|
334
|
+
if any((group_id in candidate.evidence_group_ids) != (candidate_id in group.acceptable_candidate_ids)
|
|
335
|
+
for candidate_id, candidate in candidates.items() for group_id, group in groups.items()):
|
|
336
|
+
raise WorkflowContractError("native candidate and evidence-group links must be reciprocal")
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _validate_builtin_assessment(value: Any) -> None:
|
|
340
|
+
fields = {"schema_version", "criterion_id", "evaluator_id", "case_id", "kind", "availability",
|
|
341
|
+
"value", "reason", "evidence_ids", "run_id", "grading_id", "trial", "attempt"}
|
|
342
|
+
if type(value) is not dict or set(value) != fields or value["schema_version"] != ASSESSMENT_VIEW_SCHEMA:
|
|
343
|
+
raise WorkflowContractError("builtin assessments require the exact v1 view fields")
|
|
344
|
+
if value["kind"] not in {"assertion", "score", "label"}:
|
|
345
|
+
raise WorkflowContractError("builtin assessment schema does not accept custom payload kinds")
|
|
346
|
+
Assessment(**{key: item for key, item in value.items() if key != "schema_version"})
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _metric_spec(definition):
|
|
350
|
+
return MetricSpec(definition.metric_id, definition.release, definition.unit, definition.direction,
|
|
351
|
+
aggregation_policy=_AGGREGATION_POLICY_ID, comparison_policy=_COMPARISON_POLICY_ID,
|
|
352
|
+
population={"eligible_population": definition.eligible_population,
|
|
353
|
+
"undefined_policy": definition.undefined_policy, "missing_policy": definition.missing_policy},
|
|
354
|
+
metadata={"native_aggregation": definition.aggregation, "primary": definition.primary})
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def register_rerank_backend(registry: WorkflowRegistry) -> RerankBackend:
|
|
358
|
+
"""Register this adapter and its exact declared contracts without reading assets.
|
|
359
|
+
|
|
360
|
+
The registry must explicitly permit the native bare metric and dataset IDs.
|
|
361
|
+
``build_rerank_registry`` supplies that narrow legacy-ID list. Registration
|
|
362
|
+
is not admission, does not run validators, and never loads grading labels.
|
|
363
|
+
Existing bindings are not silently replaced; duplicate registration fails.
|
|
364
|
+
"""
|
|
365
|
+
if not isinstance(registry, WorkflowRegistry):
|
|
366
|
+
raise WorkflowContractError("native reranking registration requires a WorkflowRegistry")
|
|
367
|
+
backend = RerankBackend()
|
|
368
|
+
suite = backend.describe(PACK_ID)
|
|
369
|
+
registry.register_backend(backend)
|
|
370
|
+
input_schema = registry.register_schema(PayloadSchema(
|
|
371
|
+
REQUEST_SCHEMA_VERSION, "v1", "sphynx.contracts.validate_candidate_request.v1",
|
|
372
|
+
_validate_native_request,
|
|
373
|
+
metadata={"native_protocol": REQUEST_SCHEMA_VERSION, "candidate_permitted": True},
|
|
374
|
+
))
|
|
375
|
+
reference_schema = registry.register_schema(PayloadSchema(
|
|
376
|
+
RERANK_REFERENCE_SCHEMA, "v1", "sphynx.rerank.native-case-references.v1",
|
|
377
|
+
_validate_native_references,
|
|
378
|
+
metadata={"access": "backend_authorized_only", "native_grader_binds_exact_labels": True},
|
|
379
|
+
))
|
|
380
|
+
registry.register_schema(PayloadSchema(
|
|
381
|
+
ASSESSMENT_VIEW_SCHEMA, "v1", "sphynx.assessment-contracts.builtin-v1",
|
|
382
|
+
_validate_builtin_assessment,
|
|
383
|
+
metadata={"kinds": ["assertion", "score", "label"], "native_records_authoritative": True},
|
|
384
|
+
))
|
|
385
|
+
registry.register_provider(Dataset(
|
|
386
|
+
definition=suite.datasets[0], input_schema=input_schema,
|
|
387
|
+
case_source=lambda selected: backend.cases(suite), reference_schema=reference_schema,
|
|
388
|
+
))
|
|
389
|
+
registry.register_policy(
|
|
390
|
+
_AGGREGATION_POLICY_ID, version="v1",
|
|
391
|
+
implementation_id=METRIC_RELEASE + ".aggregate-case-metrics", kind="aggregation",
|
|
392
|
+
metadata={"execution": "backend_owned", "native_operation": "sphynx.metrics.aggregate_case_metrics",
|
|
393
|
+
"preserve_eligible_missing_undefined_populations": True},
|
|
394
|
+
)
|
|
395
|
+
registry.register_policy(
|
|
396
|
+
_COMPARISON_POLICY_ID, version="v1", implementation_id="sphynx.results.compare.v1",
|
|
397
|
+
kind="comparison", metadata={"execution": "backend_owned",
|
|
398
|
+
"native_operation": "sphynx.results.compare",
|
|
399
|
+
"delta_direction": "candidate_minus_baseline"},
|
|
400
|
+
)
|
|
401
|
+
for definition in metric_definitions():
|
|
402
|
+
registry.register_criterion(Criterion(
|
|
403
|
+
definition.metric_id, definition.release, definition.metric_id,
|
|
404
|
+
configuration={"native_metric_mapping": True,
|
|
405
|
+
"dimension": "measurement" if definition.metric_id == "runner_latency_ms" else "quality"},
|
|
406
|
+
))
|
|
407
|
+
registry.register_metric(_metric_spec(definition))
|
|
408
|
+
registry.register_evaluator(EvaluatorDefinition(
|
|
409
|
+
GRADER_RELEASE, "v1", "sphynx.results.grade:" + GRADER_RELEASE,
|
|
410
|
+
criterion_ids=suite.criterion_ids, assessment_schema_ids=(ASSESSMENT_VIEW_SCHEMA,),
|
|
411
|
+
metric_ids=suite.metric_ids, required_capabilities=("sphynx.saved-assessment.v1",),
|
|
412
|
+
configuration={"execution": "backend_owned", "native_grading_records_authoritative": True},
|
|
413
|
+
))
|
|
414
|
+
registry.register_suite(suite)
|
|
415
|
+
return backend
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def build_rerank_registry() -> WorkflowRegistry:
|
|
419
|
+
"""Return a fresh registry for the built-in native reranking adapter."""
|
|
420
|
+
suite = RerankBackend().describe(PACK_ID)
|
|
421
|
+
registry = WorkflowRegistry(legacy_ids=(*suite.criterion_ids, *suite.metric_ids,
|
|
422
|
+
*(dataset.dataset_id for dataset in suite.datasets)))
|
|
423
|
+
register_rerank_backend(registry)
|
|
424
|
+
return registry
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Pinned operator-side candidate entry point for local research parity.
|
|
2
|
+
|
|
3
|
+
This protocol uses an installed SDK and owner-provided assets. It is not an
|
|
4
|
+
admission into the hosted worker's smaller repository/runtime allowlist.
|
|
5
|
+
Configuration contains file references and digests, never credential values.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from .contracts import CandidateSpec, ContractError, canonical_json, validate_candidate_request
|
|
15
|
+
from .integration_contracts import CandidateConfiguration, ComparisonIdentity, _freeze, _wire, require_digest, value_digest
|
|
16
|
+
from .model_adapters import create_model_scorer
|
|
17
|
+
from .recorded_scores import create_recorded_scorer
|
|
18
|
+
|
|
19
|
+
SCHEMA = "sphynx.research-candidate.v1"
|
|
20
|
+
INTERFACE = "sphynx.rag.rerank.v1"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _json(raw):
|
|
24
|
+
def pairs(items):
|
|
25
|
+
result = {}
|
|
26
|
+
for key, value in items:
|
|
27
|
+
if key in result:
|
|
28
|
+
raise ContractError("duplicate JSON field")
|
|
29
|
+
result[key] = value
|
|
30
|
+
return result
|
|
31
|
+
def invalid(value):
|
|
32
|
+
raise ContractError("nonfinite JSON value")
|
|
33
|
+
return json.loads(raw, object_pairs_hook=pairs, parse_constant=invalid)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _read(path, expected, limit=128 * 1024 * 1024):
|
|
37
|
+
require_digest(expected, "asset digest")
|
|
38
|
+
source = Path(path).expanduser().resolve(strict=True)
|
|
39
|
+
if not source.is_file() or source.stat().st_size > limit:
|
|
40
|
+
raise ContractError("asset must be a bounded file")
|
|
41
|
+
raw = source.read_bytes()
|
|
42
|
+
if "sha256:" + hashlib.sha256(raw).hexdigest() != expected:
|
|
43
|
+
raise ContractError("asset bytes differ from pinned digest")
|
|
44
|
+
return _json(raw)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _fields(value, keys):
|
|
48
|
+
if not isinstance(value, dict) or set(value) != set(keys):
|
|
49
|
+
raise ContractError("research candidate configuration fields differ")
|
|
50
|
+
_freeze(value, reject_secrets=True)
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _source(value):
|
|
55
|
+
if not isinstance(value, dict):
|
|
56
|
+
raise ContractError("source scorer configuration required")
|
|
57
|
+
if value.get("kind") == "offline_model":
|
|
58
|
+
_fields(value, ("kind", "model_id", "snapshot_path", "snapshot_digest", "threads"))
|
|
59
|
+
return create_model_scorer(value["model_id"], value["snapshot_path"], value["snapshot_digest"], threads=value["threads"])
|
|
60
|
+
if value.get("kind") == "recorded_model":
|
|
61
|
+
_fields(value, ("kind", "recording_path", "recording_digest", "requests_path", "requests_digest"))
|
|
62
|
+
artifact = _read(value["recording_path"], value["recording_digest"])
|
|
63
|
+
requests = _read(value["requests_path"], value["requests_digest"])
|
|
64
|
+
if not isinstance(requests, list):
|
|
65
|
+
raise ContractError("recorded source requires a complete ordered request array")
|
|
66
|
+
checked = [validate_candidate_request(request).to_dict() for request in requests]
|
|
67
|
+
return create_recorded_scorer(value["recording_path"], value["recording_digest"],
|
|
68
|
+
identity=ComparisonIdentity.from_dict(artifact["identity"]), requests=checked, expected_model=artifact["model"])
|
|
69
|
+
raise ContractError("source kind must be offline_model or recorded_model")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class RecordedFeatures:
|
|
73
|
+
"""Complete request-bound, label-free learned features; no guessed joins."""
|
|
74
|
+
def __init__(self, config):
|
|
75
|
+
from .learned_rerank import FEATURE_NAMES
|
|
76
|
+
_fields(config, ("path", "digest"))
|
|
77
|
+
self.path, self.digest = config["path"], config["digest"]
|
|
78
|
+
artifact = _read(self.path, self.digest)
|
|
79
|
+
_fields(artifact, ("schema_version", "cases"))
|
|
80
|
+
if artifact["schema_version"] != "sphynx.rerank-features.v1" or not isinstance(artifact["cases"], list):
|
|
81
|
+
raise ContractError("unsupported recorded feature schema")
|
|
82
|
+
rows = {}
|
|
83
|
+
for row in artifact["cases"]:
|
|
84
|
+
_fields(row, ("case_id", "request_digest", "features"))
|
|
85
|
+
require_digest(row["request_digest"], "feature request digest")
|
|
86
|
+
if not isinstance(row["case_id"], str) or row["case_id"] in rows or not isinstance(row["features"], dict):
|
|
87
|
+
raise ContractError("feature case identity differs")
|
|
88
|
+
for features in row["features"].values():
|
|
89
|
+
_fields(features, FEATURE_NAMES)
|
|
90
|
+
if any(value is not None and (type(value) not in (int, float)) for value in features.values()):
|
|
91
|
+
raise ContractError("feature values must be finite numbers or explicit null")
|
|
92
|
+
rows[row["case_id"]] = row
|
|
93
|
+
self._rows = _freeze(rows)
|
|
94
|
+
self.configuration = CandidateConfiguration("recorded-features-v1", "sphynx.rerank-features.v1",
|
|
95
|
+
"sha256:" + hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), {"asset_digest": self.digest}, "recorded")
|
|
96
|
+
|
|
97
|
+
def __call__(self, request):
|
|
98
|
+
_read(self.path, self.digest)
|
|
99
|
+
row = self._rows.get(request["case_id"])
|
|
100
|
+
if row is None or row["request_digest"] != value_digest(request):
|
|
101
|
+
raise ContractError("feature recording request differs")
|
|
102
|
+
if set(row["features"]) != {candidate["candidate_id"] for candidate in request["candidates"]}:
|
|
103
|
+
raise ContractError("feature passage membership differs")
|
|
104
|
+
return _wire(row["features"])
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def load_research_candidate(configuration_path, expected_digest):
|
|
108
|
+
"""Construct a frozen reranker without loading weights or calling providers."""
|
|
109
|
+
from . import __version__
|
|
110
|
+
config = _read(configuration_path, expected_digest, 1024 * 1024)
|
|
111
|
+
_fields(config, ("schema_version", "sdk_version", "family", "method", "parameters", "sources", "fitted_state", "features"))
|
|
112
|
+
if config["schema_version"] != SCHEMA or config["sdk_version"] != __version__:
|
|
113
|
+
raise ContractError("research candidate schema or installed SDK version differs")
|
|
114
|
+
if not isinstance(config["sources"], dict) or not isinstance(config["parameters"], dict):
|
|
115
|
+
raise ContractError("research candidate sources and parameters must be objects")
|
|
116
|
+
sources = {name: _source(value) for name, value in config["sources"].items()}
|
|
117
|
+
family, method = config["family"], config["method"]
|
|
118
|
+
if family in ("statistical", "learned"):
|
|
119
|
+
for slot, source in sources.items():
|
|
120
|
+
params = source.configuration.parameters
|
|
121
|
+
model = params.get("model", params)
|
|
122
|
+
model_id = model.get("id", model.get("model_id"))
|
|
123
|
+
if model_id != {"minilm": "minilm-l6-v2", "jina": "jina-turbo-v1"}.get(slot):
|
|
124
|
+
raise ContractError("source model identity differs from selected family slot")
|
|
125
|
+
if model.get("activation") != "identity":
|
|
126
|
+
raise ContractError("family source requires raw identity-activation logits")
|
|
127
|
+
if family == "pointwise":
|
|
128
|
+
if set(sources) != {"model"} or config["parameters"] or config["fitted_state"] is not None or config["features"] is not None:
|
|
129
|
+
raise ContractError("pointwise candidate must declare exactly one source")
|
|
130
|
+
source = sources["model"]
|
|
131
|
+
if source.configuration.parameters.get("model_id", source.configuration.parameters.get("model", {}).get("id")) != method:
|
|
132
|
+
raise ContractError("selected pointwise method differs from source model")
|
|
133
|
+
return source
|
|
134
|
+
if family == "statistical":
|
|
135
|
+
from .statistical_rerank import create_statistical_scorer, METHOD_REGISTRY
|
|
136
|
+
if config["fitted_state"] is not None or config["features"] is not None or set(sources) - {"minilm", "jina"}:
|
|
137
|
+
raise ContractError("statistical source fields differ")
|
|
138
|
+
if method not in METHOD_REGISTRY:
|
|
139
|
+
raise ContractError("unknown statistical method")
|
|
140
|
+
required = set() if method in ("S1", "Dense-only") else ({"minilm", "jina"} if method == "Guarded-Jina-E2"
|
|
141
|
+
else {"jina"} if method.startswith(("Jina", "S6")) or method == "S7" else {"minilm"})
|
|
142
|
+
if set(sources) != required:
|
|
143
|
+
raise ContractError("selected statistical method requires its exact source scorers")
|
|
144
|
+
return create_statistical_scorer(method, scorers=sources, parameters=config["parameters"])
|
|
145
|
+
if family == "learned":
|
|
146
|
+
from .learned_rerank import create_learned_scorer
|
|
147
|
+
from .rerank_family_contracts import load_fitted_state
|
|
148
|
+
state = _fields(config["fitted_state"], ("path", "digest"))
|
|
149
|
+
params = _fields(config["parameters"], ("evaluation_cohort_digest", "domain_id"))
|
|
150
|
+
if set(sources) != {"minilm"}:
|
|
151
|
+
raise ContractError("learned candidate requires the MiniLM source")
|
|
152
|
+
fitted = load_fitted_state(state["path"], state["digest"])
|
|
153
|
+
if fitted.method != method:
|
|
154
|
+
raise ContractError("selected learned method differs from fitted state")
|
|
155
|
+
return create_learned_scorer(fitted, minilm_scorer=sources["minilm"],
|
|
156
|
+
feature_provider=RecordedFeatures(config["features"]), **params)
|
|
157
|
+
raise ContractError("unsupported research candidate family")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def make_research_candidate(configuration_path, expected_digest):
|
|
161
|
+
"""Return the existing SDK subprocess protocol with pinned configuration."""
|
|
162
|
+
scorer = load_research_candidate(configuration_path, expected_digest)
|
|
163
|
+
return CandidateSpec(scorer.configuration.configuration_id, scorer.configuration.digest,
|
|
164
|
+
(sys.executable, "-m", "sphynx.research_candidate", str(Path(configuration_path).resolve()), expected_digest,
|
|
165
|
+
scorer.configuration.digest), INTERFACE)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def protocol_main(argv=None):
|
|
169
|
+
try:
|
|
170
|
+
args = sys.argv[1:] if argv is None else argv
|
|
171
|
+
if len(args) not in (2, 3):
|
|
172
|
+
raise ContractError("expected configuration path, exact digest and optional resolved scorer digest")
|
|
173
|
+
scorer = load_research_candidate(args[0], args[1])
|
|
174
|
+
if len(args) == 3:
|
|
175
|
+
require_digest(args[2], "resolved scorer digest")
|
|
176
|
+
if scorer.configuration.digest != args[2]:
|
|
177
|
+
raise ContractError("resolved scorer configuration changed before execution")
|
|
178
|
+
raw = sys.stdin.buffer.read(1024 * 1024 + 1)
|
|
179
|
+
if len(raw) > 1024 * 1024:
|
|
180
|
+
raise ContractError("request too large")
|
|
181
|
+
request = validate_candidate_request(_json(raw)).to_dict()
|
|
182
|
+
print(canonical_json(scorer(request)), flush=True)
|
|
183
|
+
return 0
|
|
184
|
+
except Exception as exc:
|
|
185
|
+
print("research_candidate_failure:" + type(exc).__name__, file=sys.stderr, flush=True)
|
|
186
|
+
return 2
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
raise SystemExit(protocol_main())
|