Skip to content

Commit 15ec774

Browse files
author
specops0
committed
fix(monitor): fallback thread trace to checkpoints for swebench runs
1 parent 7f2d47a commit 15ec774

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

backend/web/monitor.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,13 +394,130 @@ def load_run_candidates(thread_id: str, limit: int = 20) -> list[dict]:
394394
]
395395

396396

397+
def _msg_text(content: object) -> str:
398+
if isinstance(content, str):
399+
return content
400+
if isinstance(content, list):
401+
texts: list[str] = []
402+
for block in content:
403+
if isinstance(block, dict) and block.get("type") == "text":
404+
texts.append(str(block.get("text", "")))
405+
return "".join(texts)
406+
return str(content or "")
407+
408+
409+
def _load_checkpoint_events(thread_id: str, limit: int) -> tuple[list[dict], dict[str, int]]:
410+
with sqlite3.connect(str(DB_PATH)) as conn:
411+
row = conn.execute(
412+
"SELECT checkpoint FROM checkpoints WHERE thread_id=? ORDER BY rowid DESC LIMIT 1",
413+
(thread_id,),
414+
).fetchone()
415+
if not row:
416+
return [], {}
417+
418+
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
419+
420+
checkpoint_blob = row[0]
421+
serde = JsonPlusSerializer()
422+
checkpoint = serde.loads_typed(("msgpack", checkpoint_blob))
423+
messages = checkpoint.get("channel_values", {}).get("messages", [])
424+
425+
call_name_by_id: dict[str, str] = {}
426+
events: list[dict] = []
427+
counts: dict[str, int] = {}
428+
seq = 1
429+
for msg in messages:
430+
cls = msg.__class__.__name__
431+
if cls == "AIMessage":
432+
text = _msg_text(getattr(msg, "content", ""))
433+
if text.strip():
434+
payload = {"content": text, "_seq": seq, "_run_id": "checkpoint"}
435+
events.append(
436+
{
437+
"seq": seq,
438+
"event_type": "text",
439+
"payload": payload,
440+
"message_id": None,
441+
"created_at": None,
442+
"created_ago": None,
443+
}
444+
)
445+
counts["text"] = counts.get("text", 0) + 1
446+
seq += 1
447+
for call in getattr(msg, "tool_calls", None) or []:
448+
call_id = str(call.get("id", ""))
449+
name = str(call.get("name", "tool"))
450+
if call_id:
451+
call_name_by_id[call_id] = name
452+
payload = {"id": call_id, "name": name, "args": call.get("args", {}), "_seq": seq, "_run_id": "checkpoint"}
453+
events.append(
454+
{
455+
"seq": seq,
456+
"event_type": "tool_call",
457+
"payload": payload,
458+
"message_id": None,
459+
"created_at": None,
460+
"created_ago": None,
461+
}
462+
)
463+
counts["tool_call"] = counts.get("tool_call", 0) + 1
464+
seq += 1
465+
elif cls == "ToolMessage":
466+
tool_call_id = str(getattr(msg, "tool_call_id", "") or "")
467+
name = call_name_by_id.get(tool_call_id, "tool")
468+
payload = {
469+
"tool_call_id": tool_call_id,
470+
"name": name,
471+
"content": _msg_text(getattr(msg, "content", "")),
472+
"_seq": seq,
473+
"_run_id": "checkpoint",
474+
}
475+
events.append(
476+
{
477+
"seq": seq,
478+
"event_type": "tool_result",
479+
"payload": payload,
480+
"message_id": None,
481+
"created_at": None,
482+
"created_ago": None,
483+
}
484+
)
485+
counts["tool_result"] = counts.get("tool_result", 0) + 1
486+
seq += 1
487+
# @@@checkpoint-trace-fallback - convert latest checkpoint messages into event-like rows so thread trace still renders when run_events are absent.
488+
if limit > 0:
489+
events = events[-limit:]
490+
return events, counts
491+
492+
397493
def load_thread_trace_payload(thread_id: str, run_id: str | None = None, limit: int = 2000) -> dict:
398494
"""Load persisted trace bound to thread/run (not session)."""
399495
run_candidates = load_run_candidates(thread_id, limit=50)
400496
if not run_id:
401497
run_id = run_candidates[0]["run_id"] if run_candidates else None
402498

499+
if run_id == "checkpoint":
500+
checkpoint_events, checkpoint_counts = _load_checkpoint_events(thread_id, limit)
501+
return {
502+
"thread_id": thread_id,
503+
"run_id": "checkpoint",
504+
"run_candidates": [],
505+
"event_count": len(checkpoint_events),
506+
"events": checkpoint_events,
507+
"event_type_counts": checkpoint_counts,
508+
}
509+
403510
if not run_id:
511+
checkpoint_events, checkpoint_counts = _load_checkpoint_events(thread_id, limit)
512+
if checkpoint_events:
513+
return {
514+
"thread_id": thread_id,
515+
"run_id": "checkpoint",
516+
"run_candidates": [],
517+
"event_count": len(checkpoint_events),
518+
"events": checkpoint_events,
519+
"event_type_counts": checkpoint_counts,
520+
}
404521
return {
405522
"thread_id": thread_id,
406523
"run_id": None,

0 commit comments

Comments
 (0)