Jev maintenance tools: shared client, spec coverage audit, voice check, nightly triage
This commit is contained in:
1 parent
0ff888e41b
commit
3ed0503bdf
9 files changed
+722
-48
No files matched your search
@@ -17,24 +17,18 @@ Exit code 1 when any claim is contradicted or has no source, so the script can g
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "jev"))
|
||||
from client import MODEL, api_key, system_one
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
API_URL = "https://api.typesafe.ai/v1/systemone"
|
||||
MODEL = "jev-latest"
|
||||
AUTO_ACCEPT = 0.8 # cookbook default; evaluate on our own claims before trusting it
|
||||
TOP_K = 3
|
||||
DEFAULT_SOURCES = ["docs/validation", "docs/known_unknowns.md", "SIMURGARRAY_FUNCTIONAL_SPEC.md"]
|
||||
CACHE = ROOT / ".cache" / "claims"
|
||||
|
||||
RELATION = {
|
||||
"type": "choice",
|
||||
@@ -255,33 +249,6 @@ def build_request(claim: Claim, cands: list[Passage]) -> dict[str, object]:
|
||||
return {"model": MODEL, "state": state, "questions": questions}
|
||||
|
||||
|
||||
def call_api(payload: dict[str, object], key: str, retries: int = 5) -> dict[str, object]:
|
||||
body = json.dumps(payload).encode()
|
||||
digest = hashlib.sha256(body).hexdigest()
|
||||
cached = CACHE / f"{digest}.json"
|
||||
if cached.exists():
|
||||
return json.loads(cached.read_text()) # type: ignore[no-any-return]
|
||||
headers = {
|
||||
"Authorization": f"Bearer {key}",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "simurgarray-claims/1",
|
||||
}
|
||||
req = urllib.request.Request(API_URL, data=body, method="POST", headers=headers)
|
||||
for attempt in range(retries):
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
res = json.loads(r.read())
|
||||
CACHE.mkdir(parents=True, exist_ok=True)
|
||||
cached.write_text(json.dumps(res))
|
||||
return res # type: ignore[no-any-return]
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code in (429, 529) and attempt < retries - 1:
|
||||
time.sleep(2**attempt)
|
||||
continue
|
||||
raise RuntimeError(f"TypeSafe API error {e.code}: {e.read()[:200]!r}") from e
|
||||
raise RuntimeError("TypeSafe API: retries exhausted")
|
||||
|
||||
|
||||
def verdict(answers: dict[str, dict[str, object]], threshold: float = AUTO_ACCEPT) -> tuple[str, str | None, float]:
|
||||
"""Combine per-section answers: any confident 'supports' wins; else a confident 'contradicts'; else a confident
|
||||
'says_nothing' from every section; anything else goes to a person."""
|
||||
@@ -306,15 +273,6 @@ def verdict(answers: dict[str, dict[str, object]], threshold: float = AUTO_ACCEP
|
||||
# ------------------------------------------------------------------ report
|
||||
|
||||
|
||||
def _key_from_file(path: Path) -> str:
|
||||
"""``TYPESAFE_API_KEY=...`` from a private local file (never from the repository)."""
|
||||
if path.exists():
|
||||
for ln in path.read_text().splitlines():
|
||||
if ln.startswith("TYPESAFE_API_KEY="):
|
||||
return ln.split("=", 1)[1].strip()
|
||||
return ""
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
|
||||
ap.add_argument("files", nargs="+", type=Path, help="texts to check (markdown or plain text)")
|
||||
@@ -323,7 +281,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
ap.add_argument("--threshold", type=float, default=AUTO_ACCEPT)
|
||||
ap.add_argument("--dry-run", action="store_true", help="extract claims and evidence, no API calls")
|
||||
a = ap.parse_args(argv)
|
||||
key = os.environ.get("TYPESAFE_API_KEY", "") or _key_from_file(Path.home() / ".config" / "typesafe" / "credentials")
|
||||
key = api_key()
|
||||
if not a.dry_run and not key:
|
||||
print("TYPESAFE_API_KEY is not set (use --dry-run to check extraction without the API).", file=sys.stderr)
|
||||
return 2
|
||||
@@ -338,8 +296,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
if a.dry_run:
|
||||
rows.append(("planned", c, cands[0], 0.0, f"{len(cands)} candidate(s)"))
|
||||
continue
|
||||
res = call_api(build_request(c, cands), key)
|
||||
answers = res["answers"] # type: ignore[index]
|
||||
req = build_request(c, cands)
|
||||
answers = system_one(req["state"], req["questions"], key) # type: ignore[arg-type]
|
||||
v, qid, conf = verdict(answers, a.threshold) # type: ignore[arg-type]
|
||||
src = cands[int(qid[1:])] if qid else cands[0]
|
||||
probs = ", ".join(f"{q}: {answers[q]['choice']} {answers[q]['confidence']:.2f}" for q in answers) # type: ignore[index]
|
||||
|
||||
Reference in new issue
Block a user