zcbot/scripts/diag_run_python_empty.py

80 lines
3.1 KiB
Python

"""扫最近的 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}")