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
sphynx/learned_rerank.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""Frozen learned-ranker and score-mapping inference, with no fitting API.
|
|
2
|
+
|
|
3
|
+
T2/T7/T14/T17/T18 need an external verified ranker/calibrator. T19--T22
|
|
4
|
+
consume frozen source artifacts. Raw probabilities remain distinct from rank.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import math
|
|
10
|
+
from collections.abc import Mapping
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from types import MappingProxyType
|
|
13
|
+
|
|
14
|
+
from .contracts import ContractError
|
|
15
|
+
from .integration_contracts import _wire, value_digest
|
|
16
|
+
from .rerank_family_contracts import FrozenFittedState, SOURCE_PINS, family_configuration, finite, validate_signals
|
|
17
|
+
from .statistical_rerank import mean, percentile, deviation, sigmoid
|
|
18
|
+
|
|
19
|
+
FEATURE_NAMES = ('minilm_logit','dense_score','negative_dense_rank','title_query_match','section_query_match',
|
|
20
|
+
'passage_log_length','selected_window_position','source_authority','duplicate_cluster_size','retrieval_signal_disagreement')
|
|
21
|
+
GATE_FEATURE_NAMES = ('a0_t19_rank_disagreement','a0_top_margin','a0_score_mad','missing_metadata_fraction','t19_ood_distance')
|
|
22
|
+
FULL_CALIBRATION_FEATURES=('t2_score','negative_rank_fraction','gap_from_top','gap_to_previous','gap_to_next','query_score_standard_deviation')
|
|
23
|
+
BOUNDED_CALIBRATION_FEATURES=('bounded_t2_score','bounded_robust_gap_to_next')
|
|
24
|
+
METHODS = ('T2','T7','T14','T17','T18','T19','T20','T21','T22')
|
|
25
|
+
METHOD_REGISTRY = MappingProxyType({name: MappingProxyType({'implementation':'available',
|
|
26
|
+
'fitted_state_required':True,'fitting_api':False,'historical_cohort_available':False,
|
|
27
|
+
'ranking_source':'T2' if name in ('T7','T14','T17','T18') else name,
|
|
28
|
+
'historical_state_available':name in ('T19','T20','T21','T22')}) for name in METHODS})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _linear_model(model, *, names=FEATURE_NAMES, intercept=False):
|
|
32
|
+
if not isinstance(model,Mapping):raise ContractError('frozen linear model is missing')
|
|
33
|
+
features=model.get('feature_names')
|
|
34
|
+
if not isinstance(features,(list,tuple)) or not features or len(set(features))!=len(features) or not set(features)<=set(names):
|
|
35
|
+
raise ContractError('linear model feature order is unknown or duplicate')
|
|
36
|
+
for field in ('means','standard_deviations','coefficients'):
|
|
37
|
+
values=model.get(field)
|
|
38
|
+
if not isinstance(values,(list,tuple)) or len(values)!=len(features):raise ContractError('linear model arrays differ from feature order')
|
|
39
|
+
for value in values:finite(value,field)
|
|
40
|
+
if any(v<=0 for v in model['standard_deviations']):raise ContractError('frozen standard deviations must be positive')
|
|
41
|
+
if intercept:finite(model.get('intercept'),'intercept')
|
|
42
|
+
return model
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _linear(model, features, *, intercept=False):
|
|
46
|
+
return (model['intercept'] if intercept else 0.)+sum(
|
|
47
|
+
((features[name] if features[name] is not None else 0.)-model['means'][i])/model['standard_deviations'][i]*model['coefficients'][i]
|
|
48
|
+
for i,name in enumerate(model['feature_names']))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _features(rows):
|
|
52
|
+
result=[]
|
|
53
|
+
for row in rows:
|
|
54
|
+
values=row.get('feature_values')
|
|
55
|
+
if not isinstance(values,Mapping) or set(values)!=set(FEATURE_NAMES):
|
|
56
|
+
raise ContractError('learned ranking requires exact declared label-free feature_values; missing metadata must be explicit null')
|
|
57
|
+
values=dict(values)
|
|
58
|
+
for key,value in values.items():
|
|
59
|
+
if value is not None:values[key]=finite(value,key)
|
|
60
|
+
# The source signals are authoritative for the three direct features.
|
|
61
|
+
for key,value in [('dense_score',row['dense_score']),('minilm_logit',row['minilm_logit']),('negative_dense_rank',-row['dense_rank'])]:
|
|
62
|
+
if values[key] != value:raise ContractError('learned direct feature differs from its frozen signal')
|
|
63
|
+
result.append(values)
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _a0(rows):
|
|
68
|
+
dense=_nextgen_zscores([r['dense_score'] for r in rows]);mini=_nextgen_zscores([r['minilm_logit'] for r in rows])
|
|
69
|
+
return [.45*x+.55*y for x,y in zip(dense,mini)]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _nextgen_zscores(values):
|
|
73
|
+
# This source convention differs from Cherry's scale=1 fallback: almost
|
|
74
|
+
# constant signals are exactly neutral in the next-generation family.
|
|
75
|
+
center=mean(values);std=deviation(values)
|
|
76
|
+
return [0.]*len(values) if std<=1e-12 else [(value-center)/std for value in values]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def rank_geometry(scores, rows, *, bounded=False):
|
|
80
|
+
order=sorted(range(len(rows)),key=lambda i:(-scores[i],rows[i]['dense_rank']))
|
|
81
|
+
ordered=[scores[i] for i in order];center=percentile(ordered);mad=percentile([abs(x-center) for x in ordered]);std=deviation(ordered)
|
|
82
|
+
scale=mad if mad>1e-9 else std if std>1e-9 else 1.
|
|
83
|
+
result=[None]*len(rows);ranks=[0]*len(rows)
|
|
84
|
+
for index,position in enumerate(order):
|
|
85
|
+
current=ordered[index];previous=current if index==0 else ordered[index-1];nxt=current if index==len(order)-1 else ordered[index+1]
|
|
86
|
+
result[position]=[math.tanh(current/2.),math.tanh((current-nxt)/(scale*2.))] if bounded else [current,0. if len(order)<=1 else -index/(len(order)-1),ordered[0]-current,previous-current,current-nxt,std]
|
|
87
|
+
ranks[position]=index+1
|
|
88
|
+
return result,ranks
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def logistic_probabilities(calibrator, features):
|
|
92
|
+
if not isinstance(calibrator,Mapping):raise ContractError('frozen calibrator is missing')
|
|
93
|
+
names=calibrator.get('feature_names')
|
|
94
|
+
if not isinstance(names,(list,tuple)) or not names:raise ContractError('calibrator feature order is missing')
|
|
95
|
+
expected=FULL_CALIBRATION_FEATURES if features and len(features[0])==6 else BOUNDED_CALIBRATION_FEATURES
|
|
96
|
+
if tuple(names)!=expected:raise ContractError('calibrator feature order differs from source geometry')
|
|
97
|
+
_linear_model(calibrator,names=names,intercept=True)
|
|
98
|
+
if any(len(row)!=len(names) for row in features):raise ContractError('calibrator geometry shape differs')
|
|
99
|
+
lamb=calibrator.get('adaptive_tempering_lambda')
|
|
100
|
+
if lamb is not None and finite(lamb,'tempering lambda')<0:raise ContractError('negative tempering lambda')
|
|
101
|
+
output=[]
|
|
102
|
+
for row in features:
|
|
103
|
+
centered=sum((value-calibrator['means'][i])/calibrator['standard_deviations'][i]*calibrator['coefficients'][i] for i,value in enumerate(row))
|
|
104
|
+
temperature=1. if lamb is None else 1+lamb*(1-max(0.,min(1.,row[1])))
|
|
105
|
+
output.append(sigmoid(calibrator['intercept']+centered/temperature))
|
|
106
|
+
return output
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _source_calibrator(calibrator, method):
|
|
110
|
+
variants={'T7':'T7_full','T14':'T14_bounded_two_feature','T17':'T17_margin_adaptive_tempering'}
|
|
111
|
+
if not isinstance(calibrator,Mapping) or calibrator.get('method')!='rank_aware_logistic' or calibrator.get('variant')!=variants[method]:
|
|
112
|
+
raise ContractError('calibrator method/variant differs from the selected source treatment')
|
|
113
|
+
if ('adaptive_tempering_lambda' not in calibrator or
|
|
114
|
+
(method=='T17') != (calibrator['adaptive_tempering_lambda'] is not None)):
|
|
115
|
+
raise ContractError('calibrator tempering policy differs from the selected source treatment')
|
|
116
|
+
return calibrator
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def robust_query_relative(scores, config):
|
|
120
|
+
fields={'robust_scale_epsilon','relative_score_temperature','constant_score_fallback'}
|
|
121
|
+
if not isinstance(config,Mapping) or set(config)!=fields:raise ContractError('query-relative policy differs from exact schema')
|
|
122
|
+
epsilon=finite(config['robust_scale_epsilon']);temperature=finite(config['relative_score_temperature'])
|
|
123
|
+
if epsilon<0 or temperature<=0 or config['constant_score_fallback'] not in ('normalized_rank','neutral'):raise ContractError('invalid query-relative policy')
|
|
124
|
+
center=percentile(scores);mad=percentile([abs(x-center) for x in scores]);std=deviation(scores)
|
|
125
|
+
if mad>epsilon:scale=mad;fallback='mad'
|
|
126
|
+
elif std>epsilon:scale=std;fallback='standard_deviation'
|
|
127
|
+
else:
|
|
128
|
+
values=[0. if len(scores)<=1 else 1-2*i/(len(scores)-1) for i in range(len(scores))] if config['constant_score_fallback']=='normalized_rank' else [0.]*len(scores)
|
|
129
|
+
return {'values':values,'center':center,'scale':0.,'fallback':config['constant_score_fallback']}
|
|
130
|
+
return {'values':[math.tanh((x-center)/scale/temperature) for x in scores],'center':center,'scale':scale,'fallback':fallback}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _isotonic(observations,target):
|
|
134
|
+
grouped={}
|
|
135
|
+
for score,label in observations:
|
|
136
|
+
weight,positives=grouped.get(score,(0,0));grouped[score]=(weight+1,positives+label)
|
|
137
|
+
blocks=[[score,score,*grouped[score]] for score in sorted(grouped)];index=0
|
|
138
|
+
while index<len(blocks)-1:
|
|
139
|
+
left,right=blocks[index],blocks[index+1]
|
|
140
|
+
if left[3]/left[2]<=right[3]/right[2]+1e-15:index+=1;continue
|
|
141
|
+
blocks[index:index+2]=[[left[0],right[1],left[2]+right[2],left[3]+right[3]]];index=max(0,index-1)
|
|
142
|
+
block=next((b for b in blocks if b[0]<=target<=b[1]),blocks[0] if target<blocks[0][0] else blocks[-1])
|
|
143
|
+
return block[3]/block[2]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def predict_venn_abers(model,score):
|
|
147
|
+
"""Original inductive Venn-Abers prediction; stored calibration only."""
|
|
148
|
+
if not isinstance(model,Mapping) or model.get('method')!='inductive_venn_abers' or model.get('label_mode') not in ('fully_labeled','known_judgments_only','positive_unlabeled_diagnostic'):
|
|
149
|
+
raise ContractError('unknown frozen Venn-Abers model/label mode')
|
|
150
|
+
observations=model.get('observations')
|
|
151
|
+
if not isinstance(observations,(list,tuple)) or not 1<=len(observations)<=10000:raise ContractError('bounded calibration observations required')
|
|
152
|
+
base=[]
|
|
153
|
+
for row in observations:
|
|
154
|
+
if not isinstance(row,Mapping) or type(row.get('label')) is not int or row['label'] not in (0,1):raise ContractError('invalid calibration observation')
|
|
155
|
+
base.append((finite(row.get('score')),row['label']))
|
|
156
|
+
score=finite(score);p0=_isotonic([*base,(score,0)],score);p1=_isotonic([*base,(score,1)],score);lower=max(0.,min(1.,min(p0,p1)));upper=max(lower,min(1.,max(p0,p1)));denominator=1-lower+upper
|
|
157
|
+
return {'p0':lower,'p1':upper,'probability':(lower+upper)/2 if denominator<=1e-12 else upper/denominator,'interval_width':upper-lower}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def gate_features(rows,features,ranker,a0,t19):
|
|
161
|
+
pairs=[float((a0[i]>a0[j])-(a0[i]<a0[j]) != (t19[i]>t19[j])-(t19[i]<t19[j])) for i in range(len(rows)) for j in range(i+1,len(rows))]
|
|
162
|
+
distances=[math.sqrt(mean([((f[name] or 0.)-ranker['means'][i])**2/ranker['standard_deviations'][i]**2 for i,name in enumerate(ranker['feature_names'])])) for f in features]
|
|
163
|
+
metadata=('title_query_match','section_query_match','selected_window_position','source_authority','duplicate_cluster_size');ordered=sorted(a0,reverse=True);median=percentile(a0)
|
|
164
|
+
return {'a0_t19_rank_disagreement':mean(pairs),'a0_top_margin':ordered[0]-(ordered[1] if len(ordered)>1 else ordered[0]),'a0_score_mad':percentile([abs(x-median) for x in a0]),'missing_metadata_fraction':sum(f[key] is None for f in features for key in metadata)/(len(rows)*len(metadata)),'t19_ood_distance':mean(distances)}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def decide_gate(model,features,config):
|
|
168
|
+
if not isinstance(model,Mapping) or model.get('method')!='bootstrap_regularized_logistic_gate':raise ContractError('frozen gate is missing')
|
|
169
|
+
fields={'minimum_improvement_probability','uncertainty_standard_deviations','maximum_ood_distance'}
|
|
170
|
+
if not isinstance(config,Mapping) or set(config)!=fields:raise ContractError('gate policy fields differ')
|
|
171
|
+
threshold=finite(config['minimum_improvement_probability']);uncertainty_scale=finite(config['uncertainty_standard_deviations']);ood=finite(config['maximum_ood_distance'])
|
|
172
|
+
if not 0<=threshold<=1 or uncertainty_scale<0 or ood<0:raise ContractError('gate policy bounds invalid')
|
|
173
|
+
members=model.get('members')
|
|
174
|
+
if not isinstance(members,(list,tuple)) or not 1<=len(members)<=500:raise ContractError('bounded frozen gate ensemble is missing')
|
|
175
|
+
probabilities=[]
|
|
176
|
+
for member in members:_linear_model(member,names=GATE_FEATURE_NAMES,intercept=True);probabilities.append(sigmoid(_linear(member,features,intercept=True)))
|
|
177
|
+
estimate=mean(probabilities);uncertainty=deviation(probabilities);lower=estimate-uncertainty_scale*uncertainty;reasons=[]
|
|
178
|
+
if lower<threshold:reasons.append('uncertain')
|
|
179
|
+
if features['t19_ood_distance']>ood:reasons.append('out_of_distribution')
|
|
180
|
+
return {'estimated_improvement_probability':estimate,'uncertainty_standard_deviation':uncertainty,'lower_confidence_score':lower,'gate_weight':0. if reasons else estimate,'fallback_to_a0':bool(reasons),'fallback_reasons':reasons}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def score_learned_signals(signals,state,*,evaluation_cohort_digest,domain_id):
|
|
184
|
+
if not isinstance(state,FrozenFittedState) or state.method not in METHODS:raise ContractError('a verified frozen fitted state is required')
|
|
185
|
+
state.ensure_evaluation_cohort(evaluation_cohort_digest)
|
|
186
|
+
source=SOURCE_PINS['minilm-treatments.ts' if state.method in ('T2','T7','T14','T17','T18') else 'nextgen-rerankers.ts']
|
|
187
|
+
if state.provenance['source_digest']!=source:raise ContractError('fitted source implementation differs from the admitted family')
|
|
188
|
+
if not isinstance(domain_id,str) or not domain_id:raise ContractError('explicit domain identity required')
|
|
189
|
+
rows=validate_signals(signals,('minilm_logit',));features=_features(rows);payload=state.payload;method=state.method
|
|
190
|
+
expected_fields={'T2':{'ranker'},'T7':{'ranker','calibrator'},'T14':{'ranker','calibrator'},'T17':{'ranker','calibrator'},'T18':{'ranker','calibrator'},'T19':{'ranker'},'T20':{'ranker','calibrator','relative_policy'},'T21':{'ranker','gate','gate_policy','relative_policy','calibrator'},'T22':{'ranker','calibrator','domain_policy'}}
|
|
191
|
+
if set(payload)!=expected_fields[method]:raise ContractError('frozen learned payload differs from the method schema')
|
|
192
|
+
ranker=_linear_model(payload.get('ranker'));base=_a0(rows);learned=[_linear(ranker,f) for f in features]
|
|
193
|
+
if method in ('T19','T20','T21','T22') and ranker.get('method')!='a0_anchored_pairwise_residual':raise ContractError('T19 source residual method differs')
|
|
194
|
+
utility=learned if method in ('T2','T7','T14','T17','T18') else [x+y for x,y in zip(base,learned)]
|
|
195
|
+
diagnostics={'fitted_state_digest':state.artifact_digest,'lineage_authority':'caller_declared_unverified','refitting_performed':False};probabilities=None;intervals=None
|
|
196
|
+
if method in ('T7','T14','T17','T18'):
|
|
197
|
+
full,ranks=rank_geometry(utility,rows);bounded,_=rank_geometry(utility,rows,bounded=True)
|
|
198
|
+
if method=='T18':
|
|
199
|
+
blend=payload.get('calibrator')
|
|
200
|
+
if (not isinstance(blend,Mapping) or blend.get('target_ranks') not in ([4,5],(4,5))
|
|
201
|
+
or blend.get('method')!='rank_4_to_5_probability_blend'
|
|
202
|
+
or blend.get('variant')!='T18_T14_T7_boundary_blend'
|
|
203
|
+
or blend.get('base_treatment')!='T14' or blend.get('boundary_treatment')!='T7'):
|
|
204
|
+
raise ContractError('T18 source policy only changes ranks four and five')
|
|
205
|
+
alpha=finite(blend.get('alpha'))
|
|
206
|
+
if not 0<=alpha<=1:raise ContractError('T18 alpha outside [0,1]')
|
|
207
|
+
t14=logistic_probabilities(_source_calibrator(blend.get('base_calibrator'),'T14'),bounded);t7=logistic_probabilities(_source_calibrator(blend.get('boundary_calibrator'),'T7'),full);probabilities=[(1-alpha)*x+alpha*y if 4<=rank<=5 else x for x,y,rank in zip(t14,t7,ranks)]
|
|
208
|
+
else:probabilities=logistic_probabilities(_source_calibrator(payload.get('calibrator'),method),full if method=='T7' else bounded)
|
|
209
|
+
elif method=='T20':
|
|
210
|
+
relative=robust_query_relative(utility,payload.get('relative_policy'));diagnostics['relative_fallback']=relative['fallback'];intervals=[predict_venn_abers(payload.get('calibrator'),x) for x in relative['values']];probabilities=[x['probability'] for x in intervals]
|
|
211
|
+
elif method=='T21':
|
|
212
|
+
gate=decide_gate(payload.get('gate'),gate_features(rows,features,ranker,base,utility),payload.get('gate_policy'));diagnostics['gate']=gate;weight=gate['gate_weight'];a,b=_nextgen_zscores(base),_nextgen_zscores(utility);utility=[(1-weight)*x+weight*y for x,y in zip(a,b)]
|
|
213
|
+
relative=robust_query_relative(utility,payload.get('relative_policy'));intervals=[predict_venn_abers(payload.get('calibrator'),x) for x in relative['values']];probabilities=[x['probability'] for x in intervals]
|
|
214
|
+
elif method=='T22':
|
|
215
|
+
policy=payload.get('domain_policy')
|
|
216
|
+
if (not isinstance(policy,Mapping) or set(policy)!={'in_distribution_domains','ranking_policy'}
|
|
217
|
+
or policy['ranking_policy']!='t19_in_distribution_a0_otherwise'
|
|
218
|
+
or not isinstance(policy['in_distribution_domains'],(list,tuple)) or not policy['in_distribution_domains']
|
|
219
|
+
or any(not isinstance(x,str) or x not in ('sable','scifact','nfcorpus') for x in policy['in_distribution_domains'])
|
|
220
|
+
or len(set(policy['in_distribution_domains']))!=len(policy['in_distribution_domains'])
|
|
221
|
+
or domain_id not in ('sable','scifact','nfcorpus')):raise ContractError('unknown T22 domain policy')
|
|
222
|
+
intervals=[predict_venn_abers(payload.get('calibrator'),x) for x in utility];probabilities=[x['probability'] for x in intervals];use_learned=domain_id in policy['in_distribution_domains'];diagnostics['ranking_path']='T19' if use_learned else 'A0';utility=utility if use_learned else base
|
|
223
|
+
fields={'ranking_utility':{'kind':'utility','values':utility},'raw_minilm_logit':{'kind':'utility','values':[r['minilm_logit'] for r in rows]}}
|
|
224
|
+
if probabilities is not None:
|
|
225
|
+
if method in ('T20','T21','T22') and payload['calibrator'].get('label_mode')!=state.provenance['label_mode']:raise ContractError('calibrator label mode differs from frozen provenance')
|
|
226
|
+
kind='diagnostic' if state.provenance['label_mode']=='positive_unlabeled_diagnostic' else 'probability';fields['evidence_probability']={'kind':kind,'values':probabilities,'probability_target':'frozen_strong_relevance' if kind=='probability' else None}
|
|
227
|
+
if intervals is not None:
|
|
228
|
+
for key in ('p0','p1','interval_width'):fields['venn_abers_'+key]={'kind':'diagnostic','values':[row[key] for row in intervals]}
|
|
229
|
+
if any(not math.isfinite(v) for field in fields.values() for v in field['values']):raise ContractError('derived learned score is not finite')
|
|
230
|
+
return {'schema_version':'sphynx.rerank-family.scores.v1','method':method,'method_version':'v1','candidate_ids':[r['candidate_id'] for r in rows],'fields':fields,'diagnostics':diagnostics,'reference_sources':dict(SOURCE_PINS),'inference_observed':False}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class LearnedScorer:
|
|
234
|
+
"""A frozen model-logit and metadata composition behind the legacy protocol."""
|
|
235
|
+
def __init__(self,state,*,minilm_scorer,feature_provider,evaluation_cohort_digest,domain_id):
|
|
236
|
+
if not isinstance(state,FrozenFittedState):raise ContractError('frozen fitted state required')
|
|
237
|
+
state.ensure_evaluation_cohort(evaluation_cohort_digest)
|
|
238
|
+
self.state,self.minilm_scorer,self.feature_provider=state,minilm_scorer,feature_provider
|
|
239
|
+
self.evaluation_cohort_digest,self.domain_id=evaluation_cohort_digest,domain_id
|
|
240
|
+
self._configuration=family_configuration(state.method,LEARNED_IMPLEMENTATION_DIGEST,
|
|
241
|
+
parameters={'evaluation_cohort_digest':evaluation_cohort_digest,'domain_id':domain_id},
|
|
242
|
+
scorers={'minilm':minilm_scorer,'features':feature_provider},fitted_state=state)
|
|
243
|
+
@property
|
|
244
|
+
def configuration(self):return self._configuration
|
|
245
|
+
def score_fields(self,request):
|
|
246
|
+
from .model_adapters import request_pairs
|
|
247
|
+
request_pairs(request)
|
|
248
|
+
frozen=self.configuration.to_dict()['parameters'];expected=frozen['source_scorers']
|
|
249
|
+
if (self.state.method!=frozen['method'] or self.state.artifact_digest!=frozen['fitted_state']['artifact_digest']
|
|
250
|
+
or _wire(self.state.provenance)!=frozen['fitted_state']['provenance']
|
|
251
|
+
or self.evaluation_cohort_digest!=frozen['parameters']['evaluation_cohort_digest'] or self.domain_id!=frozen['parameters']['domain_id']):raise ContractError('frozen learned configuration changed')
|
|
252
|
+
if self.minilm_scorer.configuration.to_dict()!=expected['minilm'] or self.feature_provider.configuration.to_dict()!=expected['features']:raise ContractError('frozen source/feature provider configuration changed')
|
|
253
|
+
response=self.minilm_scorer(request);supplied=self.feature_provider(request)
|
|
254
|
+
if response.get('schema_version')!='sphynx.rag.rerank.response.v1' or response.get('query_id')!=request['query_id'] or response.get('pool_digest')!=request['pool_digest']:raise ContractError('model scorer request identity differs')
|
|
255
|
+
scores=response.get('scores');values={r.get('candidate_id'):r.get('ranking_score') for r in scores} if isinstance(scores,list) and all(isinstance(r,dict) for r in scores) else {}
|
|
256
|
+
ids={r['candidate_id'] for r in request['candidates']}
|
|
257
|
+
if not isinstance(supplied,Mapping) or set(supplied)!=ids or set(values)!=ids or len(scores)!=len(ids):raise ContractError('model/feature provider membership differs')
|
|
258
|
+
rows=[{'candidate_id':r['candidate_id'],'dense_score':r['dense_score'],'dense_rank':r['dense_rank'],'minilm_logit':values[r['candidate_id']],'feature_values':supplied[r['candidate_id']]} for r in request['candidates']]
|
|
259
|
+
return score_learned_signals(rows,self.state,evaluation_cohort_digest=self.evaluation_cohort_digest,domain_id=self.domain_id)
|
|
260
|
+
def __call__(self,request):
|
|
261
|
+
result=self.score_fields(request)
|
|
262
|
+
return {'schema_version':'sphynx.rag.rerank.response.v1','query_id':request['query_id'],'pool_digest':request['pool_digest'],'scores':[{'candidate_id':cid,'ranking_score':value} for cid,value in zip(result['candidate_ids'],result['fields']['ranking_utility']['values'])]}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def create_learned_scorer(state,*,minilm_scorer,feature_provider,evaluation_cohort_digest,domain_id):
|
|
266
|
+
return LearnedScorer(state,minilm_scorer=minilm_scorer,feature_provider=feature_provider,evaluation_cohort_digest=evaluation_cohort_digest,domain_id=domain_id)
|
|
267
|
+
LEARNED_IMPLEMENTATION_DIGEST=value_digest({p.name:'sha256:'+hashlib.sha256(p.read_bytes()).hexdigest() for p in (Path(__file__),Path(__file__).with_name('rerank_family_contracts.py'),Path(__file__).with_name('statistical_rerank.py'))})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Mechanical same-output audit of the old Cherry answer-only capture.
|
|
2
|
+
|
|
3
|
+
This imports historical verdicts as declared history, not fresh semantic truth.
|
|
4
|
+
No private answers, source text, token, URLs or credentials appear in the report.
|
|
5
|
+
The missing retrieval/context trace cannot be reconstructed from these answers.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import statistics
|
|
9
|
+
from fractions import Fraction
|
|
10
|
+
from ._track_capture import byte_digest,strict_json
|
|
11
|
+
from .pipeline import _require,_number
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def audit_legacy_cherry_full_rag(capture_path,scorecard_path,reference_path,*,expected_capture_digest,
|
|
15
|
+
expected_scorecard_digest,expected_reference_digest):
|
|
16
|
+
values=[];bindings=[]
|
|
17
|
+
for path,digest in ((capture_path,expected_capture_digest),(scorecard_path,expected_scorecard_digest),(reference_path,expected_reference_digest)):
|
|
18
|
+
raw=Path(path).read_bytes();_require(byte_digest(raw)==digest,"legacy artifact hash differs")
|
|
19
|
+
values.append(strict_json(raw));bindings.append({"digest":digest,"size_bytes":len(raw)})
|
|
20
|
+
capture,scorecard,reference=values
|
|
21
|
+
_require(capture.get("schema")=="cherry-full-rag-run/v1" and scorecard.get("schema")=="cherry-full-rag-scorecard/v1"
|
|
22
|
+
and reference.get("suite_id")=="cherry-full-rag-v1" and reference.get("evaluation_level")==3,"legacy schema differs")
|
|
23
|
+
_require(type(capture.get("cases")) is list and type(reference.get("cases")) is list
|
|
24
|
+
and type(scorecard.get("verdicts")) is list,"legacy case arrays are required")
|
|
25
|
+
answers={row.get("query_index"):row for row in capture["cases"] if type(row) is dict}
|
|
26
|
+
refs={row.get("query_index"):row for row in reference["cases"] if type(row) is dict}
|
|
27
|
+
verdicts={row.get("case_id"):row for row in scorecard["verdicts"] if type(row) is dict}
|
|
28
|
+
_require(answers and len(answers)==len(capture["cases"]) and len(refs)==len(reference["cases"])
|
|
29
|
+
and len(verdicts)==len(scorecard["verdicts"]) and set(answers)==set(refs)
|
|
30
|
+
and all(type(key) is int and key>=0 for key in answers),"legacy query membership differs")
|
|
31
|
+
_require(set(verdicts)=={row.get("case_id") for row in refs.values()},"legacy verdict membership differs")
|
|
32
|
+
timings=[];rows=[];counts={"pass":0,"partial":0,"fail":0}
|
|
33
|
+
for index in sorted(answers):
|
|
34
|
+
answer=answers[index];truth=refs[index];verdict=verdicts[truth["case_id"]].get("verdict")
|
|
35
|
+
_require(verdict in counts and type(answer.get("query")) is str and type(answer.get("answer")) is str
|
|
36
|
+
and type(answer.get("timed_out")) is bool,"legacy answer/verdict fields differ")
|
|
37
|
+
timing=answer.get("observed_first_text_latency_ms")
|
|
38
|
+
_require(_number(timing) and timing>=0,"legacy browser timing differs")
|
|
39
|
+
counts[verdict]+=1;timings.append(timing)
|
|
40
|
+
rows.append({"case_id":truth["case_id"],"query_index":index,"query_digest":byte_digest(answer["query"].encode()),
|
|
41
|
+
"answer_digest":byte_digest(answer["answer"].encode()),"legacy_verdict":verdict,"timed_out":answer["timed_out"],
|
|
42
|
+
"browser_first_text_wall_ms":timing,"semantic_regraded":False})
|
|
43
|
+
n=len(rows);weighted=(counts["pass"]+.5*counts["partial"])/n
|
|
44
|
+
grading=scorecard.get("grading")
|
|
45
|
+
expected={"strict_pass_count":counts["pass"],"partial_count":counts["partial"],"fail_count":counts["fail"],"case_count":n,
|
|
46
|
+
"strict_pass_rate":counts["pass"]/n,"substantively_correct_or_partial_rate":(counts["pass"]+counts["partial"])/n,"weighted_score":weighted}
|
|
47
|
+
_require(type(grading) is dict and all(grading.get(key)==value for key,value in expected.items())
|
|
48
|
+
and grading.get("pass_weight")==1 and grading.get("partial_weight")==.5 and grading.get("fail_weight")==0,
|
|
49
|
+
"legacy declared grade arithmetic differs")
|
|
50
|
+
ordered=sorted(timings);position=Fraction((n-1)*95,100);lower=int(position)
|
|
51
|
+
# The retained report records the decimal linear quantile. Exact rational
|
|
52
|
+
# interpolation avoids introducing a binary .95 rounding artifact here.
|
|
53
|
+
p95=float(Fraction(str(ordered[lower]))+(Fraction(str(ordered[min(lower+1,n-1)]))-Fraction(str(ordered[lower])))*(position-lower))
|
|
54
|
+
latency={"count":n,"mean":sum(timings)/n,"p50":statistics.median(timings),"p95_linear":p95,"min":min(timings),"max":max(timings)}
|
|
55
|
+
_require(type(scorecard.get("latency_ms")) is dict and all(scorecard["latency_ms"].get(key)==value for key,value in latency.items()),
|
|
56
|
+
"legacy declared latency arithmetic differs")
|
|
57
|
+
# All cases share one historical deployed-agent session. The independent
|
|
58
|
+
# trace/review contracts required by the new SDK are absent from this schema.
|
|
59
|
+
return {"schema_version":"sphynx.rag.legacy-opaque-audit.v1","status":"partial",
|
|
60
|
+
"execution_mode":"saved_capture_audit","new_candidate_execution":False,"case_count":n,
|
|
61
|
+
"capture":bindings[0],"legacy_scorecard":bindings[1],"reference":bindings[2],"cases":rows,
|
|
62
|
+
"legacy_declared_grading":expected,"browser_first_text_latency_ms":latency,
|
|
63
|
+
"latency_scope":"query-submit-to-first-rendered-text_not_model_ttft","independent_per_query":False,
|
|
64
|
+
"semantic_review_verified":False,"complete_trace_eligible":False,
|
|
65
|
+
"blockers":["retrieval_and_handoff_trace_missing","capture_bound_independent_review_missing","independent_query_reset_missing"],
|
|
66
|
+
"current_full_rag_metrics":{key:None for key in ("rag_call_precision","rag_call_recall","rewrite_preservation",
|
|
67
|
+
"evidence_handoff_completeness","answer_completeness","faithfulness","unsupported_claim_rate","citation_support")}}
|