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/pytest_plugin.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Opt-in pytest fixture over the existing synchronous Plan/Run/Report path.
|
|
2
|
+
|
|
3
|
+
Load with ``pytest -p sphynx.pytest_plugin``. Importing sphynx does not load
|
|
4
|
+
pytest. Every test supplies its own check; no acceptance rule is selected here.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
import inspect
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from .workflow_contracts import WorkflowContractError
|
|
15
|
+
from .workflows import Plan, Report
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EvaluationExecutionError(RuntimeError):
|
|
19
|
+
"""Execution/grading did not complete. The permitted report is retained."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, report: Report):
|
|
22
|
+
self.report = report
|
|
23
|
+
super().__init__(f"Evaluation is {report.status}; inspect run {report.run_id} "
|
|
24
|
+
f"and grading {report.grading_id} before checking quality")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class EvaluationCheckFailed(AssertionError):
|
|
28
|
+
"""The test's explicit check returned False for a completed report."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, report: Report):
|
|
31
|
+
self.report = report
|
|
32
|
+
super().__init__(f"Evaluation check returned False for run {report.run_id}, "
|
|
33
|
+
f"grading {report.grading_id}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def sphynx_evaluate(tmp_path_factory) -> Callable[..., Report]:
|
|
38
|
+
"""Run and grade a Plan, then apply the test's explicit boolean check.
|
|
39
|
+
|
|
40
|
+
Configuration/authorization errors keep their existing exception types.
|
|
41
|
+
Incomplete reports raise EvaluationExecutionError without invoking check.
|
|
42
|
+
Checks must return exactly bool. A False raises EvaluationCheckFailed.
|
|
43
|
+
Both exceptions retain the report; no new candidate retry is introduced.
|
|
44
|
+
"""
|
|
45
|
+
def evaluate(plan: Plan, *, check: Callable[[Report], bool],
|
|
46
|
+
output_dir: str | Path | None = None) -> Report:
|
|
47
|
+
if (not isinstance(plan, Plan) or not callable(check)
|
|
48
|
+
or inspect.iscoroutinefunction(check)
|
|
49
|
+
or inspect.iscoroutinefunction(getattr(check, "__call__", None))):
|
|
50
|
+
raise WorkflowContractError("sphynx_evaluate needs a Plan and a synchronous report check")
|
|
51
|
+
destination = output_dir if output_dir is not None else tmp_path_factory.mktemp("sx")
|
|
52
|
+
report = plan.run(destination).assess()
|
|
53
|
+
if report.status != "completed":
|
|
54
|
+
raise EvaluationExecutionError(report)
|
|
55
|
+
passed = check(report)
|
|
56
|
+
if type(passed) is not bool:
|
|
57
|
+
raise WorkflowContractError("the pytest report check must return exactly True or False")
|
|
58
|
+
if not passed:
|
|
59
|
+
raise EvaluationCheckFailed(report)
|
|
60
|
+
return report
|
|
61
|
+
|
|
62
|
+
return evaluate
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
"""Paired question wording, observed query calls and owner-reviewed query fidelity.
|
|
2
|
+
|
|
3
|
+
Local adapters are trusted Python code. The pilot remains provisional until a
|
|
4
|
+
new owner-reviewed release passes every review flag; hiding Python data is not
|
|
5
|
+
a security boundary against the machine's owner.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
import io
|
|
11
|
+
import time
|
|
12
|
+
from contextlib import redirect_stdout, redirect_stderr
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Callable, Mapping
|
|
15
|
+
|
|
16
|
+
from ._local_eval import (LocalEvaluationRef, LocalRunRef, LocalScorecard, capture, clustered_interval,
|
|
17
|
+
compare_scorecards, digest, encoded, identity, metric, pinned_file, read_json,
|
|
18
|
+
report_evaluation, require, save_evaluation, text, verified_capture)
|
|
19
|
+
from .contracts import ContractError
|
|
20
|
+
from .retrieval import _strict_json
|
|
21
|
+
|
|
22
|
+
QUESTION_ROBUSTNESS_PACK_ID = "rag.question_robustness.v1"
|
|
23
|
+
QUESTION_ROBUSTNESS_INTERFACE = "sphynx.rag.question_robustness.v1"
|
|
24
|
+
PILOT_MANIFEST_SHA256 = "cbb64ec35a6798f3f500ef61adbd48434b7433ea6b18e8cdc1674eb3295c5b78"
|
|
25
|
+
METRIC_RELEASE = "rag.question_robustness.metric.v1"
|
|
26
|
+
GRADER_RELEASE = "rag.question_robustness.grader.v1"
|
|
27
|
+
VARIANTS = ("direct", "embedded", "implicit", "distractor")
|
|
28
|
+
ACTIONS = ("CALL_RAG", "ASK_BUYER", "ANSWER_FROM_CONTEXT")
|
|
29
|
+
_FAMILY_FLAGS = ("anchor_identity", "intent_constraints", "routing_label", "semantic_evidence_sufficiency",
|
|
30
|
+
"temporal_scope", "variant_equivalence")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class QuestionRobustnessPack:
|
|
34
|
+
def __init__(self, manifest_digest, cases, corpus, inputs, reviewed):
|
|
35
|
+
self.cases, self.corpus, self.inputs = cases, corpus, inputs
|
|
36
|
+
self.identity = {"pack_id": QUESTION_ROBUSTNESS_PACK_ID, "pack_release": "v1.external",
|
|
37
|
+
"dataset_digest": manifest_digest, "corpus_digest": identity(corpus),
|
|
38
|
+
"cohort_digest": identity(cases), "label_status": "owner_reviewed" if reviewed else "provisional",
|
|
39
|
+
"development_only": True, "human_review_complete": reviewed}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def load_question_robustness_pack(data_dir: str | Path, *, expected_sha256: str = PILOT_MANIFEST_SHA256) -> QuestionRobustnessPack:
|
|
43
|
+
root = Path(data_dir).expanduser().resolve(strict=True)
|
|
44
|
+
manifest_raw = (root / "manifest.json").read_bytes()
|
|
45
|
+
require(digest(manifest_raw) == "sha256:" + expected_sha256.removeprefix("sha256:"), "robustness manifest SHA-256 differs")
|
|
46
|
+
manifest = _strict_json(manifest_raw)
|
|
47
|
+
require(manifest.get("schema_version") == "sphynx.question_robustness.pilot.manifest.v1", "unsupported robustness manifest")
|
|
48
|
+
files = manifest.get("files")
|
|
49
|
+
required = {"owner-dataset.json", "candidate-input.jsonl", "corpus.json", "review/decisions.json"}
|
|
50
|
+
require(isinstance(files, dict) and required <= set(files), "robustness manifest lacks required files")
|
|
51
|
+
payloads = {name: pinned_file(root, name, entry) for name, entry in files.items()}
|
|
52
|
+
owner = _strict_json(payloads["owner-dataset.json"])
|
|
53
|
+
corpus = _strict_json(payloads["corpus.json"])
|
|
54
|
+
review = _strict_json(payloads["review/decisions.json"])
|
|
55
|
+
require(owner.get("schema_version") == "sphynx.rag.question_robustness.pilot.draft.v1"
|
|
56
|
+
and corpus.get("schema_version") == "sphynx.rag.question_robustness.pilot.corpus.v1"
|
|
57
|
+
and review.get("schema_version") == "sphynx.question_robustness.owner_review.v1", "unsupported pilot schemas")
|
|
58
|
+
chunks = corpus.get("chunks")
|
|
59
|
+
require(isinstance(chunks, list) and chunks, "pilot corpus must contain chunks")
|
|
60
|
+
chunk_map = {}
|
|
61
|
+
for chunk in chunks:
|
|
62
|
+
require(isinstance(chunk, dict) and set(chunk) == {"chunk_id", "customer", "document_id", "source_class", "source_uri", "text", "text_sha256"},
|
|
63
|
+
"invalid corpus chunk fields")
|
|
64
|
+
for key in chunk:
|
|
65
|
+
text(chunk[key], "chunk." + key)
|
|
66
|
+
require(digest(chunk["text"].encode("utf-8")) == "sha256:" + chunk["text_sha256"].removeprefix("sha256:"), "chunk text digest differs")
|
|
67
|
+
require(chunk["chunk_id"] not in chunk_map, "duplicate corpus chunk")
|
|
68
|
+
chunk_map[chunk["chunk_id"]] = chunk
|
|
69
|
+
cases = owner.get("cases")
|
|
70
|
+
families = owner.get("families")
|
|
71
|
+
require(isinstance(cases, list) and cases and isinstance(families, list), "pilot must contain cases and families")
|
|
72
|
+
case_map, family_map = {}, {}
|
|
73
|
+
for case in cases:
|
|
74
|
+
cid = text(case.get("case_id"), "case_id")
|
|
75
|
+
require(cid not in case_map, "duplicate robustness case")
|
|
76
|
+
case_map[cid] = case
|
|
77
|
+
text(case.get("customer"), "customer")
|
|
78
|
+
text(case.get("user_turn"), "user_turn")
|
|
79
|
+
require(isinstance(case.get("conversation_context"), list), "context must be the frozen JSON list")
|
|
80
|
+
refs = case.get("references")
|
|
81
|
+
require(isinstance(refs, dict) and refs.get("expected_action") in ACTIONS, "case requires an owner routing reference")
|
|
82
|
+
groups = refs.get("evidence_groups")
|
|
83
|
+
require(isinstance(groups, list), "reference evidence groups must be a list")
|
|
84
|
+
group_ids = set()
|
|
85
|
+
for group in groups:
|
|
86
|
+
gid = text(group.get("evidence_group_id"), "evidence_group_id")
|
|
87
|
+
anchors = group.get("acceptable_chunk_ids")
|
|
88
|
+
require(gid not in group_ids and type(group.get("required")) is bool
|
|
89
|
+
and isinstance(anchors, list) and anchors and len(set(anchors)) == len(anchors), "invalid evidence group")
|
|
90
|
+
group_ids.add(gid)
|
|
91
|
+
require(all(a in chunk_map and chunk_map[a]["customer"] == case["customer"] for a in anchors), "reference crosses customer scope")
|
|
92
|
+
if case.get("family_id"):
|
|
93
|
+
require(case.get("variant") in VARIANTS and refs["expected_action"] == "CALL_RAG"
|
|
94
|
+
and any(group["required"] for group in groups), "positive family must be corpus-answerable CALL_RAG")
|
|
95
|
+
rubric = refs.get("intent_rubric")
|
|
96
|
+
require(isinstance(rubric, dict) and isinstance(rubric.get("required_constraints"), list), "family requires an intent rubric")
|
|
97
|
+
text(rubric.get("information_need"), "information_need")
|
|
98
|
+
constraints = rubric["required_constraints"]
|
|
99
|
+
require(len({text(c.get("constraint_id"), "constraint_id") for c in constraints}) == len(constraints), "duplicate constraint")
|
|
100
|
+
qrels = refs.get("graded_relevance")
|
|
101
|
+
require(isinstance(qrels, dict) and all(a in chunk_map and chunk_map[a]["customer"] == case["customer"]
|
|
102
|
+
and type(g) is int and 0 <= g <= 3 for a, g in qrels.items()), "invalid same-customer graded references")
|
|
103
|
+
require(all(qrels.get(a, 0) > 0 for group in groups for a in group["acceptable_chunk_ids"]), "evidence anchors need positive relevance references")
|
|
104
|
+
family_map.setdefault(case["family_id"], []).append(case)
|
|
105
|
+
else:
|
|
106
|
+
require(case.get("variant") is None and refs["expected_action"] != "CALL_RAG", "controls must be separate non-lookup cases")
|
|
107
|
+
require(len(families) == len(family_map) and len({f.get("family_id") for f in families}) == len(families), "family inventory differs")
|
|
108
|
+
for family in families:
|
|
109
|
+
siblings = family_map.get(family.get("family_id"), [])
|
|
110
|
+
require(len(siblings) == 4 and {c["variant"] for c in siblings} == set(VARIANTS)
|
|
111
|
+
and set(family.get("case_ids", [])) == {c["case_id"] for c in siblings}, "family must contain exactly four equivalent variants")
|
|
112
|
+
direct = next(c for c in siblings if c["variant"] == "direct")
|
|
113
|
+
if "references" in family:
|
|
114
|
+
require(family["references"] == direct["references"], "family reference differs from its variants")
|
|
115
|
+
require(all((c["customer"], c["conversation_context"], c["references"], c["split"])
|
|
116
|
+
== (direct["customer"], direct["conversation_context"], direct["references"], direct["split"]) for c in siblings),
|
|
117
|
+
"paired variants differ in context, scope, split or references")
|
|
118
|
+
inputs = [_strict_json(line) for line in payloads["candidate-input.jsonl"].splitlines() if line.strip()]
|
|
119
|
+
require(len(inputs) == len(cases) and {r.get("case_id") for r in inputs} == set(case_map), "candidate input cohort differs")
|
|
120
|
+
for row in inputs:
|
|
121
|
+
require(set(row) == {"case_id", "customer", "user_turn", "conversation_context"}
|
|
122
|
+
and row == {key: case_map[row["case_id"]][key] for key in row}, "candidate inputs differ or contain references")
|
|
123
|
+
# Reordering follows the frozen, shuffled candidate file, never family order.
|
|
124
|
+
ordered = [case_map[row["case_id"]] for row in inputs]
|
|
125
|
+
require(type(review.get("human_review_complete")) is bool, "review completeness must be boolean")
|
|
126
|
+
require(len(review.get("families", [])) == len(family_map)
|
|
127
|
+
and len(review.get("controls", [])) == sum(not c.get("family_id") for c in cases)
|
|
128
|
+
and {r.get("family_id") for r in review.get("families", [])} == set(family_map)
|
|
129
|
+
and {r.get("case_id") for r in review.get("controls", [])} == {c["case_id"] for c in cases if not c.get("family_id")}, "review inventory differs")
|
|
130
|
+
reviewed = review["human_review_complete"]
|
|
131
|
+
if reviewed:
|
|
132
|
+
require(all(c["references"].get("review_status") == "owner_reviewed" for c in cases), "case references still have draft review status")
|
|
133
|
+
for row in review["families"]:
|
|
134
|
+
require(all(row.get(flag) == "approved" for flag in _FAMILY_FLAGS), "family human review is incomplete")
|
|
135
|
+
for row in review["controls"]:
|
|
136
|
+
require(all(row.get(flag) == "approved" for flag in ("context_sufficiency_or_clarification", "non_lookup_label")), "control human review is incomplete")
|
|
137
|
+
for row in review["families"] + review["controls"]:
|
|
138
|
+
text(row.get("reviewed_by"), "reviewed_by")
|
|
139
|
+
text(row.get("reviewed_at"), "reviewed_at")
|
|
140
|
+
return QuestionRobustnessPack(digest(manifest_raw), ordered, chunks, inputs, reviewed)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _policy(mode: str, max_context_bytes: int) -> dict[str, Any]:
|
|
144
|
+
require(mode in ("routing_only", "observed_pipeline"), "mode must be routing_only or observed_pipeline")
|
|
145
|
+
require(type(max_context_bytes) is int and max_context_bytes > 0, "context budget must be a positive integer")
|
|
146
|
+
return {"policy_version": "rag.question_robustness.policy.v1", "mode": mode, "max_search_calls": 4,
|
|
147
|
+
"candidate_depth": 50, "max_context_bytes": max_context_bytes, "context_separator": "\n\n",
|
|
148
|
+
"trials": 1, "timeout_seconds": None, "isolation": "trusted_local_in_process",
|
|
149
|
+
"cache_policy": "caller_managed_not_observed", "paired_weights": "equal_family_equal_variant"}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def build_question_robustness_request(pack: QuestionRobustnessPack, case_id: str, *,
|
|
153
|
+
mode: str = "routing_only", max_context_bytes: int = 16000) -> dict[str, Any]:
|
|
154
|
+
row = next((row for row in pack.inputs if row["case_id"] == case_id), None)
|
|
155
|
+
require(row is not None, "unknown robustness case")
|
|
156
|
+
request = {"schema_version": "sphynx.rag.question_robustness.request.v1", "pack_id": QUESTION_ROBUSTNESS_PACK_ID,
|
|
157
|
+
**copy.deepcopy(row), "corpus_digest": pack.identity["corpus_digest"],
|
|
158
|
+
"execution_policy": _policy(mode, max_context_bytes)}
|
|
159
|
+
if mode == "observed_pipeline":
|
|
160
|
+
request["corpus"] = [copy.deepcopy(c) for c in pack.corpus if c["customer"] == row["customer"]]
|
|
161
|
+
request["request_sha256"] = identity(request)
|
|
162
|
+
return request
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _stages(value: Any, request: dict[str, Any]) -> dict[str, Any]:
|
|
166
|
+
require(isinstance(value, dict) and set(value) == {"stages", "context_text"}, "retrieval evidence requires stages and exact context_text")
|
|
167
|
+
stages = value["stages"]
|
|
168
|
+
require(isinstance(stages, dict) and set(stages) == {"candidate", "fused", "reranked", "context"}, "all four retrieval stages must be captured")
|
|
169
|
+
allowed = {c["chunk_id"]: c for c in request.get("corpus", [])}
|
|
170
|
+
previous = None
|
|
171
|
+
for stage in ("candidate", "fused", "reranked", "context"):
|
|
172
|
+
ids = stages[stage]
|
|
173
|
+
require(isinstance(ids, list) and all(isinstance(cid, str) for cid in ids) and len(set(ids)) == len(ids), "stage IDs must be unique strings")
|
|
174
|
+
require(set(ids) <= set(allowed) and (previous is None or set(ids) <= previous), "stage contains unknown, wrong-customer or unobserved chunks")
|
|
175
|
+
require(len(ids) <= request["execution_policy"]["candidate_depth"], "stage exceeds candidate budget")
|
|
176
|
+
previous = set(ids)
|
|
177
|
+
expected = "\n\n".join(allowed[cid]["text"] for cid in stages["context"])
|
|
178
|
+
require(value["context_text"] == expected and len(expected.encode("utf-8")) <= request["execution_policy"]["max_context_bytes"], "final context differs from source bytes or exceeds budget")
|
|
179
|
+
return copy.deepcopy(value)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class ObservedSearch:
|
|
183
|
+
"""SDK wrapper around actual local search calls, distinct from claimed queries."""
|
|
184
|
+
def __init__(self, request: dict[str, Any]):
|
|
185
|
+
self._request = copy.deepcopy(request)
|
|
186
|
+
self._calls: list[dict[str, Any]] = []
|
|
187
|
+
|
|
188
|
+
def search(self, query: str, retrieve: Callable[[str, list[dict[str, Any]]], dict[str, Any]]) -> dict[str, Any]:
|
|
189
|
+
require(self._request["execution_policy"]["mode"] == "observed_pipeline", "routing-only run cannot observe retrieval")
|
|
190
|
+
text(query, "observed query")
|
|
191
|
+
require(len(self._calls) < self._request["execution_policy"]["max_search_calls"], "search-call budget exceeded")
|
|
192
|
+
record = {"query": query, "result": None, "failure": None}
|
|
193
|
+
self._calls.append(record)
|
|
194
|
+
start = time.perf_counter()
|
|
195
|
+
try:
|
|
196
|
+
raw = retrieve(query, copy.deepcopy(self._request["corpus"]))
|
|
197
|
+
# A malformed backend result must still leave a terminal capture;
|
|
198
|
+
# unserializable Python objects/NaN cannot be put in the manifest.
|
|
199
|
+
record["raw_result"] = _strict_json(encoded(raw))
|
|
200
|
+
record["result"] = _stages(raw, self._request)
|
|
201
|
+
return copy.deepcopy(record["result"])
|
|
202
|
+
except Exception as exc:
|
|
203
|
+
record["failure"] = type(exc).__name__
|
|
204
|
+
raise
|
|
205
|
+
finally:
|
|
206
|
+
record["latency_ms"] = (time.perf_counter() - start) * 1000
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def run_question_robustness(candidate: Mapping[str, Any], adapter: Callable, data_dir: str | Path,
|
|
210
|
+
output_dir: str | Path, *, mode: str = "routing_only", max_context_bytes: int = 16000,
|
|
211
|
+
expected_sha256: str = PILOT_MANIFEST_SHA256) -> LocalRunRef:
|
|
212
|
+
"""Call adapter(request, observed_search). No providers are chosen or called by the SDK."""
|
|
213
|
+
require(isinstance(candidate, Mapping) and callable(adapter), "candidate metadata and adapter callable are required")
|
|
214
|
+
candidate = {"name": text(candidate.get("name"), "candidate.name"), "version": text(candidate.get("version"), "candidate.version"),
|
|
215
|
+
"code_revision": text(candidate.get("code_revision"), "candidate.code_revision"),
|
|
216
|
+
"configuration": copy.deepcopy(candidate.get("configuration")), "interface_version": QUESTION_ROBUSTNESS_INTERFACE,
|
|
217
|
+
"identity_authority": "caller_declared_not_code_attestation"}
|
|
218
|
+
require(isinstance(candidate["configuration"], dict) and candidate["configuration"], "pin query/model/index/reranker configuration")
|
|
219
|
+
encoded(candidate) # Reject unserializable configuration before invoking any candidate.
|
|
220
|
+
pack = load_question_robustness_pack(data_dir, expected_sha256=expected_sha256)
|
|
221
|
+
policy = _policy(mode, max_context_bytes)
|
|
222
|
+
requests = [build_question_robustness_request(pack, row["case_id"], mode=mode, max_context_bytes=max_context_bytes) for row in pack.inputs]
|
|
223
|
+
def execute(raw_request, request):
|
|
224
|
+
observer = ObservedSearch(request)
|
|
225
|
+
out, err = io.StringIO(), io.StringIO()
|
|
226
|
+
failure = None
|
|
227
|
+
start = time.perf_counter()
|
|
228
|
+
try:
|
|
229
|
+
with redirect_stdout(out), redirect_stderr(err):
|
|
230
|
+
raw = encoded(adapter(copy.deepcopy(request), observer))
|
|
231
|
+
except Exception as exc:
|
|
232
|
+
failure = "adapter_error:" + type(exc).__name__
|
|
233
|
+
raw = b""
|
|
234
|
+
latency = (time.perf_counter() - start) * 1000
|
|
235
|
+
return raw, (out.getvalue() + err.getvalue()).encode("utf-8"), 0 if failure is None else 1, failure, latency, {"search_calls": observer._calls}
|
|
236
|
+
return capture(pack.identity, candidate, policy, requests, execute, output_dir)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _outcome(record: dict[str, Any], request: dict[str, Any]) -> dict[str, Any]:
|
|
240
|
+
raw = record["response"]
|
|
241
|
+
require(record["failure"] is None and isinstance(raw, dict), "candidate failed or response missing")
|
|
242
|
+
envelope = {"schema_version", "case_id", "request_sha256", "action"}
|
|
243
|
+
require(raw.get("schema_version") == "sphynx.rag.question_robustness.response.v1"
|
|
244
|
+
and raw.get("case_id") == request["case_id"] and raw.get("request_sha256") == request["request_sha256"], "response must echo its request identity")
|
|
245
|
+
require(raw.get("action") in ACTIONS and envelope <= set(raw) <= envelope | {"queries", "stages", "context_text", "diagnostics"}, "invalid robustness response")
|
|
246
|
+
if "queries" in raw:
|
|
247
|
+
require(isinstance(raw["queries"], list) and all(isinstance(q, str) and q.strip() for q in raw["queries"]), "claimed queries must be a list of text")
|
|
248
|
+
calls = record["observed"].get("search_calls")
|
|
249
|
+
require(isinstance(calls, list) and len(calls) <= request["execution_policy"]["max_search_calls"], "invalid observed search inventory")
|
|
250
|
+
if request["execution_policy"]["mode"] == "routing_only":
|
|
251
|
+
require(not calls and "stages" not in raw and "context_text" not in raw, "routing-only response cannot claim observed retrieval")
|
|
252
|
+
for call in calls:
|
|
253
|
+
text(call.get("query"), "observed query")
|
|
254
|
+
require(call.get("failure") is None and call.get("result") == _stages(call.get("raw_result"), request), "observed retrieval failed or changed")
|
|
255
|
+
require(not calls or raw["action"] == "CALL_RAG", "observed lookup contradicts routing action")
|
|
256
|
+
if calls and "queries" in raw:
|
|
257
|
+
require(raw["queries"] == [c["query"] for c in calls], "claimed query set differs from actual search calls")
|
|
258
|
+
final = None
|
|
259
|
+
if "stages" in raw or "context_text" in raw:
|
|
260
|
+
require(bool(calls), "final retrieval evidence needs an observed search call")
|
|
261
|
+
final = _stages({"stages": raw.get("stages"), "context_text": raw.get("context_text")}, request)
|
|
262
|
+
recovered = set(cid for c in calls for cid in c["result"]["stages"]["candidate"])
|
|
263
|
+
require(set(final["stages"]["candidate"]) <= recovered, "final stages include chunks never returned by observed search")
|
|
264
|
+
return {"action": raw["action"], "queries": [c["query"] for c in calls],
|
|
265
|
+
"query_set_sha256": identity([c["query"] for c in calls]) if calls else None,
|
|
266
|
+
"context_ids": final["stages"]["context"] if final else None}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def build_question_robustness_response(request: Mapping[str, Any], action: str, **evidence: Any) -> dict[str, Any]:
|
|
270
|
+
"""Construct an echoed response; the grader independently validates all evidence."""
|
|
271
|
+
require(action in ACTIONS and set(evidence) <= {"queries", "stages", "context_text", "diagnostics"}, "unsupported response fields")
|
|
272
|
+
return {"schema_version": "sphynx.rag.question_robustness.response.v1", "case_id": request["case_id"],
|
|
273
|
+
"request_sha256": request["request_sha256"], "action": action, **evidence}
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _judgments(annotations: Any, run: LocalRunRef, pack: QuestionRobustnessPack, outcomes: dict[str, Any]) -> dict[str, Any]:
|
|
277
|
+
if annotations is None:
|
|
278
|
+
return {}
|
|
279
|
+
require(isinstance(annotations, dict) and set(annotations) == {"schema_version", "review_protocol", "run_id", "dataset_digest", "judgments"}
|
|
280
|
+
and annotations["schema_version"] == "sphynx.question-query-review.v1"
|
|
281
|
+
and annotations["review_protocol"] == "whole-query-set-owner-review.v1"
|
|
282
|
+
and annotations["run_id"] == run.run_id and annotations["dataset_digest"] == pack.identity["dataset_digest"], "query judgments must bind this capture and review protocol")
|
|
283
|
+
require(isinstance(annotations["judgments"], list), "judgments must be a list")
|
|
284
|
+
indexed = {}
|
|
285
|
+
case_map = {c["case_id"]: c for c in pack.cases}
|
|
286
|
+
for judgment in annotations["judgments"]:
|
|
287
|
+
require(isinstance(judgment, dict) and set(judgment) == {"case_id", "query_set_sha256", "intent_preserved", "constraints_preserved", "reviewed_by", "reviewed_at"}, "invalid query judgment fields")
|
|
288
|
+
cid = judgment["case_id"]
|
|
289
|
+
outcome = outcomes.get(cid)
|
|
290
|
+
require(cid not in indexed and outcome is not None and outcome["action"] == "CALL_RAG" and bool(outcome["queries"])
|
|
291
|
+
and case_map[cid]["references"]["expected_action"] == "CALL_RAG"
|
|
292
|
+
and judgment["query_set_sha256"] == outcome["query_set_sha256"], "judgment does not bind an eligible observed query set")
|
|
293
|
+
constraint_ids = {c["constraint_id"] for c in case_map[cid]["references"]["intent_rubric"]["required_constraints"]}
|
|
294
|
+
constraints = judgment["constraints_preserved"]
|
|
295
|
+
require(type(judgment["intent_preserved"]) is bool and isinstance(constraints, dict) and set(constraints) == constraint_ids
|
|
296
|
+
and all(type(v) is bool for v in constraints.values()), "judgment must decide intent and every required constraint")
|
|
297
|
+
text(judgment["reviewed_by"], "reviewed_by")
|
|
298
|
+
text(judgment["reviewed_at"], "reviewed_at")
|
|
299
|
+
indexed[cid] = judgment
|
|
300
|
+
return indexed
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _paired(cases: list[dict[str, Any]], field: str) -> dict[str, Any]:
|
|
304
|
+
families = {}
|
|
305
|
+
for case in cases:
|
|
306
|
+
if case["family_id"]:
|
|
307
|
+
families.setdefault(case["family_id"], {})[case["variant"]] = case
|
|
308
|
+
slices, combined, bootstrap_rows = {}, [], []
|
|
309
|
+
for variant in VARIANTS[1:]:
|
|
310
|
+
values, rows = [], []
|
|
311
|
+
for fid, siblings in families.items():
|
|
312
|
+
direct, alternate = siblings["direct"][field], siblings[variant][field]
|
|
313
|
+
value = (direct - alternate) * 100 if direct is not None and alternate is not None else None
|
|
314
|
+
values.append(value)
|
|
315
|
+
combined.append(value)
|
|
316
|
+
if value is not None:
|
|
317
|
+
rows.append((siblings["direct"]["customer"], fid, value))
|
|
318
|
+
bootstrap_rows.append(rows[-1])
|
|
319
|
+
slices[variant] = {**metric(values), "ci95_family_bootstrap_pp": clustered_interval(rows) if None not in values else None}
|
|
320
|
+
return {**metric(combined), "weighting": "equal_family_equal_variant", "variant_slices": slices,
|
|
321
|
+
"ci95_family_bootstrap_pp": clustered_interval(bootstrap_rows) if None not in combined else None}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _compute(run: LocalRunRef, pack: QuestionRobustnessPack, annotations: Any) -> dict[str, Any]:
|
|
325
|
+
manifest = read_json(run.path / "manifest.json")
|
|
326
|
+
policy = manifest.get("execution_policy", {})
|
|
327
|
+
policy = _policy(policy.get("mode"), policy.get("max_context_bytes"))
|
|
328
|
+
requests = [build_question_robustness_request(pack, c["case_id"], mode=policy["mode"], max_context_bytes=policy["max_context_bytes"]) for c in pack.cases]
|
|
329
|
+
manifest, captures = verified_capture(run, pack.identity, requests, policy)
|
|
330
|
+
outcomes, errors = {}, {}
|
|
331
|
+
for case, record, request in zip(pack.cases, captures, requests):
|
|
332
|
+
try:
|
|
333
|
+
outcomes[case["case_id"]] = _outcome(record, request)
|
|
334
|
+
except (ContractError, ValueError, TypeError, KeyError):
|
|
335
|
+
errors[case["case_id"]] = record["failure"] or "invalid_response_or_observed_evidence"
|
|
336
|
+
judgments = _judgments(annotations, run, pack, outcomes)
|
|
337
|
+
rows = []
|
|
338
|
+
query_capture_missing, required_without_query = 0, 0
|
|
339
|
+
for case, record in zip(pack.cases, captures):
|
|
340
|
+
refs = case["references"]
|
|
341
|
+
positive = refs["expected_action"] == "CALL_RAG"
|
|
342
|
+
outcome = outcomes.get(case["case_id"])
|
|
343
|
+
action = outcome["action"] if outcome else None
|
|
344
|
+
lookup = float(action == "CALL_RAG") if outcome and positive else None
|
|
345
|
+
evidence = None
|
|
346
|
+
if positive and outcome:
|
|
347
|
+
if action != "CALL_RAG":
|
|
348
|
+
evidence = 0.0
|
|
349
|
+
elif outcome["queries"] and outcome["context_ids"] is not None:
|
|
350
|
+
evidence = float(all(set(g["acceptable_chunk_ids"]) & set(outcome["context_ids"]) for g in refs["evidence_groups"] if g["required"]))
|
|
351
|
+
intent, constraints = None, None
|
|
352
|
+
query_eligible = positive and (action == "CALL_RAG" or outcome is None)
|
|
353
|
+
constraint_count = len(refs.get("intent_rubric", {}).get("required_constraints", []))
|
|
354
|
+
if positive and (not outcome or not outcome["queries"]):
|
|
355
|
+
required_without_query += 1
|
|
356
|
+
if query_eligible and (outcome is None or not outcome["queries"]):
|
|
357
|
+
query_capture_missing += 1
|
|
358
|
+
judgment = judgments.get(case["case_id"])
|
|
359
|
+
if judgment:
|
|
360
|
+
intent = float(judgment["intent_preserved"])
|
|
361
|
+
constraints = sum(judgment["constraints_preserved"].values()) / constraint_count if constraint_count else None
|
|
362
|
+
rows.append({"case_id": case["case_id"], "customer": case["customer"], "family_id": case.get("family_id"),
|
|
363
|
+
"variant": case.get("variant"), "positive": positive, "action": action, "failure": errors.get(case["case_id"]),
|
|
364
|
+
"lookup": lookup, "evidence": evidence, "intent": intent, "constraints": constraints,
|
|
365
|
+
"query_eligible": query_eligible, "constraint_count": constraint_count,
|
|
366
|
+
"query_set_sha256": outcome["query_set_sha256"] if outcome else None,
|
|
367
|
+
"observed_queries": outcome["queries"] if outcome else [],
|
|
368
|
+
"runner_latency_ms": record["runner_latency_ms"]})
|
|
369
|
+
metrics = {"required_lookup_recall": metric([r["lookup"] for r in rows if r["positive"]]),
|
|
370
|
+
"unnecessary_call_rate": metric([float(r["action"] == "CALL_RAG") if r["action"] else None for r in rows if not r["positive"]]),
|
|
371
|
+
"query_intent_preservation": metric([r["intent"] for r in rows if r["query_eligible"]]),
|
|
372
|
+
"required_constraint_coverage": metric([r["constraints"] for r in rows if r["query_eligible"] and r["constraint_count"]]),
|
|
373
|
+
"complete_evidence_success": metric([r["evidence"] for r in rows if r["positive"]]),
|
|
374
|
+
"paired_lookup_drop_pp": _paired(rows, "lookup"), "paired_evidence_drop_pp": _paired(rows, "evidence"),
|
|
375
|
+
"valid_capture_coverage": metric([float(r["action"] is not None) for r in rows])}
|
|
376
|
+
unknown = sum(r["positive"] and r["action"] is None for r in rows)
|
|
377
|
+
for key in ("query_intent_preservation", "required_constraint_coverage"):
|
|
378
|
+
metrics[key]["eligibility_unknown_count"] = unknown
|
|
379
|
+
if policy["mode"] == "routing_only":
|
|
380
|
+
for key in ("query_intent_preservation", "required_constraint_coverage", "complete_evidence_success", "paired_evidence_drop_pp"):
|
|
381
|
+
if metrics[key]["missing_count"]:
|
|
382
|
+
metrics[key]["status"] = "unavailable"
|
|
383
|
+
elif query_capture_missing:
|
|
384
|
+
for key in ("query_intent_preservation", "required_constraint_coverage"):
|
|
385
|
+
metrics[key]["status"] = "incomplete"
|
|
386
|
+
slices = {variant: {"required_lookup_recall": metric([r["lookup"] for r in rows if r["variant"] == variant]),
|
|
387
|
+
"complete_evidence_success": metric([r["evidence"] for r in rows if r["variant"] == variant])} for variant in VARIANTS}
|
|
388
|
+
customers = {customer: {"required_lookup_recall": metric([r["lookup"] for r in rows if r["customer"] == customer and r["positive"]]),
|
|
389
|
+
"valid_capture_coverage": metric([float(r["action"] is not None) for r in rows if r["customer"] == customer])}
|
|
390
|
+
for customer in sorted({r["customer"] for r in rows})}
|
|
391
|
+
return {"schema_version": "sphynx.question-robustness.scorecard.v1", "pack_id": QUESTION_ROBUSTNESS_PACK_ID,
|
|
392
|
+
"track": "rag.question_robustness", "pack": pack.identity, "candidate": manifest["candidate"],
|
|
393
|
+
"execution_policy": policy, "runtime": manifest["runtime"], "trust_tier": "local_unverified", "development_only": True,
|
|
394
|
+
"label_status": pack.identity["label_status"], "human_review_complete": pack.identity["human_review_complete"],
|
|
395
|
+
"label_basis": "owner_reviewed" if pack.identity["human_review_complete"] else "provisional_source_anchor_recovery_and_assistant_authored_controls",
|
|
396
|
+
"metric_semantics": "Paired development wording; evidence recovery is a provisional source-anchor proxy until owner review.",
|
|
397
|
+
"metric_release": METRIC_RELEASE, "grader_release": GRADER_RELEASE, "metrics": metrics,
|
|
398
|
+
"variant_slices": slices, "customer_slices": customers, "cases": rows,
|
|
399
|
+
"diagnostics": {"required_opportunities_without_observed_query": required_without_query,
|
|
400
|
+
"claimed_lookup_without_observed_query": query_capture_missing,
|
|
401
|
+
"query_eligibility_unknown_count": unknown,
|
|
402
|
+
"query_review_count": len(judgments)},
|
|
403
|
+
"counts": {"attempted_count": len(rows), "success_count": len(outcomes), "failure_count": len(errors)}}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def grade_question_robustness(run: LocalRunRef, data_dir: str | Path, *, query_judgments_path: str | Path | None = None,
|
|
407
|
+
expected_sha256: str = PILOT_MANIFEST_SHA256) -> LocalEvaluationRef:
|
|
408
|
+
pack = load_question_robustness_pack(data_dir, expected_sha256=expected_sha256)
|
|
409
|
+
annotations = read_json(Path(query_judgments_path)) if query_judgments_path is not None else None
|
|
410
|
+
return save_evaluation(run, _compute(run, pack, annotations), annotations)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def report_question_robustness(evaluation: LocalEvaluationRef, data_dir: str | Path, *, expected_sha256: str = PILOT_MANIFEST_SHA256) -> LocalScorecard:
|
|
414
|
+
pack = load_question_robustness_pack(data_dir, expected_sha256=expected_sha256)
|
|
415
|
+
return report_evaluation(evaluation, lambda annotations: _compute(evaluation.run, pack, annotations))
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def compare_question_robustness(a: LocalEvaluationRef, b: LocalEvaluationRef, data_dir: str | Path, *, expected_sha256: str = PILOT_MANIFEST_SHA256) -> dict[str, Any]:
|
|
419
|
+
return compare_scorecards(report_question_robustness(a, data_dir, expected_sha256=expected_sha256),
|
|
420
|
+
report_question_robustness(b, data_dir, expected_sha256=expected_sha256))
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def create_query_review_template(run: LocalRunRef, data_dir: str | Path, output_path: str | Path, *, expected_sha256: str = PILOT_MANIFEST_SHA256) -> Path:
|
|
424
|
+
"""Write owner-only unfilled judgments; None decisions never confer credit."""
|
|
425
|
+
pack = load_question_robustness_pack(data_dir, expected_sha256=expected_sha256)
|
|
426
|
+
data = _compute(run, pack, None)
|
|
427
|
+
case_map = {c["case_id"]: c for c in pack.cases}
|
|
428
|
+
judgments = [{"case_id": r["case_id"], "query_set_sha256": r["query_set_sha256"], "intent_preserved": None,
|
|
429
|
+
"constraints_preserved": {c["constraint_id"]: None for c in case_map[r["case_id"]]["references"]["intent_rubric"]["required_constraints"]},
|
|
430
|
+
"reviewed_by": None, "reviewed_at": None} for r in data["cases"] if r["query_eligible"] and r["observed_queries"]]
|
|
431
|
+
template = {"schema_version": "sphynx.question-query-review.v1", "review_protocol": "whole-query-set-owner-review.v1",
|
|
432
|
+
"run_id": run.run_id, "dataset_digest": pack.identity["dataset_digest"], "judgments": judgments}
|
|
433
|
+
target = Path(output_path).expanduser().resolve()
|
|
434
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
435
|
+
with target.open("xb") as stream:
|
|
436
|
+
stream.write(encoded(template))
|
|
437
|
+
return target
|