mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-09-13 19:52:20 +02:00
160 lines
5.1 KiB
Python
160 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Summarize Odysseus tool-use eval artifacts and optional per-case deltas."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
SCORE_FIELDS = (
|
|
"native_success",
|
|
"command_contract_success",
|
|
"tool_invocation_success",
|
|
"command_outcome_success",
|
|
"execution_success",
|
|
"response_quality_success",
|
|
)
|
|
|
|
|
|
def _is_infra_failure_error(error: dict[str, Any]) -> bool:
|
|
if not isinstance(error, dict):
|
|
return False
|
|
status = error.get("status")
|
|
text = " ".join(
|
|
str(error.get(key) or "")
|
|
for key in ("error", "message", "detail", "type")
|
|
).lower()
|
|
if status in {502, 503, 504, 520, 521, 522, 523, 524}:
|
|
return True
|
|
return bool(
|
|
"cannot reach" in text
|
|
or "connection refused" in text
|
|
or "connection reset" in text
|
|
or "connect timeout" in text
|
|
or "read timeout" in text
|
|
or "unreachable" in text
|
|
or "cooldown active" in text
|
|
or "upstream protocol error" in text
|
|
or ("upstream" in text and "failed" in text)
|
|
)
|
|
|
|
|
|
def _record_has_infra_error(record: dict[str, Any]) -> bool:
|
|
if record.get("infra_failure") is True:
|
|
return True
|
|
errors = list(record.get("stream_errors") or [])
|
|
stream_exception = record.get("stream_exception")
|
|
if isinstance(stream_exception, dict):
|
|
errors.append(stream_exception)
|
|
return any(_is_infra_failure_error(error) for error in errors)
|
|
|
|
|
|
def _load(path: Path) -> dict[str, Any]:
|
|
with path.open("r", encoding="utf-8") as handle:
|
|
return json.load(handle)
|
|
|
|
|
|
def _records_by_case(artifact: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
return {
|
|
str(record.get("case")): record
|
|
for record in artifact.get("records", [])
|
|
if record.get("case")
|
|
}
|
|
|
|
|
|
def _metric(record: dict[str, Any], key: str) -> Any:
|
|
metrics = record.get("metrics") or {}
|
|
return metrics.get(key)
|
|
|
|
|
|
def _fmt_num(value: Any, suffix: str = "") -> str:
|
|
if value is None:
|
|
return "n/a"
|
|
if isinstance(value, float):
|
|
return f"{value:.2f}{suffix}"
|
|
return f"{value}{suffix}"
|
|
|
|
|
|
def _print_summary(label: str, path: Path, artifact: dict[str, Any]) -> None:
|
|
cases = artifact.get("cases")
|
|
infra = artifact.get("infra_failures")
|
|
evaluable = artifact.get("evaluable_cases")
|
|
inferred_infra = sum(
|
|
1 for record in artifact.get("records", []) if _record_has_infra_error(record)
|
|
)
|
|
print(f"{label}: {path}")
|
|
print(f" model: {artifact.get('model')}")
|
|
print(f" cases: {cases}")
|
|
if infra is not None:
|
|
print(f" infra_failures: {infra}")
|
|
print(f" evaluable_cases: {evaluable}")
|
|
elif inferred_infra:
|
|
print(f" inferred_infra_records: {inferred_infra}")
|
|
for field in SCORE_FIELDS:
|
|
value = artifact.get(field)
|
|
if value is not None:
|
|
print(f" {field}: {value}/{cases}")
|
|
ev_value = artifact.get(f"{field}_evaluable")
|
|
if ev_value is not None:
|
|
print(f" {field}_evaluable: {ev_value}/{evaluable}")
|
|
print(f" duplicate_textual_calls: {artifact.get('duplicate_textual_calls')}")
|
|
print(f" repetitive_tool_calls: {artifact.get('repetitive_tool_calls')}")
|
|
print(f" stream_errors: {artifact.get('stream_errors')}")
|
|
|
|
|
|
def _print_delta(before: dict[str, Any], after: dict[str, Any]) -> None:
|
|
before_records = _records_by_case(before)
|
|
after_records = _records_by_case(after)
|
|
shared = sorted(set(before_records) & set(after_records))
|
|
if not shared:
|
|
print("delta: no shared cases")
|
|
return
|
|
print("delta by shared case:")
|
|
for case in shared:
|
|
old = before_records[case]
|
|
new = after_records[case]
|
|
old_input = _metric(old, "input_tokens")
|
|
new_input = _metric(new, "input_tokens")
|
|
old_time = _metric(old, "response_time")
|
|
new_time = _metric(new, "response_time")
|
|
old_elapsed = old.get("elapsed_seconds")
|
|
new_elapsed = new.get("elapsed_seconds")
|
|
print(
|
|
" "
|
|
+ case
|
|
+ ": input "
|
|
+ f"{_fmt_num(old_input)} -> {_fmt_num(new_input)}; "
|
|
+ "response "
|
|
+ f"{_fmt_num(old_time, 's')} -> {_fmt_num(new_time, 's')}; "
|
|
+ "elapsed "
|
|
+ f"{_fmt_num(old_elapsed, 's')} -> {_fmt_num(new_elapsed, 's')}; "
|
|
+ "tool "
|
|
+ f"{old.get('tool_invocation_ok')} -> {new.get('tool_invocation_ok')}; "
|
|
+ "outcome "
|
|
+ f"{old.get('command_outcome_ok')} -> {new.get('command_outcome_ok')}"
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("artifact", type=Path)
|
|
parser.add_argument("--compare", type=Path, help="Compare artifact against this earlier baseline.")
|
|
args = parser.parse_args()
|
|
|
|
current = _load(args.artifact)
|
|
_print_summary("artifact", args.artifact, current)
|
|
if args.compare:
|
|
baseline = _load(args.compare)
|
|
print()
|
|
_print_summary("baseline", args.compare, baseline)
|
|
print()
|
|
_print_delta(baseline, current)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|