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.
Files changed (174) hide show
  1. sable_sphynx-0.7.0.dev0.dist-info/METADATA +152 -0
  2. sable_sphynx-0.7.0.dev0.dist-info/RECORD +174 -0
  3. sable_sphynx-0.7.0.dev0.dist-info/WHEEL +5 -0
  4. sable_sphynx-0.7.0.dev0.dist-info/top_level.txt +1 -0
  5. sphynx/__init__.py +469 -0
  6. sphynx/__init__.pyi +376 -0
  7. sphynx/_analytical_client.py +77 -0
  8. sphynx/_analytical_values.py +252 -0
  9. sphynx/_audit_client.py +173 -0
  10. sphynx/_comparison_adapters.py +113 -0
  11. sphynx/_componentized_full_rag.py +34 -0
  12. sphynx/_composed_pipeline.py +109 -0
  13. sphynx/_full_rag_grading.py +218 -0
  14. sphynx/_full_rag_trace.py +221 -0
  15. sphynx/_local_eval.py +268 -0
  16. sphynx/_pipeline_core.py +243 -0
  17. sphynx/_pr_client.py +237 -0
  18. sphynx/_rag_diagnostics_calibration.py +285 -0
  19. sphynx/_rag_diagnostics_catalog.py +39 -0
  20. sphynx/_rag_diagnostics_common.py +26 -0
  21. sphynx/_rag_diagnostics_contracts.py +186 -0
  22. sphynx/_rag_diagnostics_engine.py +243 -0
  23. sphynx/_rag_diagnostics_importers.py +242 -0
  24. sphynx/_rag_diagnostics_ranking.py +81 -0
  25. sphynx/_rag_diagnostics_semantic.py +325 -0
  26. sphynx/_rag_ranking.py +149 -0
  27. sphynx/_speech_benchmark_scoring.py +140 -0
  28. sphynx/_speech_benchmark_sources.py +275 -0
  29. sphynx/_speech_complete_scoring.py +218 -0
  30. sphynx/_speech_complete_source.py +118 -0
  31. sphynx/_speech_kido.py +294 -0
  32. sphynx/_speech_scoring.py +77 -0
  33. sphynx/_track_capture.py +213 -0
  34. sphynx/analytical_views.py +515 -0
  35. sphynx/assessment_contracts.py +378 -0
  36. sphynx/assessment_selection.py +57 -0
  37. sphynx/audit_access.py +245 -0
  38. sphynx/audit_contracts.py +204 -0
  39. sphynx/audit_requests.py +180 -0
  40. sphynx/authoring.py +188 -0
  41. sphynx/benchmarks.py +568 -0
  42. sphynx/candidates.py +62 -0
  43. sphynx/catalog.py +272 -0
  44. sphynx/chunking.py +372 -0
  45. sphynx/clickhouse_reader.py +169 -0
  46. sphynx/client.py +486 -0
  47. sphynx/comparison_core.py +149 -0
  48. sphynx/comparison_views.py +223 -0
  49. sphynx/components.py +1661 -0
  50. sphynx/contracts.py +514 -0
  51. sphynx/customer_rerank.py +299 -0
  52. sphynx/customer_retrieval.py +146 -0
  53. sphynx/data/benchmark-releases.v1.json +164 -0
  54. sphynx/data/public-benchmark-release.v1.json +354 -0
  55. sphynx/data/rag-diagnostics-synthetic.v1.json +6982 -0
  56. sphynx/data/rag-paper-references.v1.json +176 -0
  57. sphynx/data/rag-paper-schemas.v1.json +1069 -0
  58. sphynx/data/rag-rerank-v1.json +507 -0
  59. sphynx/dataset_catalog_data.py +1214 -0
  60. sphynx/dataset_readiness.py +138 -0
  61. sphynx/dataset_v3.py +999 -0
  62. sphynx/demo.py +130 -0
  63. sphynx/demo_candidates/__init__.py +1 -0
  64. sphynx/demo_candidates/dense.py +11 -0
  65. sphynx/demo_candidates/lexical.py +21 -0
  66. sphynx/diagnostics.py +207 -0
  67. sphynx/documentation.py +737 -0
  68. sphynx/documentation_candidate.py +939 -0
  69. sphynx/evals.py +538 -0
  70. sphynx/evaluation_protocols.py +124 -0
  71. sphynx/evaluator_bindings.py +370 -0
  72. sphynx/evidence.py +352 -0
  73. sphynx/execution_capabilities.py +109 -0
  74. sphynx/fast_search.py +285 -0
  75. sphynx/full_rag.py +341 -0
  76. sphynx/hosted/__init__.py +18 -0
  77. sphynx/hosted/benchmark_intake.py +218 -0
  78. sphynx/hosted/contracts.py +361 -0
  79. sphynx/hosted/model_admission.py +130 -0
  80. sphynx/hosted/model_artifacts.py +298 -0
  81. sphynx/hosted/model_contracts.py +63 -0
  82. sphynx/hosted/model_gcs.py +181 -0
  83. sphynx/hosted/model_history.py +84 -0
  84. sphynx/hosted/model_session.py +233 -0
  85. sphynx/hosted/public_suite.py +74 -0
  86. sphynx/hosted/registry.py +97 -0
  87. sphynx/hosted/revisions.py +126 -0
  88. sphynx/hosted/sandbox.py +105 -0
  89. sphynx/hosted/submission.py +287 -0
  90. sphynx/hosted_workflow.py +451 -0
  91. sphynx/http_transport.py +86 -0
  92. sphynx/integrated_rag.py +352 -0
  93. sphynx/integrated_workflow.py +552 -0
  94. sphynx/integration_contracts.py +474 -0
  95. sphynx/invocation.py +791 -0
  96. sphynx/invocation_candidates.py +392 -0
  97. sphynx/invocation_catalog_data.py +40 -0
  98. sphynx/learned_catalog_data.py +30 -0
  99. sphynx/learned_rerank.py +267 -0
  100. sphynx/legacy_full_rag.py +67 -0
  101. sphynx/lifecycle.py +885 -0
  102. sphynx/lifecycle_workflow.py +490 -0
  103. sphynx/metric_views.py +474 -0
  104. sphynx/metrics.py +282 -0
  105. sphynx/model_adapters.py +438 -0
  106. sphynx/model_catalog_data.py +30 -0
  107. sphynx/official_benchmarks.py +385 -0
  108. sphynx/packs.py +258 -0
  109. sphynx/pipeline.py +313 -0
  110. sphynx/pipeline_workflow.py +551 -0
  111. sphynx/pr_evaluations.py +560 -0
  112. sphynx/progress.py +89 -0
  113. sphynx/public_bundle.py +173 -0
  114. sphynx/public_eval.py +62 -0
  115. sphynx/public_rerank.py +355 -0
  116. sphynx/py.typed +0 -0
  117. sphynx/pytest_plugin.py +62 -0
  118. sphynx/question_robustness.py +437 -0
  119. sphynx/rag.py +329 -0
  120. sphynx/rag_diagnostics.py +309 -0
  121. sphynx/rag_papers/__init__.py +29 -0
  122. sphynx/rag_papers/__main__.py +45 -0
  123. sphynx/rag_papers/candidates.py +19 -0
  124. sphynx/rag_papers/contracts.py +282 -0
  125. sphynx/rag_papers/datasets.py +291 -0
  126. sphynx/rag_papers/experiments.py +116 -0
  127. sphynx/rag_papers/judges.py +308 -0
  128. sphynx/rag_papers/registry.py +33 -0
  129. sphynx/rag_papers/runner.py +176 -0
  130. sphynx/rag_papers/sable.py +135 -0
  131. sphynx/rag_papers/schemas.py +12 -0
  132. sphynx/rag_papers/scoring.py +249 -0
  133. sphynx/rag_papers/semantic.py +288 -0
  134. sphynx/rag_papers/trec.py +80 -0
  135. sphynx/recorded_scores.py +352 -0
  136. sphynx/records.py +124 -0
  137. sphynx/reflex_traces.py +193 -0
  138. sphynx/report_rendering.py +71 -0
  139. sphynx/rerank_family_contracts.py +150 -0
  140. sphynx/rerank_workflow.py +424 -0
  141. sphynx/research_candidate.py +190 -0
  142. sphynx/results.py +852 -0
  143. sphynx/retrieval.py +750 -0
  144. sphynx/retrieval_candidates.py +295 -0
  145. sphynx/retrieval_catalog_data.py +19 -0
  146. sphynx/retrieval_recordings.py +125 -0
  147. sphynx/reviewer_audit.py +266 -0
  148. sphynx/routing_policies.py +375 -0
  149. sphynx/runner.py +544 -0
  150. sphynx/sable_rerank.py +489 -0
  151. sphynx/score_reliability.py +393 -0
  152. sphynx/speech_benchmarks.py +204 -0
  153. sphynx/speech_campaigns.py +378 -0
  154. sphynx/speech_complete.py +236 -0
  155. sphynx/speech_contracts.py +225 -0
  156. sphynx/speech_evidence.py +429 -0
  157. sphynx/speech_listening.py +115 -0
  158. sphynx/speech_multilingual.py +71 -0
  159. sphynx/speech_tts.py +479 -0
  160. sphynx/sql/001_sync_v1.sql +30 -0
  161. sphynx/stage_contracts.py +199 -0
  162. sphynx/statistical_catalog_data.py +53 -0
  163. sphynx/statistical_rerank.py +232 -0
  164. sphynx/sync.py +797 -0
  165. sphynx/workbench.py +378 -0
  166. sphynx/workbench_catalog_data.py +13 -0
  167. sphynx/workbench_tracks.py +78 -0
  168. sphynx/workflow_async.py +197 -0
  169. sphynx/workflow_contracts.py +594 -0
  170. sphynx/workflow_decisions.py +211 -0
  171. sphynx/workflow_defaults.py +16 -0
  172. sphynx/workflow_extensions.py +212 -0
  173. sphynx/workflow_registry.py +513 -0
  174. sphynx/workflows.py +792 -0
@@ -0,0 +1,77 @@
1
+ """Pure, versioned speech scoring; no providers or RAG dependencies.
2
+
3
+ The normalization and structured-token compatibility score reproduce
4
+ sable-tts-eval/scripts/wer_cer.py at 71ec7e8. Its token matcher deliberately
5
+ uses substring presence; this is not a stricter entity-boundary metric.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ import unicodedata
11
+
12
+ NORMALIZATION = "kido.nfkc-lower-punctuation.v1"
13
+ TOKEN_SCORER = "kido.structured-substring-presence.v1"
14
+ PATTERNS = {
15
+ "identifier": r"\b[A-Z]{2,}-\d{3,}-?[A-Z]?\b",
16
+ "currency": r"\$\s?\d[\d,]*(?:\.\d+)?",
17
+ "number": r"\b\d[\d,]*(?:\.\d+)?\b",
18
+ "email": r"[\w.+-]+@[\w-]+\.[\w.-]+",
19
+ "url": r"\b(?:[\w-]+\.)+[a-z]{2,}(?:/[\w./-]*)?\b",
20
+ "acronym": r"\b[A-Z]{2,6}(?:\s?\d)?\b",
21
+ }
22
+
23
+
24
+ def normalize(text: str, *, strip_punctuation: bool = True) -> str:
25
+ text = unicodedata.normalize("NFKC", text).lower()
26
+ if strip_punctuation:
27
+ text = re.sub(r"['’]", "", text)
28
+ text = re.sub(r"[^\w\s@./\-]", " ", text)
29
+ return re.sub(r"\s+", " ", text).strip()
30
+
31
+
32
+ def edit_distance(a, b) -> int:
33
+ previous = list(range(len(b) + 1))
34
+ for i, value in enumerate(a, 1):
35
+ current = [i]
36
+ for j, other in enumerate(b, 1):
37
+ current.append(min(previous[j] + 1, current[-1] + 1,
38
+ previous[j - 1] + (value != other)))
39
+ previous = current
40
+ return previous[-1]
41
+
42
+
43
+ def score_transcript(reference: str, hypothesis: str) -> dict:
44
+ ref, hyp = normalize(reference), normalize(hypothesis)
45
+ words, chars = ref.split(), list(ref)
46
+ word_errors, char_errors = edit_distance(words, hyp.split()), edit_distance(chars, list(hyp))
47
+ tokens = {}
48
+ for name, pattern in PATTERNS.items():
49
+ expected = re.findall(pattern, reference)
50
+ observed = normalize(hypothesis, strip_punctuation=False)
51
+ matched = sum(normalize(token, strip_punctuation=False) in observed for token in expected)
52
+ tokens[name] = {"matched": matched, "reference_tokens": len(expected),
53
+ "accuracy": matched / len(expected) if expected else None}
54
+ return {"reference_words": len(words), "word_errors": word_errors,
55
+ "wer": word_errors / len(words) if words else None,
56
+ "reference_characters": len(chars), "character_errors": char_errors,
57
+ "cer": char_errors / len(chars) if chars else None,
58
+ "structured_tokens": tokens,
59
+ "silence_inserted_words": len(hyp.split()) if not words else 0}
60
+
61
+
62
+ def timing_summary(values: list[float], *, convention: str) -> dict:
63
+ """Retain two historical quantile conventions explicitly, never pool them."""
64
+ import statistics
65
+ values = sorted(values)
66
+ if not values:
67
+ return {"n": 0, "mean": None, "p50": None, "p95": None, "convention": convention}
68
+ n = len(values)
69
+ if convention == "kido.isolated-rounded-index.v1":
70
+ quantile = lambda p: values[max(0, min(n - 1, round(n * p) - 1))]
71
+ p50, p95 = quantile(.5), quantile(.95)
72
+ elif convention == "kido.live-median-rounded-index.v1":
73
+ p50, p95 = statistics.median(values), values[min(n - 1, round(.95 * (n - 1)))]
74
+ else:
75
+ raise ValueError("unknown speech percentile convention")
76
+ return {"n": n, "mean": sum(values) / n, "p50": p50, "p95": p95,
77
+ "convention": convention}
@@ -0,0 +1,213 @@
1
+ """Durable local captures shared by chunking, pipeline and full-RAG tracks."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import json
6
+ import math
7
+ from collections.abc import Mapping
8
+ from pathlib import Path
9
+ from uuid import uuid4
10
+ from .contracts import ContractError, canonical_json, _id
11
+ from .integration_contracts import (ArtifactBinding, TrackCaptureManifest, TrackRunRef,
12
+ TrackEvaluationManifest, TrackEvaluationRef, require_digest)
13
+
14
+
15
+ def byte_digest(data):
16
+ return 'sha256:' + hashlib.sha256(data).hexdigest()
17
+
18
+
19
+ def strict_json(data):
20
+ def pairs(items):
21
+ result = {}
22
+ for key, value in items:
23
+ if key in result:
24
+ raise ContractError('duplicate JSON object key')
25
+ result[key] = value
26
+ return result
27
+ def constant(value):
28
+ raise ContractError('nonfinite JSON constant')
29
+ try:
30
+ result = json.loads(data, object_pairs_hook=pairs, parse_constant=constant)
31
+ # Reject overflow such as 1e999 as well as NaN literals.
32
+ canonical_json(result)
33
+ def check(value):
34
+ if isinstance(value, float) and not math.isfinite(value):
35
+ raise ContractError('nonfinite JSON number')
36
+ if isinstance(value, dict):
37
+ for item in value.values(): check(item)
38
+ if isinstance(value, list):
39
+ for item in value: check(item)
40
+ check(result)
41
+ return result
42
+ except (ValueError, TypeError, UnicodeError) as error:
43
+ raise ContractError('invalid strict JSON artifact') from error
44
+
45
+
46
+ def _request_bytes(value):
47
+ """Validate JSON types before encoding; JSON must never coerce object keys."""
48
+ def validate(item):
49
+ if item is None or type(item) in (str, bool, int):
50
+ return item
51
+ if type(item) is float and math.isfinite(item):
52
+ return item
53
+ if isinstance(item, Mapping):
54
+ if any(type(key) is not str for key in item):
55
+ raise ContractError('request JSON keys must be strings')
56
+ return {key: validate(child) for key, child in item.items()}
57
+ if isinstance(item, (list, tuple)):
58
+ return [validate(child) for child in item]
59
+ raise ContractError('request must contain finite JSON values')
60
+ encoded = canonical_json(validate(value)).encode('utf-8')
61
+ strict_json(encoded)
62
+ return encoded
63
+
64
+
65
+ def safe_path(root, relative):
66
+ # Reuse the frozen wire path validator before touching the filesystem.
67
+ ArtifactBinding(relative, 'sha256:' + '0' * 64, 0)
68
+ root = Path(root).absolute()
69
+ if root.is_symlink():
70
+ raise ContractError('capture root cannot be a symlink')
71
+ resolved_root = root.resolve()
72
+ current = root
73
+ for part in relative.split('/'):
74
+ current = current / part
75
+ if current.is_symlink():
76
+ raise ContractError('capture artifact cannot traverse a symlink')
77
+ if not current.resolve().is_relative_to(resolved_root):
78
+ raise ContractError('artifact escapes capture root')
79
+ return current
80
+
81
+
82
+ def _read_file(path, expected_digest):
83
+ require_digest(expected_digest, 'expected digest')
84
+ path = Path(path)
85
+ if path.is_symlink() or not path.is_file():
86
+ raise ContractError('expected regular local artifact')
87
+ data = path.read_bytes()
88
+ if byte_digest(data) != expected_digest:
89
+ raise ContractError('artifact digest mismatch')
90
+ return strict_json(data)
91
+
92
+
93
+ def read_artifact(run_dir, binding):
94
+ path = safe_path(run_dir, binding.path)
95
+ if not path.is_file() or path.stat().st_size != binding.size_bytes:
96
+ raise ContractError('artifact missing or byte size mismatch')
97
+ return _read_file(path, binding.sha256)
98
+
99
+
100
+ def write_artifact(run_dir, relative_path, value):
101
+ data = canonical_json(value).encode('utf-8')
102
+ strict_json(data)
103
+ path = safe_path(run_dir, relative_path)
104
+ path.parent.mkdir(parents=True, exist_ok=True)
105
+ with path.open('xb') as stream:
106
+ stream.write(data)
107
+ return ArtifactBinding(relative_path, byte_digest(data), len(data))
108
+
109
+
110
+ def create_run(output_dir, identity, candidate, case_ids, *, run_id=None):
111
+ run_id = run_id or 'run-' + uuid4().hex
112
+ _id(run_id, 'run_id')
113
+ # Validate identity/configuration before creating files.
114
+ identity.to_dict(); candidate.to_dict()
115
+ if not case_ids or len(case_ids) != len(set(case_ids)):
116
+ raise ContractError('run requires distinct ordered cases')
117
+ path = safe_path(output_dir, 'runs/' + run_id)
118
+ path.mkdir(parents=True, exist_ok=False)
119
+ return path
120
+
121
+
122
+ def finish_run(run_dir, identity, candidate, case_ids, captures):
123
+ path = Path(run_dir)
124
+ manifest = TrackCaptureManifest(path.name, identity, candidate, tuple(case_ids), tuple(captures))
125
+ for capture in manifest.captures:
126
+ for binding in (capture.request, capture.response, capture.observed):
127
+ if binding is not None: read_artifact(path, binding)
128
+ binding = write_artifact(path, 'manifest.json', manifest.to_dict())
129
+ ref = TrackRunRef(path, binding.sha256)
130
+ verify_track_run(ref)
131
+ return ref
132
+
133
+
134
+ def open_track_run(path, *, expected_manifest_sha256):
135
+ ref = TrackRunRef(Path(path), expected_manifest_sha256)
136
+ verify_track_run(ref)
137
+ return ref
138
+
139
+
140
+ def verify_track_run(ref, *, expected_case_ids=None, expected_requests=None, trials=None, attempts=None):
141
+ manifest = TrackCaptureManifest.from_dict(_read_file(safe_path(ref.path, 'manifest.json'), ref.manifest_sha256))
142
+ if manifest.run_id != ref.run_id:
143
+ raise ContractError('run identity mismatch')
144
+ if expected_case_ids is not None and tuple(expected_case_ids) != manifest.case_ids:
145
+ raise ContractError('ordered case identity mismatch')
146
+ if expected_requests is not None and set(expected_requests) != set(manifest.case_ids):
147
+ raise ContractError('expected request cohort mismatch')
148
+ expected_bytes = None if expected_requests is None else {
149
+ case: _request_bytes(value) for case, value in expected_requests.items()}
150
+ for number, name in ((trials, 'trials'), (attempts, 'attempts')):
151
+ if number is not None and (type(number) is not int or number < 1):
152
+ raise ContractError(name + ' must be a positive integer')
153
+ keys = {(c.case_id, c.trial_index, c.attempt_index) for c in manifest.captures}
154
+ if trials is not None:
155
+ for case in manifest.case_ids:
156
+ actual = {c.trial_index for c in manifest.captures if c.case_id == case}
157
+ if actual != set(range(1, trials + 1)):
158
+ raise ContractError('declared trial coverage mismatch')
159
+ if attempts is not None:
160
+ for case, trial in {(c.case_id, c.trial_index) for c in manifest.captures}:
161
+ actual = {a for cid, t, a in keys if cid == case and t == trial}
162
+ if actual != set(range(1, attempts + 1)):
163
+ raise ContractError('declared attempt coverage mismatch')
164
+ for capture in manifest.captures:
165
+ request = read_artifact(ref.path, capture.request)
166
+ if expected_requests is not None:
167
+ if _request_bytes(request) != expected_bytes[capture.case_id]:
168
+ raise ContractError('captured request identity differs from frozen input')
169
+ for binding in (capture.response, capture.observed):
170
+ if binding is not None: read_artifact(ref.path, binding)
171
+ return manifest
172
+
173
+
174
+ def _evaluation_binding(evaluation, run_manifest, run):
175
+ if evaluation.run_id != run.run_id or evaluation.capture_manifest_digest != run.manifest_sha256:
176
+ raise ContractError('evaluation original capture mismatch')
177
+ left, right = evaluation.identity.to_dict(), run_manifest.identity.to_dict()
178
+ # A new grading policy may regrade the original execution capture.
179
+ for key in ('metric_release', 'grader_release', 'judgment_policy_digest'):
180
+ left.pop(key); right.pop(key)
181
+ if left != right:
182
+ raise ContractError('evaluation execution identity mismatch')
183
+
184
+
185
+ def write_evaluation(run, identity, metrics, *, judgment_artifact_digest=None):
186
+ manifest = verify_track_run(run)
187
+ evaluation_id = 'eval-' + uuid4().hex
188
+ evaluation = TrackEvaluationManifest(evaluation_id, run.run_id, run.manifest_sha256,
189
+ identity, metrics, judgment_artifact_digest)
190
+ _evaluation_binding(evaluation, manifest, run)
191
+ relative = 'evaluations/' + evaluation_id + '/scorecard.json'
192
+ binding = write_artifact(run.path, relative, evaluation.to_dict())
193
+ return TrackEvaluationRef(run.path / 'evaluations' / evaluation_id, binding.sha256, run)
194
+
195
+
196
+ def open_track_evaluation(path, *, expected_scorecard_sha256, run=None):
197
+ path = Path(path)
198
+ evaluation = TrackEvaluationManifest.from_dict(_read_file(safe_path(path, 'scorecard.json'), expected_scorecard_sha256))
199
+ if run is None:
200
+ run = open_track_run(path.parent.parent, expected_manifest_sha256=evaluation.capture_manifest_digest)
201
+ ref = TrackEvaluationRef(path, expected_scorecard_sha256, run)
202
+ verify_track_evaluation(ref)
203
+ return ref
204
+
205
+
206
+ def verify_track_evaluation(ref):
207
+ manifest = verify_track_run(ref.run)
208
+ relative = 'evaluations/' + ref.evaluation_id + '/scorecard.json'
209
+ evaluation = TrackEvaluationManifest.from_dict(_read_file(safe_path(ref.run.path, relative), ref.scorecard_sha256))
210
+ if evaluation.evaluation_id != ref.evaluation_id:
211
+ raise ContractError('evaluation identity mismatch')
212
+ _evaluation_binding(evaluation, manifest, ref.run)
213
+ return evaluation