项目文件夹

文件
wehub-resource-sync e64161ec32
CI / ci (3.11) (push) Has been cancelled
CI / ci (3.10) (push) Has been cancelled
CI / dependabot (push) Has been cancelled
Release / release_and_publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:15 +08:00

310 行
9.0 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
"""
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")
# 正确解析 tagLoop_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),
}