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,29 @@
|
|
|
1
|
+
"""Named research protocols for the separate, local rag.papers.v1 suite.
|
|
2
|
+
|
|
3
|
+
Import specific paper names to make a call's evaluation approach visible.
|
|
4
|
+
These SDK 0.5.0 submodule APIs preserve the existing root exports and do not imply
|
|
5
|
+
official-framework parity, human calibration, or hosted-suite admission.
|
|
6
|
+
"""
|
|
7
|
+
from .contracts import (PaperEvaluationError, make_case, make_trace, judgment,
|
|
8
|
+
candidate_input, validate_case, validate_trace, validate_judgment)
|
|
9
|
+
from .datasets import (PaperDataset, prepare_paper_dataset, prepare_official_paper_dataset,
|
|
10
|
+
prepare_canonical_paper_dataset, load_paper_dataset)
|
|
11
|
+
from .registry import describe_paper_methods
|
|
12
|
+
from .scoring import (evaluate_beir, evaluate_hotpotqa, evaluate_musique,
|
|
13
|
+
evaluate_qasper, evaluate_rgb, evaluate_crag)
|
|
14
|
+
from .semantic import (evaluate_sufficient_context, evaluate_ragas, evaluate_alce,
|
|
15
|
+
evaluate_ares, evaluate_factscore, evaluate_ragchecker,
|
|
16
|
+
evaluate_lost_in_the_middle, evaluate_trec_rag_2025)
|
|
17
|
+
from .judges import JsonJudgeAdapter, JudgeSession, run_paper_judge, prepare_ares_training, train_ares_judge
|
|
18
|
+
from .candidates import candidate_trace
|
|
19
|
+
from .runner import (run_paper_suite, load_paper_run, compare_paper_runs, capture_paper_candidate)
|
|
20
|
+
from .experiments import plan_lost_in_the_middle, plan_rgb, plan_musique, prepare_intervention_dataset
|
|
21
|
+
from .sable import (prepare_sable_paper_dataset, evaluate_sable_evidence,
|
|
22
|
+
bind_reflex_case_history, trace_from_level3, trace_from_full_rag_capture)
|
|
23
|
+
from .trec import prepare_trec_rag_2025_dataset, trace_from_trec_rag_2025
|
|
24
|
+
from .schemas import get_paper_schema
|
|
25
|
+
|
|
26
|
+
__all__ = [name for name in globals() if name.startswith(('evaluate_', 'prepare_', 'validate_', 'make_', 'plan_'))] + [
|
|
27
|
+
'PaperEvaluationError','PaperDataset','judgment','candidate_input','describe_paper_methods','load_paper_dataset',
|
|
28
|
+
'JsonJudgeAdapter','JudgeSession','run_paper_judge','run_paper_suite','load_paper_run','candidate_trace','train_ares_judge',
|
|
29
|
+
'compare_paper_runs','capture_paper_candidate','bind_reflex_case_history','trace_from_level3','trace_from_full_rag_capture','trace_from_trec_rag_2025','get_paper_schema']
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""A distinct invocation: python -m sphynx.rag_papers --help."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .contracts import PaperEvaluationError, parse
|
|
8
|
+
from .datasets import prepare_paper_dataset, prepare_official_paper_dataset, load_paper_dataset
|
|
9
|
+
from .registry import describe_paper_methods
|
|
10
|
+
from .runner import run_paper_suite
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main(argv=None):
|
|
14
|
+
parser=argparse.ArgumentParser(description='Source-only rag.papers.v1; offline intake and grading, no implicit model execution')
|
|
15
|
+
sub=parser.add_subparsers(dest='command',required=True)
|
|
16
|
+
sub.add_parser('methods')
|
|
17
|
+
native=sub.add_parser('prepare')
|
|
18
|
+
native.add_argument('paper', choices=('beir','hotpotqa','musique','qasper','alce','rgb','crag','lost_in_the_middle'))
|
|
19
|
+
native.add_argument('source');native.add_argument('output');native.add_argument('--sha256',required=True)
|
|
20
|
+
native.add_argument('--source-url');native.add_argument('--split',default='development');native.add_argument('--scenario')
|
|
21
|
+
bridge=sub.add_parser('bridge-official');bridge.add_argument('source');bridge.add_argument('output');bridge.add_argument('--manifest-sha256',required=True)
|
|
22
|
+
grade=sub.add_parser('grade');grade.add_argument('dataset');grade.add_argument('traces');grade.add_argument('output')
|
|
23
|
+
grade.add_argument('--manifest-sha256',required=True);grade.add_argument('--methods',nargs='+',required=True)
|
|
24
|
+
grade.add_argument('--candidate',required=True);grade.add_argument('--source-sha256',required=True)
|
|
25
|
+
grade.add_argument('--judgments')
|
|
26
|
+
args=parser.parse_args(argv)
|
|
27
|
+
def lines(path):
|
|
28
|
+
file=Path(path)
|
|
29
|
+
if file.stat().st_size>200_000_000:raise PaperEvaluationError('Evidence file exceeds limit')
|
|
30
|
+
return [parse(line) for line in file.read_bytes().splitlines() if line.strip()]
|
|
31
|
+
try:
|
|
32
|
+
if args.command=='methods':out=describe_paper_methods()
|
|
33
|
+
elif args.command=='prepare':out=prepare_paper_dataset(args.paper,args.source,args.output,expected_sha256=args.sha256,
|
|
34
|
+
source_url=args.source_url,split=args.split,scenario=args.scenario)
|
|
35
|
+
elif args.command=='bridge-official':out=prepare_official_paper_dataset(args.source,args.output,expected_manifest_sha256=args.manifest_sha256)
|
|
36
|
+
else:
|
|
37
|
+
dataset=load_paper_dataset(args.dataset,expected_manifest_sha256=args.manifest_sha256)
|
|
38
|
+
out=run_paper_suite(dataset,lines(args.traces),args.output,methods=args.methods,
|
|
39
|
+
judgments=[] if not args.judgments else lines(args.judgments),candidate_id=args.candidate,source_sha256=args.source_sha256)
|
|
40
|
+
print(json.dumps(out,indent=2,allow_nan=False));return 0
|
|
41
|
+
except (ValueError,OSError,KeyError,TypeError):
|
|
42
|
+
parser.exit(2,'Paper evaluation failed: invalid inputs, identities or output path; inspect the pinned input contract.\n')
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__=='__main__':raise SystemExit(main())
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Candidate-side trace construction using only the projected public input."""
|
|
2
|
+
from .contracts import STAGES, TRACE_SCHEMA, digest, exact, require, validate_trace
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def candidate_trace(payload, *, candidate_id, source_sha256, text='', ranked=None,
|
|
6
|
+
prompt=None, status='ok', abstained=False, attempt_id='attempt-1'):
|
|
7
|
+
"""Build a return value inside a trusted candidate without receiving gold."""
|
|
8
|
+
exact(payload,('case_id','question','conversation_history','retrieval_query','context','corpus_snapshot_id','documents'), 'candidate input')
|
|
9
|
+
permitted={d['id'] for d in payload['documents']}
|
|
10
|
+
trace=dict(schema_version=TRACE_SCHEMA,case_id=payload['case_id'],attempt_id=attempt_id,
|
|
11
|
+
input_sha256=digest(payload),candidate=dict(id=candidate_id,source_sha256=source_sha256),
|
|
12
|
+
rewritten_query=payload['retrieval_query'],native={},stages=dict.fromkeys(STAGES),
|
|
13
|
+
response=dict(status=status,text=text,abstained=abstained,support_ids=None,support_facts=None),
|
|
14
|
+
operational=dict(tokens=None,cost_usd=None))
|
|
15
|
+
trace['stages'].update(retrieved=ranked,prompt=prompt)
|
|
16
|
+
validate_trace(trace)
|
|
17
|
+
for stage in trace['stages'].values():
|
|
18
|
+
require(stage is None or set(stage)<=permitted,'Candidate trace uses unavailable documents')
|
|
19
|
+
return trace
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""Paper evaluation wire contracts. Native gold remains separate from inputs."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import copy
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import math
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
CASE_SCHEMA = 'sphynx.rag.case.v1'
|
|
11
|
+
TRACE_SCHEMA = 'sphynx.rag.trace.v1'
|
|
12
|
+
JUDGMENT_SCHEMA = 'sphynx.rag.judgment.v1'
|
|
13
|
+
RESULT_SCHEMA = 'sphynx.rag.paper-result.v1'
|
|
14
|
+
STAGES = ('indexed', 'retrieved', 'reranked', 'selected', 'prompt')
|
|
15
|
+
REFERENCE_FIELDS = ('answers', 'qrels', 'support_ids', 'support_facts', 'answerable',
|
|
16
|
+
'expected_action', 'required_atoms', 'atom_support',
|
|
17
|
+
'required_relations', 'relation_support', 'annotation_sets', 'native')
|
|
18
|
+
MAX_BYTES = 200_000_000
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PaperEvaluationError(ValueError):
|
|
22
|
+
"""A paper protocol's data, identity or execution boundary is invalid."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def require(condition, message):
|
|
26
|
+
if not condition:
|
|
27
|
+
raise PaperEvaluationError(message)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def canonical(value):
|
|
31
|
+
try:
|
|
32
|
+
return json.dumps(value, ensure_ascii=False, sort_keys=True,
|
|
33
|
+
separators=(',', ':'), allow_nan=False).encode('utf-8')
|
|
34
|
+
except (ValueError, TypeError, RecursionError):
|
|
35
|
+
raise PaperEvaluationError('Expected bounded finite JSON') from None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def digest(value):
|
|
39
|
+
return hashlib.sha256(canonical(value)).hexdigest()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def parse(raw):
|
|
43
|
+
require(len(raw) <= MAX_BYTES, 'JSON exceeds the local byte limit')
|
|
44
|
+
def pairs(items):
|
|
45
|
+
out = {}
|
|
46
|
+
for key, value in items:
|
|
47
|
+
require(key not in out, 'Duplicate JSON object key')
|
|
48
|
+
out[key] = value
|
|
49
|
+
return out
|
|
50
|
+
try:
|
|
51
|
+
result = json.loads(raw, object_pairs_hook=pairs,
|
|
52
|
+
parse_constant=lambda _: require(False, 'Nonfinite JSON value'))
|
|
53
|
+
canonical(result)
|
|
54
|
+
return result
|
|
55
|
+
except (ValueError, UnicodeError, RecursionError):
|
|
56
|
+
raise PaperEvaluationError('Invalid finite UTF-8 JSON') from None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def exact(value, fields, where):
|
|
60
|
+
require(type(value) is dict and set(value) == set(fields), where + ' fields differ')
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def identity(value):
|
|
64
|
+
require(type(value) is str and 0 < len(value) <= 256
|
|
65
|
+
and not any(ord(c) < 32 for c in value), 'Invalid record identity')
|
|
66
|
+
return value
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def checksum(value):
|
|
70
|
+
require(type(value) is str and len(value) == 64
|
|
71
|
+
and all(c in '0123456789abcdef' for c in value), 'Expected lowercase SHA256')
|
|
72
|
+
return value
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def ids(values):
|
|
76
|
+
require(type(values) is list and len(values) <= 1_000_000, 'Expected bounded ID list')
|
|
77
|
+
for value in values:
|
|
78
|
+
identity(value)
|
|
79
|
+
require(len(set(values)) == len(values), 'Duplicate record identity')
|
|
80
|
+
return set(values)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def facts(values):
|
|
84
|
+
require(values is None or type(values) is list and all(type(row) is list and len(row)==2
|
|
85
|
+
and type(row[0]) is str and type(row[1]) is int and row[1]>=0 for row in values), 'Invalid title/sentence-index supporting facts')
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def make_case(case_id, question, document_ids, *, references=None, history=(), query=None,
|
|
89
|
+
snapshot='unspecified', origin='synthetic', family='unspecified', split='development',
|
|
90
|
+
cluster_id=None, native_id=None, source_id='caller-supplied', context=None):
|
|
91
|
+
refs = dict.fromkeys(REFERENCE_FIELDS)
|
|
92
|
+
refs['native'] = {}
|
|
93
|
+
if references:
|
|
94
|
+
require(set(references) <= set(refs), 'Unknown reference fields')
|
|
95
|
+
refs.update(copy.deepcopy(references))
|
|
96
|
+
value = dict(schema_version=CASE_SCHEMA, id=case_id, question=question,
|
|
97
|
+
conversation_history=copy.deepcopy(list(history)), retrieval_query=query,
|
|
98
|
+
context=copy.deepcopy(context or {}),
|
|
99
|
+
corpus_snapshot_id=snapshot, document_ids=list(document_ids), references=refs,
|
|
100
|
+
metadata=dict(origin=origin, scenario_family=family, split=split,
|
|
101
|
+
cluster_id=cluster_id or case_id, source_id=source_id,
|
|
102
|
+
native_id=native_id or case_id))
|
|
103
|
+
return validate_case(value)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def validate_case(value, corpus=None):
|
|
107
|
+
exact(value, ('schema_version', 'id', 'question', 'conversation_history', 'retrieval_query',
|
|
108
|
+
'context', 'corpus_snapshot_id', 'document_ids', 'references', 'metadata'), 'case')
|
|
109
|
+
require(value['schema_version'] == CASE_SCHEMA, 'Unsupported RAG case schema')
|
|
110
|
+
identity(value['id']); identity(value['corpus_snapshot_id'])
|
|
111
|
+
require(type(value['question']) is str and 0 < len(value['question']) <= 100_000,
|
|
112
|
+
'Original question is required')
|
|
113
|
+
require(value['retrieval_query'] is None or type(value['retrieval_query']) is str,
|
|
114
|
+
'Retrieval query must be text or unknown')
|
|
115
|
+
require(type(value['context']) is dict and set(value['context']) <= {'as_of', 'locale'}
|
|
116
|
+
and all(type(v) is str for v in value['context'].values()), 'Invalid public task context')
|
|
117
|
+
require(type(value['conversation_history']) is list and len(value['conversation_history']) <= 1000,
|
|
118
|
+
'Invalid conversation history')
|
|
119
|
+
for turn in value['conversation_history']:
|
|
120
|
+
exact(turn, ('role', 'content'), 'history turn')
|
|
121
|
+
require(turn['role'] in ('system', 'user', 'assistant', 'tool')
|
|
122
|
+
and type(turn['content']) is str, 'Invalid history turn')
|
|
123
|
+
documents = ids(value['document_ids'])
|
|
124
|
+
if corpus is not None:
|
|
125
|
+
require(all(d in corpus for d in documents), 'Case references absent corpus documents')
|
|
126
|
+
meta = value['metadata']
|
|
127
|
+
exact(meta, ('origin', 'scenario_family', 'split', 'cluster_id', 'source_id', 'native_id'), 'metadata')
|
|
128
|
+
require(meta['origin'] in ('synthetic', 'public', 'owner-held', 'recorded'), 'Invalid origin')
|
|
129
|
+
for field in meta:
|
|
130
|
+
identity(meta[field])
|
|
131
|
+
refs = value['references']; exact(refs, REFERENCE_FIELDS, 'references')
|
|
132
|
+
# A known source may be absent from a particular candidate's context. Gold
|
|
133
|
+
# identities bind the shared corpus, not only documents delivered to it.
|
|
134
|
+
reference_universe = corpus
|
|
135
|
+
for field in ('answerable',):
|
|
136
|
+
require(refs[field] is None or type(refs[field]) is bool, 'Invalid answerability label')
|
|
137
|
+
require(refs['expected_action'] in (None, 'answer', 'abstain', 'clarify', 'no_rag'),
|
|
138
|
+
'Invalid expected action')
|
|
139
|
+
for field in ('answers', 'support_ids', 'required_atoms', 'required_relations'):
|
|
140
|
+
require(refs[field] is None or type(refs[field]) is list
|
|
141
|
+
and all(type(x) is str for x in refs[field]), 'Invalid reference text/ID list')
|
|
142
|
+
for field in ('support_ids',):
|
|
143
|
+
if refs[field] is not None:
|
|
144
|
+
support = ids(refs[field])
|
|
145
|
+
require(reference_universe is None or all(d in reference_universe for d in support), 'Gold support outside frozen corpus')
|
|
146
|
+
if refs['qrels'] is not None:
|
|
147
|
+
require(type(refs['qrels']) is dict
|
|
148
|
+
and all(type(v) is int and 0 <= v <= 100 for v in refs['qrels'].values()),
|
|
149
|
+
'Invalid native qrels')
|
|
150
|
+
keys=ids(list(refs['qrels']))
|
|
151
|
+
require(reference_universe is None or all(d in reference_universe for d in keys), 'Qrel outside frozen corpus')
|
|
152
|
+
require(type(refs['native']) is dict, 'Native annotations must be an object')
|
|
153
|
+
for field in ('atom_support', 'relation_support'):
|
|
154
|
+
if refs[field] is not None:
|
|
155
|
+
require(type(refs[field]) is dict, 'Invalid evidence mapping')
|
|
156
|
+
for required, support in refs[field].items():
|
|
157
|
+
identity(required)
|
|
158
|
+
support_ids=ids(support)
|
|
159
|
+
require(reference_universe is None or all(d in reference_universe for d in support_ids), 'Evidence support outside frozen corpus')
|
|
160
|
+
for field in ('support_facts', 'annotation_sets'):
|
|
161
|
+
require(refs[field] is None or type(refs[field]) is list, 'Invalid annotation list')
|
|
162
|
+
facts(refs['support_facts'])
|
|
163
|
+
if refs['annotation_sets'] is not None:
|
|
164
|
+
for annotation in refs['annotation_sets']:
|
|
165
|
+
exact(annotation,('answer','evidence_ids'),'answer annotation')
|
|
166
|
+
require(type(annotation['answer']) is str,'Invalid annotated answer')
|
|
167
|
+
if annotation['evidence_ids'] is not None:
|
|
168
|
+
evidence=ids(annotation['evidence_ids'])
|
|
169
|
+
require(reference_universe is None or all(d in reference_universe for d in evidence),'Annotated evidence outside corpus')
|
|
170
|
+
canonical(value)
|
|
171
|
+
return value
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def candidate_input(case, corpus):
|
|
175
|
+
validate_case(case, corpus)
|
|
176
|
+
# This projection is deliberately built field by field, including corpus
|
|
177
|
+
# rows. Native annotations and accidental extra corpus fields never enter it.
|
|
178
|
+
return copy.deepcopy(dict(case_id=case['id'], question=case['question'],
|
|
179
|
+
conversation_history=case['conversation_history'], retrieval_query=case['retrieval_query'],
|
|
180
|
+
context=case['context'],
|
|
181
|
+
corpus_snapshot_id=case['corpus_snapshot_id'], documents=[
|
|
182
|
+
dict(id=did, text=corpus[did]['text'], title=corpus[did].get('title', ''))
|
|
183
|
+
for did in case['document_ids']]))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def make_trace(case, corpus, *, text='', ranked=None, prompt=None, status='ok', abstained=False,
|
|
187
|
+
attempt_id='attempt-1', candidate_id='fixture', source_digest=None,
|
|
188
|
+
support_ids=None, support_facts=None):
|
|
189
|
+
value = dict(schema_version=TRACE_SCHEMA, case_id=case['id'], attempt_id=attempt_id,
|
|
190
|
+
rewritten_query=case['retrieval_query'], native={},
|
|
191
|
+
input_sha256=digest(candidate_input(case, corpus)), candidate=dict(id=candidate_id,
|
|
192
|
+
source_sha256=source_digest or digest({'fixture': candidate_id})),
|
|
193
|
+
stages=dict.fromkeys(STAGES), response=dict(status=status, text=text, abstained=abstained,
|
|
194
|
+
support_ids=support_ids, support_facts=support_facts), operational=dict(tokens=None, cost_usd=None))
|
|
195
|
+
value['stages']['retrieved'] = ranked
|
|
196
|
+
value['stages']['prompt'] = prompt
|
|
197
|
+
return validate_trace(value, case, corpus)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def validate_trace(value, case=None, corpus=None):
|
|
201
|
+
exact(value, ('schema_version', 'case_id', 'attempt_id', 'input_sha256', 'candidate', 'rewritten_query', 'native',
|
|
202
|
+
'stages', 'response', 'operational'), 'trace')
|
|
203
|
+
require(value['schema_version'] == TRACE_SCHEMA, 'Unsupported RAG trace schema')
|
|
204
|
+
identity(value['case_id']); identity(value['attempt_id']); checksum(value['input_sha256'])
|
|
205
|
+
require(value['rewritten_query'] is None or type(value['rewritten_query']) is str, 'Invalid captured rewritten query')
|
|
206
|
+
require(type(value['native']) is dict, 'Native trace capture must be an object')
|
|
207
|
+
exact(value['candidate'], ('id', 'source_sha256'), 'candidate')
|
|
208
|
+
identity(value['candidate']['id']); checksum(value['candidate']['source_sha256'])
|
|
209
|
+
exact(value['stages'], STAGES, 'trace stages')
|
|
210
|
+
previous = None
|
|
211
|
+
for stage in STAGES:
|
|
212
|
+
current = value['stages'][stage]
|
|
213
|
+
if current is None:
|
|
214
|
+
continue
|
|
215
|
+
current = ids(current)
|
|
216
|
+
if previous is not None:
|
|
217
|
+
require(current <= previous, 'Later stage invents documents')
|
|
218
|
+
if case is not None:
|
|
219
|
+
require(current <= set(case['document_ids']), 'Trace uses unavailable documents')
|
|
220
|
+
previous = current
|
|
221
|
+
response = value['response']
|
|
222
|
+
exact(response, ('status', 'text', 'abstained', 'support_ids', 'support_facts'), 'response')
|
|
223
|
+
require(response['status'] in ('ok', 'error', 'timeout', 'missing'), 'Unknown response status')
|
|
224
|
+
require(type(response['text']) is str and type(response['abstained']) is bool, 'Invalid response')
|
|
225
|
+
if response['support_ids'] is not None:
|
|
226
|
+
support = ids(response['support_ids'])
|
|
227
|
+
if case is not None:
|
|
228
|
+
require(support <= set(case['document_ids']), 'Predicted support outside case')
|
|
229
|
+
require(response['support_facts'] is None or type(response['support_facts']) is list,
|
|
230
|
+
'Invalid predicted supporting facts')
|
|
231
|
+
facts(response['support_facts'])
|
|
232
|
+
exact(value['operational'], ('tokens', 'cost_usd'), 'operations')
|
|
233
|
+
for item in value['operational'].values():
|
|
234
|
+
require(item is None or type(item) in (int, float) and math.isfinite(item) and item >= 0,
|
|
235
|
+
'Invalid recorded usage')
|
|
236
|
+
if case is not None:
|
|
237
|
+
require(value['case_id'] == case['id'], 'Trace case identity differs')
|
|
238
|
+
if corpus is not None:
|
|
239
|
+
require(value['input_sha256'] == digest(candidate_input(case, corpus)), 'Trace inputs changed')
|
|
240
|
+
canonical(value)
|
|
241
|
+
return value
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def judgment(case, trace, task, labels, *, origin='fixture', model='fixture', rubric='v1'):
|
|
245
|
+
value = dict(schema_version=JUDGMENT_SCHEMA, case_id=case['id'], trace_sha256=digest(trace),
|
|
246
|
+
input_sha256=trace['input_sha256'], task=task, labels=copy.deepcopy(labels),
|
|
247
|
+
provenance=dict(origin=origin, model=model, rubric=rubric), status='judged',
|
|
248
|
+
usage=dict(tokens=None, cost_usd=None))
|
|
249
|
+
validate_judgment(value, case, trace, task)
|
|
250
|
+
return value
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def validate_judgment(value, case=None, trace=None, task=None):
|
|
254
|
+
exact(value, ('schema_version', 'case_id', 'trace_sha256', 'input_sha256', 'task',
|
|
255
|
+
'labels', 'provenance', 'status', 'usage'), 'judgment')
|
|
256
|
+
require(value['schema_version'] == JUDGMENT_SCHEMA, 'Unknown judgment schema')
|
|
257
|
+
identity(value['case_id']); checksum(value['trace_sha256']); checksum(value['input_sha256'])
|
|
258
|
+
require((case is None) == (trace is None), 'Supply both case and trace to bind a review')
|
|
259
|
+
if case is not None:
|
|
260
|
+
require(value['case_id'] == case['id'] and value['trace_sha256'] == digest(trace)
|
|
261
|
+
and value['input_sha256'] == trace['input_sha256'], 'Judgment does not bind the exact case/trace inputs')
|
|
262
|
+
require(task is None or value['task'] == task, 'Judgment task differs')
|
|
263
|
+
identity(value['task']); require(type(value['labels']) is dict, 'Judgment labels must be an object')
|
|
264
|
+
exact(value['provenance'], ('origin', 'model', 'rubric'), 'judgment provenance')
|
|
265
|
+
require(value['provenance']['origin'] in ('fixture', 'model', 'human'), 'Invalid review origin')
|
|
266
|
+
for field in ('model', 'rubric'):
|
|
267
|
+
identity(value['provenance'][field])
|
|
268
|
+
require(value['status'] in ('judged', 'abstained', 'error', 'timeout'), 'Invalid judgment status')
|
|
269
|
+
exact(value['usage'], ('tokens', 'cost_usd'), 'judgment usage')
|
|
270
|
+
for item in value['usage'].values():
|
|
271
|
+
require(item is None or type(item) in (int, float) and math.isfinite(item) and item >= 0,
|
|
272
|
+
'Invalid judgment usage')
|
|
273
|
+
canonical(value)
|
|
274
|
+
return value
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def save_json(value, path):
|
|
278
|
+
target = Path(path)
|
|
279
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
280
|
+
with target.open('xb') as handle:
|
|
281
|
+
handle.write(canonical(value) + b'\n')
|
|
282
|
+
return hashlib.sha256(target.read_bytes()).hexdigest()
|