Download evals/postmortem/forensics/common.py from SaylorTwift/hermes-agent: direct link, hf CLI and curl.
- Browser
- Download file 9.35 kB
-
https://huggingface.co/SaylorTwift/hermes-agent/resolve/main/evals/postmortem/forensics/common.py
- Command line
-
hf download hf://SaylorTwift/hermes-agent/evals/postmortem/forensics/common.py
-
curl -L -o common.py https://huggingface.co/SaylorTwift/hermes-agent/resolve/main/evals/postmortem/forensics/common.py
9.35 kB
| """Shared loaders for the post-mortem forensics: point at ANY Hermes ``state.db`` (a copy, never the live | |
| file) and get the run tree, the in-run session set, fitted pricing and message iterators. | |
| Nothing here knows about a particular run. The root is discovered as the session with the most | |
| descendants unless ``--root`` is given; compression-rollover children (a child whose ``id`` the parent's | |
| ``compaction`` metadata names as its continuation) are excluded from the tree so cost populations stay | |
| disjoint. Pricing is fitted by least squares from ``sessions`` usage columns to ``estimated_cost_usd``, so | |
| the recomputed dollars match what THAT Hermes recorded, not an invoice. | |
| Usage from a lane script:: | |
| from evals.postmortem.forensics.common import Run | |
| run = Run.from_args() # --db, --root, --out | |
| for sid in run.in_run: # ordered session ids | |
| ... | |
| run.write("q1_cost.json", data) | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import collections | |
| import json | |
| import sqlite3 | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| from typing import Any, Dict, Iterable, Iterator, List, Optional | |
| USAGE_COLS = ("input_tokens", "cache_read_tokens", "cache_write_tokens", "output_tokens") | |
| def _lstsq(rows: List[List[float]], y: List[float]) -> List[float]: | |
| """Ordinary least squares without numpy (4 unknowns): normal equations solved by Gaussian elimination.""" | |
| n = len(rows[0]) | |
| ata = [[sum(r[i] * r[j] for r in rows) for j in range(n)] for i in range(n)] | |
| atb = [sum(r[i] * yy for r, yy in zip(rows, y)) for i in range(n)] | |
| m = [row[:] + [b] for row, b in zip(ata, atb)] | |
| for c in range(n): | |
| piv = max(range(c, n), key=lambda r: abs(m[r][c])) | |
| m[c], m[piv] = m[piv], m[c] | |
| if abs(m[c][c]) < 1e-12: | |
| continue | |
| for r in range(n): | |
| if r != c: | |
| f = m[r][c] / m[c][c] | |
| m[r] = [a - f * b for a, b in zip(m[r], m[c])] | |
| return [m[i][n] / m[i][i] if abs(m[i][i]) > 1e-12 else 0.0 for i in range(n)] | |
| class Run: | |
| db_path: Path | |
| out_dir: Path | |
| root: str | |
| sessions: Dict[str, Dict[str, Any]] | |
| depth: Dict[str, int] | |
| in_run: List[str] # root + descendants, rollover excluded, dispatch order | |
| price_per_token: Dict[str, float] # fitted: USD per token for each USAGE_COLS entry | |
| _conn: sqlite3.Connection = field(repr=False) | |
| # ββ construction ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def parser(cls, description: str = "") -> argparse.ArgumentParser: | |
| ap = argparse.ArgumentParser(description=description) | |
| ap.add_argument("--db", required=True, help="path to a COPY of ~/.hermes/state.db") | |
| ap.add_argument("--root", default=None, help="root session id (default: the session with the most descendants)") | |
| ap.add_argument("--out", default="postmortem_out", help="directory for JSON/markdown outputs") | |
| return ap | |
| def from_args(cls, argv: Optional[List[str]] = None, description: str = "") -> "Run": | |
| a = cls.parser(description).parse_args(argv) | |
| return cls.open(a.db, root=a.root, out=a.out) | |
| def open(cls, db: str, *, root: Optional[str] = None, out: str = "postmortem_out") -> "Run": # noqa: C901 | |
| conn = sqlite3.connect(f"file:{db}?mode=ro", uri=True) | |
| conn.row_factory = sqlite3.Row | |
| sessions = {r["id"]: dict(r) for r in conn.execute("SELECT * FROM sessions")} | |
| children: Dict[Optional[str], List[str]] = collections.defaultdict(list) | |
| for sid, s in sessions.items(): | |
| children[s.get("parent_session_id")].append(sid) | |
| rollover = cls._rollover_ids(conn, sessions) | |
| if root is None: | |
| def size(sid: str) -> int: | |
| n, stack = 0, [sid] | |
| while stack: | |
| cur = stack.pop() | |
| for c in children[cur]: | |
| if c not in rollover: | |
| n += 1; stack.append(c) | |
| return n | |
| root = str(max((sid for sid in sessions if sessions[sid].get("parent_session_id") is None), key=size)) | |
| depth: Dict[str, int] = {} | |
| order: List[str] = [] | |
| stack = [(root, 0)] | |
| while stack: | |
| sid, d = stack.pop() | |
| depth[sid] = d; order.append(sid) | |
| stack.extend((c, d + 1) for c in sorted(children[sid], key=lambda x: sessions[x].get("started_at") or 0, reverse=True) if c not in rollover) | |
| order.sort(key=lambda s: sessions[s].get("started_at") or 0) | |
| price = cls._fit_pricing([sessions[s] for s in order]) | |
| out_dir = Path(out); out_dir.mkdir(parents=True, exist_ok=True) | |
| return cls(Path(db), out_dir, root, sessions, depth, order, price, conn) | |
| def _rollover_ids(conn: sqlite3.Connection, sessions: Dict[str, Dict[str, Any]]) -> set: | |
| """Children created by in-place compression rollover, not by delegation: a child whose ``source`` | |
| is a top-level surface (cli/tui/telegram/...), not ``subagent``, whose parent shares that source, | |
| and which started within a few seconds of the parent ending. Their whole later lifetime belongs to | |
| the continuing conversation, not to the fan-out, so they are kept out of the run population.""" | |
| out = set() | |
| for sid, s in sessions.items(): | |
| pid = s.get("parent_session_id") | |
| if not pid or pid not in sessions or (s.get("source") or "") == "subagent": | |
| continue | |
| parent = sessions[pid] | |
| if (s.get("source") or "") != (parent.get("source") or ""): | |
| continue | |
| try: | |
| gap = float(s.get("started_at") or 0) - float(parent.get("ended_at") or 0) | |
| except (TypeError, ValueError): | |
| continue | |
| if -5.0 <= gap <= 5.0: | |
| out.add(sid) | |
| return out | |
| def _fit_pricing(rows: Iterable[Dict[str, Any]]) -> Dict[str, float]: | |
| X, y = [], [] | |
| for s in rows: | |
| cost = s.get("estimated_cost_usd") | |
| if not cost: | |
| continue | |
| X.append([float(s.get(c) or 0) for c in USAGE_COLS]); y.append(float(cost)) | |
| if len(X) < 8: | |
| return {c: 0.0 for c in USAGE_COLS} | |
| # Columns with negligible mass (e.g. input_tokens on cache-heavy Anthropic routes) make the | |
| # normal equations ill-conditioned; fit only columns carrying >0.1% of all tokens. | |
| mass = [sum(r[i] for r in X) for i in range(len(USAGE_COLS))] | |
| keep = [i for i, m in enumerate(mass) if m > 0.001 * sum(mass)] | |
| coef = _lstsq([[r[i] for i in keep] for r in X], y) | |
| price = {c: 0.0 for c in USAGE_COLS} | |
| for i, k in enumerate(keep): | |
| price[USAGE_COLS[k]] = max(0.0, coef[i]) | |
| return price | |
| # ββ accessors βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def cost(self, sid: str) -> float: | |
| return float(self.sessions[sid].get("estimated_cost_usd") or 0.0) | |
| def messages(self, sid: str, cols: str = "*") -> List[Dict[str, Any]]: | |
| return [dict(r) for r in self._conn.execute(f"SELECT {cols} FROM messages WHERE session_id=? ORDER BY id", (sid,))] | |
| def iter_messages(self, sids: Iterable[str], cols: str = "*") -> Iterator[Dict[str, Any]]: | |
| for sid in sids: | |
| yield from self.messages(sid, cols) | |
| def system_prompt_len(self, sid: str) -> int: | |
| h = self.sessions[sid].get("system_prompt_hash") | |
| if not h: | |
| return 0 | |
| r = self._conn.execute("SELECT length(prompt) AS n FROM system_prompts WHERE hash=?", (h,)).fetchone() | |
| return int(r["n"]) if r else 0 | |
| def by_depth(self) -> Dict[int, List[str]]: | |
| out: Dict[int, List[str]] = collections.defaultdict(list) | |
| for sid in self.in_run: | |
| out[self.depth[sid]].append(sid) | |
| return dict(out) | |
| def write(self, name: str, data: Any) -> Path: | |
| p = self.out_dir / name | |
| p.write_text(json.dumps(data, indent=1, default=str) if not name.endswith(".md") else str(data), encoding="utf-8") | |
| return p | |
| def summary(self) -> Dict[str, Any]: | |
| tot = {c: sum(float(self.sessions[s].get(c) or 0) for s in self.in_run) for c in USAGE_COLS} | |
| return { | |
| "root": self.root, "sessions": len(self.in_run), "children": len(self.in_run) - 1, | |
| "by_depth": {d: len(v) for d, v in sorted(self.by_depth().items())}, | |
| "api_calls": sum(int(self.sessions[s].get("api_call_count") or 0) for s in self.in_run), | |
| "cost_usd": round(sum(self.cost(s) for s in self.in_run), 2), | |
| "usage_tokens": tot, | |
| "fitted_price_per_million": {c: round(p * 1e6, 4) for c, p in self.price_per_token.items()}, | |
| "cost_by_bucket_usd": {c: round(tot[c] * self.price_per_token[c], 2) for c in USAGE_COLS}, | |
| } | |