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/workbench.py
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""Pinned native RAG batches with validation and explicit execution.
|
|
2
|
+
|
|
3
|
+
The canonical batch calls retain the existing coordinator and record formats.
|
|
4
|
+
Native experiment names remain compatible entry points and wire identities.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import math
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from collections.abc import Mapping
|
|
10
|
+
from types import MappingProxyType
|
|
11
|
+
from typing import Any, cast
|
|
12
|
+
from .components import *
|
|
13
|
+
from .dataset_v3 import DatasetV3, PARTITIONS, Partition, validate_dataset_v3
|
|
14
|
+
JsonValue = Any
|
|
15
|
+
CONTEXT_CONDITIONS = frozenset({'oracle', 'dense', 'a0', 'challenger', 'empty'})
|
|
16
|
+
WORKBENCH_WORKLOAD_SCHEMA_VERSION = 'sable.rag.workbench-workload.v1'
|
|
17
|
+
class WorkbenchError(ValueError):
|
|
18
|
+
"""Raised when a native RAG batch cannot execute or support comparison."""
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class Level3ClaimTruth:
|
|
22
|
+
"""An exact answer phrase and the frozen evidence IDs that support it."""
|
|
23
|
+
text: str
|
|
24
|
+
supported_by_evidence_ids: tuple[str, ...] = ()
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_mapping(cls, raw: object, context: str) -> Level3ClaimTruth:
|
|
28
|
+
value = _mapping(raw, context)
|
|
29
|
+
if set(value) != {'text', 'supported_by_evidence_ids'}:
|
|
30
|
+
raise WorkbenchError(f'{context} keys must be exactly text, supported_by_evidence_ids')
|
|
31
|
+
return cls(_text(value['text'], f'{context}.text'), _strings(value['supported_by_evidence_ids'], f'{context}.supported_by_evidence_ids'))
|
|
32
|
+
|
|
33
|
+
def __post_init__(self) -> None:
|
|
34
|
+
_text(self.text, 'Level 3 claim text')
|
|
35
|
+
evidence_ids = _sequence_values(self.supported_by_evidence_ids, 'claim evidence IDs')
|
|
36
|
+
object.__setattr__(self, 'supported_by_evidence_ids', tuple(_text(item, 'claim evidence ID') for item in evidence_ids))
|
|
37
|
+
if len(self.supported_by_evidence_ids) != len(set(self.supported_by_evidence_ids)):
|
|
38
|
+
raise WorkbenchError('Level 3 claim evidence IDs must be unique')
|
|
39
|
+
|
|
40
|
+
def to_mapping(self) -> dict[str, JsonValue]:
|
|
41
|
+
return {'text': self.text, 'supported_by_evidence_ids': list(self.supported_by_evidence_ids)}
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True, slots=True)
|
|
44
|
+
class Level3TaskGrading:
|
|
45
|
+
"""Sealed deterministic semantic truth for one full-RAG task."""
|
|
46
|
+
required_facts: Mapping[str, tuple[str, ...]] = field(default_factory=dict)
|
|
47
|
+
exact_values: tuple[str, ...] = ()
|
|
48
|
+
claims: tuple[Level3ClaimTruth, ...] = ()
|
|
49
|
+
|
|
50
|
+
def __post_init__(self) -> None:
|
|
51
|
+
_mapping(self.required_facts, 'Level 3 required facts')
|
|
52
|
+
_sequence_values(self.exact_values, 'Level 3 exact values')
|
|
53
|
+
_sequence_values(self.claims, 'Level 3 claims')
|
|
54
|
+
facts: dict[str, tuple[str, ...]] = {}
|
|
55
|
+
for raw_fact_id, raw_phrases in self.required_facts.items():
|
|
56
|
+
fact_id = _text(raw_fact_id, 'Level 3 required fact ID')
|
|
57
|
+
phrases = tuple((_text(phrase, f'Level 3 required fact {fact_id} phrase') for phrase in _sequence_values(raw_phrases, 'required fact phrases')))
|
|
58
|
+
if not phrases or len(phrases) != len(set(phrases)):
|
|
59
|
+
raise WorkbenchError(f'Level 3 required fact {fact_id} needs unique accepted phrases')
|
|
60
|
+
facts[fact_id] = phrases
|
|
61
|
+
exact_values = tuple((_text(value, 'Level 3 exact value') for value in self.exact_values))
|
|
62
|
+
if len(exact_values) != len(set(exact_values)):
|
|
63
|
+
raise WorkbenchError('Level 3 exact values must be unique')
|
|
64
|
+
if any((not isinstance(claim, Level3ClaimTruth) for claim in self.claims)):
|
|
65
|
+
raise WorkbenchError('Level 3 claims must be Level3ClaimTruth values')
|
|
66
|
+
claim_texts = [claim.text for claim in self.claims]
|
|
67
|
+
if len(claim_texts) != len(set(claim_texts)):
|
|
68
|
+
raise WorkbenchError('Level 3 claim texts must be unique')
|
|
69
|
+
object.__setattr__(self, 'required_facts', MappingProxyType(dict(sorted(facts.items()))))
|
|
70
|
+
object.__setattr__(self, 'exact_values', exact_values)
|
|
71
|
+
object.__setattr__(self, 'claims', tuple(self.claims))
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_mapping(cls, raw: object, context: str) -> Level3TaskGrading:
|
|
75
|
+
value = _mapping(raw, context)
|
|
76
|
+
if set(value) != {'required_facts', 'exact_values', 'claims'}:
|
|
77
|
+
raise WorkbenchError(f'{context} keys must be exactly claims, exact_values, required_facts')
|
|
78
|
+
raw_facts = _mapping(value['required_facts'], f'{context}.required_facts')
|
|
79
|
+
facts = {fact_id: _strings(phrases, f'{context}.required_facts.{fact_id}') for fact_id, phrases in raw_facts.items()}
|
|
80
|
+
raw_claims = value['claims']
|
|
81
|
+
if not isinstance(raw_claims, list):
|
|
82
|
+
raise WorkbenchError(f'{context}.claims must be an array')
|
|
83
|
+
return cls(required_facts=facts, exact_values=_strings(value['exact_values'], f'{context}.exact_values'), claims=tuple((Level3ClaimTruth.from_mapping(claim, f'{context}.claims[{index}]') for index, claim in enumerate(raw_claims))))
|
|
84
|
+
|
|
85
|
+
def to_mapping(self) -> dict[str, JsonValue]:
|
|
86
|
+
return {'required_facts': {fact_id: list(phrases) for fact_id, phrases in sorted(self.required_facts.items())}, 'exact_values': list(self.exact_values), 'claims': [claim.to_mapping() for claim in self.claims]}
|
|
87
|
+
|
|
88
|
+
@dataclass(frozen=True, slots=True)
|
|
89
|
+
class WorkbenchWorkload:
|
|
90
|
+
"""Validated data selection plus optional forced Level 3 contexts."""
|
|
91
|
+
dataset: DatasetV3
|
|
92
|
+
customer_ids: tuple[str, ...] = ()
|
|
93
|
+
partitions: tuple[Partition, ...] = ('development',)
|
|
94
|
+
level3_context_condition: str = 'dense'
|
|
95
|
+
level3_contexts: Mapping[str, tuple[str, ...]] = field(default_factory=dict)
|
|
96
|
+
level3_grading: Mapping[str, Level3TaskGrading] = field(default_factory=dict)
|
|
97
|
+
_dataset_sha256: str = field(init=False, repr=False)
|
|
98
|
+
_level3_grading_sha256: str = field(init=False, repr=False)
|
|
99
|
+
|
|
100
|
+
def __post_init__(self) -> None:
|
|
101
|
+
if type(self.dataset) is not DatasetV3:
|
|
102
|
+
raise WorkbenchError('workload requires a validated DatasetV3')
|
|
103
|
+
_sequence_values(self.customer_ids, 'customer IDs')
|
|
104
|
+
_sequence_values(self.partitions, 'partitions')
|
|
105
|
+
_mapping(self.level3_contexts, 'level3 contexts')
|
|
106
|
+
_mapping(self.level3_grading, 'level3 grading')
|
|
107
|
+
_text(self.level3_context_condition, 'level3 context condition')
|
|
108
|
+
object.__setattr__(self, 'customer_ids', tuple(sorted((_text(customer_id, 'workload customer ID') for customer_id in self.customer_ids))))
|
|
109
|
+
object.__setattr__(self, 'partitions', tuple(sorted(_text(item, 'partition') for item in self.partitions)))
|
|
110
|
+
contexts: dict[str, tuple[str, ...]] = {}
|
|
111
|
+
for raw_task_id, raw_candidate_ids in self.level3_contexts.items():
|
|
112
|
+
task_id = _text(raw_task_id, 'level3 context task ID')
|
|
113
|
+
contexts[task_id] = tuple((_text(candidate_id, f'level3 contexts for {task_id}') for candidate_id in _sequence_values(raw_candidate_ids, 'context candidate IDs')))
|
|
114
|
+
object.__setattr__(self, 'level3_contexts', MappingProxyType(contexts))
|
|
115
|
+
grading: dict[str, Level3TaskGrading] = {}
|
|
116
|
+
for raw_task_id, truth in self.level3_grading.items():
|
|
117
|
+
task_id = _text(raw_task_id, 'Level 3 grading task ID')
|
|
118
|
+
if not isinstance(truth, Level3TaskGrading):
|
|
119
|
+
raise WorkbenchError('Level 3 grading values must be Level3TaskGrading values')
|
|
120
|
+
grading[task_id] = truth
|
|
121
|
+
object.__setattr__(self, 'level3_grading', MappingProxyType(dict(sorted(grading.items()))))
|
|
122
|
+
object.__setattr__(self, '_dataset_sha256', json_sha256(self.dataset.value))
|
|
123
|
+
object.__setattr__(self, '_level3_grading_sha256', json_sha256({task_id: truth.to_mapping() for task_id, truth in sorted(grading.items())}))
|
|
124
|
+
if self.level3_context_condition not in CONTEXT_CONDITIONS:
|
|
125
|
+
raise WorkbenchError('Level 3 context condition is invalid')
|
|
126
|
+
if len(self.customer_ids) != len(set(self.customer_ids)):
|
|
127
|
+
raise WorkbenchError('workload customer IDs must be unique')
|
|
128
|
+
if not self.partitions or len(self.partitions) != len(set(self.partitions)):
|
|
129
|
+
raise WorkbenchError('workload partitions must be non-empty and unique')
|
|
130
|
+
if not set(self.partitions) <= PARTITIONS:
|
|
131
|
+
raise WorkbenchError('workload contains an invalid partition')
|
|
132
|
+
available_partitions = {identity.partition for identity in self.dataset.tasks}
|
|
133
|
+
missing_partitions = set(self.partitions) - available_partitions
|
|
134
|
+
if missing_partitions:
|
|
135
|
+
raise WorkbenchError(f'workload partitions are absent from the dataset: {sorted(missing_partitions)}')
|
|
136
|
+
known = {identity.customer_id for identity in self.dataset.tasks}
|
|
137
|
+
unknown = set(self.customer_ids) - known
|
|
138
|
+
if unknown:
|
|
139
|
+
raise WorkbenchError(f'workload references unknown customers: {sorted(unknown)}')
|
|
140
|
+
known_tasks = {identity.task_id for identity in self.dataset.tasks}
|
|
141
|
+
unknown_tasks = set(self.level3_contexts) - known_tasks
|
|
142
|
+
if unknown_tasks:
|
|
143
|
+
raise WorkbenchError(f'Level 3 contexts reference unknown tasks: {sorted(unknown_tasks)}')
|
|
144
|
+
for task_id, candidate_ids in self.level3_contexts.items():
|
|
145
|
+
if len(candidate_ids) != len(set(candidate_ids)):
|
|
146
|
+
raise WorkbenchError(f'Level 3 contexts for {task_id} contain duplicate IDs')
|
|
147
|
+
unknown_grading_tasks = set(self.level3_grading) - known_tasks
|
|
148
|
+
if unknown_grading_tasks:
|
|
149
|
+
raise WorkbenchError(f'Level 3 grading references unknown tasks: {sorted(unknown_grading_tasks)}')
|
|
150
|
+
raw_tasks = {_text(task.get('task_id'), 'dataset task ID'): task for task in _rows(_mapping(self.dataset.value, 'dataset').get('tasks'), 'dataset.tasks')}
|
|
151
|
+
for task_id, truth in self.level3_grading.items():
|
|
152
|
+
known_candidate_ids = {_text(candidate.get('candidate_id'), f'{task_id} candidate ID') for candidate in _rows(raw_tasks[task_id].get('candidates'), 'task.candidates')}
|
|
153
|
+
cited_ids = {evidence_id for claim in truth.claims for evidence_id in claim.supported_by_evidence_ids}
|
|
154
|
+
if not cited_ids <= known_candidate_ids:
|
|
155
|
+
raise WorkbenchError(f'Level 3 grading for {task_id} references unknown evidence IDs: {sorted(cited_ids - known_candidate_ids)}')
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def dataset_sha256(self) -> str:
|
|
159
|
+
return self._dataset_sha256
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def level3_grading_sha256(self) -> str:
|
|
163
|
+
return self._level3_grading_sha256
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def from_mapping(cls, dataset: DatasetV3, raw: object) -> WorkbenchWorkload:
|
|
167
|
+
"""Load the strict JSON control document used by CLI or service front ends."""
|
|
168
|
+
root = _mapping(raw, 'workbench workload')
|
|
169
|
+
required = {'schema_version', 'dataset', 'customer_ids', 'partitions', 'level3_context_condition', 'level3_contexts'}
|
|
170
|
+
allowed = required | {'level3_grading'}
|
|
171
|
+
if not required <= set(root) or not set(root) <= allowed:
|
|
172
|
+
raise WorkbenchError('workbench workload keys must be exactly the required control fields, with optional level3_grading')
|
|
173
|
+
if root['schema_version'] != WORKBENCH_WORKLOAD_SCHEMA_VERSION:
|
|
174
|
+
raise WorkbenchError('workbench workload schema_version is unsupported')
|
|
175
|
+
dataset_commitment = _mapping(root['dataset'], 'workbench workload.dataset')
|
|
176
|
+
if set(dataset_commitment) != {'dataset_id', 'version', 'sha256'}:
|
|
177
|
+
raise WorkbenchError('workbench workload.dataset fields differ')
|
|
178
|
+
expected_dataset = {'dataset_id': dataset.dataset_id, 'version': dataset.version, 'sha256': json_sha256(dataset.value)}
|
|
179
|
+
if dict(dataset_commitment) != expected_dataset:
|
|
180
|
+
raise WorkbenchError('workbench workload does not bind the supplied dataset')
|
|
181
|
+
contexts_raw = _mapping(root['level3_contexts'], 'level3_contexts')
|
|
182
|
+
contexts = {task_id: _strings(candidate_ids, f'level3_contexts.{task_id}') for task_id, candidate_ids in contexts_raw.items()}
|
|
183
|
+
grading_raw = _mapping(root.get('level3_grading', {}), 'level3_grading')
|
|
184
|
+
grading = {task_id: Level3TaskGrading.from_mapping(truth, f'level3_grading.{task_id}') for task_id, truth in grading_raw.items()}
|
|
185
|
+
partitions = _strings(root['partitions'], 'partitions')
|
|
186
|
+
return cls(dataset=dataset, customer_ids=_strings(root['customer_ids'], 'customer_ids'), partitions=cast('tuple[Partition, ...]', partitions), level3_context_condition=_text(root['level3_context_condition'], 'level3_context_condition'), level3_contexts=contexts, level3_grading=grading)
|
|
187
|
+
|
|
188
|
+
def to_mapping(self) -> dict[str, JsonValue]:
|
|
189
|
+
result: dict[str, JsonValue] = {'schema_version': WORKBENCH_WORKLOAD_SCHEMA_VERSION, 'dataset': {'dataset_id': self.dataset.dataset_id, 'version': self.dataset.version, 'sha256': self.dataset_sha256}, 'customer_ids': list(self.customer_ids), 'partitions': list(self.partitions), 'level3_context_condition': self.level3_context_condition, 'level3_contexts': {task_id: list(candidate_ids) for task_id, candidate_ids in sorted(self.level3_contexts.items())}}
|
|
190
|
+
if self.level3_grading:
|
|
191
|
+
result['level3_grading'] = {task_id: truth.to_mapping() for task_id, truth in sorted(self.level3_grading.items())}
|
|
192
|
+
return result
|
|
193
|
+
|
|
194
|
+
def _sequence_values(value, context):
|
|
195
|
+
if not isinstance(value, (list, tuple)):
|
|
196
|
+
raise WorkbenchError(f'{context} must be a list or tuple')
|
|
197
|
+
return tuple(value)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _mapping(value: object, context: str) -> Mapping[str, object]:
|
|
201
|
+
if not isinstance(value, Mapping) or any((not isinstance(key, str) for key in value)):
|
|
202
|
+
raise WorkbenchError(f'{context} must be an object')
|
|
203
|
+
return cast('Mapping[str, object]', value)
|
|
204
|
+
|
|
205
|
+
def _json_value(value: object, context: str) -> JsonValue:
|
|
206
|
+
"""Validate and detach an arbitrary value at the adapter/data boundary."""
|
|
207
|
+
if value is None or isinstance(value, bool | int | str):
|
|
208
|
+
return value
|
|
209
|
+
if isinstance(value, float):
|
|
210
|
+
if not math.isfinite(value):
|
|
211
|
+
raise WorkbenchError(f'{context} contains a non-finite number')
|
|
212
|
+
return value
|
|
213
|
+
if isinstance(value, list | tuple):
|
|
214
|
+
return [_json_value(item, f'{context}[]') for item in value]
|
|
215
|
+
if isinstance(value, Mapping):
|
|
216
|
+
result: dict[str, JsonValue] = {}
|
|
217
|
+
for key, item in value.items():
|
|
218
|
+
if not isinstance(key, str):
|
|
219
|
+
raise WorkbenchError(f'{context} contains a non-string object key')
|
|
220
|
+
result[key] = _json_value(item, f'{context}.{key}')
|
|
221
|
+
return result
|
|
222
|
+
raise WorkbenchError(f'{context} is not JSON-compatible')
|
|
223
|
+
|
|
224
|
+
def _rows(value: object, context: str) -> list[Mapping[str, object]]:
|
|
225
|
+
if not isinstance(value, list):
|
|
226
|
+
raise WorkbenchError(f'{context} must be an array')
|
|
227
|
+
return [_mapping(row, f'{context}[{index}]') for index, row in enumerate(value)]
|
|
228
|
+
|
|
229
|
+
def _strings(value: object, context: str) -> tuple[str, ...]:
|
|
230
|
+
if not isinstance(value, list):
|
|
231
|
+
raise WorkbenchError(f'{context} must be an array')
|
|
232
|
+
return tuple((_text(item, f'{context}[]') for item in value))
|
|
233
|
+
|
|
234
|
+
def _text(value: object, context: str) -> str:
|
|
235
|
+
if not isinstance(value, str) or not value:
|
|
236
|
+
raise WorkbenchError(f'{context} must be non-empty text')
|
|
237
|
+
return value
|
|
238
|
+
|
|
239
|
+
def _tasks(workload: WorkbenchWorkload) -> tuple[Mapping[str, object], ...]:
|
|
240
|
+
if json_sha256(workload.dataset.value) != workload.dataset_sha256:
|
|
241
|
+
raise WorkbenchError('workload dataset changed after it was frozen')
|
|
242
|
+
raw = _mapping(workload.dataset.value, 'dataset')
|
|
243
|
+
selected = set(workload.customer_ids)
|
|
244
|
+
tasks = tuple((task for task in _rows(raw.get('tasks'), 'dataset.tasks') if (not selected or task.get('customer_id') in selected) and task.get('partition') in workload.partitions))
|
|
245
|
+
if not tasks:
|
|
246
|
+
raise WorkbenchError('workload selection contains no tasks')
|
|
247
|
+
return tasks
|
|
248
|
+
|
|
249
|
+
from pathlib import Path
|
|
250
|
+
from .integration_contracts import CandidateConfiguration, value_digest
|
|
251
|
+
from .dataset_v3 import load_dataset_v3
|
|
252
|
+
from ._track_capture import strict_json
|
|
253
|
+
|
|
254
|
+
@dataclass(frozen=True)
|
|
255
|
+
class WorkbenchExecutionPolicy:
|
|
256
|
+
"""Explicit caller permissions and already registered adapters; no dynamic imports."""
|
|
257
|
+
registry: ComponentRegistry
|
|
258
|
+
allowed_implementation_kinds: tuple[str, ...] = ('builtin', 'recorded')
|
|
259
|
+
synthetic: bool = False
|
|
260
|
+
owner_authorized: bool = False
|
|
261
|
+
pack_manifest_path: Path | None = None
|
|
262
|
+
expected_sha256: str | None = None
|
|
263
|
+
# Downstream tracks may supply an explicit ordinary Python runner.
|
|
264
|
+
track_runners: Mapping = field(default_factory=dict)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _validated(components, experiment, dataset, workload):
|
|
268
|
+
if not isinstance(components, ComponentManifest):
|
|
269
|
+
components=validate_component_manifest(components)
|
|
270
|
+
else:
|
|
271
|
+
components=validate_component_manifest(components.value)
|
|
272
|
+
if isinstance(experiment, ExperimentManifest): experiment=experiment.value
|
|
273
|
+
experiment=validate_experiment_manifest(experiment,components)
|
|
274
|
+
if not isinstance(dataset,DatasetV3):
|
|
275
|
+
dataset=load_dataset_v3(dataset) if isinstance(dataset,(str,Path)) else validate_dataset_v3(dataset)
|
|
276
|
+
else:
|
|
277
|
+
dataset=validate_dataset_v3(dataset.value)
|
|
278
|
+
if isinstance(workload,WorkbenchWorkload): workload=workload.to_mapping()
|
|
279
|
+
workload=WorkbenchWorkload.from_mapping(dataset,workload)
|
|
280
|
+
tasks=_tasks(workload)
|
|
281
|
+
# Source workload validates duplicate IDs; also reject context references outside a case.
|
|
282
|
+
for task in tasks:
|
|
283
|
+
supplied=set(workload.level3_contexts.get(task['task_id'],()))
|
|
284
|
+
known={c['candidate_id'] for c in task['candidates']}
|
|
285
|
+
if not supplied<=known:
|
|
286
|
+
raise WorkbenchError('workload context references unknown evidence')
|
|
287
|
+
return components,experiment,dataset,workload,tasks
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def validate_experiment(components, experiment, dataset, workload):
|
|
291
|
+
"""Validate frozen controls without constructing, importing or executing adapters."""
|
|
292
|
+
components,experiment,dataset,workload,tasks=_validated(components,experiment,dataset,workload)
|
|
293
|
+
selected={r.component_id for cfg in (experiment.baseline,*(a.configuration for a in experiment.alternatives)) for r in cfg.components}
|
|
294
|
+
external=sorted(c.component_id for c in components.components if c.component_id in selected and c.implementation.kind=='command')
|
|
295
|
+
return {'valid':True,'dataset_digest':'sha256:'+workload.dataset_sha256,
|
|
296
|
+
'workload_digest':value_digest(workload.to_mapping()),
|
|
297
|
+
'component_manifest_digest':'sha256:'+components.sha256,
|
|
298
|
+
'experiment_digest':'sha256:'+experiment.sha256,
|
|
299
|
+
'case_ids':[t['task_id'] for t in tasks],
|
|
300
|
+
'targets':[{'level':t.level,'track':t.track} for t in experiment.targets],
|
|
301
|
+
'external_components':external,
|
|
302
|
+
'execution_ready':False,
|
|
303
|
+
'reason':'Execution needs explicit policy and manifest-bound adapter registrations.'}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def run_experiment(components, experiment, dataset, workload, output_dir, *, execution_policy):
|
|
307
|
+
"""Execute the fixed baseline and declared alternatives under explicit permissions."""
|
|
308
|
+
from .chunking import run_chunking, grade_chunking
|
|
309
|
+
if not isinstance(execution_policy,WorkbenchExecutionPolicy):
|
|
310
|
+
raise WorkbenchError('explicit WorkbenchExecutionPolicy is required')
|
|
311
|
+
if not isinstance(dataset,(str,Path)) or execution_policy.expected_sha256 is None:
|
|
312
|
+
raise WorkbenchError('execution requires an external pinned dataset path')
|
|
313
|
+
dataset_path=Path(dataset)
|
|
314
|
+
data=load_dataset_v3(dataset_path,expected_sha256=execution_policy.expected_sha256)
|
|
315
|
+
components,experiment,data,workload,tasks=_validated(components,experiment,data,workload)
|
|
316
|
+
policy=execution_policy
|
|
317
|
+
if policy.registry.manifest.sha256!=components.sha256:
|
|
318
|
+
raise WorkbenchError('runtime registry differs from frozen catalog')
|
|
319
|
+
configurations=(experiment.baseline,*(a.configuration for a in experiment.alternatives))
|
|
320
|
+
for configuration in configurations:
|
|
321
|
+
for ref in configuration.components:
|
|
322
|
+
component=components.by_id[ref.component_id]
|
|
323
|
+
if component.implementation.kind not in policy.allowed_implementation_kinds:
|
|
324
|
+
raise WorkbenchError('component execution kind is not authorized')
|
|
325
|
+
adapter=policy.registry.resolve(ref.component_id,component.capabilities[0],OPERATIONS_BY_KIND[component.kind][0])
|
|
326
|
+
if adapter.descriptor!=AdapterDescriptor.from_component(component):
|
|
327
|
+
raise WorkbenchError('registered adapter identity drift')
|
|
328
|
+
for target in experiment.targets:
|
|
329
|
+
if target.track!='chunking' and target.track not in policy.track_runners:
|
|
330
|
+
raise WorkbenchError('target requires an explicit registered track runner: '+target.track)
|
|
331
|
+
# One native batch boundary; component changes are captured separately.
|
|
332
|
+
boundary=value_digest({'experiment':experiment.value,'workload':workload.to_mapping()})
|
|
333
|
+
results=[]
|
|
334
|
+
for configuration in configurations:
|
|
335
|
+
for target in experiment.targets:
|
|
336
|
+
if target.track=='chunking':
|
|
337
|
+
component=components.by_id[configuration.by_kind['chunker'].component_id]
|
|
338
|
+
registered=policy.registry.resolve(component.component_id,target,'chunk')
|
|
339
|
+
candidate=CandidateConfiguration(configuration.configuration_id,'sphynx.chunk.v1',
|
|
340
|
+
'sha256:'+component.implementation.artifact_sha256,
|
|
341
|
+
{'component_id':component.component_id,'config':component.config,
|
|
342
|
+
'workload_digest':value_digest(workload.to_mapping())},'trusted_local')
|
|
343
|
+
def adapter(request, registered=registered):
|
|
344
|
+
output=registered.invoke('chunk',{'documents':[
|
|
345
|
+
{'document_id':d['document_id'],'text':d['text']} for d in request['documents']]})
|
|
346
|
+
return {'schema_version':'sphynx.chunk.response.v1','case_id':request['case_id'],'chunks':[{key:row[key] for key in ('chunk_id','document_id','start','end','text')} for row in output['chunks']]}
|
|
347
|
+
run=run_chunking(candidate,adapter,dataset_path,output_dir,
|
|
348
|
+
expected_sha256=policy.expected_sha256,synthetic=policy.synthetic,
|
|
349
|
+
owner_authorized=policy.owner_authorized,pack_manifest_path=policy.pack_manifest_path,
|
|
350
|
+
case_ids=[t['task_id'] for t in tasks],boundary_digest=boundary)
|
|
351
|
+
evaluation=grade_chunking(run,dataset_path,expected_sha256=policy.expected_sha256)
|
|
352
|
+
results.append({'configuration_id':configuration.configuration_id,'track':target.track,
|
|
353
|
+
'run':run,'evaluation':evaluation})
|
|
354
|
+
else:
|
|
355
|
+
result=policy.track_runners[target.track](components=components,configuration=configuration,
|
|
356
|
+
registry=policy.registry,dataset_path=dataset_path,workload=workload,
|
|
357
|
+
output_dir=output_dir,execution_policy=policy,boundary_digest=boundary)
|
|
358
|
+
results.append({'configuration_id':configuration.configuration_id,'track':target.track,'result':result})
|
|
359
|
+
return tuple(results)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def validate_batch(components, batch, dataset, workload):
|
|
363
|
+
"""Validate a native RAG batch without executing adapters.
|
|
364
|
+
|
|
365
|
+
This is the canonical spelling of ``validate_experiment``. Returned native
|
|
366
|
+
field names, including ``experiment_digest``, retain their original meaning.
|
|
367
|
+
"""
|
|
368
|
+
return validate_experiment(components, batch, dataset, workload)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def run_batch(components, batch, dataset, workload, output_dir, *, execution_policy):
|
|
372
|
+
"""Run the native baseline and alternatives under the existing policy.
|
|
373
|
+
|
|
374
|
+
This call delegates to ``run_experiment``. It does not add common-workflow
|
|
375
|
+
pipeline substitution, new execution targets, or a new record schema.
|
|
376
|
+
"""
|
|
377
|
+
return run_experiment(components, batch, dataset, workload, output_dir,
|
|
378
|
+
execution_policy=execution_policy)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Metadata-only component candidates; owner data stays external."""
|
|
2
|
+
from .catalog import CandidateDescriptor
|
|
3
|
+
DATASETS = () # The eleven blocked customer rows already belong to I-02.
|
|
4
|
+
CANDIDATES = (
|
|
5
|
+
CandidateDescriptor('workbench.synthetic-chunker.v1', 'rag_chunk', 'sphynx.chunk.v1',
|
|
6
|
+
'sha256:f38dfca7d06360fe5e024b0d03dd326da7f324ac069de15df4776865e5dcb47d', (), ('trusted_local',), 'available',
|
|
7
|
+
local_prerequisites=('Explicit synthetic workload and a pinned component manifest.',),
|
|
8
|
+
supported_python=('>=3.11',)),
|
|
9
|
+
CandidateDescriptor('workbench.recorded-component.v1', 'rag_workbench', 'sable.rag.component.v1',
|
|
10
|
+
'sha256:f38dfca7d06360fe5e024b0d03dd326da7f324ac069de15df4776865e5dcb47d', (), ('recorded',), 'available',
|
|
11
|
+
local_prerequisites=('Complete request-bound recordings and a pinned component manifest.',),
|
|
12
|
+
supported_python=('>=3.11',)),
|
|
13
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Explicit Level 2/3 runners for the existing declarative workbench.
|
|
2
|
+
|
|
3
|
+
The seven-component pipeline begins with source documents. The componentized
|
|
4
|
+
Level 3 diagnostic begins with a frozen context and calls router, rewriter and
|
|
5
|
+
generator; it is not an integrated production retriever or opaque system.
|
|
6
|
+
"""
|
|
7
|
+
from .components import AdapterDescriptor
|
|
8
|
+
from .integration_contracts import CandidateConfiguration,value_digest
|
|
9
|
+
from .pipeline import run_pipeline,grade_pipeline,INTERFACE as PIPELINE_INTERFACE
|
|
10
|
+
from .full_rag import run_full_rag,grade_full_rag,INTERFACE as FULL_INTERFACE
|
|
11
|
+
from ._composed_pipeline import compose_pipeline
|
|
12
|
+
from ._componentized_full_rag import compose_full_rag
|
|
13
|
+
from .workbench import WorkbenchError,_tasks
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _invoker(components,configuration,registry):
|
|
17
|
+
def invoke(kind,operation,payload):
|
|
18
|
+
ref=configuration.by_kind.get(kind)
|
|
19
|
+
if ref is None:raise WorkbenchError("configuration lacks a required component: "+kind)
|
|
20
|
+
component=components.by_id[ref.component_id]
|
|
21
|
+
adapter=registry.resolve(ref.component_id,component.capabilities[0],operation)
|
|
22
|
+
if adapter.descriptor!=AdapterDescriptor.from_component(component):
|
|
23
|
+
raise WorkbenchError("registered adapter identity differs from the frozen component")
|
|
24
|
+
return adapter.invoke(operation,payload)
|
|
25
|
+
return invoke
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _configuration(components,configuration):
|
|
29
|
+
records={row["component_id"]:row for row in components.value["components"]}
|
|
30
|
+
return {ref.kind:records[ref.component_id] for ref in configuration.components}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _options(execution_policy,workload,boundary_digest):
|
|
34
|
+
return {"expected_sha256":execution_policy.expected_sha256,"synthetic":execution_policy.synthetic,
|
|
35
|
+
"owner_authorized":execution_policy.owner_authorized,"pack_manifest_path":execution_policy.pack_manifest_path,
|
|
36
|
+
"case_ids":[task["task_id"] for task in _tasks(workload)],"boundary_digest":boundary_digest}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def run_workbench_pipeline(*,components,configuration,registry,dataset_path,workload,output_dir,execution_policy,boundary_digest):
|
|
40
|
+
selected=_configuration(components,configuration)
|
|
41
|
+
if not {"chunker","embedder","retriever","reranker","fusion","dedup","gate"}<=set(selected):
|
|
42
|
+
raise WorkbenchError("pipeline requires all seven registered components")
|
|
43
|
+
invoke=_invoker(components,configuration,registry)
|
|
44
|
+
gate=components.by_id[configuration.by_kind["gate"].component_id]
|
|
45
|
+
candidate=CandidateConfiguration(configuration.configuration_id,PIPELINE_INTERFACE,value_digest(selected),
|
|
46
|
+
{"controlled_config":{"top_k":gate.config.get("top_k",5),"chunking":selected["chunker"],"embedding":selected["embedder"],
|
|
47
|
+
"retrieval":selected["retriever"],"reranker":selected["reranker"],"fusion":selected["fusion"],
|
|
48
|
+
"dedup":selected["dedup"],"gating":selected["gate"]}},"trusted_local")
|
|
49
|
+
run=run_pipeline(candidate,lambda request:compose_pipeline(request,invoke),dataset_path,output_dir,
|
|
50
|
+
boundary="composed_source_documents",**_options(execution_policy,workload,boundary_digest))
|
|
51
|
+
evaluation=grade_pipeline(run,dataset_path,expected_sha256=execution_policy.expected_sha256)
|
|
52
|
+
return {"run":run,"evaluation":evaluation,"boundary":"composed_source_documents"}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def run_workbench_full_rag(*,components,configuration,registry,dataset_path,workload,output_dir,execution_policy,boundary_digest):
|
|
56
|
+
selected=_configuration(components,configuration);invoke=_invoker(components,configuration,registry)
|
|
57
|
+
subject=components.by_id[configuration.by_kind["full_rag_subject"].component_id]
|
|
58
|
+
mode=subject.config.get("workbench_execution_mode","opaque")
|
|
59
|
+
if mode not in ("opaque","componentized"):raise WorkbenchError("full-RAG execution shape differs")
|
|
60
|
+
if mode=="componentized" and not {"router","rewriter","generator"}<=set(selected):
|
|
61
|
+
raise WorkbenchError("componentized full-RAG requires router, rewriter and generator")
|
|
62
|
+
generator=selected["generator"] if mode=="componentized" else selected["full_rag_subject"]
|
|
63
|
+
candidate=CandidateConfiguration(configuration.configuration_id,FULL_INTERFACE,value_digest(selected),
|
|
64
|
+
{"generator_digest":value_digest(generator),"prompt_digest":value_digest({"generator_config":generator["config"],"boundary":"fixed-context"}),
|
|
65
|
+
"components":selected},"trusted_local")
|
|
66
|
+
if mode=="componentized":adapter=lambda request:compose_full_rag(request,invoke)
|
|
67
|
+
else:adapter=lambda request:invoke("full_rag_subject","full_rag",request)
|
|
68
|
+
run=run_full_rag(candidate,adapter,dataset_path,output_dir,execution_shape=mode,
|
|
69
|
+
context_condition=workload.level3_context_condition,contexts=dict(workload.level3_contexts) or None,
|
|
70
|
+
**_options(execution_policy,workload,boundary_digest))
|
|
71
|
+
evaluation=grade_full_rag(run,dataset_path,expected_sha256=execution_policy.expected_sha256)
|
|
72
|
+
return {"run":run,"evaluation":evaluation,"execution_shape":mode,
|
|
73
|
+
"semantic_grading":"capture_bound_review_required","predeclared_workload_truth_digest":"sha256:"+workload.level3_grading_sha256}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def workbench_track_runners():
|
|
77
|
+
"""Pass explicitly to WorkbenchExecutionPolicy(track_runners=...)."""
|
|
78
|
+
return {"pipeline":run_workbench_pipeline,"full_rag":run_workbench_full_rag}
|