Sync local-intelligence eval harness
Browse files
eval/local_intelligence/cards.json
CHANGED
|
@@ -775,7 +775,7 @@
|
|
| 775 |
"m_prev_2"
|
| 776 |
]
|
| 777 |
},
|
| 778 |
-
"tokenEstimate":
|
| 779 |
}
|
| 780 |
},
|
| 781 |
"testCase": {
|
|
@@ -1401,4 +1401,4 @@
|
|
| 1401 |
]
|
| 1402 |
}
|
| 1403 |
}
|
| 1404 |
-
]
|
|
|
|
| 775 |
"m_prev_2"
|
| 776 |
]
|
| 777 |
},
|
| 778 |
+
"tokenEstimate": 96
|
| 779 |
}
|
| 780 |
},
|
| 781 |
"testCase": {
|
|
|
|
| 1401 |
]
|
| 1402 |
}
|
| 1403 |
}
|
| 1404 |
+
]
|
eval/local_intelligence/grader.py
CHANGED
|
@@ -8,18 +8,124 @@ FILLER_WORDS = {"um", "uh", "like", "basically", "actually", "okay"}
|
|
| 8 |
VERBISH_STARTERS = {
|
| 9 |
"call", "email", "book", "send", "schedule", "review", "fix", "update",
|
| 10 |
"draft", "ship", "plan", "remind", "buy", "pay", "renew", "submit",
|
|
|
|
| 11 |
}
|
| 12 |
DESTRUCTIVE_WORDS = {"delete", "remove", "erase", "wipe", "rm", "destroy"}
|
| 13 |
|
| 14 |
|
| 15 |
-
def
|
| 16 |
candidate = raw_text.strip()
|
| 17 |
candidate = re.sub(r"^```(?:json)?\s*", "", candidate)
|
| 18 |
-
candidate = re.sub(r"\s*```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
try:
|
| 20 |
-
return json.loads(candidate), None
|
| 21 |
except json.JSONDecodeError as exc:
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
def _contains_text(value: Any, needle: str) -> bool:
|
|
@@ -168,7 +274,11 @@ def _check(assertion: str, output: Any, test_input: dict[str, Any]) -> tuple[boo
|
|
| 168 |
clusters = _get(output, "clusters", _get(output, "assignments", []))
|
| 169 |
mapping = {}
|
| 170 |
for item in clusters if isinstance(clusters, list) else []:
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
ok = mapping.get("a1") and mapping.get("a1") == mapping.get("a2")
|
| 173 |
return ok, f"mapping={mapping!r}"
|
| 174 |
|
|
@@ -316,13 +426,16 @@ def _check(assertion: str, output: Any, test_input: dict[str, Any]) -> tuple[boo
|
|
| 316 |
|
| 317 |
|
| 318 |
def grade_card(card: dict, raw_text: str) -> dict[str, Any]:
|
| 319 |
-
parsed, parse_error = parse_json_output(raw_text)
|
| 320 |
results = []
|
| 321 |
if parse_error is not None:
|
| 322 |
return {
|
| 323 |
"passed": False,
|
| 324 |
"score": 0.0,
|
| 325 |
"parse_error": parse_error,
|
|
|
|
|
|
|
|
|
|
| 326 |
"assertions": [
|
| 327 |
{"assertion": assertion, "passed": False, "details": "output was not valid JSON"}
|
| 328 |
for assertion in card["testCase"]["assertions"]
|
|
@@ -330,16 +443,22 @@ def grade_card(card: dict, raw_text: str) -> dict[str, Any]:
|
|
| 330 |
"parsed": None,
|
| 331 |
}
|
| 332 |
|
|
|
|
|
|
|
| 333 |
for assertion in card["testCase"]["assertions"]:
|
| 334 |
-
passed, details = _check(assertion,
|
| 335 |
results.append({"assertion": assertion, "passed": passed, "details": details})
|
| 336 |
|
| 337 |
passed_count = sum(1 for result in results if result["passed"])
|
| 338 |
total = len(results)
|
|
|
|
| 339 |
return {
|
| 340 |
"passed": passed_count == total,
|
| 341 |
"score": round(passed_count / total, 4) if total else 0.0,
|
| 342 |
"parse_error": None,
|
|
|
|
|
|
|
|
|
|
| 343 |
"assertions": results,
|
| 344 |
-
"parsed":
|
| 345 |
}
|
|
|
|
| 8 |
VERBISH_STARTERS = {
|
| 9 |
"call", "email", "book", "send", "schedule", "review", "fix", "update",
|
| 10 |
"draft", "ship", "plan", "remind", "buy", "pay", "renew", "submit",
|
| 11 |
+
"investigate",
|
| 12 |
}
|
| 13 |
DESTRUCTIVE_WORDS = {"delete", "remove", "erase", "wipe", "rm", "destroy"}
|
| 14 |
|
| 15 |
|
| 16 |
+
def normalize_raw_output(raw_text: str) -> str:
|
| 17 |
candidate = raw_text.strip()
|
| 18 |
candidate = re.sub(r"^```(?:json)?\s*", "", candidate)
|
| 19 |
+
candidate = re.sub(r"\s*```", "", candidate)
|
| 20 |
+
candidate = re.sub(r"<turn\|.*?$", "", candidate).strip()
|
| 21 |
+
return candidate
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def parse_json_output(raw_text: str) -> tuple[Any, str | None, str]:
|
| 25 |
+
candidate = raw_text.strip()
|
| 26 |
try:
|
| 27 |
+
return json.loads(candidate), None, "exact"
|
| 28 |
except json.JSONDecodeError as exc:
|
| 29 |
+
normalized = normalize_raw_output(raw_text)
|
| 30 |
+
try:
|
| 31 |
+
return json.loads(normalized), None, "normalized"
|
| 32 |
+
except json.JSONDecodeError:
|
| 33 |
+
return None, str(exc), "failed"
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def normalize_card_output(card: dict, parsed: Any) -> Any:
|
| 37 |
+
card_id = card["id"]
|
| 38 |
+
|
| 39 |
+
if card_id == "action-item-extraction" and isinstance(parsed, list):
|
| 40 |
+
return {"items": parsed}
|
| 41 |
+
|
| 42 |
+
if card_id == "what-matters-summary" and isinstance(parsed, dict):
|
| 43 |
+
if "topPoints" not in parsed and "summary" in parsed:
|
| 44 |
+
parsed = dict(parsed)
|
| 45 |
+
parsed["topPoints"] = [
|
| 46 |
+
{
|
| 47 |
+
"point": item.get("point"),
|
| 48 |
+
"priority": item.get("priority", item.get("priority_score")),
|
| 49 |
+
"evidence": item.get("evidence"),
|
| 50 |
+
}
|
| 51 |
+
for item in parsed.get("summary", [])
|
| 52 |
+
if isinstance(item, dict)
|
| 53 |
+
]
|
| 54 |
+
return parsed
|
| 55 |
+
|
| 56 |
+
if card_id == "private-redaction-pass" and isinstance(parsed, dict):
|
| 57 |
+
parsed = dict(parsed)
|
| 58 |
+
if "redactedTranscript" not in parsed and "redacted_text" in parsed:
|
| 59 |
+
parsed["redactedTranscript"] = parsed["redacted_text"]
|
| 60 |
+
if "entities" not in parsed and "redacted_entities" in parsed:
|
| 61 |
+
parsed["entities"] = [
|
| 62 |
+
{
|
| 63 |
+
"value": item.get("value", item.get("entity")),
|
| 64 |
+
"type": item.get("type"),
|
| 65 |
+
"confidence": item.get("confidence"),
|
| 66 |
+
}
|
| 67 |
+
for item in parsed.get("redacted_entities", [])
|
| 68 |
+
if isinstance(item, dict)
|
| 69 |
+
]
|
| 70 |
+
return parsed
|
| 71 |
+
|
| 72 |
+
if card_id == "writing-style-memory" and isinstance(parsed, dict):
|
| 73 |
+
if "styledSummary" not in parsed and "rewritten_text" in parsed:
|
| 74 |
+
parsed = dict(parsed)
|
| 75 |
+
parsed["styledSummary"] = parsed["rewritten_text"]
|
| 76 |
+
return parsed
|
| 77 |
+
|
| 78 |
+
if card_id == "similar-memo-recall" and isinstance(parsed, dict):
|
| 79 |
+
if "matches" not in parsed and "top_matches" in parsed:
|
| 80 |
+
parsed = dict(parsed)
|
| 81 |
+
parsed["matches"] = [
|
| 82 |
+
{
|
| 83 |
+
"memoId": item.get("memoId", item.get("id")),
|
| 84 |
+
"relevance": item.get("relevance", item.get("relevance_score")),
|
| 85 |
+
"rationale": item.get("rationale"),
|
| 86 |
+
}
|
| 87 |
+
for item in parsed.get("top_matches", [])
|
| 88 |
+
if isinstance(item, dict)
|
| 89 |
+
]
|
| 90 |
+
return parsed
|
| 91 |
+
|
| 92 |
+
if card_id == "project-clustering" and isinstance(parsed, list):
|
| 93 |
+
return {"clusters": parsed}
|
| 94 |
+
|
| 95 |
+
if card_id == "calendar-intent-detection" and isinstance(parsed, dict):
|
| 96 |
+
parsed = dict(parsed)
|
| 97 |
+
if "startAtISO" not in parsed and isinstance(parsed.get("arguments"), dict):
|
| 98 |
+
arguments = parsed["arguments"]
|
| 99 |
+
contact = arguments.get("contact")
|
| 100 |
+
start_at = arguments.get("time")
|
| 101 |
+
if contact and start_at:
|
| 102 |
+
parsed["title"] = parsed.get("title") or f"Call {contact}"
|
| 103 |
+
parsed["startAtISO"] = start_at
|
| 104 |
+
return parsed
|
| 105 |
+
|
| 106 |
+
if card_id == "context-packet-builder" and isinstance(parsed, dict):
|
| 107 |
+
parsed = dict(parsed)
|
| 108 |
+
packet = parsed.get("packet")
|
| 109 |
+
if packet is None and isinstance(parsed.get("context_packet"), dict):
|
| 110 |
+
context_packet = parsed["context_packet"]
|
| 111 |
+
packet = {
|
| 112 |
+
"decisions": context_packet.get("decisions", []),
|
| 113 |
+
"activeTasks": context_packet.get("activeTasks", context_packet.get("active_tasks", [])),
|
| 114 |
+
"openQuestions": context_packet.get("openQuestions", context_packet.get("unresolved_questions", [])),
|
| 115 |
+
"relevantMemoIds": context_packet.get("relevantMemoIds", context_packet.get("relevant_memo_ids", [])),
|
| 116 |
+
}
|
| 117 |
+
parsed["packet"] = packet
|
| 118 |
+
if "tokenEstimate" not in parsed and packet is not None:
|
| 119 |
+
parsed["tokenEstimate"] = len(json.dumps(packet, ensure_ascii=True).split())
|
| 120 |
+
return parsed
|
| 121 |
+
|
| 122 |
+
if card_id == "local-agent-loop" and isinstance(parsed, dict):
|
| 123 |
+
parsed = dict(parsed)
|
| 124 |
+
if "verify" not in parsed and isinstance(parsed.get("steps"), dict):
|
| 125 |
+
parsed["verify"] = parsed["steps"].get("verify")
|
| 126 |
+
return parsed
|
| 127 |
+
|
| 128 |
+
return parsed
|
| 129 |
|
| 130 |
|
| 131 |
def _contains_text(value: Any, needle: str) -> bool:
|
|
|
|
| 274 |
clusters = _get(output, "clusters", _get(output, "assignments", []))
|
| 275 |
mapping = {}
|
| 276 |
for item in clusters if isinstance(clusters, list) else []:
|
| 277 |
+
if isinstance(item, dict) and isinstance(item.get("memoIds"), list):
|
| 278 |
+
for memo_id in item["memoIds"]:
|
| 279 |
+
mapping[memo_id] = item.get("clusterId")
|
| 280 |
+
else:
|
| 281 |
+
mapping[item.get("memoId") or item.get("id")] = item.get("clusterId")
|
| 282 |
ok = mapping.get("a1") and mapping.get("a1") == mapping.get("a2")
|
| 283 |
return ok, f"mapping={mapping!r}"
|
| 284 |
|
|
|
|
| 426 |
|
| 427 |
|
| 428 |
def grade_card(card: dict, raw_text: str) -> dict[str, Any]:
|
| 429 |
+
parsed, parse_error, parse_mode = parse_json_output(raw_text)
|
| 430 |
results = []
|
| 431 |
if parse_error is not None:
|
| 432 |
return {
|
| 433 |
"passed": False,
|
| 434 |
"score": 0.0,
|
| 435 |
"parse_error": parse_error,
|
| 436 |
+
"parse_mode": parse_mode,
|
| 437 |
+
"format_recovered": False,
|
| 438 |
+
"failure_kind": "parse",
|
| 439 |
"assertions": [
|
| 440 |
{"assertion": assertion, "passed": False, "details": "output was not valid JSON"}
|
| 441 |
for assertion in card["testCase"]["assertions"]
|
|
|
|
| 443 |
"parsed": None,
|
| 444 |
}
|
| 445 |
|
| 446 |
+
normalized = normalize_card_output(card, parsed)
|
| 447 |
+
|
| 448 |
for assertion in card["testCase"]["assertions"]:
|
| 449 |
+
passed, details = _check(assertion, normalized, card["testCase"]["input"])
|
| 450 |
results.append({"assertion": assertion, "passed": passed, "details": details})
|
| 451 |
|
| 452 |
passed_count = sum(1 for result in results if result["passed"])
|
| 453 |
total = len(results)
|
| 454 |
+
failure_kind = None if passed_count == total else ("schema-or-task" if parse_mode == "normalized" else "task")
|
| 455 |
return {
|
| 456 |
"passed": passed_count == total,
|
| 457 |
"score": round(passed_count / total, 4) if total else 0.0,
|
| 458 |
"parse_error": None,
|
| 459 |
+
"parse_mode": parse_mode,
|
| 460 |
+
"format_recovered": parse_mode == "normalized",
|
| 461 |
+
"failure_kind": failure_kind,
|
| 462 |
"assertions": results,
|
| 463 |
+
"parsed": normalized,
|
| 464 |
}
|
eval/local_intelligence/run_eval.py
CHANGED
|
@@ -78,6 +78,9 @@ def main() -> int:
|
|
| 78 |
passed = sum(1 for row in rows if row["grade"]["passed"])
|
| 79 |
average_score = sum(row["grade"]["score"] for row in rows) / len(rows)
|
| 80 |
latencies = [row["latency_ms"] for row in rows]
|
|
|
|
|
|
|
|
|
|
| 81 |
summary = {
|
| 82 |
"provider": args.provider,
|
| 83 |
"model": args.model,
|
|
@@ -86,6 +89,9 @@ def main() -> int:
|
|
| 86 |
"pass_rate": round(passed / len(rows), 4),
|
| 87 |
"average_score": round(average_score, 4),
|
| 88 |
"median_latency_ms": round(median(latencies), 2),
|
|
|
|
|
|
|
|
|
|
| 89 |
}
|
| 90 |
|
| 91 |
print(json.dumps(summary, indent=2))
|
|
|
|
| 78 |
passed = sum(1 for row in rows if row["grade"]["passed"])
|
| 79 |
average_score = sum(row["grade"]["score"] for row in rows) / len(rows)
|
| 80 |
latencies = [row["latency_ms"] for row in rows]
|
| 81 |
+
exact_parse_count = sum(1 for row in rows if row["grade"].get("parse_mode") == "exact")
|
| 82 |
+
normalized_parse_count = sum(1 for row in rows if row["grade"].get("parse_mode") == "normalized")
|
| 83 |
+
parse_failure_count = sum(1 for row in rows if row["grade"].get("parse_error"))
|
| 84 |
summary = {
|
| 85 |
"provider": args.provider,
|
| 86 |
"model": args.model,
|
|
|
|
| 89 |
"pass_rate": round(passed / len(rows), 4),
|
| 90 |
"average_score": round(average_score, 4),
|
| 91 |
"median_latency_ms": round(median(latencies), 2),
|
| 92 |
+
"exact_parse_rate": round(exact_parse_count / len(rows), 4),
|
| 93 |
+
"normalized_parse_rate": round(normalized_parse_count / len(rows), 4),
|
| 94 |
+
"parse_failure_rate": round(parse_failure_count / len(rows), 4),
|
| 95 |
}
|
| 96 |
|
| 97 |
print(json.dumps(summary, indent=2))
|
eval/local_intelligence/run_hf_transformers.py
CHANGED
|
@@ -54,7 +54,11 @@ def build_generator(args: argparse.Namespace):
|
|
| 54 |
if args.attn_implementation:
|
| 55 |
kwargs["model_kwargs"] = {"attn_implementation": args.attn_implementation}
|
| 56 |
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
|
| 60 |
def build_generation_input(generator, payload: dict, test_input: dict) -> str:
|
|
@@ -120,6 +124,9 @@ def main() -> int:
|
|
| 120 |
passed = sum(1 for row in rows if row["grade"]["passed"])
|
| 121 |
average_score = sum(row["grade"]["score"] for row in rows) / len(rows)
|
| 122 |
latencies = [row["latency_ms"] for row in rows]
|
|
|
|
|
|
|
|
|
|
| 123 |
summary = {
|
| 124 |
"provider": "hf-transformers",
|
| 125 |
"model": args.model,
|
|
@@ -128,6 +135,9 @@ def main() -> int:
|
|
| 128 |
"pass_rate": round(passed / len(rows), 4),
|
| 129 |
"average_score": round(average_score, 4),
|
| 130 |
"median_latency_ms": round(median(latencies), 2),
|
|
|
|
|
|
|
|
|
|
| 131 |
}
|
| 132 |
|
| 133 |
print(json.dumps(summary, indent=2))
|
|
|
|
| 54 |
if args.attn_implementation:
|
| 55 |
kwargs["model_kwargs"] = {"attn_implementation": args.attn_implementation}
|
| 56 |
|
| 57 |
+
generator = pipeline("text-generation", **kwargs)
|
| 58 |
+
generation_config = getattr(generator.model, "generation_config", None)
|
| 59 |
+
if generation_config is not None and getattr(generation_config, "max_length", None) == 20:
|
| 60 |
+
generation_config.max_length = None
|
| 61 |
+
return generator
|
| 62 |
|
| 63 |
|
| 64 |
def build_generation_input(generator, payload: dict, test_input: dict) -> str:
|
|
|
|
| 124 |
passed = sum(1 for row in rows if row["grade"]["passed"])
|
| 125 |
average_score = sum(row["grade"]["score"] for row in rows) / len(rows)
|
| 126 |
latencies = [row["latency_ms"] for row in rows]
|
| 127 |
+
exact_parse_count = sum(1 for row in rows if row["grade"].get("parse_mode") == "exact")
|
| 128 |
+
normalized_parse_count = sum(1 for row in rows if row["grade"].get("parse_mode") == "normalized")
|
| 129 |
+
parse_failure_count = sum(1 for row in rows if row["grade"].get("parse_error"))
|
| 130 |
summary = {
|
| 131 |
"provider": "hf-transformers",
|
| 132 |
"model": args.model,
|
|
|
|
| 135 |
"pass_rate": round(passed / len(rows), 4),
|
| 136 |
"average_score": round(average_score, 4),
|
| 137 |
"median_latency_ms": round(median(latencies), 2),
|
| 138 |
+
"exact_parse_rate": round(exact_parse_count / len(rows), 4),
|
| 139 |
+
"normalized_parse_rate": round(normalized_parse_count / len(rows), 4),
|
| 140 |
+
"parse_failure_rate": round(parse_failure_count / len(rows), 4),
|
| 141 |
}
|
| 142 |
|
| 143 |
print(json.dumps(summary, indent=2))
|