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,252 @@
|
|
|
1
|
+
"""Permitted analytical value schemas; no service, policy or credential code."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from dataclasses import dataclass,field,fields
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import math
|
|
8
|
+
import re
|
|
9
|
+
import struct
|
|
10
|
+
from types import MappingProxyType
|
|
11
|
+
from typing import Any,Callable,cast
|
|
12
|
+
from .evaluation_protocols import VersionedIdentity
|
|
13
|
+
from .workflow_contracts import WorkflowContractError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def require(ok: object,message: str='invalid analytical response') -> None:
|
|
17
|
+
if not ok:raise WorkflowContractError(message)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def identifier(value: Any,*,empty: bool=False) -> str:
|
|
21
|
+
require(type(value) is str and ((empty and value=='') or
|
|
22
|
+
re.fullmatch(r'[A-Za-z0-9._:-]{1,128}',value) is not None and value not in ('.','..')),
|
|
23
|
+
'invalid private analytical identifier')
|
|
24
|
+
return value
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def text(value: Any) -> str:
|
|
28
|
+
require(type(value) is str and bool(value.strip()) and '\0' not in value)
|
|
29
|
+
try:require(len(value.encode('utf-8'))<=1024)
|
|
30
|
+
except UnicodeError:raise WorkflowContractError('invalid analytical UTF-8 text') from None
|
|
31
|
+
return value
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def digest(value: Any) -> str:
|
|
35
|
+
require(type(value) is str and re.fullmatch(r'sha256:[0-9a-f]{64}',value) is not None)
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def count(value: Any,*,nullable: bool=True,maximum: int=2**63-1) -> int|None:
|
|
40
|
+
require(value is None and nullable or type(value) is int and 0<=value<=maximum)
|
|
41
|
+
return value
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def keys(value: Any,names: set[str]) -> dict[str,Any]:
|
|
45
|
+
require(isinstance(value,Mapping) and set(value)==names,'invalid analytical response fields')
|
|
46
|
+
return dict(value)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def identity(value: Any) -> VersionedIdentity:
|
|
50
|
+
values=keys(value,{'schema_version','identity_id','version','digest'})
|
|
51
|
+
text(values['identity_id']);text(values['version'])
|
|
52
|
+
return VersionedIdentity.from_dict(values)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def score_bits(value: Any) -> float:
|
|
56
|
+
require(type(value) is str and re.fullmatch('[0-9a-f]{16}',value) is not None)
|
|
57
|
+
number=struct.unpack('>d',bytes.fromhex(value))[0]
|
|
58
|
+
require(math.isfinite(number),'analytical score must have exact finite bits')
|
|
59
|
+
return number
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def sha(value: str) -> str:
|
|
63
|
+
return 'sha256:'+hashlib.sha256(value.encode('utf-8')).hexdigest()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def frame(profile: str,*values: object) -> str:
|
|
67
|
+
return profile+'\n'+'\n'.join(str(value) for value in values)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def parse_json(raw: Any,*,maximum: int=65536) -> Any:
|
|
71
|
+
require(type(raw) is str)
|
|
72
|
+
try:
|
|
73
|
+
require(len(raw.encode('utf-8'))<=maximum)
|
|
74
|
+
def unique(pairs):
|
|
75
|
+
result={}
|
|
76
|
+
for key,value in pairs:require(key not in result);result[key]=value
|
|
77
|
+
return result
|
|
78
|
+
return json.loads(raw,object_pairs_hook=unique,
|
|
79
|
+
parse_constant=lambda _:(_ for _ in ()).throw(ValueError()))
|
|
80
|
+
except (ValueError,TypeError,UnicodeError,RecursionError):
|
|
81
|
+
raise WorkflowContractError('invalid analytical JSON') from None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def wire(value: Any) -> Any:
|
|
85
|
+
if isinstance(value,(VersionedIdentity,_Values)):return value.to_dict()
|
|
86
|
+
if isinstance(value,Mapping):return {key:wire(item) for key,item in value.items()}
|
|
87
|
+
if isinstance(value,(tuple,list)):return [wire(item) for item in value]
|
|
88
|
+
return value
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class _Values:
|
|
92
|
+
def to_dict(self) -> dict[str,Any]:
|
|
93
|
+
return {item.name:wire(getattr(self,item.name)) for item in fields(cast(Any,self))}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True)
|
|
97
|
+
class ProjectionCoverage(_Values):
|
|
98
|
+
"""Native counts and unit, including explicit unknown values."""
|
|
99
|
+
unit: str
|
|
100
|
+
required: int|None
|
|
101
|
+
observed: int|None
|
|
102
|
+
missing: int|None
|
|
103
|
+
undefined: int|None
|
|
104
|
+
def __post_init__(self) -> None:
|
|
105
|
+
text(self.unit)
|
|
106
|
+
for name in ('required','observed','missing','undefined'):count(getattr(self,name))
|
|
107
|
+
@classmethod
|
|
108
|
+
def from_dict(cls,value: Mapping[str,Any]) -> ProjectionCoverage:
|
|
109
|
+
return cls(**keys(value,{'unit','required','observed','missing','undefined'}))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass(frozen=True)
|
|
113
|
+
class ReportProjectionSummary(_Values):
|
|
114
|
+
run_id: str
|
|
115
|
+
grading_id: str
|
|
116
|
+
scope: str
|
|
117
|
+
mode: str
|
|
118
|
+
status: str
|
|
119
|
+
trust_tier: str
|
|
120
|
+
bindings: Mapping[str,VersionedIdentity]=field(repr=False)
|
|
121
|
+
coverage: ProjectionCoverage
|
|
122
|
+
def __post_init__(self) -> None:
|
|
123
|
+
for value in (self.run_id,self.grading_id,self.status,self.trust_tier):text(value)
|
|
124
|
+
require(self.scope in ('component','subsystem','system') and self.mode in ('local','hosted'))
|
|
125
|
+
require(isinstance(self.coverage,ProjectionCoverage))
|
|
126
|
+
require(isinstance(self.bindings,Mapping) and {'candidate','protocol','bank','evaluator','runtime'}<=set(self.bindings)
|
|
127
|
+
and set(self.bindings)<={'candidate','protocol','bank','evaluator','runtime','labels','references','hardware'})
|
|
128
|
+
require(all(isinstance(value,VersionedIdentity) for value in self.bindings.values()))
|
|
129
|
+
object.__setattr__(self,'bindings',MappingProxyType(dict(self.bindings)))
|
|
130
|
+
@classmethod
|
|
131
|
+
def from_dict(cls,value: Mapping[str,Any]) -> ReportProjectionSummary:
|
|
132
|
+
values=keys(value,{'run_id','grading_id','scope','mode','status','trust_tier','bindings','coverage'})
|
|
133
|
+
require(isinstance(values['bindings'],Mapping))
|
|
134
|
+
values['bindings']={key:identity(item) for key,item in values['bindings'].items()}
|
|
135
|
+
values['coverage']=ProjectionCoverage.from_dict(values['coverage'])
|
|
136
|
+
return cls(**values)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(frozen=True)
|
|
140
|
+
class MetricProjectionSummary(_Values):
|
|
141
|
+
run_id: str
|
|
142
|
+
grading_id: str
|
|
143
|
+
metric_id: str
|
|
144
|
+
unit: str
|
|
145
|
+
direction: str
|
|
146
|
+
availability: str
|
|
147
|
+
value_kind: str
|
|
148
|
+
value_bits: str|None=field(repr=False)
|
|
149
|
+
assertion: bool|None
|
|
150
|
+
label: str|None=field(repr=False)
|
|
151
|
+
coverage: ProjectionCoverage
|
|
152
|
+
population_id: str
|
|
153
|
+
method_id: str
|
|
154
|
+
uncertainty_id: str|None
|
|
155
|
+
def __post_init__(self) -> None:
|
|
156
|
+
for name in ('run_id','grading_id','metric_id','unit','direction','population_id','method_id'):text(getattr(self,name))
|
|
157
|
+
if self.uncertainty_id is not None:text(self.uncertainty_id)
|
|
158
|
+
require(isinstance(self.coverage,ProjectionCoverage))
|
|
159
|
+
require(self.availability in ('available','missing','undefined','unavailable')
|
|
160
|
+
and self.value_kind in ('score','assertion','label','custom'))
|
|
161
|
+
available=self.availability=='available'
|
|
162
|
+
if available and self.value_kind=='score':score_bits(self.value_bits)
|
|
163
|
+
else:require(self.value_bits is None)
|
|
164
|
+
require(type(self.assertion) is bool if available and self.value_kind=='assertion' else self.assertion is None)
|
|
165
|
+
if available and self.value_kind=='label':text(self.label)
|
|
166
|
+
else:require(self.label is None)
|
|
167
|
+
@property
|
|
168
|
+
def value(self) -> float|bool|str|None:
|
|
169
|
+
"""A custom value stays in its native artifact; availability is explicit."""
|
|
170
|
+
if self.availability!='available':return None
|
|
171
|
+
if self.value_kind=='score':return score_bits(self.value_bits)
|
|
172
|
+
return self.assertion if self.value_kind=='assertion' else self.label
|
|
173
|
+
@classmethod
|
|
174
|
+
def from_dict(cls,value: Mapping[str,Any]) -> MetricProjectionSummary:
|
|
175
|
+
values=keys(value,{'run_id','grading_id','metric_id','unit','direction','availability','value_kind',
|
|
176
|
+
'value_bits','assertion','label','coverage','population_id','method_id','uncertainty_id'})
|
|
177
|
+
values['coverage']=ProjectionCoverage.from_dict(values['coverage']);return cls(**values)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass(frozen=True)
|
|
181
|
+
class ComparisonProjectionSummary(_Values):
|
|
182
|
+
baseline_grading_id: str
|
|
183
|
+
candidate_grading_id: str
|
|
184
|
+
status: str
|
|
185
|
+
reason: str|None
|
|
186
|
+
policy_id: str
|
|
187
|
+
population_id: str
|
|
188
|
+
metric_ids: tuple[str,...]
|
|
189
|
+
artifact_digest: str
|
|
190
|
+
def __post_init__(self) -> None:
|
|
191
|
+
for name in ('baseline_grading_id','candidate_grading_id','policy_id','population_id'):text(getattr(self,name))
|
|
192
|
+
require(self.status in ('supported','incomplete','incompatible','unavailable'))
|
|
193
|
+
if self.reason is not None:text(self.reason)
|
|
194
|
+
require(self.status=='supported' or self.reason is not None)
|
|
195
|
+
require(isinstance(self.metric_ids,(tuple,list)) and len(self.metric_ids)<=128)
|
|
196
|
+
for name in self.metric_ids:text(name)
|
|
197
|
+
object.__setattr__(self,'metric_ids',tuple(self.metric_ids));digest(self.artifact_digest)
|
|
198
|
+
@classmethod
|
|
199
|
+
def from_dict(cls,value: Mapping[str,Any]) -> ComparisonProjectionSummary:
|
|
200
|
+
return cls(**keys(value,{'baseline_grading_id','candidate_grading_id','status','reason','policy_id',
|
|
201
|
+
'population_id','metric_ids','artifact_digest'}))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass(frozen=True)
|
|
205
|
+
class EligibilityView(_Values):
|
|
206
|
+
"""A recorded status. This object cannot grant current service permission."""
|
|
207
|
+
eligible: bool
|
|
208
|
+
reasons: tuple[str,...]
|
|
209
|
+
def __post_init__(self) -> None:
|
|
210
|
+
require(type(self.eligible) is bool and isinstance(self.reasons,(tuple,list)))
|
|
211
|
+
for reason in self.reasons:text(reason)
|
|
212
|
+
require(self.eligible or bool(self.reasons));object.__setattr__(self,'reasons',tuple(self.reasons))
|
|
213
|
+
@classmethod
|
|
214
|
+
def from_dict(cls,value: Mapping[str,Any]) -> EligibilityView:
|
|
215
|
+
return cls(**keys(value,{'eligible','reasons'}))
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@dataclass(frozen=True)
|
|
219
|
+
class AuditProjectionSummary(_Values):
|
|
220
|
+
"""Historical review status, with no protected target or judgment content."""
|
|
221
|
+
review_status: str
|
|
222
|
+
state: str
|
|
223
|
+
policy_id: VersionedIdentity
|
|
224
|
+
eligibility: Mapping[str,EligibilityView]=field(repr=False)
|
|
225
|
+
counts: Mapping[str,int]
|
|
226
|
+
def __post_init__(self) -> None:
|
|
227
|
+
require(self.review_status in ('pending','provisional','reviewed','disputed','needs_changes')
|
|
228
|
+
and self.state in ('active','held','withdrawn') and isinstance(self.policy_id,VersionedIdentity))
|
|
229
|
+
require(isinstance(self.eligibility,Mapping) and set(self.eligibility)=={'development','ranking','acceptance'})
|
|
230
|
+
require(all(isinstance(value,EligibilityView) and (self.state=='active' or not value.eligible)
|
|
231
|
+
for value in self.eligibility.values()))
|
|
232
|
+
require(isinstance(self.counts,Mapping) and len(self.counts)<=32)
|
|
233
|
+
for name,value in self.counts.items():identifier(name);count(value,nullable=False)
|
|
234
|
+
object.__setattr__(self,'counts',MappingProxyType(dict(self.counts)))
|
|
235
|
+
object.__setattr__(self,'eligibility',MappingProxyType(dict(self.eligibility)))
|
|
236
|
+
@classmethod
|
|
237
|
+
def from_dict(cls,value: Mapping[str,Any]) -> AuditProjectionSummary:
|
|
238
|
+
values=keys(value,{'review_status','state','policy_id','eligibility','counts'})
|
|
239
|
+
values['policy_id']=identity(values['policy_id']);require(isinstance(values['eligibility'],Mapping))
|
|
240
|
+
values['eligibility']={key:EligibilityView.from_dict(item) for key,item in values['eligibility'].items()}
|
|
241
|
+
return cls(**values)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
ProjectionSummary=ReportProjectionSummary|MetricProjectionSummary|ComparisonProjectionSummary|AuditProjectionSummary
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def summary(kind: str,value: Mapping[str,Any]) -> ProjectionSummary:
|
|
248
|
+
readers: dict[str,Callable[[Mapping[str,Any]],ProjectionSummary]]={
|
|
249
|
+
'report':ReportProjectionSummary.from_dict,'metric':MetricProjectionSummary.from_dict,
|
|
250
|
+
'comparison':ComparisonProjectionSummary.from_dict,'audit':AuditProjectionSummary.from_dict}
|
|
251
|
+
require(type(kind) is str and kind in readers)
|
|
252
|
+
return readers[kind](value)
|
sphynx/_audit_client.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Lazy audit transport helpers over the existing authenticated Client."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
import json
|
|
5
|
+
from dataclasses import replace
|
|
6
|
+
from typing import Any, Literal, TYPE_CHECKING
|
|
7
|
+
from urllib.parse import quote, urlencode
|
|
8
|
+
|
|
9
|
+
from .audit_access import ReviewHistoryPage, ReviewOperationReceipt, ReviewTargetPage, TargetReviewPage
|
|
10
|
+
from .audit_contracts import BankRelease, ReviewContribution
|
|
11
|
+
from .audit_requests import (BankReleaseDeclaration, BankReleaseTransition, ReviewAdjudication,
|
|
12
|
+
_bounded, _identifier, _key, _positive_version)
|
|
13
|
+
from .client import HostedClientError
|
|
14
|
+
from .workflow_contracts import WorkflowContractError
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from .diagnostics import Diagnostic
|
|
18
|
+
|
|
19
|
+
AuditRequest = BankReleaseDeclaration | ReviewContribution | ReviewAdjudication | BankReleaseTransition
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AuditRequestError(HostedClientError):
|
|
23
|
+
"""Preserve the exact original write for explicit uncertain-outcome recovery."""
|
|
24
|
+
request: AuditRequest
|
|
25
|
+
|
|
26
|
+
def __init__(self, cause: HostedClientError, *, bank_id: str, release_id: str,
|
|
27
|
+
operation: str, request: AuditRequest):
|
|
28
|
+
self.bank_id, self.release_id, self.operation = bank_id, release_id, operation
|
|
29
|
+
self.request, self.idempotency_key = request, request.idempotency_key
|
|
30
|
+
super().__init__(cause.code, 'audit request did not return a verified receipt; retain the original request and key')
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def diagnostic(self) -> Diagnostic:
|
|
34
|
+
view = super().diagnostic
|
|
35
|
+
if self.code in {'hosted_transport_failed', 'invalid_server_response', 'store_unavailable',
|
|
36
|
+
'service_unavailable', 'hosted_request_failed'}:
|
|
37
|
+
return replace(view, summary='The audit write outcome needs to be reconciled.',
|
|
38
|
+
next_actions=('Read current status or explicitly replay the original request and key before a new judgment/version.',),
|
|
39
|
+
retryability='reconcile_before_retry')
|
|
40
|
+
return view
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _invalid():
|
|
44
|
+
return HostedClientError('invalid_server_response', 'the selected-bank audit response could not be verified')
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _path(bank, release=None):
|
|
48
|
+
path = '/v1/banks/' + quote(_identifier(bank), safe='') + '/releases'
|
|
49
|
+
if release is not None:
|
|
50
|
+
path += '/' + quote(_identifier(release), safe='')
|
|
51
|
+
return path
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _query(*, after, through_version, limit, maximum, target_cursor=False):
|
|
55
|
+
if type(limit) is not int or not 1 <= limit <= maximum:
|
|
56
|
+
raise WorkflowContractError('audit page limit is outside its supported range')
|
|
57
|
+
data: dict[str, Any] = {'limit': limit}
|
|
58
|
+
if after is not None:
|
|
59
|
+
if target_cursor:
|
|
60
|
+
data['after'] = _identifier(after)
|
|
61
|
+
elif type(after) is not int or not 0 <= after <= 2**63 - 1:
|
|
62
|
+
raise WorkflowContractError('audit history cursor must be a nonnegative signed 64-bit integer')
|
|
63
|
+
else:
|
|
64
|
+
data['after'] = after
|
|
65
|
+
if through_version is not None:
|
|
66
|
+
data['through_version'] = _positive_version(through_version)
|
|
67
|
+
if not target_cursor and after is not None and after > through_version:
|
|
68
|
+
raise WorkflowContractError('audit cursor exceeds its selected snapshot')
|
|
69
|
+
return data
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _decode(cls, value, bank, release):
|
|
73
|
+
try:
|
|
74
|
+
result = cls.from_dict(value)
|
|
75
|
+
if (result.bank_id, result.release_id) != (bank, release):
|
|
76
|
+
raise WorkflowContractError('selected audit response identity changed')
|
|
77
|
+
return result
|
|
78
|
+
except (WorkflowContractError, ValueError, TypeError, KeyError, AttributeError, RecursionError):
|
|
79
|
+
raise _invalid() from None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _page(cls, client, path, query, bank, release, *, protected=None):
|
|
83
|
+
result = _decode(cls, client._request('GET', path + '?' + urlencode(query)), bank, release)
|
|
84
|
+
if (len(result.items) > query['limit'] or
|
|
85
|
+
(query.get('through_version') is not None and result.through_version != query['through_version'])):
|
|
86
|
+
raise _invalid()
|
|
87
|
+
after = query.get('after')
|
|
88
|
+
if after is not None and any((item.target_id if isinstance(result, ReviewTargetPage) else item.version) <= after
|
|
89
|
+
for item in result.items):
|
|
90
|
+
raise _invalid()
|
|
91
|
+
if protected is not None and result.protected is not protected:
|
|
92
|
+
raise _invalid()
|
|
93
|
+
return result
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def release(client, bank, release_id) -> BankRelease:
|
|
97
|
+
return _decode(BankRelease, client._request('GET', _path(bank, release_id)), bank, release_id)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def targets(client, bank, release_id, *, after=None, through_version=None, limit=20) -> ReviewTargetPage:
|
|
101
|
+
path = _path(bank, release_id) + '/targets'
|
|
102
|
+
query = _query(after=after, through_version=through_version, limit=limit, maximum=20, target_cursor=True)
|
|
103
|
+
return _page(ReviewTargetPage, client, path, query, bank, release_id)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def history(client, bank, release_id, *, after=0, through_version=None, limit=None, protected=False) -> ReviewHistoryPage:
|
|
107
|
+
if type(protected) is not bool:
|
|
108
|
+
raise WorkflowContractError('protected history selection must be boolean')
|
|
109
|
+
maximum = 10 if protected else 100
|
|
110
|
+
path = _path(bank, release_id) + '/history'
|
|
111
|
+
query = _query(after=after, through_version=through_version, limit=maximum if limit is None else limit, maximum=maximum)
|
|
112
|
+
if protected:
|
|
113
|
+
query['protected'] = 'true'
|
|
114
|
+
return _page(ReviewHistoryPage, client, path, query, bank, release_id, protected=protected)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def details(client, bank, release_id, target_id, *, after=0, through_version=None, limit=10) -> TargetReviewPage:
|
|
118
|
+
path = _path(bank, release_id) + '/targets/' + quote(_identifier(target_id), safe='') + '/reviews'
|
|
119
|
+
query = _query(after=after, through_version=through_version, limit=limit, maximum=10)
|
|
120
|
+
result = _page(TargetReviewPage, client, path, query, bank, release_id)
|
|
121
|
+
if result.target_id != target_id:
|
|
122
|
+
raise _invalid()
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def write(client, bank: str, release_id: str | None, operation: Literal['create', 'contribute', 'adjudicate', 'transition'],
|
|
127
|
+
request: AuditRequest) -> ReviewOperationReceipt:
|
|
128
|
+
kind, suffix, event_kind, maximum = {
|
|
129
|
+
'create': (BankReleaseDeclaration, '', 'release_created', 262144),
|
|
130
|
+
'contribute': (ReviewContribution, '/contributions', 'contribution', 131072),
|
|
131
|
+
'adjudicate': (ReviewAdjudication, '/adjudications', 'adjudication', 16384),
|
|
132
|
+
'transition': (BankReleaseTransition, '/transitions', None, 16384),
|
|
133
|
+
}[operation]
|
|
134
|
+
if not isinstance(request, kind):
|
|
135
|
+
raise WorkflowContractError('audit operation needs its matching immutable request type')
|
|
136
|
+
path = _path(bank, release_id) + suffix
|
|
137
|
+
if operation == 'create':
|
|
138
|
+
assert isinstance(request, BankReleaseDeclaration)
|
|
139
|
+
release_id = request.release_id
|
|
140
|
+
assert release_id is not None
|
|
141
|
+
body = request.to_dict()
|
|
142
|
+
_key(request.idempotency_key)
|
|
143
|
+
if isinstance(request, ReviewContribution):
|
|
144
|
+
_identifier(request.target_id); _positive_version(request.expected_version)
|
|
145
|
+
if request.supersedes_contribution_id is not None:
|
|
146
|
+
_identifier(request.supersedes_contribution_id)
|
|
147
|
+
_bounded(body['judgment'], 65536)
|
|
148
|
+
_bounded(body, maximum)
|
|
149
|
+
# Existing Client serializes non-ASCII as escapes. Bound the actual request
|
|
150
|
+
# bytes as well as the decoded domain JSON, before any network work.
|
|
151
|
+
if len(json.dumps(body, separators=(',', ':'), allow_nan=False).encode()) > maximum:
|
|
152
|
+
raise WorkflowContractError('encoded audit request exceeds its route body limit')
|
|
153
|
+
try:
|
|
154
|
+
raw = client._request('POST', path, body, retry_key=request.idempotency_key)
|
|
155
|
+
try:
|
|
156
|
+
result = ReviewOperationReceipt.from_dict(raw)
|
|
157
|
+
expected_kind = request.action if isinstance(request, BankReleaseTransition) else event_kind
|
|
158
|
+
event = result.event
|
|
159
|
+
if ((event.bank_id, event.release_id, event.kind, result.operation_key) !=
|
|
160
|
+
(bank, release_id, expected_kind, request.idempotency_key)):
|
|
161
|
+
raise WorkflowContractError('audit receipt belongs to another operation')
|
|
162
|
+
if not isinstance(request, BankReleaseDeclaration) and event.version != request.expected_version + 1:
|
|
163
|
+
raise WorkflowContractError('audit receipt version differs from the original write')
|
|
164
|
+
if isinstance(request, BankReleaseDeclaration) and event.version != 1:
|
|
165
|
+
raise WorkflowContractError('new release receipt must have version one')
|
|
166
|
+
if isinstance(request, (ReviewContribution, ReviewAdjudication)) and event.details.get('target_id') != request.target_id:
|
|
167
|
+
raise WorkflowContractError('audit receipt belongs to another target')
|
|
168
|
+
return result
|
|
169
|
+
except (WorkflowContractError, ValueError, TypeError, KeyError, AttributeError, RecursionError):
|
|
170
|
+
raise _invalid() from None
|
|
171
|
+
except HostedClientError as error:
|
|
172
|
+
raise AuditRequestError(error, bank_id=bank, release_id=release_id,
|
|
173
|
+
operation=operation, request=request) from None
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Explicit projections of already verified permitted native comparisons.
|
|
2
|
+
|
|
3
|
+
These functions neither authenticate nor compare evidence. Their backend caller
|
|
4
|
+
must retain native verification and compatibility gates before projection.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .comparison_views import ComparisonMetric, ComparisonPair, ComparisonView
|
|
9
|
+
from .workflow_contracts import _thaw
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _sources(backend, baseline, candidate, *, conditions=None):
|
|
13
|
+
sources = []
|
|
14
|
+
for index, ref in enumerate((baseline, candidate)):
|
|
15
|
+
binding = backend.reference(ref, "grading")
|
|
16
|
+
source = {"grading_id": binding.record_id, "run_id": binding.run_id,
|
|
17
|
+
"status": binding.status, "trust_tier": binding.trust_tier,
|
|
18
|
+
"metadata": _thaw(binding.metadata)}
|
|
19
|
+
if conditions is not None:
|
|
20
|
+
source["conditions"] = conditions[index]
|
|
21
|
+
sources.append(source)
|
|
22
|
+
return {"baseline": sources[0], "candidate": sources[1], "backend": backend.descriptor.to_dict()}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _view(sources, native, metrics=(), *, reason=None, status=None):
|
|
26
|
+
if status is None:
|
|
27
|
+
status = "incomplete" if any(item.status == "incomplete" for item in metrics) else "supported"
|
|
28
|
+
return ComparisonView(**sources, status=status, native=native, metrics=tuple(metrics), reason=reason)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _rerank_view(sources, native, specs):
|
|
32
|
+
if not native["compatible"]:
|
|
33
|
+
return _view(sources, native, status="incompatible", reason=native["reason"])
|
|
34
|
+
metrics = []
|
|
35
|
+
for identifier, data in native["metrics"].items():
|
|
36
|
+
spec = specs[identifier]
|
|
37
|
+
pairs = []
|
|
38
|
+
for row in data["per_case"]:
|
|
39
|
+
available = row["delta_b_minus_a"] is not None
|
|
40
|
+
pairs.append(ComparisonPair({"case_id": row["case_id"]}, row["value_a"], row["value_b"],
|
|
41
|
+
row["delta_b_minus_a"], "available" if available else
|
|
42
|
+
"undefined" if row["reason"] == "unsupported_query" else "missing", row["reason"]))
|
|
43
|
+
status = "supported" if data["complete"] else "incomplete" if data["missing_pair_count"] else "unavailable"
|
|
44
|
+
metrics.append(ComparisonMetric(identifier, data["unit"], data["direction"], "candidate_minus_baseline",
|
|
45
|
+
status, _thaw(spec.population),
|
|
46
|
+
{"policy_id": spec.comparison_policy, "aggregation": "equal_case_mean_of_paired_differences",
|
|
47
|
+
"sampling_unit": "case_id", "uncertainty": "not_estimated"},
|
|
48
|
+
delta=data["mean_delta"], observed_delta=data["observed_mean_delta"],
|
|
49
|
+
counts={key: data[key] for key in ("eligible_count", "paired_count", "missing_pair_count", "undefined_count")},
|
|
50
|
+
pairs=tuple(pairs), reason="no_eligible_observed_pairs" if status == "unavailable" else None))
|
|
51
|
+
return _view(sources, native, metrics)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _integrated_view(sources, native, specs):
|
|
55
|
+
metrics = []
|
|
56
|
+
for identifier, data in native["metrics"].items():
|
|
57
|
+
spec = specs[identifier]
|
|
58
|
+
pairs = tuple(ComparisonPair({"case_id": row["case_id"], "trial": row["trial"]},
|
|
59
|
+
row["baseline"], row["candidate"], row["delta"], row["availability"],
|
|
60
|
+
None if row["availability"] == "available" else
|
|
61
|
+
"baseline=" + str(row["baseline_reason"]) + "; candidate=" + str(row["candidate_reason"]),
|
|
62
|
+
{key: row[key] for key in ("baseline_availability", "candidate_availability", "baseline_reason", "candidate_reason")})
|
|
63
|
+
for row in native["pairs"] if row["criterion_id"] == identifier)
|
|
64
|
+
status = "incomplete" if data["excluded_pairs"] else "supported"
|
|
65
|
+
metrics.append(ComparisonMetric(identifier, spec.unit, spec.direction, "candidate_minus_baseline", status,
|
|
66
|
+
data["population"], {"policy_id": native["policy_id"],
|
|
67
|
+
"aggregation": "mean_available_trial_deltas_within_case_then_equal_case_mean",
|
|
68
|
+
"grouping_keys": ["case_id"], "paired_unit": ["case_id", "trial"],
|
|
69
|
+
"trials_independent": False, "uncertainty": "not_estimated"},
|
|
70
|
+
delta=data["candidate_minus_baseline"] if status == "supported" else None,
|
|
71
|
+
observed_delta=data["candidate_minus_baseline"],
|
|
72
|
+
counts={key: data[key] for key in ("planned_pairs", "eligible_pairs", "excluded_pairs", "eligible_cases",
|
|
73
|
+
"baseline_ungraded_trials", "candidate_ungraded_trials")}, pairs=pairs))
|
|
74
|
+
return _view(sources, native, metrics, reason=native["reason"])
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _pipeline_view(sources, native, specs, reports):
|
|
78
|
+
metrics = []
|
|
79
|
+
for stage, values in native["delta"].items():
|
|
80
|
+
for name, delta in values.items():
|
|
81
|
+
identifier = "sphynx.pipeline." + stage + "." + name
|
|
82
|
+
spec = specs[identifier]
|
|
83
|
+
accounting = [report["metrics"]["stage_metric_accounting"][stage][name] for report in reports]
|
|
84
|
+
missing = any(item["missing_count"] for item in accounting)
|
|
85
|
+
status = "incomplete" if missing else "unavailable" if delta is None else "supported"
|
|
86
|
+
metrics.append(ComparisonMetric(identifier, spec.unit, spec.direction,
|
|
87
|
+
"candidate_minus_baseline", status,
|
|
88
|
+
{**_thaw(spec.population), "boundary": "native_stage_retained_inherited_pool",
|
|
89
|
+
"eligibility": "native_side_specific_metric_eligibility"},
|
|
90
|
+
{"policy_id": spec.comparison_policy, "aggregation": "difference_of_native_stage_aggregates",
|
|
91
|
+
"pair_details": "not_exposed", "uncertainty": "not_estimated",
|
|
92
|
+
"timing_comparable": native["timing_comparable"]},
|
|
93
|
+
delta=delta if status == "supported" else None, observed_delta=delta,
|
|
94
|
+
counts={side + "_" + key: value for side, item in zip(("baseline", "candidate"), accounting)
|
|
95
|
+
for key, value in item.items()},
|
|
96
|
+
reason="native_aggregate_unavailable" if status == "unavailable" else None))
|
|
97
|
+
return _view(sources, native, metrics)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _score_reliability_view(sources, native):
|
|
101
|
+
"""Retain native cluster-bootstrap meaning; callers verify source bindings."""
|
|
102
|
+
partial = native["conditional_on_judged_subset"]
|
|
103
|
+
value = native["mean_improvement"]
|
|
104
|
+
status = "unavailable" if value is None else "incomplete" if partial else "supported"
|
|
105
|
+
metric = ComparisonMetric("brier_score", "squared_probability_error", "lower",
|
|
106
|
+
native["direction"], status,
|
|
107
|
+
{"reference_digest": native["reference_digest"], "label_policy": native["label_policy"],
|
|
108
|
+
"conditional_on_judged_subset": partial},
|
|
109
|
+
{"aggregation": native["aggregation"], "paired_unit": "query_id", "grouping_keys": ["cluster_id"],
|
|
110
|
+
"trials_independent": False}, delta=value if status == "supported" else None,
|
|
111
|
+
observed_delta=value, uncertainty=native["bootstrap"],
|
|
112
|
+
reason="no_observed_pairs" if status == "unavailable" else None)
|
|
113
|
+
return _view(sources, native, (metric,))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Controlled router/rewriter/generator diagnostic over an explicit context."""
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from .contracts import ContractError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def compose_full_rag(request,invoke):
|
|
7
|
+
"""Call registered components; this boundary does not execute retrieval."""
|
|
8
|
+
stages={"router":None,"rewriter":None,"generator":None}
|
|
9
|
+
def call(kind,operation,payload):
|
|
10
|
+
result=invoke(kind,operation,deepcopy(payload))
|
|
11
|
+
if type(result) is not dict:raise ContractError("component output must be an object")
|
|
12
|
+
stages[kind]=deepcopy(result);return result
|
|
13
|
+
routing=call("router","route",{"task_id":request["case_id"],"query":request["query"],"conversation_context":request["conversation_context"]})
|
|
14
|
+
should_call=routing.get("should_call_rag")
|
|
15
|
+
if type(should_call) is not bool:raise ContractError("router must declare a boolean action")
|
|
16
|
+
rewritten=None;answer=None;citations=[];contexts=[];abstained=True
|
|
17
|
+
if should_call:
|
|
18
|
+
rewrite=call("rewriter","rewrite",{"task_id":request["case_id"],"query":request["query"]})
|
|
19
|
+
rewritten=rewrite.get("rewritten_query")
|
|
20
|
+
if type(rewritten) is not str or not rewritten.strip():raise ContractError("rewriter query is incomplete")
|
|
21
|
+
by_id={row["candidate_id"]:row for row in request["candidates"]}
|
|
22
|
+
contexts=list(request["context_candidate_ids"])
|
|
23
|
+
output=call("generator","generate",{"task_id":request["case_id"],"query":rewritten,
|
|
24
|
+
"contexts":[by_id[cid] for cid in contexts]})
|
|
25
|
+
answer=output.get("answer");abstained=output.get("abstained",False);citations=output.get("citations",[])
|
|
26
|
+
if type(answer) is not str or type(abstained) is not bool:raise ContractError("generator answer/action differs")
|
|
27
|
+
trace={"schema_version":"sable.rag.level3-trace.v1","task_id":request["case_id"],
|
|
28
|
+
"actual_rag_call_count":int(should_call),"call_outcome":"success" if should_call else "no_call",
|
|
29
|
+
"error":None,"as_uttered_query":request["query"],"rewritten_query":rewritten,
|
|
30
|
+
"retrieved_candidate_ids":contexts,"retrieved_context_ids":contexts,"handed_context_ids":contexts,
|
|
31
|
+
"answer":answer,"citations":citations,"abstained":abstained,"deferred":False,
|
|
32
|
+
"token_counts":{"prompt":0,"completion":0},"latency_ms":{}}
|
|
33
|
+
return {"schema_version":"sphynx.rag.full_rag.response.v1","case_id":request["case_id"],
|
|
34
|
+
"input_digest":request["input_digest"],"trace":trace,"stages":stages}
|