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,130 @@
|
|
|
1
|
+
"""Trusted, generation-pinned admission metadata for the first CPU model.
|
|
2
|
+
|
|
3
|
+
Only an operator can supply this record. Its digest detects changed bytes; a
|
|
4
|
+
candidate declaration and a readiness message do not establish model use. The
|
|
5
|
+
actual probe is performed separately before the operator publishes admission.
|
|
6
|
+
No ML packages, candidate code or network clients are loaded by this module.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import re
|
|
15
|
+
|
|
16
|
+
from ..contracts import ContractError, canonical_json
|
|
17
|
+
from .contracts import HostedError
|
|
18
|
+
from .model_artifacts import MODEL_REF, model_artifact_inventory
|
|
19
|
+
from .model_contracts import MODEL_RUNTIME_PROFILE, MODEL_SUITE, MODEL_BASELINE
|
|
20
|
+
from .model_gcs import ModelObjectReceipt, ModelRegistration
|
|
21
|
+
from .model_session import SESSION_POLICY, TIMING_POLICY
|
|
22
|
+
|
|
23
|
+
ADMISSION_SCHEMA = "sphynx.hosted.model-runtime-admission.v1"
|
|
24
|
+
TRUSTED_BUCKET = "sphynx-hosted-pilot-510311"
|
|
25
|
+
ALLOWED_REPOSITORIES = (1360688838, 1396941909)
|
|
26
|
+
IMAGE_PREFIX = "us-central1-docker.pkg.dev/sphynx-510311/sphynx-hosted/minilm-cpu@sha256:"
|
|
27
|
+
MAX_ADMISSION_BYTES = 262_144
|
|
28
|
+
_FIELDS = {"schema_version", "model_ref", "manifest_digest", "runtime_profile", "image_digest",
|
|
29
|
+
"dependency_lock", "model_assets", "resource_policy", "timing_policy", "probe",
|
|
30
|
+
"allowed_repositories", "team_id", "admission_digest"}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _require(condition, code="invalid_model_admission"):
|
|
34
|
+
if not condition:
|
|
35
|
+
raise HostedError(code, "operator model runtime admission is missing, invalid or outside its scope")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def admission_digest(record):
|
|
39
|
+
"""Hash the full canonical operator record except the hash field itself."""
|
|
40
|
+
_require(type(record) is dict)
|
|
41
|
+
try:
|
|
42
|
+
raw = canonical_json({k: v for k, v in record.items() if k != "admission_digest"})
|
|
43
|
+
except (ContractError, RecursionError):
|
|
44
|
+
raise HostedError("invalid_model_admission", "operator model admission must be finite bounded JSON") from None
|
|
45
|
+
return "sha256:" + hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def validate_model_runtime(record, *, trusted_bucket=TRUSTED_BUCKET):
|
|
49
|
+
"""Validate and detach metadata; no receipt bytes or model use are inferred."""
|
|
50
|
+
_require(type(record) is dict and set(record) == _FIELDS)
|
|
51
|
+
try:
|
|
52
|
+
raw = canonical_json(record)
|
|
53
|
+
_require(len(raw.encode("utf-8")) <= MAX_ADMISSION_BYTES)
|
|
54
|
+
except (ContractError, RecursionError):
|
|
55
|
+
raise HostedError("invalid_model_admission", "operator model admission must be finite bounded JSON") from None
|
|
56
|
+
_require(record["schema_version"] == ADMISSION_SCHEMA and record["model_ref"] == MODEL_REF
|
|
57
|
+
and record["runtime_profile"] == MODEL_RUNTIME_PROFILE and record["team_id"] == "sable")
|
|
58
|
+
_require(trusted_bucket == TRUSTED_BUCKET)
|
|
59
|
+
inventory = model_artifact_inventory()
|
|
60
|
+
pin = inventory["manifest.json"]["digest"]
|
|
61
|
+
_require(record["manifest_digest"] == pin, "unapproved_model_manifest")
|
|
62
|
+
registration = ModelRegistration.from_dict(record["model_assets"], trusted_bucket=trusted_bucket,
|
|
63
|
+
approved_manifest_pins={MODEL_REF: pin})
|
|
64
|
+
_require(registration.manifest_digest == pin)
|
|
65
|
+
image = record["image_digest"]
|
|
66
|
+
_require(type(image) is str and re.fullmatch(re.escape(IMAGE_PREFIX) + "[0-9a-f]{64}", image),
|
|
67
|
+
"invalid_model_runtime_identity")
|
|
68
|
+
# Canonical equality preserves JSON types (True is not the CPU count 1).
|
|
69
|
+
_require(canonical_json(record["resource_policy"]) == canonical_json(SESSION_POLICY)
|
|
70
|
+
and canonical_json(record["timing_policy"]) == canonical_json(TIMING_POLICY),
|
|
71
|
+
"invalid_model_runtime_policy")
|
|
72
|
+
repos = record["allowed_repositories"]
|
|
73
|
+
_require(type(repos) is list and all(type(item) is int for item in repos)
|
|
74
|
+
and repos == list(ALLOWED_REPOSITORIES), "model_runtime_not_authorized")
|
|
75
|
+
lock = ModelObjectReceipt.from_dict(record["dependency_lock"])
|
|
76
|
+
probe = ModelObjectReceipt.from_dict(record["probe"])
|
|
77
|
+
_require(lock.bucket == trusted_bucket and lock.size_bytes <= 2 * 1024 * 1024
|
|
78
|
+
and re.fullmatch(r"prepared/runtimes/minilm-cpu/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/[A-Za-z0-9_./-]+", lock.object_name),
|
|
79
|
+
"invalid_model_dependency_lock")
|
|
80
|
+
image_sha = image.rsplit(":", 1)[1]
|
|
81
|
+
_require(probe.bucket == trusted_bucket and probe.size_bytes <= 2 * 1024 * 1024
|
|
82
|
+
and re.fullmatch(r"captures/model-probes/" + image_sha + r"/[0-9a-f]{64}/probe\.json", probe.object_name),
|
|
83
|
+
"invalid_model_probe_receipt")
|
|
84
|
+
_require(type(record["admission_digest"]) is str and re.fullmatch(r"sha256:[0-9a-f]{64}", record["admission_digest"])
|
|
85
|
+
and record["admission_digest"] == admission_digest(record), "model_admission_digest_mismatch")
|
|
86
|
+
return json.loads(raw)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def require_model_authorization(record, *, team_id, base_repository_id, head_repository_id,
|
|
90
|
+
suite, baseline, trusted_bucket=TRUSTED_BUCKET):
|
|
91
|
+
admitted = validate_model_runtime(record, trusted_bucket=trusted_bucket)
|
|
92
|
+
_require(team_id == admitted["team_id"] and type(base_repository_id) is int
|
|
93
|
+
and type(head_repository_id) is int and base_repository_id in admitted["allowed_repositories"]
|
|
94
|
+
and head_repository_id in admitted["allowed_repositories"], "model_runtime_not_authorized")
|
|
95
|
+
_require(suite == MODEL_SUITE, "model_suite_not_supported")
|
|
96
|
+
_require(baseline == MODEL_BASELINE, "model_baseline_not_supported")
|
|
97
|
+
return admitted
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True)
|
|
101
|
+
class ModelRuntimeAdmission:
|
|
102
|
+
"""Immutable operator record; callers receive detached JSON dictionaries."""
|
|
103
|
+
record_json: str
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def from_dict(cls, record, *, trusted_bucket=TRUSTED_BUCKET):
|
|
107
|
+
return cls(canonical_json(validate_model_runtime(record, trusted_bucket=trusted_bucket)))
|
|
108
|
+
|
|
109
|
+
def to_dict(self):
|
|
110
|
+
return json.loads(self.record_json)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def load_model_runtime(path, *, trusted_bucket=TRUSTED_BUCKET):
|
|
114
|
+
try:
|
|
115
|
+
with Path(path).open("rb") as stream:
|
|
116
|
+
raw = stream.read(MAX_ADMISSION_BYTES + 1)
|
|
117
|
+
_require(0 < len(raw) <= MAX_ADMISSION_BYTES)
|
|
118
|
+
def unique(pairs):
|
|
119
|
+
value = {}
|
|
120
|
+
for key, item in pairs:
|
|
121
|
+
_require(key not in value)
|
|
122
|
+
value[key] = item
|
|
123
|
+
return value
|
|
124
|
+
record = json.loads(raw, object_pairs_hook=unique,
|
|
125
|
+
parse_constant=lambda _: (_ for _ in ()).throw(ValueError()))
|
|
126
|
+
return ModelRuntimeAdmission.from_dict(record, trusted_bucket=trusted_bucket)
|
|
127
|
+
except HostedError:
|
|
128
|
+
raise
|
|
129
|
+
except (OSError, ValueError, TypeError, RecursionError):
|
|
130
|
+
raise HostedError("invalid_model_admission", "operator model admission could not be loaded") from None
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""Trusted offline MiniLM artifact intake; never imports an ML backend.
|
|
2
|
+
|
|
3
|
+
Only one operator-reviewed, safetensors-only closure is admitted. A candidate
|
|
4
|
+
cannot choose a URL, redirect, local path, replacement revision or digest pin.
|
|
5
|
+
Artifact availability is separate from runtime admission and inference proof.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import math
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import re
|
|
15
|
+
import shutil
|
|
16
|
+
import stat
|
|
17
|
+
import struct
|
|
18
|
+
from types import MappingProxyType
|
|
19
|
+
|
|
20
|
+
from ..contracts import canonical_json
|
|
21
|
+
from ..integration_contracts import value_digest
|
|
22
|
+
from .contracts import HostedError
|
|
23
|
+
|
|
24
|
+
SCHEMA = "sphynx.hosted.model-artifact.v1"
|
|
25
|
+
MODEL_ID = "minilm-l6-v2"
|
|
26
|
+
CHECKPOINT = "cross-encoder/ms-marco-MiniLM-L-6-v2"
|
|
27
|
+
PUBLISHER_CHECKPOINT = "cross-encoder/ms-marco-MiniLM-L6-v2"
|
|
28
|
+
REVISION = "233902d25c440f23af6f7d6e94d2946bac0bee0a"
|
|
29
|
+
MODEL_REF = MODEL_ID + "@" + REVISION
|
|
30
|
+
SNAPSHOT_LOCATOR = "snapshot/" + REVISION
|
|
31
|
+
REGISTERED_MODEL_LOCATORS = MappingProxyType({MODEL_REF: SNAPSHOT_LOCATOR})
|
|
32
|
+
REVIEWED_FILES = MappingProxyType({
|
|
33
|
+
"config.json": (794, "380e02c93f431831be65d99a4e7e5f67c133985bf2e77d9d4eba46847190bacc"),
|
|
34
|
+
"model.safetensors": (90870598, "821d1aa69520101d6e0737f78a042ae25b19e5cb9160701909d10434f4aeb0ae"),
|
|
35
|
+
"README.md": (3673, "7c0ec39941d0d1d766ccde671f592971d82fe45b7eea550a80d1ea864ebc3baa"),
|
|
36
|
+
"special_tokens_map.json": (132, "3c3507f36dff57bce437223db3b3081d1e2b52ec3e56ee55438193ecb2c94dd6"),
|
|
37
|
+
"tokenizer.json": (711396, "d241a60d5e8f04cc1b2b3e9ef7a4921b27bf526d9f6050ab90f9267a1f9e5c66"),
|
|
38
|
+
"tokenizer_config.json": (1330, "a5c2e5a7b1a29a0702cd28c08a399b5ecc110c263009d17f7e3b415f25905fd8"),
|
|
39
|
+
"vocab.txt": (231508, "07eced375cec144d27c900241f3e339478dec958f92fddbc551f295c992038a3"),
|
|
40
|
+
})
|
|
41
|
+
MAX_FILE_BYTES = 128 * 1024 * 1024
|
|
42
|
+
MAX_JSON_BYTES = 2 * 1024 * 1024
|
|
43
|
+
MAX_HEADER_BYTES = 64 * 1024
|
|
44
|
+
MODEL_CARD_URL = "https://huggingface.co/" + PUBLISHER_CHECKPOINT + "/blob/" + REVISION + "/README.md"
|
|
45
|
+
NOTICE = ("# MiniLM model artifact notice\n\n"
|
|
46
|
+
"Model: cross-encoder/ms-marco-MiniLM-L6-v2.\n"
|
|
47
|
+
"Pinned revision: " + REVISION + ".\n"
|
|
48
|
+
"The historical SDK alias is cross-encoder/ms-marco-MiniLM-L-6-v2.\n"
|
|
49
|
+
"The publisher model card declares Apache-2.0; the unmodified card is retained in README.md.\n"
|
|
50
|
+
"Publisher card: " + MODEL_CARD_URL + "\n"
|
|
51
|
+
"License text: https://www.apache.org/licenses/LICENSE-2.0\n\n"
|
|
52
|
+
"This is the seven-file CPU safetensors inference closure, not a complete repository mirror.\n"
|
|
53
|
+
"The snapshot contains no standalone LICENSE file. No redistribution approval or human review\n"
|
|
54
|
+
"receipt is asserted here. Model weights and code licensing are separate from dataset rights.\n"
|
|
55
|
+
"All scores are raw ranking utilities, not calibrated probabilities.\n").encode("utf-8")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _require(condition, code="invalid_model_artifact"):
|
|
59
|
+
if not condition:
|
|
60
|
+
raise HostedError(code, "model artifact identity or local closure is invalid")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _encoded(value):
|
|
64
|
+
return (canonical_json(value) + "\n").encode("utf-8")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _digest(raw):
|
|
68
|
+
return "sha256:" + hashlib.sha256(raw).hexdigest()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _json(raw):
|
|
72
|
+
_require(type(raw) is bytes and 0 < len(raw) <= MAX_JSON_BYTES)
|
|
73
|
+
def unique(pairs):
|
|
74
|
+
result = {}
|
|
75
|
+
for key, value in pairs:
|
|
76
|
+
_require(key not in result, "duplicate_model_field")
|
|
77
|
+
result[key] = value
|
|
78
|
+
return result
|
|
79
|
+
try:
|
|
80
|
+
return json.loads(raw, object_pairs_hook=unique,
|
|
81
|
+
parse_constant=lambda _: (_ for _ in ()).throw(ValueError()))
|
|
82
|
+
except (ValueError, UnicodeError, RecursionError):
|
|
83
|
+
raise HostedError("invalid_model_artifact", "model metadata JSON is invalid") from None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _alias(path):
|
|
87
|
+
info = path.lstat()
|
|
88
|
+
return path.is_symlink() or bool(getattr(info, "st_file_attributes", 0) & 0x400)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _root(directory):
|
|
92
|
+
path = Path(directory).absolute()
|
|
93
|
+
_require(path.is_dir() and not _alias(path), "invalid_model_path")
|
|
94
|
+
return path.resolve()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _inventory(root, allowed_directories=()):
|
|
98
|
+
files = {}
|
|
99
|
+
pending = [root]
|
|
100
|
+
while pending:
|
|
101
|
+
for path in pending.pop().iterdir():
|
|
102
|
+
_require(not _alias(path), "invalid_model_path")
|
|
103
|
+
mode = path.lstat().st_mode
|
|
104
|
+
if stat.S_ISDIR(mode):
|
|
105
|
+
_require(path.relative_to(root).as_posix() in allowed_directories, "model_closure_mismatch")
|
|
106
|
+
pending.append(path)
|
|
107
|
+
else:
|
|
108
|
+
_require(stat.S_ISREG(mode), "invalid_model_path")
|
|
109
|
+
files[path.relative_to(root).as_posix()] = path
|
|
110
|
+
return files
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _file_hash(path):
|
|
114
|
+
h = hashlib.sha256()
|
|
115
|
+
with path.open("rb") as stream:
|
|
116
|
+
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
|
117
|
+
h.update(block)
|
|
118
|
+
return "sha256:" + h.hexdigest()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _validate_header(path):
|
|
122
|
+
"""Read only the safe serialization header; no tensors are allocated."""
|
|
123
|
+
size = path.stat().st_size
|
|
124
|
+
with path.open("rb") as stream:
|
|
125
|
+
prefix = stream.read(8)
|
|
126
|
+
_require(len(prefix) == 8, "invalid_model_weights")
|
|
127
|
+
length = struct.unpack("<Q", prefix)[0]
|
|
128
|
+
_require(0 < length <= MAX_HEADER_BYTES and 8 + length < size, "invalid_model_weights")
|
|
129
|
+
header = _json(stream.read(length))
|
|
130
|
+
_require(type(header) is dict and header.get("__metadata__") == {"format": "pt"}, "invalid_model_weights")
|
|
131
|
+
tensors = {name: value for name, value in header.items() if name != "__metadata__"}
|
|
132
|
+
_require(len(tensors) == 106, "invalid_model_weights")
|
|
133
|
+
segments, dtypes = [], set()
|
|
134
|
+
for name, tensor in tensors.items():
|
|
135
|
+
_require(type(name) is str and bool(name) and type(tensor) is dict
|
|
136
|
+
and set(tensor) == {"dtype", "shape", "data_offsets"}, "invalid_model_weights")
|
|
137
|
+
dtype, shape, offsets = tensor["dtype"], tensor["shape"], tensor["data_offsets"]
|
|
138
|
+
_require(dtype in ("F32", "I64") and type(shape) is list and len(shape) <= 4
|
|
139
|
+
and all(type(n) is int and 0 < n <= 1000000 for n in shape), "invalid_model_weights")
|
|
140
|
+
_require(type(offsets) is list and len(offsets) == 2
|
|
141
|
+
and all(type(n) is int and 0 <= n <= MAX_FILE_BYTES for n in offsets), "invalid_model_weights")
|
|
142
|
+
start, end = offsets
|
|
143
|
+
_require(end > start and end - start == math.prod(shape) * (4 if dtype == "F32" else 8), "invalid_model_weights")
|
|
144
|
+
segments.append((start, end)); dtypes.add(dtype)
|
|
145
|
+
_require(dtypes == {"F32", "I64"}, "invalid_model_weights")
|
|
146
|
+
cursor = 0
|
|
147
|
+
for start, end in sorted(segments):
|
|
148
|
+
_require(start == cursor, "invalid_model_weights")
|
|
149
|
+
cursor = end
|
|
150
|
+
_require(cursor == size - 8 - length, "invalid_model_weights")
|
|
151
|
+
return {"serialization": "safetensors", "tensor_count": 106, "dtypes": ["F32", "I64"],
|
|
152
|
+
"tensor_execution": False}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def verify_minilm_snapshot(snapshot_dir):
|
|
156
|
+
"""Check the operator-reviewed seven files and metadata without loading ML."""
|
|
157
|
+
root = _root(snapshot_dir)
|
|
158
|
+
actual = _inventory(root)
|
|
159
|
+
_require(set(actual) == set(REVIEWED_FILES), "model_closure_mismatch")
|
|
160
|
+
hashes = {}
|
|
161
|
+
for name, (expected_size, expected_sha) in REVIEWED_FILES.items():
|
|
162
|
+
path = actual[name]
|
|
163
|
+
_require(type(expected_size) is int and 0 < expected_size <= MAX_FILE_BYTES)
|
|
164
|
+
_require(path.stat().st_size == expected_size, "model_file_size_mismatch")
|
|
165
|
+
digest = _file_hash(path)
|
|
166
|
+
_require(digest == "sha256:" + expected_sha, "model_file_digest_mismatch")
|
|
167
|
+
hashes[name] = digest
|
|
168
|
+
config = _json(actual["config.json"].read_bytes())
|
|
169
|
+
tokenizer = _json(actual["tokenizer_config.json"].read_bytes())
|
|
170
|
+
_require(type(config) is dict and type(tokenizer) is dict
|
|
171
|
+
and "auto_map" not in config and "auto_map" not in tokenizer, "unsupported_model_code")
|
|
172
|
+
expected = {"architectures": ["BertForSequenceClassification"], "model_type": "bert", "hidden_size": 384,
|
|
173
|
+
"num_hidden_layers": 6, "num_attention_heads": 12, "max_position_embeddings": 512,
|
|
174
|
+
"vocab_size": 30522, "sbert_ce_default_activation_function": "torch.nn.modules.linear.Identity"}
|
|
175
|
+
_require(all(config.get(k) == v for k, v in expected.items()), "unsupported_model_architecture")
|
|
176
|
+
_require(tokenizer.get("model_max_length") == 512 and tokenizer.get("tokenizer_class") == "BertTokenizer",
|
|
177
|
+
"unsupported_model_tokenizer")
|
|
178
|
+
serialization = _validate_header(actual["model.safetensors"])
|
|
179
|
+
return {"snapshot_files": hashes, "snapshot_digest": value_digest(hashes), "serialization": serialization}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _manifest():
|
|
183
|
+
hashes = {name: "sha256:" + pair[1] for name, pair in REVIEWED_FILES.items()}
|
|
184
|
+
return {"schema_version": SCHEMA, "model_ref": MODEL_REF, "model_id": MODEL_ID,
|
|
185
|
+
"checkpoint": CHECKPOINT, "publisher_checkpoint": PUBLISHER_CHECKPOINT, "revision": REVISION,
|
|
186
|
+
"tokenizer_revision": REVISION, "snapshot_digest": value_digest(hashes), "snapshot_locator": SNAPSHOT_LOCATOR,
|
|
187
|
+
"files": {name: {"size_bytes": size, "digest": "sha256:" + digest} for name, (size, digest) in REVIEWED_FILES.items()},
|
|
188
|
+
"notice": {"locator": "MODEL_NOTICE.md", "size_bytes": len(NOTICE), "digest": _digest(NOTICE)},
|
|
189
|
+
"serialization": {"format": "safetensors", "tensor_count": 106, "dtypes": ["F32", "I64"], "trust_remote_code": False},
|
|
190
|
+
"scoring_parameters": {"maximum_tokens": 512, "batch_size": 16, "activation": "identity",
|
|
191
|
+
"query_prefix": "", "truncation": "longest_first", "seed": 0, "device": "cpu", "torch_threads": 1},
|
|
192
|
+
"license_provenance": {"declared_license": "Apache-2.0", "declaration_file": "README.md",
|
|
193
|
+
"model_card_url": MODEL_CARD_URL, "standalone_license_in_snapshot": False,
|
|
194
|
+
"redistribution_approval_receipt": None},
|
|
195
|
+
"runtime_requirements": {"python": "3.12", "backend": "sentence_transformers.CrossEncoder",
|
|
196
|
+
"suggested_profile": "python-minilm-cpu.v1", "torch_wheel": "cpu-only",
|
|
197
|
+
"suggested_memory_bytes": 2147483648, "network": "none", "weights_mount": "read-only",
|
|
198
|
+
"immutable_runtime_digest": None, "dependency_lock_digest": None,
|
|
199
|
+
"required_gates": ["hashed_linux_cpu_dependency_lock", "immutable_runtime_image",
|
|
200
|
+
"sandbox_backend_compatibility", "bounded_cold_start_and_peak_rss", "case_and_watchdog_timeout_budget"]},
|
|
201
|
+
"verification_scope": {"source_file_hashes": True, "safetensors_header": True,
|
|
202
|
+
"backend_imported": False, "model_executed": False, "hosted_execution_admitted": False}}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def model_artifact_inventory():
|
|
206
|
+
"""Return the exact reviewed nine-file inventory for an operator registry."""
|
|
207
|
+
manifest = _encoded(_manifest())
|
|
208
|
+
inventory = {SNAPSHOT_LOCATOR + "/" + name: {"digest": "sha256:" + sha, "size_bytes": size}
|
|
209
|
+
for name, (size, sha) in REVIEWED_FILES.items()}
|
|
210
|
+
inventory["manifest.json"] = {"digest": _digest(manifest), "size_bytes": len(manifest)}
|
|
211
|
+
inventory["MODEL_NOTICE.md"] = {"digest": _digest(NOTICE), "size_bytes": len(NOTICE)}
|
|
212
|
+
return inventory
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@dataclass(frozen=True)
|
|
216
|
+
class ModelArtifactExport:
|
|
217
|
+
directory: Path
|
|
218
|
+
manifest_sha256: str
|
|
219
|
+
model_ref: str
|
|
220
|
+
snapshot_digest: str
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@dataclass(frozen=True)
|
|
224
|
+
class VerifiedModelArtifact:
|
|
225
|
+
directory: Path
|
|
226
|
+
snapshot_path: Path
|
|
227
|
+
manifest_sha256: str
|
|
228
|
+
manifest_json: str
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def manifest(self):
|
|
232
|
+
return json.loads(self.manifest_json)
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def model_ref(self):
|
|
236
|
+
return self.manifest["model_ref"]
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def snapshot_digest(self):
|
|
240
|
+
return self.manifest["snapshot_digest"]
|
|
241
|
+
|
|
242
|
+
def reverify(self):
|
|
243
|
+
"""Call before execution or use an independently protected read-only mount."""
|
|
244
|
+
return load_model_artifact(self.directory, expected_manifest_sha256=self.manifest_sha256,
|
|
245
|
+
registered_asset_locators=REGISTERED_MODEL_LOCATORS)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def export_minilm_artifact(snapshot_dir, output_dir):
|
|
249
|
+
"""Copy only a verified existing snapshot into a fresh deterministic package.
|
|
250
|
+
|
|
251
|
+
No network, model downloads, backend imports, cloud writes or inference occur.
|
|
252
|
+
A failed export can leave an incomplete fresh directory; it is never admitted.
|
|
253
|
+
"""
|
|
254
|
+
identity = verify_minilm_snapshot(snapshot_dir)
|
|
255
|
+
source = _root(snapshot_dir)
|
|
256
|
+
output = Path(output_dir).absolute()
|
|
257
|
+
_require(not output.exists(), "model_export_exists")
|
|
258
|
+
output.mkdir(parents=True, exist_ok=False)
|
|
259
|
+
target = output / SNAPSHOT_LOCATOR
|
|
260
|
+
target.mkdir(parents=True)
|
|
261
|
+
for name in REVIEWED_FILES:
|
|
262
|
+
shutil.copyfile(source / name, target / name)
|
|
263
|
+
# Recheck the copies, so edits between source validation and copying fail.
|
|
264
|
+
_require(verify_minilm_snapshot(target) == identity, "model_copy_identity_mismatch")
|
|
265
|
+
(output / "MODEL_NOTICE.md").write_bytes(NOTICE)
|
|
266
|
+
raw = _encoded(_manifest())
|
|
267
|
+
(output / "manifest.json").write_bytes(raw)
|
|
268
|
+
loaded = load_model_artifact(output, expected_manifest_sha256=_digest(raw),
|
|
269
|
+
registered_asset_locators=REGISTERED_MODEL_LOCATORS)
|
|
270
|
+
return ModelArtifactExport(output, loaded.manifest_sha256, loaded.model_ref, loaded.snapshot_digest)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def load_model_artifact(bundle_dir, *, expected_manifest_sha256, registered_asset_locators):
|
|
274
|
+
"""Verify a staged, operator-registered package against an independent pin.
|
|
275
|
+
|
|
276
|
+
A hosted caller must first verify generation-pinned GCS receipts. This loader
|
|
277
|
+
performs no URL resolution and cannot derive authority from a candidate's pin.
|
|
278
|
+
"""
|
|
279
|
+
_require(type(expected_manifest_sha256) is str
|
|
280
|
+
and re.fullmatch(r"sha256:[0-9a-f]{64}", expected_manifest_sha256), "invalid_model_manifest_pin")
|
|
281
|
+
_require(isinstance(registered_asset_locators, (dict, MappingProxyType))
|
|
282
|
+
and dict(registered_asset_locators) == dict(REGISTERED_MODEL_LOCATORS), "unregistered_model_locator")
|
|
283
|
+
root = _root(bundle_dir)
|
|
284
|
+
inventory = _inventory(root, {"snapshot", SNAPSHOT_LOCATOR})
|
|
285
|
+
expected = {"manifest.json", "MODEL_NOTICE.md"} | {SNAPSHOT_LOCATOR + "/" + name for name in REVIEWED_FILES}
|
|
286
|
+
_require(set(inventory) == expected, "model_closure_mismatch")
|
|
287
|
+
path = inventory["manifest.json"]
|
|
288
|
+
_require(0 < path.stat().st_size <= MAX_JSON_BYTES)
|
|
289
|
+
raw = path.read_bytes()
|
|
290
|
+
_require(_digest(raw) == expected_manifest_sha256, "model_manifest_digest_mismatch")
|
|
291
|
+
parsed = _json(raw)
|
|
292
|
+
_require(_encoded(parsed) == _encoded(_manifest()), "model_manifest_identity_mismatch")
|
|
293
|
+
_require(inventory["MODEL_NOTICE.md"].stat().st_size == len(NOTICE), "model_notice_mismatch")
|
|
294
|
+
_require(inventory["MODEL_NOTICE.md"].read_bytes() == NOTICE, "model_notice_mismatch")
|
|
295
|
+
snapshot_path = root / SNAPSHOT_LOCATOR
|
|
296
|
+
identity = verify_minilm_snapshot(snapshot_path)
|
|
297
|
+
_require(identity["snapshot_digest"] == parsed["snapshot_digest"], "model_snapshot_digest_mismatch")
|
|
298
|
+
return VerifiedModelArtifact(root, snapshot_path, expected_manifest_sha256, canonical_json(parsed))
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Additive configuration for one operator-admitted, offline CPU model runtime.
|
|
2
|
+
|
|
3
|
+
Configuration describes an approach; it does not prove that submitted code used
|
|
4
|
+
the declared model. Parsing never imports ML libraries or retrieves artifacts.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, Mapping
|
|
10
|
+
|
|
11
|
+
from .contracts import CandidateConfig, HostedError, _parse_candidate_config, fields
|
|
12
|
+
from .model_artifacts import MODEL_REF
|
|
13
|
+
|
|
14
|
+
MODEL_CONFIG_SCHEMA = "sphynx.candidate.repository.v2"
|
|
15
|
+
MODEL_SUBMISSION_SCHEMA = "sphynx.hosted.prepared-submission.v3"
|
|
16
|
+
MODEL_RUNTIME_PROFILE = "python-minilm-cpu.v1"
|
|
17
|
+
MODEL_TRANSPORT = "jsonl-session.v1"
|
|
18
|
+
MODEL_SUITE = "rag.rerank.public.v1"
|
|
19
|
+
MODEL_BASELINE = "supplied-score:v1"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class RegisteredModelCandidateConfig(CandidateConfig):
|
|
24
|
+
"""The legacy source contract with a separately admitted session runtime."""
|
|
25
|
+
|
|
26
|
+
def to_dict(self) -> dict[str, Any]:
|
|
27
|
+
value = super().to_dict()
|
|
28
|
+
value["schema_version"] = MODEL_CONFIG_SCHEMA
|
|
29
|
+
value["runtime"] = {"profile": MODEL_RUNTIME_PROFILE, "python": "3.12",
|
|
30
|
+
"transport": MODEL_TRANSPORT}
|
|
31
|
+
value["model"] = {"kind": "registered", "ref": MODEL_REF}
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def parse_model_configuration(raw: Mapping[str, Any]) -> RegisteredModelCandidateConfig:
|
|
36
|
+
fields(raw, {"schema_version", "candidate", "build", "runtime", "model"} |
|
|
37
|
+
({"method"} if "method" in raw else set()), "model configuration")
|
|
38
|
+
if raw["schema_version"] != MODEL_CONFIG_SCHEMA:
|
|
39
|
+
raise HostedError("unsupported_configuration_version", "expected model repository v2")
|
|
40
|
+
runtime = fields(raw["runtime"], {"profile", "python", "transport"}, "model runtime")
|
|
41
|
+
if dict(runtime) != {"profile": MODEL_RUNTIME_PROFILE, "python": "3.12", "transport": MODEL_TRANSPORT}:
|
|
42
|
+
raise HostedError("unsupported_runtime", "model candidates require the admitted CPU session profile")
|
|
43
|
+
model = fields(raw["model"], {"kind", "ref"}, "registered model")
|
|
44
|
+
if dict(model) != {"kind": "registered", "ref": MODEL_REF}:
|
|
45
|
+
raise HostedError("unapproved_model_manifest", "model candidates require the registered MiniLM revision")
|
|
46
|
+
build = fields(raw["build"], {"commands", "dependencies"}, "model build")
|
|
47
|
+
if type(build["commands"]) is not list or build["commands"]:
|
|
48
|
+
raise HostedError("model_build_not_supported", "model candidates must use the prebuilt runtime without build commands")
|
|
49
|
+
if type(build["dependencies"]) is not list or build["dependencies"]:
|
|
50
|
+
raise HostedError("dependencies_not_supported", "model candidates must use the operator-frozen offline dependencies")
|
|
51
|
+
# Reuse all existing source/argv/interface/method checks without admitting a
|
|
52
|
+
# model through the legacy parser or a family.v1 method.
|
|
53
|
+
legacy = dict(raw)
|
|
54
|
+
legacy.update(schema_version="sphynx.candidate.repository.v1",
|
|
55
|
+
runtime={"profile": "python-cpu.v1", "python": "3.12"}, model={"kind": "none"})
|
|
56
|
+
parsed = _parse_candidate_config(legacy)
|
|
57
|
+
values = dict(vars(parsed))
|
|
58
|
+
values.update(runtime_profile=MODEL_RUNTIME_PROFILE, model_kind="registered")
|
|
59
|
+
return RegisteredModelCandidateConfig(**values)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def is_model_configuration(config) -> bool:
|
|
63
|
+
return isinstance(config, RegisteredModelCandidateConfig)
|