chore(semantics): add v6 release gate pipeline

- Add reproducible v6 boundary, blessing, and consensus-adjudication
  corpora, plus the tiny-transformer trainer and v6 release-gate
  evaluator that gate every candidate on the deployed baselines.
- Wire consensus-label merging, product-policy anchor evaluation, and
  sealed blessing benchmark review with their pytest coverage.
- Refresh open-training corpus generation, iterative retraining runner,
  and random-holdout evaluation so v6 candidates can be benchmarked
  end-to-end.
This commit is contained in:
Rocky
2026-08-29 11:51:42 +08:00
parent b275b6b0d9
commit aa37067f79
50 changed files with 12107 additions and 197 deletions
@@ -0,0 +1,563 @@
#!/usr/bin/env python3
"""Prepare and merge evidence-backed AI adjudication of consensus conflicts."""
from __future__ import annotations
import argparse
import hashlib
import json
import unicodedata
from collections import Counter
from pathlib import Path
from merge_consensus_labels_v2 import DOMAINS, INTENT_LABELS, stable_split
FLAG_FIELDS = {"ambiguous", "quotedOrMeta"}
LABEL_STATES = {"true", "false", "unknown"}
SENTIMENT_STATES = {"positive", "neutral", "negative", "unknown"}
RECORD_DISPOSITIONS = {"keep", "exclude-device-command"}
PRODUCT_POLICY_FIELDS = (
"task",
"question",
"invitation",
"complaint",
"followUpReminder",
"blessing",
"replyableMessage",
"assistantCommand",
"informationQuery",
"systemNotification",
"domain",
)
PROMPT_VERSION = "clipboard-adjudication-v5"
def normalize(value: str) -> str:
return " ".join(unicodedata.normalize("NFKC", value).casefold().split())
def read_json_lines(path: Path) -> list[dict]:
return [
json.loads(line)
for line in path.read_text(encoding="utf-8").splitlines()
if line.strip()
]
def write_json_lines(path: Path, records: list[dict]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as handle:
for record in records:
handle.write(
json.dumps(record, ensure_ascii=False, sort_keys=True) + "\n"
)
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def prepare(arguments: argparse.Namespace) -> dict:
conflicts = read_json_lines(arguments.conflicts)
include_policy_fields = getattr(
arguments,
"include_product_policy_fields",
False,
)
queue = [
{
"id": record["id"],
"text": record["text"],
"language": record["language"],
"unresolvedFields": list(
dict.fromkeys(
(
*PRODUCT_POLICY_FIELDS,
*record["unresolvedFields"],
)
if include_policy_fields
else record["unresolvedFields"]
)
),
}
for record in conflicts
]
if len({record["id"] for record in queue}) != len(queue):
raise ValueError("Conflict queue contains duplicate ids")
write_json_lines(arguments.queue, queue)
arguments.chunk_directory.mkdir(parents=True, exist_ok=True)
chunks = []
for start in range(0, len(queue), arguments.chunk_size):
index = len(chunks) + 1
path = arguments.chunk_directory / f"chunk-{index:03d}.jsonl"
values = queue[start : start + arguments.chunk_size]
write_json_lines(path, values)
chunks.append(
{
"path": str(path),
"records": len(values),
"sha256": sha256_file(path),
}
)
report = {
"schemaVersion": 1,
"promptVersion": PROMPT_VERSION,
"queueCount": len(queue),
"queueSHA256": sha256_file(arguments.queue),
"chunkSize": arguments.chunk_size,
"chunkCount": len(chunks),
"includesProductPolicyFields": include_policy_fields,
"productPolicyFields": (
list(PRODUCT_POLICY_FIELDS) if include_policy_fields else []
),
"chunks": chunks,
}
arguments.report.write_text(
json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
return report
def valid_state(field: str, value: object) -> bool:
if field == "sentiment":
return value in SENTIMENT_STATES
if field == "domain":
return value in {*DOMAINS, "unknown"}
return value in LABEL_STATES
def validate_adjudication(record: dict, queue_record: dict) -> dict:
identifier = queue_record["id"]
if record.get("id") != identifier:
raise ValueError(f"Unexpected adjudication id: {record.get('id')}")
disposition = record.get("recordDisposition")
if disposition not in RECORD_DISPOSITIONS:
raise ValueError(f"Invalid record disposition: {identifier}")
disposition_confidence = record.get("dispositionConfidence")
if (
not isinstance(disposition_confidence, (int, float))
or not 0 <= disposition_confidence <= 1
):
raise ValueError(f"Invalid disposition confidence: {identifier}")
disposition_evidence = record.get("dispositionEvidence")
text = normalize(queue_record["text"])
if (
not isinstance(disposition_evidence, str)
or not normalize(disposition_evidence)
or normalize(disposition_evidence) not in text
):
raise ValueError(f"Invalid disposition evidence: {identifier}")
expected = set(queue_record["unresolvedFields"])
for key in ("resolutions", "confidence", "evidence"):
if not isinstance(record.get(key), dict) or set(record[key]) != expected:
raise ValueError(f"{key} fields do not match unresolved fields: {identifier}")
for field in expected:
if not valid_state(field, record["resolutions"][field]):
raise ValueError(f"Invalid resolution for {identifier}/{field}")
confidence = record["confidence"][field]
if not isinstance(confidence, (int, float)) or not 0 <= confidence <= 1:
raise ValueError(f"Invalid confidence for {identifier}/{field}")
evidence = record["evidence"][field]
if not isinstance(evidence, str) or not normalize(evidence):
raise ValueError(f"Missing evidence for {identifier}/{field}")
if normalize(evidence) not in text:
raise ValueError(f"Evidence is not an exact text quote: {identifier}/{field}")
return {
"id": identifier,
"recordDisposition": disposition,
"dispositionConfidence": round(float(disposition_confidence), 4),
"dispositionEvidence": disposition_evidence,
"resolutions": {
field: record["resolutions"][field] for field in sorted(expected)
},
"confidence": {
field: round(float(record["confidence"][field]), 4)
for field in sorted(expected)
},
"evidence": {
field: record["evidence"][field] for field in sorted(expected)
},
}
def load_adjudicator(
paths: list[Path],
queue_by_id: dict[str, dict],
) -> dict[str, dict]:
values = []
for path in paths:
values.extend(read_json_lines(path))
by_id = {}
for value in values:
identifier = value.get("id")
if identifier not in queue_by_id:
raise ValueError(f"Unexpected adjudication id: {identifier}")
if identifier in by_id:
raise ValueError(f"Duplicate adjudication id: {identifier}")
by_id[identifier] = validate_adjudication(
value,
queue_by_id[identifier],
)
if set(by_id) != set(queue_by_id):
raise ValueError("Adjudicator outputs do not cover the complete queue")
return by_id
def resolved_base_field(conflict: dict, field: str) -> str:
votes = conflict.get("modelVotes", {}).get(field, {})
if not votes:
return "unknown"
value, count = max(votes.items(), key=lambda item: item[1])
return value if count >= 4 else "unknown"
def tier_c_record(conflict: dict, resolutions: dict, evidence: dict) -> dict:
states = {}
for field in (*INTENT_LABELS, "sentiment", "domain"):
states[field] = resolutions.get(
field,
resolved_base_field(conflict, field),
)
known_labels = [
field
for field in (*INTENT_LABELS, "sentiment", "domain")
if states[field] != "unknown"
]
return {
"id": f"adjudicated-v5-{conflict['id']}",
"sourceRecordID": conflict["id"],
"text": conflict["text"],
"language": conflict["language"],
"family": "ai_adjudicated_v5",
"split": stable_split(conflict["id"]),
**{
("replyable" if label == "replyableMessage" else label): (
states[label] == "true"
)
for label in INTENT_LABELS
},
"sentiment": (
states["sentiment"]
if states["sentiment"] != "unknown"
else "neutral"
),
"domain": (
states["domain"] if states["domain"] != "unknown" else None
),
"knownLabels": known_labels,
"labelQualityTier": "C",
"sampleWeight": 0.35,
"promptVersion": PROMPT_VERSION,
"adjudicationEvidence": evidence,
}
def merge(arguments: argparse.Namespace) -> dict:
conflicts = read_json_lines(arguments.conflicts)
conflicts_by_id = {record["id"]: record for record in conflicts}
queue = read_json_lines(arguments.queue)
queue_by_id = {record["id"]: record for record in queue}
if set(conflicts_by_id) != set(queue_by_id):
raise ValueError("Conflict and adjudication queue ids differ")
adjudicator_a = load_adjudicator(arguments.adjudicator_a, queue_by_id)
adjudicator_b = load_adjudicator(arguments.adjudicator_b, queue_by_id)
accepted = []
excluded = []
remaining = []
rejection_reasons = Counter()
for identifier in sorted(queue_by_id):
conflict = conflicts_by_id[identifier]
first = adjudicator_a[identifier]
second = adjudicator_b[identifier]
resolutions = {}
evidence = {}
rejected_fields = {}
first_disposition = first["recordDisposition"]
second_disposition = second["recordDisposition"]
disposition_reasons = []
if first_disposition != second_disposition:
disposition_reasons.append("adjudicator-disagreement")
if min(
first["dispositionConfidence"],
second["dispositionConfidence"],
) < arguments.minimum_confidence:
disposition_reasons.append("low-confidence")
if disposition_reasons:
rejected_fields["recordDisposition"] = sorted(
set(disposition_reasons)
)
rejection_reasons.update(set(disposition_reasons))
elif first_disposition == "exclude-device-command":
excluded.append(
{
"id": identifier,
"text": conflict["text"],
"language": conflict["language"],
"disposition": first_disposition,
"promptVersion": PROMPT_VERSION,
"evidence": {
arguments.adjudicator_a_name: first[
"dispositionEvidence"
],
arguments.adjudicator_b_name: second[
"dispositionEvidence"
],
},
}
)
continue
for field in queue_by_id[identifier]["unresolvedFields"]:
first_value = first["resolutions"][field]
second_value = second["resolutions"][field]
reasons = []
if first_value != second_value:
reasons.append("adjudicator-disagreement")
if "unknown" in {first_value, second_value}:
reasons.append("unknown")
if min(
first["confidence"][field],
second["confidence"][field],
) < arguments.minimum_confidence:
reasons.append("low-confidence")
if field == "ambiguous" and first_value == "true":
reasons.append("materially-ambiguous")
if reasons:
rejected_fields[field] = sorted(set(reasons))
rejection_reasons.update(set(reasons))
continue
resolutions[field] = first_value
evidence[field] = {
arguments.adjudicator_a_name: first["evidence"][field],
arguments.adjudicator_b_name: second["evidence"][field],
}
if rejected_fields:
remaining.append(
{
**conflict,
"aiAdjudication": {
"rejectedFields": rejected_fields,
"adjudicatorA": first,
"adjudicatorB": second,
},
}
)
continue
accepted.append(
tier_c_record(conflict, resolutions, evidence)
)
write_json_lines(arguments.accepted, accepted)
write_json_lines(arguments.excluded, excluded)
write_json_lines(arguments.remaining, remaining)
report = {
"schemaVersion": 1,
"promptVersion": PROMPT_VERSION,
"queueCount": len(queue),
"queueSHA256": sha256_file(arguments.queue),
"minimumConfidence": arguments.minimum_confidence,
"acceptedTierCCount": len(accepted),
"excludedDeviceCommandCount": len(excluded),
"remainingHumanReviewCount": len(remaining),
"resolvedCount": len(accepted) + len(excluded),
"resolutionRate": round(
(len(accepted) + len(excluded)) / max(len(queue), 1),
4,
),
"rejectionReasonCounts": dict(sorted(rejection_reasons.items())),
"adjudicators": [
arguments.adjudicator_a_name,
arguments.adjudicator_b_name,
],
"acceptedLanguageCounts": dict(
sorted(Counter(record["language"] for record in accepted).items())
),
"remainingLanguageCounts": dict(
sorted(Counter(record["language"] for record in remaining).items())
),
}
arguments.report.write_text(
json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
return report
def review_priority(record: dict) -> tuple:
reasons = {
reason
for field_reasons in record["aiAdjudication"]["rejectedFields"].values()
for reason in field_reasons
}
severity = (
0 if "adjudicator-disagreement" in reasons else 1,
0 if "unknown" in reasons else 1,
0 if "materially-ambiguous" in reasons else 1,
)
return (*severity, record["id"])
def adjudicator_field_review(adjudication: dict, field: str) -> dict:
if field == "recordDisposition":
return {
"value": adjudication["recordDisposition"],
"confidence": adjudication["dispositionConfidence"],
"evidence": adjudication["dispositionEvidence"],
}
return {
"value": adjudication["resolutions"][field],
"confidence": adjudication["confidence"][field],
"evidence": adjudication["evidence"][field],
}
def review_sample(arguments: argparse.Namespace) -> dict:
records = read_json_lines(arguments.remaining)
grouped: dict[tuple[str, str], list[dict]] = {}
for record in records:
for field in record["aiAdjudication"]["rejectedFields"]:
grouped.setdefault((record["language"], field), []).append(record)
for values in grouped.values():
values.sort(key=review_priority)
selected = []
selected_ids = set()
offsets = {key: 0 for key in grouped}
keys = sorted(grouped)
while len(selected) < min(arguments.sample_size, len(records)):
added = False
for key in keys:
values = grouped[key]
while (
offsets[key] < len(values)
and values[offsets[key]]["id"] in selected_ids
):
offsets[key] += 1
if offsets[key] >= len(values):
continue
record = values[offsets[key]]
offsets[key] += 1
selected.append(record)
selected_ids.add(record["id"])
added = True
if len(selected) >= arguments.sample_size:
break
if not added:
break
output = []
for record in selected:
first = record["aiAdjudication"]["adjudicatorA"]
second = record["aiAdjudication"]["adjudicatorB"]
fields = record["aiAdjudication"]["rejectedFields"]
output.append(
{
"id": record["id"],
"text": record["text"],
"language": record["language"],
"fieldReviews": {
field: {
"rejectionReasons": fields[field],
"adjudicatorA": adjudicator_field_review(first, field),
"adjudicatorB": adjudicator_field_review(second, field),
}
for field in sorted(fields)
},
"humanDecision": {field: None for field in sorted(fields)},
"notes": "",
}
)
write_json_lines(arguments.sample, output)
report = {
"schemaVersion": 1,
"promptVersion": PROMPT_VERSION,
"remainingCount": len(records),
"sampleCount": len(output),
"sampleSHA256": sha256_file(arguments.sample),
"languageCounts": dict(
sorted(Counter(record["language"] for record in output).items())
),
"fieldCounts": dict(
sorted(
Counter(
field
for record in output
for field in record["fieldReviews"]
).items()
)
),
}
arguments.report.write_text(
json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
return report
def parser() -> argparse.ArgumentParser:
root = argparse.ArgumentParser()
commands = root.add_subparsers(dest="command", required=True)
prepare_parser = commands.add_parser("prepare")
prepare_parser.add_argument("--conflicts", type=Path, required=True)
prepare_parser.add_argument("--queue", type=Path, required=True)
prepare_parser.add_argument("--chunk-directory", type=Path, required=True)
prepare_parser.add_argument("--chunk-size", type=int, default=80)
prepare_parser.add_argument(
"--include-product-policy-fields",
action="store_true",
)
prepare_parser.add_argument("--report", type=Path, required=True)
prepare_parser.set_defaults(handler=prepare)
merge_parser = commands.add_parser("merge")
merge_parser.add_argument("--conflicts", type=Path, required=True)
merge_parser.add_argument("--queue", type=Path, required=True)
merge_parser.add_argument(
"--adjudicator-a",
action="append",
type=Path,
required=True,
)
merge_parser.add_argument(
"--adjudicator-b",
action="append",
type=Path,
required=True,
)
merge_parser.add_argument("--adjudicator-a-name", required=True)
merge_parser.add_argument("--adjudicator-b-name", required=True)
merge_parser.add_argument("--minimum-confidence", type=float, default=0.9)
merge_parser.add_argument("--accepted", type=Path, required=True)
merge_parser.add_argument("--excluded", type=Path, required=True)
merge_parser.add_argument("--remaining", type=Path, required=True)
merge_parser.add_argument("--report", type=Path, required=True)
merge_parser.set_defaults(handler=merge)
sample_parser = commands.add_parser("sample-review")
sample_parser.add_argument("--remaining", type=Path, required=True)
sample_parser.add_argument("--sample", type=Path, required=True)
sample_parser.add_argument("--sample-size", type=int, default=60)
sample_parser.add_argument("--report", type=Path, required=True)
sample_parser.set_defaults(handler=review_sample)
return root
def main() -> None:
arguments = parser().parse_args()
report = arguments.handler(arguments)
print(
f"AI_ADJUDICATION_{arguments.command.upper()} "
+ " ".join(
f"{key}={value}"
for key, value in report.items()
if key.endswith("Count")
)
)
if __name__ == "__main__":
main()