mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-09-13 19:52:20 +02:00
801 lines
28 KiB
Python
801 lines
28 KiB
Python
#!/usr/bin/env python3
|
|
"""Email SFT overseer: expand curated seed traces across coherent fixture envs.
|
|
|
|
This script is intentionally conservative:
|
|
- it can enrich target users' fixture mailboxes from Alex's richer mailbox;
|
|
- it builds a run plan from audited keep rows plus Kimi/manual repairs;
|
|
- it does not mutate chat history or run the harness unless a future run
|
|
subcommand is added explicitly.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import copy
|
|
import json
|
|
import re
|
|
import sqlite3
|
|
import time
|
|
import uuid
|
|
from collections import Counter, defaultdict
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import httpx
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DB = ROOT / "data" / "app.db"
|
|
FIXTURE = ROOT / "data" / "fixture_email_messages.json"
|
|
AUDIT_DIR = ROOT / "data" / "audits"
|
|
OUT_DIR = ROOT / "data" / "evals"
|
|
DEFAULT_BASE_URL = "http://127.0.0.1:7011"
|
|
DEFAULT_PASSWORD = "SftDemo!2026"
|
|
DEFAULT_ENDPOINT_ID = "f3904562"
|
|
DEFAULT_ENDPOINT = "https://openrouter.ai/api/v1/chat/completions"
|
|
DEFAULT_MODEL = "moonshotai/kimi-k3"
|
|
|
|
|
|
SOURCE_OWNER = "sft_alex_creator"
|
|
TARGET_OWNERS = ["sft_maya_ops", "sft_jules_research", "sft_nora_design", "sft_omar_finance"]
|
|
|
|
|
|
PROFILES: dict[str, dict[str, str]] = {
|
|
"sft_alex_creator": {
|
|
"name": "Alex Rowan",
|
|
"first": "Alex",
|
|
"primary": "alex.rowan@rowan.studio",
|
|
"secondary": "alex.research@rowan.studio",
|
|
"primary_account": "Primary Inbox",
|
|
"secondary_account": "Research Mail",
|
|
"topic": "creator operations",
|
|
"org": "Rowan Studio",
|
|
"domain": "rowan.studio",
|
|
"secondary_domain": "northstar-research.co",
|
|
},
|
|
"sft_maya_ops": {
|
|
"name": "Maya Chen",
|
|
"first": "Maya",
|
|
"primary": "maya.chen@northstar-ops.co",
|
|
"secondary": "maya.research@northstar-ops.co",
|
|
"primary_account": "Primary Inbox",
|
|
"secondary_account": "Ops Research",
|
|
"topic": "operations planning",
|
|
"org": "Northstar Ops",
|
|
"domain": "northstar-ops.co",
|
|
"secondary_domain": "northstar-research.co",
|
|
},
|
|
"sft_jules_research": {
|
|
"name": "Jules Rivera",
|
|
"first": "Jules",
|
|
"primary": "jules.rivera@rivera-lab.org",
|
|
"secondary": "jules.review@rivera-lab.org",
|
|
"primary_account": "Primary Inbox",
|
|
"secondary_account": "Research Mail",
|
|
"topic": "research synthesis",
|
|
"org": "Rivera Lab",
|
|
"domain": "rivera-lab.org",
|
|
"secondary_domain": "northstar-research.co",
|
|
},
|
|
"sft_nora_design": {
|
|
"name": "Nora Patel",
|
|
"first": "Nora",
|
|
"primary": "nora.patel@northpier.design",
|
|
"secondary": "nora.research@northpier.design",
|
|
"primary_account": "Primary Inbox",
|
|
"secondary_account": "Design Research",
|
|
"topic": "product design",
|
|
"org": "Northpier Design",
|
|
"domain": "northpier.design",
|
|
"secondary_domain": "northstar-research.co",
|
|
},
|
|
"sft_omar_finance": {
|
|
"name": "Omar Singh",
|
|
"first": "Omar",
|
|
"primary": "omar.singh@bayledger.finance",
|
|
"secondary": "omar.research@bayledger.finance",
|
|
"primary_account": "Primary Inbox",
|
|
"secondary_account": "Finance Research",
|
|
"topic": "finance analysis",
|
|
"org": "Bayledger Finance",
|
|
"domain": "bayledger.finance",
|
|
"secondary_domain": "northstar-research.co",
|
|
},
|
|
}
|
|
|
|
|
|
SENDER_DOMAIN_MAP = {
|
|
"collab.rowan.studio": "collab.{domain}",
|
|
"metrics.rowan.studio": "metrics.{domain}",
|
|
"rowan.studio": "{domain}",
|
|
"mail.rowan.studio": "mail.{domain}",
|
|
}
|
|
|
|
|
|
def read_json(path: Path) -> Any:
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def write_json(path: Path, payload: Any) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(payload, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
|
|
|
|
|
|
def db() -> sqlite3.Connection:
|
|
con = sqlite3.connect(DB)
|
|
con.row_factory = sqlite3.Row
|
|
return con
|
|
|
|
|
|
def latest_deepseek_audit() -> Path:
|
|
paths = sorted(AUDIT_DIR.glob("email_sft_deepseek_audit_sft_alex_creator_*.jsonl"))
|
|
if not paths:
|
|
raise RuntimeError("No DeepSeek email audit found")
|
|
return paths[-1]
|
|
|
|
|
|
def repair_artifact_paths() -> list[Path]:
|
|
return sorted(AUDIT_DIR.glob("email_sft_kimi_repairs_*.jsonl")) + sorted(
|
|
AUDIT_DIR.glob("email_sft_kimi_repairs_manual_date_*.jsonl")
|
|
)
|
|
|
|
|
|
def fixture_rows() -> list[dict[str, Any]]:
|
|
payload = read_json(FIXTURE)
|
|
rows = payload.get("messages") if isinstance(payload, dict) else payload
|
|
if not isinstance(rows, list):
|
|
raise RuntimeError(f"Unexpected fixture shape: {type(payload).__name__}")
|
|
return rows
|
|
|
|
|
|
def save_fixture_rows(rows: list[dict[str, Any]]) -> None:
|
|
write_json(FIXTURE, {"messages": rows})
|
|
|
|
|
|
def owner_counts(rows: list[dict[str, Any]]) -> Counter:
|
|
return Counter(str(row.get("owner") or "") for row in rows)
|
|
|
|
|
|
def account_counts(rows: list[dict[str, Any]]) -> dict[str, Counter]:
|
|
out: dict[str, Counter] = defaultdict(Counter)
|
|
for row in rows:
|
|
owner = str(row.get("owner") or "")
|
|
account = str(row.get("account") or row.get("account_id") or "Primary Inbox")
|
|
out[owner][account] += 1
|
|
return out
|
|
|
|
|
|
def transform_text(text: str, target_owner: str) -> str:
|
|
src = PROFILES[SOURCE_OWNER]
|
|
tgt = PROFILES[target_owner]
|
|
replacements = {
|
|
src["name"]: tgt["name"],
|
|
src["first"]: tgt["first"],
|
|
src["primary"]: tgt["primary"],
|
|
src["secondary"]: tgt["secondary"],
|
|
src["topic"]: tgt["topic"],
|
|
src["org"]: tgt["org"],
|
|
"creator operations": tgt["topic"],
|
|
"creator ops": tgt["topic"],
|
|
"creator cohort": "workstream cohort",
|
|
"creator": "workstream",
|
|
"Rowan Studio": tgt["org"],
|
|
"rowan.studio": tgt["domain"],
|
|
"alex-rowan": f"{tgt['first'].lower()}-{tgt['name'].split()[-1].lower()}",
|
|
}
|
|
out = text
|
|
for old, new in replacements.items():
|
|
out = out.replace(old, new)
|
|
return out
|
|
|
|
|
|
def transform_email_address(addr: str, target_owner: str) -> str:
|
|
tgt = PROFILES[target_owner]
|
|
out = addr
|
|
for old_domain, new_template in SENDER_DOMAIN_MAP.items():
|
|
out = out.replace(old_domain, new_template.format(domain=tgt["domain"]))
|
|
return out
|
|
|
|
|
|
def retarget_row(row: dict[str, Any], target_owner: str, uid_offset: int) -> dict[str, Any]:
|
|
tgt = PROFILES[target_owner]
|
|
cloned = copy.deepcopy(row)
|
|
source_uid = str(row.get("uid") or "")
|
|
try:
|
|
new_uid = str(uid_offset + int(source_uid))
|
|
except ValueError:
|
|
new_uid = f"{uid_offset}{re.sub(r'\\W+', '', source_uid)[:8]}"
|
|
|
|
cloned["owner"] = target_owner
|
|
cloned["uid"] = new_uid
|
|
cloned["source_seed_owner"] = SOURCE_OWNER
|
|
cloned["source_seed_uid"] = source_uid
|
|
cloned["overseer_generated"] = True
|
|
cloned["overseer_version"] = 1
|
|
|
|
account_id = str(row.get("account_id") or "primary-inbox")
|
|
if account_id == "research-mail":
|
|
cloned["account_id"] = "research-mail"
|
|
cloned["account"] = tgt["secondary_account"]
|
|
cloned["account_email"] = tgt["secondary"]
|
|
cloned["to"] = f"{tgt['name']} <{tgt['secondary']}>"
|
|
else:
|
|
cloned["account_id"] = "primary-inbox"
|
|
cloned["account"] = tgt["primary_account"]
|
|
cloned["account_email"] = tgt["primary"]
|
|
cloned["to"] = f"{tgt['name']} <{tgt['primary']}>"
|
|
|
|
for key in ["subject", "summary", "body", "message_id", "references"]:
|
|
if isinstance(cloned.get(key), str):
|
|
cloned[key] = transform_text(cloned[key], target_owner)
|
|
for key in ["from", "sender"]:
|
|
if isinstance(cloned.get(key), str):
|
|
cloned[key] = transform_email_address(transform_text(cloned[key], target_owner), target_owner)
|
|
|
|
if cloned.get("message_id"):
|
|
cloned["message_id"] = f"<sft-overseer-{target_owner}-{new_uid}@mail.{tgt['domain']}>"
|
|
|
|
for att in cloned.get("attachments") or []:
|
|
if isinstance(att, dict):
|
|
for key in ["filename", "content"]:
|
|
if isinstance(att.get(key), str):
|
|
att[key] = transform_text(att[key], target_owner)
|
|
|
|
return cloned
|
|
|
|
|
|
def seed_target_fixtures(targets: list[str], *, dry_run: bool = False) -> dict[str, Any]:
|
|
rows = fixture_rows()
|
|
source_rows = [
|
|
row for row in rows
|
|
if row.get("owner") == SOURCE_OWNER and not row.get("overseer_generated")
|
|
]
|
|
before = owner_counts(rows)
|
|
kept = [
|
|
row for row in rows
|
|
if not (row.get("owner") in targets and row.get("overseer_generated"))
|
|
]
|
|
generated: list[dict[str, Any]] = []
|
|
for idx, target in enumerate(targets, start=1):
|
|
offset = 1000 * idx
|
|
generated.extend(retarget_row(row, target, offset) for row in source_rows)
|
|
after_rows = kept + generated
|
|
after = owner_counts(after_rows)
|
|
summary = {
|
|
"source_owner": SOURCE_OWNER,
|
|
"source_rows": len(source_rows),
|
|
"targets": targets,
|
|
"removed_old_generated": len(rows) - len(kept),
|
|
"generated_rows": len(generated),
|
|
"before_counts": dict(sorted(before.items())),
|
|
"after_counts": dict(sorted(after.items())),
|
|
"dry_run": dry_run,
|
|
}
|
|
if not dry_run:
|
|
backup = FIXTURE.with_suffix(f".json.bak-{time.strftime('%Y%m%d_%H%M%S')}")
|
|
backup.write_text(FIXTURE.read_text(encoding="utf-8"), encoding="utf-8")
|
|
save_fixture_rows(after_rows)
|
|
summary["backup"] = str(backup)
|
|
return summary
|
|
|
|
|
|
def load_audit_rows() -> list[dict[str, Any]]:
|
|
return [json.loads(line) for line in latest_deepseek_audit().read_text(encoding="utf-8").splitlines() if line.strip()]
|
|
|
|
|
|
def load_repair_rows() -> dict[str, dict[str, Any]]:
|
|
repairs: dict[str, dict[str, Any]] = {}
|
|
for path in repair_artifact_paths():
|
|
for line in path.read_text(encoding="utf-8").splitlines():
|
|
if not line.strip():
|
|
continue
|
|
row = json.loads(line)
|
|
sid = str(row.get("session_id") or "")
|
|
if sid:
|
|
repairs[sid] = row
|
|
return repairs
|
|
|
|
|
|
def session_user_messages(session_id: str) -> list[str]:
|
|
con = db()
|
|
try:
|
|
return [
|
|
str(row["content"] or "")
|
|
for row in con.execute(
|
|
"SELECT content FROM chat_messages WHERE session_id = ? AND role = 'user' ORDER BY timestamp, id",
|
|
(session_id,),
|
|
)
|
|
if str(row["content"] or "").strip()
|
|
]
|
|
finally:
|
|
con.close()
|
|
|
|
|
|
def usable_seed_records(min_keep_score: int = 0) -> list[dict[str, Any]]:
|
|
audit_rows = load_audit_rows()
|
|
repairs = load_repair_rows()
|
|
seeds: list[dict[str, Any]] = []
|
|
for row in audit_rows:
|
|
sid = str(row.get("session_id") or "")
|
|
verdict = row.get("verdict")
|
|
score = int(row.get("trainable_score") or 0)
|
|
if verdict == "keep" and score >= min_keep_score:
|
|
users = session_user_messages(sid)
|
|
seeds.append({
|
|
"session_id": sid,
|
|
"source": "keep",
|
|
"score": score,
|
|
"session_name": row.get("session_name"),
|
|
"user_messages": users,
|
|
"first_user": users[0] if users else "",
|
|
})
|
|
elif verdict == "repair":
|
|
repair = repairs.get(sid)
|
|
if repair and repair.get("repair_decision") == "repair":
|
|
users = [
|
|
str(m.get("content") or "")
|
|
for m in repair.get("messages") or []
|
|
if m.get("role") == "user" and str(m.get("content") or "").strip()
|
|
]
|
|
seeds.append({
|
|
"session_id": sid,
|
|
"source": "repair",
|
|
"score": int(repair.get("sft_quality_after_repair") or score),
|
|
"session_name": row.get("session_name"),
|
|
"user_messages": users,
|
|
"first_user": users[0] if users else "",
|
|
})
|
|
seeds.sort(key=lambda item: (-int(item["score"]), str(item["session_name"] or "")))
|
|
return seeds
|
|
|
|
|
|
CONTEXTLESS_FIRST_TURN_RE = re.compile(
|
|
r"^\s*(?:"
|
|
r"yes\b|yeah\b|ok\b|okay\b|open (?:it|the att|the attachment)\b|"
|
|
r"read (?:it|the att|the attachment)\b|"
|
|
r"reply\b|draft reply\b|"
|
|
r".*\bthis email\b|.*\bthat email\b|.*\bopen it\b|.*\bthe attachment\b"
|
|
r")",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
|
|
def seed_is_standalone(seed: dict[str, Any]) -> bool:
|
|
first = str(seed.get("first_user") or "").strip()
|
|
if not first:
|
|
return False
|
|
if CONTEXTLESS_FIRST_TURN_RE.search(first):
|
|
return False
|
|
return True
|
|
|
|
|
|
def retarget_prompt(text: str, target_owner: str) -> str:
|
|
out = transform_text(text, target_owner)
|
|
target = PROFILES[target_owner]
|
|
# Keep prompts natural: "Alex" references inside user text should become the
|
|
# target user, but sender names such as Casey/Priya/Dana remain stable because
|
|
# matching fixture rows are generated for those senders.
|
|
out = out.replace(PROFILES[SOURCE_OWNER]["first"], target["first"])
|
|
return out
|
|
|
|
|
|
def build_plan(targets: list[str], per_target: int, min_keep_score: int) -> dict[str, Any]:
|
|
all_seeds = usable_seed_records(min_keep_score=min_keep_score)
|
|
seeds = [seed for seed in all_seeds if seed_is_standalone(seed)]
|
|
if not seeds:
|
|
raise RuntimeError("No usable seeds found. Run audit/repair first.")
|
|
cases: list[dict[str, Any]] = []
|
|
for target in targets:
|
|
for idx, seed in enumerate(seeds[:per_target], start=1):
|
|
turns = [retarget_prompt(msg, target) for msg in seed["user_messages"]]
|
|
cases.append({
|
|
"id": f"email_overseer_{target}_{idx:03d}_{seed['session_id'][:8]}",
|
|
"domain": "email",
|
|
"owner": target,
|
|
"source_owner": SOURCE_OWNER,
|
|
"source_session_id": seed["session_id"],
|
|
"source_type": seed["source"],
|
|
"source_score": seed["score"],
|
|
"session_name": seed["session_name"],
|
|
"turns": turns,
|
|
"current_date": "2026-08-24",
|
|
"timezone": "UTC",
|
|
"fixture_requirements": {
|
|
"mailbox_seeded_from": SOURCE_OWNER,
|
|
"target_primary": PROFILES[target]["primary"],
|
|
"target_secondary": PROFILES[target]["secondary"],
|
|
},
|
|
"acceptance": {
|
|
"must_use_email_tool": True,
|
|
"reject_bad_unavailable_answer": True,
|
|
"reject_claimed_action_without_tool": True,
|
|
"judge_with_deepseek": True,
|
|
"repair_with_kimi": True,
|
|
},
|
|
})
|
|
return {
|
|
"created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
"source_owner": SOURCE_OWNER,
|
|
"targets": targets,
|
|
"per_target": per_target,
|
|
"seed_count_available": len(seeds),
|
|
"seed_count_before_standalone_filter": len(all_seeds),
|
|
"seed_count_skipped_contextual_first_turn": len(all_seeds) - len(seeds),
|
|
"case_count": len(cases),
|
|
"cases": cases,
|
|
}
|
|
|
|
|
|
def write_plan(plan: dict[str, Any]) -> Path:
|
|
path = OUT_DIR / f"sft_email_overseer_plan_{time.strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:6]}.json"
|
|
write_json(path, plan)
|
|
return path
|
|
|
|
|
|
def login(client: httpx.Client, base_url: str, username: str, password: str) -> None:
|
|
res = client.post(
|
|
base_url.rstrip("/") + "/api/auth/login",
|
|
json={"username": username, "password": password, "remember": True},
|
|
timeout=30,
|
|
)
|
|
res.raise_for_status()
|
|
if not res.json().get("ok"):
|
|
raise RuntimeError(f"login failed for {username}: {res.text[:300]}")
|
|
|
|
|
|
def create_session(
|
|
client: httpx.Client,
|
|
*,
|
|
base_url: str,
|
|
owner: str,
|
|
case_id: str,
|
|
endpoint: str,
|
|
endpoint_id: str,
|
|
model: str,
|
|
) -> str:
|
|
res = client.post(
|
|
base_url.rstrip("/") + "/api/session",
|
|
data={
|
|
"name": f"SFT email overseer {owner} {case_id}",
|
|
"endpoint_url": endpoint,
|
|
"endpoint_id": endpoint_id,
|
|
"model": model,
|
|
"skip_validation": "true",
|
|
"rag": "false",
|
|
},
|
|
timeout=30,
|
|
)
|
|
res.raise_for_status()
|
|
return str(res.json()["id"])
|
|
|
|
|
|
def sse_events(response: httpx.Response) -> list[dict[str, Any]]:
|
|
events: list[dict[str, Any]] = []
|
|
event_name = "message"
|
|
data_lines: list[str] = []
|
|
for raw in response.iter_lines():
|
|
line = raw.decode("utf-8", "replace") if isinstance(raw, bytes) else raw
|
|
if line == "":
|
|
if data_lines:
|
|
raw_data = "\n".join(data_lines)
|
|
try:
|
|
payload = json.loads(raw_data)
|
|
except json.JSONDecodeError:
|
|
payload = {"type": event_name, "raw": raw_data}
|
|
events.append(payload)
|
|
event_name = "message"
|
|
data_lines = []
|
|
continue
|
|
if line.startswith("event:"):
|
|
event_name = line.split(":", 1)[1].strip()
|
|
elif line.startswith("data:"):
|
|
data_lines.append(line.split(":", 1)[1].lstrip())
|
|
if data_lines:
|
|
raw_data = "\n".join(data_lines)
|
|
try:
|
|
events.append(json.loads(raw_data))
|
|
except json.JSONDecodeError:
|
|
events.append({"type": event_name, "raw": raw_data})
|
|
return events
|
|
|
|
|
|
def event_text(event: dict[str, Any]) -> str:
|
|
for key in ("content", "text", "response", "message", "output"):
|
|
value = event.get(key)
|
|
if isinstance(value, str):
|
|
return value
|
|
return ""
|
|
|
|
|
|
def stream_turn(
|
|
client: httpx.Client,
|
|
*,
|
|
base_url: str,
|
|
session_id: str,
|
|
message: str,
|
|
endpoint: str,
|
|
endpoint_id: str,
|
|
model: str,
|
|
timeout: float,
|
|
) -> tuple[list[dict[str, Any]], str]:
|
|
form = {
|
|
"message": message,
|
|
"session": session_id,
|
|
"mode": "agent",
|
|
"agent_prompt_mode": "auto",
|
|
"selected_endpoint_id": endpoint_id,
|
|
"selected_endpoint_url": endpoint,
|
|
"selected_model": model,
|
|
"client_runtime_context": json.dumps({"timezone": "UTC", "tz_offset_min": 0}, separators=(",", ":")),
|
|
}
|
|
with client.stream(
|
|
"POST",
|
|
base_url.rstrip("/") + "/api/chat_stream",
|
|
data=form,
|
|
headers={"Accept": "text/event-stream", "X-Tz-Name": "UTC", "X-Tz-Offset": "0"},
|
|
timeout=timeout,
|
|
) as response:
|
|
response.raise_for_status()
|
|
events = sse_events(response)
|
|
final = ""
|
|
parts: list[str] = []
|
|
for event in events:
|
|
typ = str(event.get("type") or "")
|
|
text = event_text(event)
|
|
if not text:
|
|
continue
|
|
if typ == "final_response":
|
|
final = text
|
|
elif typ in {"token", "content", "assistant_delta", "message"}:
|
|
parts.append(text)
|
|
return events, (final or "".join(parts)).strip()
|
|
|
|
|
|
BAD_ANSWER_RE = re.compile(
|
|
r"\b(?:can't|cannot|don't have|do not have|not available|no .*tool|enable .*integration|setup .*integration|"
|
|
r"invalid credentials|not authenticated|i can only|i'm unable)\b",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
|
|
def tool_names(events: list[dict[str, Any]]) -> list[str]:
|
|
names = []
|
|
for event in events:
|
|
if event.get("type") == "tool_start" and event.get("tool"):
|
|
names.append(str(event["tool"]))
|
|
elif event.get("tool") and str(event.get("type") or "").startswith("tool"):
|
|
names.append(str(event["tool"]))
|
|
return names
|
|
|
|
|
|
def assistant_count(session_id: str) -> int:
|
|
con = db()
|
|
try:
|
|
return int(con.execute(
|
|
"SELECT COUNT(*) FROM chat_messages WHERE session_id = ? AND role = 'assistant'",
|
|
(session_id,),
|
|
).fetchone()[0])
|
|
finally:
|
|
con.close()
|
|
|
|
|
|
def latest_assistant_from_db(session_id: str, min_count: int) -> dict[str, Any]:
|
|
con = db()
|
|
try:
|
|
rows = list(con.execute(
|
|
"""
|
|
SELECT content, metadata, timestamp
|
|
FROM chat_messages
|
|
WHERE session_id = ? AND role = 'assistant'
|
|
ORDER BY timestamp, id
|
|
""",
|
|
(session_id,),
|
|
))
|
|
finally:
|
|
con.close()
|
|
if len(rows) <= min_count:
|
|
return {"content": "", "tool_events": [], "thinking": ""}
|
|
row = rows[-1]
|
|
meta: dict[str, Any] = {}
|
|
if row["metadata"]:
|
|
try:
|
|
meta = json.loads(row["metadata"])
|
|
except json.JSONDecodeError:
|
|
meta = {}
|
|
return {
|
|
"content": str(row["content"] or ""),
|
|
"tool_events": list(meta.get("tool_events") or []),
|
|
"thinking": str(meta.get("thinking") or ""),
|
|
}
|
|
|
|
|
|
def persisted_tool_names(tool_events: list[dict[str, Any]]) -> list[str]:
|
|
return [str(ev.get("tool") or "") for ev in tool_events if ev.get("tool")]
|
|
|
|
|
|
def score_run(case: dict[str, Any], turns: list[dict[str, Any]]) -> tuple[bool, list[str]]:
|
|
failures: list[str] = []
|
|
all_events = [event for turn in turns for event in turn.get("events", [])]
|
|
all_tools = [
|
|
name
|
|
for turn in turns
|
|
for name in (turn.get("persisted_tool_names") or turn.get("tool_names") or [])
|
|
]
|
|
combined_answer = "\n".join(str(turn.get("answer") or "") for turn in turns)
|
|
if any(str(event.get("type") or "") in {"error", "parse_error"} for event in all_events):
|
|
failures.append("stream_error")
|
|
if BAD_ANSWER_RE.search(combined_answer):
|
|
failures.append("bad_unavailable_answer")
|
|
if case.get("acceptance", {}).get("must_use_email_tool") and not any("email" in name for name in all_tools):
|
|
failures.append(f"missing_email_tool tools={all_tools}")
|
|
return not failures, failures
|
|
|
|
|
|
def run_plan(args: argparse.Namespace) -> dict[str, Any]:
|
|
plan = read_json(Path(args.plan))
|
|
cases = list(plan.get("cases") or [])
|
|
if args.owner:
|
|
owners = set(parse_targets(args.owner))
|
|
cases = [case for case in cases if case.get("owner") in owners]
|
|
cases = cases[args.offset : args.offset + args.limit]
|
|
results: list[dict[str, Any]] = []
|
|
clients: dict[str, httpx.Client] = {}
|
|
try:
|
|
for case in cases:
|
|
owner = str(case["owner"])
|
|
client = clients.get(owner)
|
|
if client is None:
|
|
client = httpx.Client(follow_redirects=True)
|
|
login(client, args.base_url, owner, args.password)
|
|
clients[owner] = client
|
|
session_id = create_session(
|
|
client,
|
|
base_url=args.base_url,
|
|
owner=owner,
|
|
case_id=case["id"],
|
|
endpoint=args.endpoint,
|
|
endpoint_id=args.endpoint_id,
|
|
model=args.model,
|
|
)
|
|
turn_results: list[dict[str, Any]] = []
|
|
started = time.time()
|
|
error = ""
|
|
try:
|
|
for message in case.get("turns") or []:
|
|
before = assistant_count(session_id)
|
|
events, streamed_answer = stream_turn(
|
|
client,
|
|
base_url=args.base_url,
|
|
session_id=session_id,
|
|
message=message,
|
|
endpoint=args.endpoint,
|
|
endpoint_id=args.endpoint_id,
|
|
model=args.model,
|
|
timeout=args.timeout,
|
|
)
|
|
persisted = latest_assistant_from_db(session_id, before)
|
|
answer = persisted["content"] or streamed_answer
|
|
ptools = persisted_tool_names(persisted["tool_events"])
|
|
turn_results.append({
|
|
"user": message,
|
|
"answer": answer,
|
|
"events": events,
|
|
"tool_names": tool_names(events),
|
|
"persisted_tool_names": ptools,
|
|
"persisted_tool_events": persisted["tool_events"],
|
|
})
|
|
passed, failures = score_run(case, turn_results)
|
|
except Exception as exc:
|
|
error = repr(exc)
|
|
passed = False
|
|
failures = [f"exception: {error}"]
|
|
results.append({
|
|
"id": case["id"],
|
|
"owner": owner,
|
|
"source_session_id": case.get("source_session_id"),
|
|
"session_id": session_id,
|
|
"pass": passed,
|
|
"failures": failures,
|
|
"turns": [
|
|
{
|
|
"user": turn["user"],
|
|
"answer": turn["answer"],
|
|
"tool_names": turn.get("persisted_tool_names") or turn["tool_names"],
|
|
}
|
|
for turn in turn_results
|
|
],
|
|
"elapsed_seconds": round(time.time() - started, 3),
|
|
"error": error,
|
|
})
|
|
finally:
|
|
for client in clients.values():
|
|
client.close()
|
|
out_dir = Path(args.out_dir)
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
payload = {
|
|
"plan": str(args.plan),
|
|
"created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
"results": results,
|
|
"summary": dict(Counter("pass" if row["pass"] else "fail" for row in results)),
|
|
}
|
|
write_json(out_dir / "actual_results.json", payload)
|
|
return payload
|
|
|
|
|
|
def status() -> dict[str, Any]:
|
|
rows = fixture_rows()
|
|
audit_rows = load_audit_rows() if latest_deepseek_audit().exists() else []
|
|
repairs = load_repair_rows()
|
|
repair_counts = Counter(row.get("repair_decision") for row in repairs.values())
|
|
usable = usable_seed_records()
|
|
return {
|
|
"fixture_counts": dict(sorted(owner_counts(rows).items())),
|
|
"fixture_accounts": {owner: dict(counter) for owner, counter in sorted(account_counts(rows).items())},
|
|
"audit_counts": dict(Counter(row.get("verdict") for row in audit_rows)),
|
|
"repair_artifact_counts": dict(repair_counts),
|
|
"usable_seed_count": len(usable),
|
|
"target_owners": TARGET_OWNERS,
|
|
}
|
|
|
|
|
|
def parse_targets(raw: str) -> list[str]:
|
|
if raw == "all":
|
|
return list(TARGET_OWNERS)
|
|
targets = [item.strip() for item in raw.split(",") if item.strip()]
|
|
unknown = [target for target in targets if target not in PROFILES or target == SOURCE_OWNER]
|
|
if unknown:
|
|
raise SystemExit(f"Unknown/non-target owners: {unknown}")
|
|
return targets
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Oversee email SFT fixture expansion and plan generation.")
|
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
|
|
sub.add_parser("status")
|
|
|
|
seed = sub.add_parser("seed-fixtures")
|
|
seed.add_argument("--targets", default="all", help="Comma list of target owners or 'all'")
|
|
seed.add_argument("--dry-run", action="store_true")
|
|
|
|
plan = sub.add_parser("build-plan")
|
|
plan.add_argument("--targets", default="all", help="Comma list of target owners or 'all'")
|
|
plan.add_argument("--per-target", type=int, default=95)
|
|
plan.add_argument("--min-keep-score", type=int, default=0)
|
|
|
|
run = sub.add_parser("run-plan")
|
|
run.add_argument("--plan", required=True)
|
|
run.add_argument("--owner", default="", help="Optional comma list of owners to run")
|
|
run.add_argument("--offset", type=int, default=0)
|
|
run.add_argument("--limit", type=int, default=4)
|
|
run.add_argument("--base-url", default=DEFAULT_BASE_URL)
|
|
run.add_argument("--password", default=DEFAULT_PASSWORD)
|
|
run.add_argument("--endpoint", default=DEFAULT_ENDPOINT)
|
|
run.add_argument("--endpoint-id", default=DEFAULT_ENDPOINT_ID)
|
|
run.add_argument("--model", default=DEFAULT_MODEL)
|
|
run.add_argument("--timeout", type=float, default=90)
|
|
run.add_argument("--out-dir", default=str(OUT_DIR / f"sft_email_overseer_run_{time.strftime('%Y%m%d_%H%M%S')}"))
|
|
|
|
args = parser.parse_args()
|
|
if args.cmd == "status":
|
|
print(json.dumps(status(), indent=2, ensure_ascii=True))
|
|
return 0
|
|
if args.cmd == "seed-fixtures":
|
|
summary = seed_target_fixtures(parse_targets(args.targets), dry_run=args.dry_run)
|
|
print(json.dumps(summary, indent=2, ensure_ascii=True))
|
|
return 0
|
|
if args.cmd == "build-plan":
|
|
built = build_plan(parse_targets(args.targets), args.per_target, args.min_keep_score)
|
|
path = write_plan(built)
|
|
print(json.dumps({"plan": str(path), "case_count": built["case_count"], "targets": built["targets"]}, indent=2))
|
|
return 0
|
|
if args.cmd == "run-plan":
|
|
payload = run_plan(args)
|
|
print(json.dumps({"summary": payload["summary"], "out": str(Path(args.out_dir) / "actual_results.json")}, indent=2))
|
|
return 0 if payload["summary"].get("fail", 0) == 0 else 1
|
|
raise AssertionError(args.cmd)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|