79 lines
2.8 KiB
Python
79 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DEFAULT_REPORT_DIR = ROOT / "reports" / "observability"
|
|
|
|
|
|
def iter_turn_audit(report_dir: Path) -> list[dict[str, Any]]:
|
|
records: list[dict[str, Any]] = []
|
|
if not report_dir.exists():
|
|
return records
|
|
for path in sorted(report_dir.rglob("turn_audit/*.jsonl")):
|
|
with path.open("r", encoding="utf-8") as handle:
|
|
for line_number, line in enumerate(handle, start=1):
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
try:
|
|
record = json.loads(line)
|
|
except json.JSONDecodeError as exc:
|
|
raise ValueError(f"{path}:{line_number}: invalid JSONL: {exc}") from exc
|
|
if isinstance(record, dict):
|
|
records.append(record)
|
|
return records
|
|
|
|
|
|
def filter_failed(records: list[dict[str, Any]], *, limit: int | None = None) -> list[dict[str, Any]]:
|
|
failed = [
|
|
{
|
|
"timestamp": record.get("timestamp"),
|
|
"turn_id": record.get("turn_id"),
|
|
"plugin": record.get("plugin"),
|
|
"project_id": record.get("project_id"),
|
|
"chat_id": record.get("chat_id"),
|
|
"request_id": record.get("request_id"),
|
|
"outcome": record.get("outcome"),
|
|
"failure_type": record.get("failure_type"),
|
|
"error_code": record.get("error_code"),
|
|
"error_message": record.get("error_message"),
|
|
"user_text": record.get("user_text"),
|
|
}
|
|
for record in records
|
|
if str(record.get("outcome") or "") != "success"
|
|
]
|
|
failed.sort(key=lambda item: str(item.get("timestamp") or ""), reverse=True)
|
|
if limit is not None:
|
|
return failed[:limit]
|
|
return failed
|
|
|
|
|
|
def write_report(path: Path, payload: dict[str, Any]) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Report failed and partial turns from observability logs.")
|
|
parser.add_argument("--report-dir", type=Path, default=DEFAULT_REPORT_DIR)
|
|
parser.add_argument("--limit", type=int, default=50)
|
|
parser.add_argument("--output", type=Path, help="Optional JSON output path.")
|
|
args = parser.parse_args()
|
|
|
|
records = iter_turn_audit(args.report_dir)
|
|
failed = filter_failed(records, limit=args.limit)
|
|
payload = {"records": len(records), "failed_turns": failed}
|
|
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
if args.output:
|
|
write_report(args.output, payload)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|