#!/usr/bin/env python3 """ Inspect the last N llm_requests rows for a given model (default: deepseek). Prints a structured summary without dumping raw payloads. Usage: python scripts/inspect_llm_requests.py [model_filter] [rows] Examples: python scripts/inspect_llm_requests.py deepseek 5 python scripts/inspect_llm_requests.py anthropic 3 """ import json import sqlite3 import sys from pathlib import Path DB_PATH = Path(__file__).parent.parent / "database.db" MODEL_FILTER = sys.argv[1] if len(sys.argv) > 1 else "deepseek" ROWS = int(sys.argv[2]) if len(sys.argv) > 2 else 5 def fmt_len(s): if s is None: return "null" return f"{len(s)} chars" def summarize_message(i, msg): role = msg.get("role", "?") content = msg.get("content") tool_calls = msg.get("tool_calls") tool_call_id = msg.get("tool_call_id") reasoning = msg.get("reasoning_content") parts = [] if isinstance(content, str): parts.append(f"{len(content)} chars") elif isinstance(content, list): total = sum(len(b.get("text", "")) for b in content if isinstance(b, dict)) cache_tags = [b for b in content if isinstance(b, dict) and "cache_control" in b] parts.append(f"{total} chars (content array, {len(content)} blocks)") if cache_tags: parts.append(f"[cache_control on {len(cache_tags)} block(s)]") elif content is None: parts.append("(no content)") if reasoning: parts.append(f"[reasoning_content: {len(reasoning)} chars]") if tool_calls: names = [tc.get("function", {}).get("name", "?") for tc in tool_calls] parts.append(f"[tool_calls: {', '.join(names)}]") if tool_call_id: parts.append(f"(tool_call_id={tool_call_id})") detail = " ".join(parts) print(f" {i:>3} {role:<12} {detail}") def est_tokens(obj) -> int: """Rough token estimate: serialized chars / 4.""" return len(json.dumps(obj)) // 4 def summarize_request(row): rid, model_name, req_json, req_headers, resp_json, input_tok, output_tok, duration_ms, created_at = row print(f"\n{'='*70}") print(f"id={rid} model={model_name} created={created_at}") print(f"tokens: input={input_tok} output={output_tok} duration={duration_ms}ms") try: req = json.loads(req_json) if req_json else {} except Exception as e: print(f" [ERROR parsing request_json: {e}]") return # Top-level params (excluding messages and tools) skip = {"messages", "tools", "model"} params = {k: v for k, v in req.items() if k not in skip} if params: print(f"\n[params]") for k, v in params.items(): print(f" {k} = {json.dumps(v)}") # Tools tools = req.get("tools", []) if tools: tool_names = [t.get("function", {}).get("name", "?") for t in tools] tools_tok = est_tokens(tools) print(f"\n[tools] {len(tools)} defined ~{tools_tok} tok est") print(f" {', '.join(tool_names)}") last = tools[-1] if "cache_control" in last: print(f" last tool has cache_control: {last['cache_control']}") # Messages messages = req.get("messages", []) sys_msgs = [m for m in messages if m.get("role") == "system"] sys_tok = est_tokens(sys_msgs) conv_msgs = [m for m in messages if m.get("role") != "system"] conv_tok = est_tokens(conv_msgs) print(f"\n[messages] {len(messages)} total (~{est_tokens(messages)} tok est: {len(sys_msgs)} system ~{sys_tok} tok, {len(conv_msgs)} conv ~{conv_tok} tok)") for i, msg in enumerate(messages): summarize_message(i, msg) # Response summary if resp_json: try: resp = json.loads(resp_json) usage = resp.get("usage", {}) if usage: print(f"\n[usage]") for k, v in usage.items(): print(f" {k} = {v}") except Exception: pass def main(): conn = sqlite3.connect(DB_PATH) rows = conn.execute( """ SELECT id, model_name, request_json, request_headers, response_json, input_tokens, output_tokens, duration_ms, created_at FROM llm_requests WHERE model_name LIKE ? ORDER BY id DESC LIMIT ? """, (f"%{MODEL_FILTER}%", ROWS), ).fetchall() conn.close() if not rows: print(f"No rows found for model filter '{MODEL_FILTER}'") return print(f"Last {len(rows)} request(s) matching '{MODEL_FILTER}' (newest first)") for row in rows: summarize_request(row) print(f"\n{'='*70}") if __name__ == "__main__": main()