"""扫最近的 task,定位「bad arguments to run_python: code or script_path must be provided」到底什么时候真正触发。 两条线: A. 直接在 tool-result 消息里搜这句错误 —— 这是运行时真的报了的铁证。 B. 看产生它的那条 assistant run_python 调用,arguments 到底长啥样。 排除 `_compacted`(那是入库后上下文压缩留下的历史占位,运行时是有 code 的,不算)。 """ import json import os from collections import Counter from pathlib import Path env = Path(__file__).resolve().parent.parent / ".env" for line in env.read_text(encoding="utf-8").splitlines(): if line.strip().startswith("ZCBOT_DB_URL="): os.environ["ZCBOT_DB_URL"] = line.split("=", 1)[1].strip() from sqlalchemy import create_engine, text # noqa: E402 engine = create_engine(os.environ["ZCBOT_DB_URL"]) ERR = "bad arguments to run_python: code or script_path must be provided" with engine.connect() as conn: tasks = conn.execute( text("select task_id, created_at from tasks order by created_at desc limit 60") ).fetchall() per_task = Counter() shapes = Counter() samples = [] for tid, created in tasks: msgs = conn.execute( text("select idx, payload from messages where task_id=:t order by idx"), {"t": tid}, ).fetchall() # 建 tool_call_id -> arguments 映射(看错误对应的调用 args) call_args = {} for idx, payload in msgs: if payload.get("role") == "assistant": for tc in payload.get("tool_calls") or []: call_args[tc.get("id")] = (tc.get("function") or {}).get("arguments") for idx, payload in msgs: if payload.get("role") != "tool": continue content = payload.get("content") or "" if isinstance(content, list): content = json.dumps(content, ensure_ascii=False) if ERR not in content: continue per_task[(str(tid)[:8], str(created)[:16])] += 1 raw = call_args.get(payload.get("tool_call_id")) # 归类 args 形态 try: args = json.loads(raw) if raw else {} except Exception: shape = "MANGLED(非法JSON)" else: if args == {}: shape = "空 {}" elif "_compacted" in args: shape = "_compacted(历史占位)" else: shape = "其他: " + repr(raw)[:80] shapes[shape] += 1 if len(samples) < 25: samples.append((str(tid)[:8], idx, shape, repr(raw)[:140])) print(f"扫了最近 {len(tasks)} 个 task") print(f"真正触发该错误的 tool-result 条数: {sum(per_task.values())}\n") print("=== 按 task 分布(task / 创建时间 / 次数)===") for (t, c), n in per_task.most_common(): print(f" {t} {c} -> {n} 次") print("\n=== 触发时 run_python 的 arguments 形态 ===") for s, n in shapes.most_common(): print(f" {n:>3}x {s}") print("\n=== 样本 ===") for t, idx, shape, raw in samples: print(f" [{t} #{idx}] {shape}: {raw}")