mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-30 17:07:43 +00:00
310 lines
9.0 KiB
Python
310 lines
9.0 KiB
Python
|
|
"""
|
|||
|
|
RL UI Data Loader
|
|||
|
|
Load pkl logs and convert to hierarchical timeline structure
|
|||
|
|
Simplified version: no EvoLoop (RL doesn't have evolution loops)
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import pickle
|
|||
|
|
import re
|
|||
|
|
from dataclasses import dataclass, field
|
|||
|
|
from datetime import datetime
|
|||
|
|
from pathlib import Path
|
|||
|
|
from typing import Any
|
|||
|
|
|
|||
|
|
import streamlit as st
|
|||
|
|
|
|||
|
|
from rdagent.app.rl.ui.config import EventType
|
|||
|
|
from rdagent.log.storage import FileStorage
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class Event:
|
|||
|
|
"""Timeline event"""
|
|||
|
|
|
|||
|
|
type: EventType
|
|||
|
|
timestamp: datetime
|
|||
|
|
tag: str
|
|||
|
|
title: str
|
|||
|
|
content: Any
|
|||
|
|
loop_id: int | None = None
|
|||
|
|
stage: str = ""
|
|||
|
|
duration: float | None = None
|
|||
|
|
success: bool | None = None
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def time_str(self) -> str:
|
|||
|
|
return self.timestamp.strftime("%H:%M:%S")
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class Loop:
|
|||
|
|
"""Main loop containing stages (no EvoLoop for RL)"""
|
|||
|
|
|
|||
|
|
loop_id: int
|
|||
|
|
proposal: list[Event] = field(default_factory=list) # hypothesis generation
|
|||
|
|
coding: list[Event] = field(default_factory=list) # code generation
|
|||
|
|
running: list[Event] = field(default_factory=list) # docker training + benchmark
|
|||
|
|
feedback: list[Event] = field(default_factory=list) # feedback
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class Session:
|
|||
|
|
"""Session containing init events and loops"""
|
|||
|
|
|
|||
|
|
init_events: list[Event] = field(default_factory=list)
|
|||
|
|
loops: dict[int, Loop] = field(default_factory=dict)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def extract_loop_id(tag: str) -> int | None:
|
|||
|
|
match = re.search(r"Loop_(\d+)", tag)
|
|||
|
|
return int(match.group(1)) if match else None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def extract_stage(tag: str) -> str:
|
|||
|
|
if "proposal" in tag or "direct_exp_gen" in tag:
|
|||
|
|
return "proposal"
|
|||
|
|
if "coding" in tag:
|
|||
|
|
return "coding"
|
|||
|
|
if "running" in tag:
|
|||
|
|
return "running"
|
|||
|
|
if "feedback" in tag:
|
|||
|
|
return "feedback"
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_valid_sessions(log_folder: Path) -> list[str]:
|
|||
|
|
if not log_folder.exists():
|
|||
|
|
return []
|
|||
|
|
sessions = []
|
|||
|
|
for d in log_folder.iterdir():
|
|||
|
|
if d.is_dir() and d.joinpath("__session__").exists():
|
|||
|
|
sessions.append(d.name)
|
|||
|
|
return sorted(sessions, reverse=True)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def parse_event(tag: str, content: Any, timestamp: datetime) -> Event | None:
|
|||
|
|
loop_id = extract_loop_id(tag)
|
|||
|
|
stage = extract_stage(tag)
|
|||
|
|
|
|||
|
|
# Scenario
|
|||
|
|
if tag == "scenario":
|
|||
|
|
return Event(type="scenario", timestamp=timestamp, tag=tag, title="Scenario", content=content)
|
|||
|
|
|
|||
|
|
# Settings
|
|||
|
|
if "SETTINGS" in tag:
|
|||
|
|
name = tag.replace("_SETTINGS", "").replace("SETTINGS", "")
|
|||
|
|
return Event(type="settings", timestamp=timestamp, tag=tag, title=f"Settings: {name}", content=content)
|
|||
|
|
|
|||
|
|
# Hypothesis
|
|||
|
|
if "hypothesis" in tag:
|
|||
|
|
return Event(
|
|||
|
|
type="hypothesis",
|
|||
|
|
timestamp=timestamp,
|
|||
|
|
tag=tag,
|
|||
|
|
title="Hypothesis",
|
|||
|
|
content=content,
|
|||
|
|
loop_id=loop_id,
|
|||
|
|
stage="proposal",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# LLM Call
|
|||
|
|
if "debug_llm" in tag:
|
|||
|
|
if isinstance(content, dict) and ("user" in content or "system" in content):
|
|||
|
|
duration = None
|
|||
|
|
if content.get("start") and content.get("end"):
|
|||
|
|
duration = (content["end"] - content["start"]).total_seconds()
|
|||
|
|
return Event(
|
|||
|
|
type="llm_call",
|
|||
|
|
timestamp=timestamp,
|
|||
|
|
tag=tag,
|
|||
|
|
title="LLM Call",
|
|||
|
|
content=content,
|
|||
|
|
loop_id=loop_id,
|
|||
|
|
stage=stage,
|
|||
|
|
duration=duration,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Template
|
|||
|
|
if "debug_tpl" in tag:
|
|||
|
|
if isinstance(content, dict) and "uri" in content:
|
|||
|
|
uri = content.get("uri", "")
|
|||
|
|
tpl_name = uri.split(":")[-1] if ":" in uri else uri
|
|||
|
|
return Event(
|
|||
|
|
type="template",
|
|||
|
|
timestamp=timestamp,
|
|||
|
|
tag=tag,
|
|||
|
|
title=f"Template: {tpl_name}",
|
|||
|
|
content=content,
|
|||
|
|
loop_id=loop_id,
|
|||
|
|
stage=stage,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Experiment/Coder result
|
|||
|
|
if "coder result" in tag or "experiment generation" in tag:
|
|||
|
|
return Event(
|
|||
|
|
type="experiment",
|
|||
|
|
timestamp=timestamp,
|
|||
|
|
tag=tag,
|
|||
|
|
title="Experiment",
|
|||
|
|
content=content,
|
|||
|
|
loop_id=loop_id,
|
|||
|
|
stage=stage or "coding",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Code
|
|||
|
|
if "evolving code" in tag or "code" in tag.lower():
|
|||
|
|
return Event(
|
|||
|
|
type="code",
|
|||
|
|
timestamp=timestamp,
|
|||
|
|
tag=tag,
|
|||
|
|
title="Code",
|
|||
|
|
content=content,
|
|||
|
|
loop_id=loop_id,
|
|||
|
|
stage=stage or "coding",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Docker run
|
|||
|
|
if "docker_run" in tag:
|
|||
|
|
exit_code = content.get("exit_code") if isinstance(content, dict) else None
|
|||
|
|
success = exit_code == 0 if exit_code is not None else None
|
|||
|
|
return Event(
|
|||
|
|
type="docker_exec",
|
|||
|
|
timestamp=timestamp,
|
|||
|
|
tag=tag,
|
|||
|
|
title=f"Docker Run {'✓' if success else '✗' if success is False else ''}",
|
|||
|
|
content=content,
|
|||
|
|
loop_id=loop_id,
|
|||
|
|
stage="running",
|
|||
|
|
success=success,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Benchmark result
|
|||
|
|
if "benchmark" in tag.lower():
|
|||
|
|
return Event(
|
|||
|
|
type="feedback",
|
|||
|
|
timestamp=timestamp,
|
|||
|
|
tag=tag,
|
|||
|
|
title="Benchmark Result",
|
|||
|
|
content=content,
|
|||
|
|
loop_id=loop_id,
|
|||
|
|
stage="running",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Feedback
|
|||
|
|
if "feedback" in tag:
|
|||
|
|
decision = getattr(content, "decision", None)
|
|||
|
|
return Event(
|
|||
|
|
type="feedback",
|
|||
|
|
timestamp=timestamp,
|
|||
|
|
tag=tag,
|
|||
|
|
title=f"Feedback: {'Accept' if decision else 'Reject'}",
|
|||
|
|
content=content,
|
|||
|
|
loop_id=loop_id,
|
|||
|
|
stage="feedback",
|
|||
|
|
success=decision,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Token cost
|
|||
|
|
if "token_cost" in tag:
|
|||
|
|
if isinstance(content, dict):
|
|||
|
|
total = content.get("total_tokens", 0)
|
|||
|
|
return Event(
|
|||
|
|
type="token",
|
|||
|
|
timestamp=timestamp,
|
|||
|
|
tag=tag,
|
|||
|
|
title=f"Token: {total}",
|
|||
|
|
content=content,
|
|||
|
|
loop_id=loop_id,
|
|||
|
|
stage=stage,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Time info
|
|||
|
|
if "time_info" in tag:
|
|||
|
|
return Event(
|
|||
|
|
type="time",
|
|||
|
|
timestamp=timestamp,
|
|||
|
|
tag=tag,
|
|||
|
|
title="Time Info",
|
|||
|
|
content=content,
|
|||
|
|
loop_id=loop_id,
|
|||
|
|
stage=stage,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
|
|||
|
|
@st.cache_data(ttl=300, hash_funcs={Path: str})
|
|||
|
|
def load_session(log_path: Path) -> Session:
|
|||
|
|
"""Load events into hierarchical session structure"""
|
|||
|
|
session = Session()
|
|||
|
|
|
|||
|
|
# 手动遍历 pkl 文件,跳过无法加载的
|
|||
|
|
events = []
|
|||
|
|
pkl_files = sorted(log_path.rglob("*.pkl"))
|
|||
|
|
for pkl_file in pkl_files:
|
|||
|
|
if pkl_file.name == "debug_llm.pkl":
|
|||
|
|
continue
|
|||
|
|
try:
|
|||
|
|
with open(pkl_file, "rb") as f:
|
|||
|
|
content = pickle.load(f)
|
|||
|
|
timestamp = datetime.strptime(pkl_file.stem, "%Y-%m-%d_%H-%M-%S-%f")
|
|||
|
|
# 正确解析 tag:Loop_5/running/debug_tpl/2957404/xxx.pkl -> Loop_5.running.debug_tpl
|
|||
|
|
tag = ".".join(pkl_file.relative_to(log_path).as_posix().replace("/", ".").split(".")[:-3])
|
|||
|
|
event = parse_event(tag, content, timestamp)
|
|||
|
|
if event:
|
|||
|
|
events.append(event)
|
|||
|
|
except (ModuleNotFoundError, ImportError, pickle.UnpicklingError, ValueError):
|
|||
|
|
# 跳过无法加载的文件(不同 Python 版本或格式错误)
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
events.sort(key=lambda e: e.timestamp)
|
|||
|
|
|
|||
|
|
for event in events:
|
|||
|
|
if event.loop_id is None:
|
|||
|
|
session.init_events.append(event)
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
if event.loop_id not in session.loops:
|
|||
|
|
session.loops[event.loop_id] = Loop(loop_id=event.loop_id)
|
|||
|
|
loop = session.loops[event.loop_id]
|
|||
|
|
|
|||
|
|
if event.stage == "proposal":
|
|||
|
|
loop.proposal.append(event)
|
|||
|
|
elif event.stage == "coding":
|
|||
|
|
loop.coding.append(event)
|
|||
|
|
elif event.stage == "running":
|
|||
|
|
loop.running.append(event)
|
|||
|
|
elif event.stage == "feedback":
|
|||
|
|
loop.feedback.append(event)
|
|||
|
|
else:
|
|||
|
|
loop.proposal.append(event)
|
|||
|
|
|
|||
|
|
return session
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_summary(session: Session) -> dict:
|
|||
|
|
"""Get summary statistics"""
|
|||
|
|
llm_calls = []
|
|||
|
|
docker_execs = []
|
|||
|
|
|
|||
|
|
for e in session.init_events:
|
|||
|
|
if e.type == "llm_call":
|
|||
|
|
llm_calls.append(e)
|
|||
|
|
elif e.type == "docker_exec":
|
|||
|
|
docker_execs.append(e)
|
|||
|
|
|
|||
|
|
for loop in session.loops.values():
|
|||
|
|
for e in loop.proposal + loop.coding + loop.running + loop.feedback:
|
|||
|
|
if e.type == "llm_call":
|
|||
|
|
llm_calls.append(e)
|
|||
|
|
elif e.type == "docker_exec":
|
|||
|
|
docker_execs.append(e)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"loop_count": len(session.loops),
|
|||
|
|
"llm_call_count": len(llm_calls),
|
|||
|
|
"llm_total_time": sum(e.duration or 0 for e in llm_calls),
|
|||
|
|
"docker_success": sum(1 for e in docker_execs if e.success is True),
|
|||
|
|
"docker_fail": sum(1 for e in docker_execs if e.success is False),
|
|||
|
|
}
|