chore: split dsapp to pages & add llm_logs showing (#733)

* split dsapp to pages & add llm_logs showing

* some changes

* small bug

* add time info

* do not test UI in test_import

* cache stdout
This commit is contained in:
XianBW
2025-04-01 20:55:10 +08:00
committed by GitHub
parent 473b9000b9
commit 3ef0b21350
4 changed files with 931 additions and 733 deletions
+317
View File
@@ -0,0 +1,317 @@
import math
import re
from pathlib import Path
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import streamlit as st
from streamlit import session_state as state
from rdagent.log.ui.conf import UI_SETTING
def get_summary_df(log_folders: list[str]) -> tuple[dict, pd.DataFrame]:
summarys = {}
for lf in log_folders:
if not (Path(lf) / "summary.pkl").exists():
st.warning(
f"No summary file found in **{lf}**\n\nRun:`dotenv run -- python rdagent/log/mle_summary.py grade_summary --log_folder={lf}`"
)
else:
summarys[lf] = pd.read_pickle(Path(lf) / "summary.pkl")
if len(summarys) == 0:
return {}, pd.DataFrame()
summary = {}
for lf, s in summarys.items():
for k, v in s.items():
stdout_p = Path(lf) / f"{k}.stdout"
v["stdout"] = []
if stdout_p.exists():
# stdout = stdout_p.read_text()
stdout = ""
if "Retrying" in stdout:
v["stdout"].append("LLM Retry")
if "Traceback (most recent call last):" in stdout[-10000:]:
v["stdout"].append("Code Error")
v["stdout"] = ", ".join([i for i in v["stdout"] if i])
# 调整实验名字
if "amlt" in lf:
summary[f"{lf[lf.rfind('amlt')+5:].split('/')[0]} - {k}"] = v
elif "ep" in lf:
summary[f"{lf[lf.rfind('ep'):]} - {k}"] = v
else:
summary[f"{lf} - {k}"] = v
summary = {k: v for k, v in summary.items() if "competition" in v}
base_df = pd.DataFrame(
columns=[
"Competition",
"Total Loops",
"Successful Final Decision",
"Made Submission",
"Valid Submission",
"V/M",
"Above Median",
"Bronze",
"Silver",
"Gold",
"Any Medal",
"Best Medal",
"SOTA Exp",
"Ours - Base",
"Ours vs Base",
"SOTA Exp Score",
"Baseline Score",
"Bronze Threshold",
"Silver Threshold",
"Gold Threshold",
"Medium Threshold",
"stdout",
],
index=summary.keys(),
)
# Read baseline results
baseline_result_path = UI_SETTING.baseline_result_path
if Path(baseline_result_path).exists():
baseline_df = pd.read_csv(baseline_result_path)
for k, v in summary.items():
loop_num = v["loop_num"]
base_df.loc[k, "Competition"] = v["competition"]
base_df.loc[k, "Total Loops"] = loop_num
if loop_num == 0:
base_df.loc[k] = "N/A"
else:
base_df.loc[k, "Successful Final Decision"] = v["success_loop_num"]
base_df.loc[k, "Made Submission"] = v["made_submission_num"]
base_df.loc[k, "Valid Submission"] = v["valid_submission_num"]
base_df.loc[k, "Above Median"] = v["above_median_num"]
base_df.loc[k, "Bronze"] = v["bronze_num"]
if v["bronze_num"] > 0:
base_df.loc[k, "Best Medal"] = "bronze"
base_df.loc[k, "Silver"] = v["silver_num"]
if v["silver_num"] > 0:
base_df.loc[k, "Best Medal"] = "silver"
base_df.loc[k, "Gold"] = v["gold_num"]
if v["gold_num"] > 0:
base_df.loc[k, "Best Medal"] = "gold"
base_df.loc[k, "Any Medal"] = v["get_medal_num"]
baseline_score = None
if Path(baseline_result_path).exists():
baseline_score = baseline_df.loc[baseline_df["competition_id"] == v["competition"], "score"].item()
base_df.loc[k, "SOTA Exp"] = v.get("sota_exp_stat", None)
if baseline_score is not None and v.get("sota_exp_score", None) is not None:
base_df.loc[k, "Ours - Base"] = v["sota_exp_score"] - baseline_score
try:
base_df.loc[k, "Ours vs Base"] = math.exp(
abs(math.log(v["sota_exp_score"] / baseline_score))
) # exp^|ln(a/b)|
except Exception as e:
base_df.loc[k, "Ours vs Base"] = None
base_df.loc[k, "SOTA Exp Score"] = v.get("sota_exp_score", None)
base_df.loc[k, "Baseline Score"] = baseline_score
base_df.loc[k, "Bronze Threshold"] = v.get("bronze_threshold", None)
base_df.loc[k, "Silver Threshold"] = v.get("silver_threshold", None)
base_df.loc[k, "Gold Threshold"] = v.get("gold_threshold", None)
base_df.loc[k, "Medium Threshold"] = v.get("median_threshold", None)
base_df.loc[k, "stdout"] = v["stdout"]
base_df["SOTA Exp"] = base_df["SOTA Exp"].replace("", pd.NA)
base_df = base_df.astype(
{
"Total Loops": int,
"Successful Final Decision": int,
"Made Submission": int,
"Valid Submission": int,
"Above Median": int,
"Bronze": int,
"Silver": int,
"Gold": int,
"Any Medal": int,
"Ours - Base": float,
"Ours vs Base": float,
"SOTA Exp Score": float,
"Baseline Score": float,
"Bronze Threshold": float,
"Silver Threshold": float,
"Gold Threshold": float,
"Medium Threshold": float,
}
)
return summary, base_df
def num2percent(num: int, total: int, show_origin=True) -> str:
if show_origin:
return f"{num} ({round(num / total * 100, 2)}%)"
return f"{round(num / total * 100, 2)}%"
def percent_df(df: pd.DataFrame, show_origin=True) -> pd.DataFrame:
base_df = df.astype("object", copy=True)
for k in base_df.index:
loop_num = int(base_df.loc[k, "Total Loops"])
if loop_num != 0:
base_df.loc[k, "Successful Final Decision"] = num2percent(
base_df.loc[k, "Successful Final Decision"], loop_num, show_origin
)
if base_df.loc[k, "Made Submission"] != 0:
base_df.loc[k, "V/M"] = (
f"{round(base_df.loc[k, 'Valid Submission'] / base_df.loc[k, 'Made Submission'] * 100, 2)}%"
)
else:
base_df.loc[k, "V/M"] = "N/A"
base_df.loc[k, "Made Submission"] = num2percent(base_df.loc[k, "Made Submission"], loop_num, show_origin)
base_df.loc[k, "Valid Submission"] = num2percent(base_df.loc[k, "Valid Submission"], loop_num, show_origin)
base_df.loc[k, "Above Median"] = num2percent(base_df.loc[k, "Above Median"], loop_num, show_origin)
base_df.loc[k, "Bronze"] = num2percent(base_df.loc[k, "Bronze"], loop_num, show_origin)
base_df.loc[k, "Silver"] = num2percent(base_df.loc[k, "Silver"], loop_num, show_origin)
base_df.loc[k, "Gold"] = num2percent(base_df.loc[k, "Gold"], loop_num, show_origin)
base_df.loc[k, "Any Medal"] = num2percent(base_df.loc[k, "Any Medal"], loop_num, show_origin)
return base_df
def days_summarize_win():
lfs1 = [re.sub(r"log\.srv\d*", "log.srv", folder) for folder in state.log_folders]
lfs2 = [re.sub(r"log\.srv\d*", "log.srv2", folder) for folder in state.log_folders]
lfs3 = [re.sub(r"log\.srv\d*", "log.srv3", folder) for folder in state.log_folders]
_, df1 = get_summary_df(lfs1)
_, df2 = get_summary_df(lfs2)
_, df3 = get_summary_df(lfs3)
df = pd.concat([df1, df2, df3], axis=0)
def mean_func(x: pd.DataFrame):
numeric_cols = x.select_dtypes(include=["int", "float"]).mean()
string_cols = x.select_dtypes(include=["object"]).agg(lambda col: ", ".join(col.fillna("none").astype(str)))
return pd.concat([numeric_cols, string_cols], axis=0).reindex(x.columns).drop("Competition")
df = df.groupby("Competition").apply(mean_func)
if st.toggle("Show Percent", key="show_percent"):
st.dataframe(percent_df(df, show_origin=False))
else:
st.dataframe(df)
def all_summarize_win():
def shorten_folder_name(folder: str) -> str:
if "amlt" in folder:
return folder[folder.rfind("amlt") + 5 :].split("/")[0]
if "ep" in folder:
return folder[folder.rfind("ep") :]
return folder
selected_folders = st.multiselect(
"Show these folders", state.log_folders, state.log_folders, format_func=shorten_folder_name
)
summary, base_df = get_summary_df(selected_folders)
if not summary:
return
base_df = percent_df(base_df)
st.dataframe(base_df)
st.markdown("Ours vs Base: `math.exp(abs(math.log(sota_exp_score / baseline_score)))`")
st.markdown(f"**统计的比赛数目: :red[{base_df.shape[0]}]**")
total_stat = (
base_df[
[
"Made Submission",
"Valid Submission",
"Above Median",
"Bronze",
"Silver",
"Gold",
"Any Medal",
]
]
!= "0 (0.0%)"
).sum()
total_stat.name = "总体统计(%)"
total_stat.loc["Bronze"] = base_df["Best Medal"].value_counts().get("bronze", 0)
total_stat.loc["Silver"] = base_df["Best Medal"].value_counts().get("silver", 0)
total_stat.loc["Gold"] = base_df["Best Medal"].value_counts().get("gold", 0)
total_stat = total_stat / base_df.shape[0] * 100
# SOTA Exp 统计
se_counts = base_df["SOTA Exp"].value_counts(dropna=True)
se_counts.loc["made_submission"] = se_counts.sum()
se_counts.loc["Any Medal"] = se_counts.get("gold", 0) + se_counts.get("silver", 0) + se_counts.get("bronze", 0)
se_counts.loc["above_median"] = se_counts.get("above_median", 0) + se_counts.get("Any Medal", 0)
se_counts.loc["valid_submission"] = se_counts.get("valid_submission", 0) + se_counts.get("above_median", 0)
sota_exp_stat = pd.Series(index=total_stat.index, dtype=int, name="SOTA Exp 统计(%)")
sota_exp_stat.loc["Made Submission"] = se_counts.get("made_submission", 0)
sota_exp_stat.loc["Valid Submission"] = se_counts.get("valid_submission", 0)
sota_exp_stat.loc["Above Median"] = se_counts.get("above_median", 0)
sota_exp_stat.loc["Bronze"] = se_counts.get("bronze", 0)
sota_exp_stat.loc["Silver"] = se_counts.get("silver", 0)
sota_exp_stat.loc["Gold"] = se_counts.get("gold", 0)
sota_exp_stat.loc["Any Medal"] = se_counts.get("Any Medal", 0)
sota_exp_stat = sota_exp_stat / base_df.shape[0] * 100
stat_df = pd.concat([total_stat, sota_exp_stat], axis=1)
stat_t0, stat_t1 = st.columns(2)
with stat_t0:
st.dataframe(stat_df.round(2))
markdown_table = f"""
| xxx | {stat_df.iloc[0,1]:.1f} | {stat_df.iloc[1,1]:.1f} | {stat_df.iloc[2,1]:.1f} | {stat_df.iloc[3,1]:.1f} | {stat_df.iloc[4,1]:.1f} | {stat_df.iloc[5,1]:.1f} | {stat_df.iloc[6,1]:.1f} |
"""
st.text(markdown_table)
with stat_t1:
Loop_counts = base_df["Total Loops"]
fig = px.histogram(Loop_counts, nbins=10, title="Total Loops Histogram (nbins=10)")
mean_value = Loop_counts.mean()
median_value = Loop_counts.median()
fig.add_vline(
x=mean_value, line_color="orange", annotation_text="Mean", annotation_position="top right", line_width=3
)
fig.add_vline(
x=median_value, line_color="red", annotation_text="Median", annotation_position="top right", line_width=3
)
st.plotly_chart(fig)
# write curve
for k, v in summary.items():
with st.container(border=True):
st.markdown(f"**:blue[{k}] - :violet[{v['competition']}]**")
fc1, fc2 = st.columns(2)
tscores = {f"loop {k-1}": v for k, v in v["test_scores"].items()}
tdf = pd.Series(tscores, name="score")
f2 = px.line(tdf, markers=True, title="Test scores")
fc2.plotly_chart(f2, key=k)
try:
vscores = {k: v.iloc[:, 0] for k, v in v["valid_scores"].items()}
if len(vscores) > 0:
metric_name = list(vscores.values())[0].name
else:
metric_name = "None"
vdf = pd.DataFrame(vscores)
vdf.columns = [f"loop {i}" for i in vdf.columns]
f1 = px.line(vdf.T, markers=True, title=f"Valid scores (metric: {metric_name})")
fc1.plotly_chart(f1, key=f"{k}_v")
except Exception as e:
import traceback
st.markdown("- Error: " + str(e))
st.code(traceback.format_exc())
st.markdown("- Valid Scores: ")
# st.write({k: type(v) for k, v in v["valid_scores"].items()})
st.json(v["valid_scores"])
with st.container(border=True):
if st.toggle("近3天平均", key="show_3days"):
days_summarize_win()
with st.container(border=True):
all_summarize_win()
+599
View File
@@ -0,0 +1,599 @@
import hashlib
import json
import pickle
import re
from collections import defaultdict
from datetime import timedelta
from pathlib import Path
import pandas as pd
import streamlit as st
from streamlit import session_state as state
from rdagent.app.data_science.loop import DataScienceRDLoop
from rdagent.log.mle_summary import extract_mle_json, is_valid_session
from rdagent.log.storage import FileStorage
from rdagent.utils import remove_ansi_codes
if "show_stdout" not in state:
state.show_stdout = False
if "show_llm_log" not in state:
state.show_llm_log = False
if "data" not in state:
state.data = defaultdict(lambda: defaultdict(dict))
if "llm_data" not in state:
state.llm_data = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
if "log_path" not in state:
state.log_path = None
if "log_folder" not in state:
state.log_folder = Path("./log")
def extract_loopid_func_name(tag):
"""提取 Loop ID 和函数名称"""
match = re.search(r"Loop_(\d+)\.([^.]+)", tag)
return match.groups() if match else (None, None)
def extract_evoid(tag):
"""提取 EVO ID"""
match = re.search(r"\.evo_loop_(\d+)\.", tag)
return match.group(1) if match else None
def load_times(log_path: Path):
"""加载时间数据"""
state.times = defaultdict(lambda: defaultdict(dict))
for msg in FileStorage(log_path).iter_msg():
if msg.tag and "llm" not in msg.tag and "session" not in msg.tag:
li, fn = extract_loopid_func_name(msg.tag)
if li:
li = int(li)
# read times
loop_obj_path = log_path / "__session__" / f"{li}" / "4_record"
if loop_obj_path.exists():
try:
state.times[li] = DataScienceRDLoop.load(loop_obj_path, do_truncate=False).loop_trace[li]
except Exception as e:
pass
def convert_defaultdict_to_dict(d):
if isinstance(d, defaultdict):
d = {k: convert_defaultdict_to_dict(v) for k, v in d.items()}
return d
@st.cache_data
def load_data(log_path: Path):
data = defaultdict(lambda: defaultdict(dict))
for msg in FileStorage(log_path).iter_msg():
if msg.tag and "llm" not in msg.tag and "session" not in msg.tag:
if msg.tag == "competition":
data["competition"] = msg.content
continue
li, fn = extract_loopid_func_name(msg.tag)
if li:
li = int(li)
ei = extract_evoid(msg.tag)
msg.tag = re.sub(r"\.evo_loop_\d+", "", msg.tag)
msg.tag = re.sub(r"Loop_\d+\.[^.]+\.?", "", msg.tag)
msg.tag = msg.tag.strip()
if ei:
if int(ei) not in data[li][fn]:
data[li][fn][int(ei)] = {}
data[li][fn][int(ei)][msg.tag] = msg.content
else:
if msg.tag:
data[li][fn][msg.tag] = msg.content
else:
if not isinstance(msg.content, str):
data[li][fn]["no_tag"] = msg.content
# debug_llm data
llm_data = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
llm_log_p = log_path / "debug_llm.pkl"
with st.spinner("正在加载 debug_llm.pkl..."):
rd = pickle.loads(llm_log_p.read_bytes())
for i, d in enumerate(rd):
t = d["tag"]
if "debug_exp_gen" in t:
continue
if "debug_tpl" in t and "filter_" in d["obj"]["uri"]:
continue
lid, fn = extract_loopid_func_name(t)
ei = extract_evoid(t)
if lid:
lid = int(lid)
if ei:
ei = int(ei)
if ei is not None:
llm_data[lid][fn][ei].append(d)
else:
llm_data[lid][fn]["no_tag"].append(d)
return convert_defaultdict_to_dict(data), convert_defaultdict_to_dict(llm_data)
@st.cache_data
def load_stdout(stdout_path: Path):
if stdout_path.exists():
stdout = stdout_path.read_text()
else:
stdout = f"Please Set: {stdout_path}"
return stdout
# UI windows
def task_win(data):
with st.container(border=True):
st.markdown(f"**:violet[{data.name}]**")
st.markdown(data.description)
if hasattr(data, "architecture"): # model task
st.markdown(
f"""
| Model_type | Architecture | hyperparameters |
|------------|--------------|-----------------|
| {data.model_type} | {data.architecture} | {data.hyperparameters} |
"""
)
def workspace_win(data, instance_id=None):
show_files = {k: v for k, v in data.file_dict.items() if "test" not in k}
base_key = str(data.workspace_path)
if instance_id is not None:
base_key += f"_{instance_id}"
unique_key = hashlib.md5(base_key.encode()).hexdigest()
if len(show_files) > 0:
with st.expander(f"Files in :blue[{replace_ep_path(data.workspace_path)}]"):
code_tabs = st.tabs(show_files.keys())
for ct, codename in zip(code_tabs, show_files.keys()):
with ct:
st.code(
show_files[codename],
language=("python" if codename.endswith(".py") else "markdown"),
wrap_lines=True,
line_numbers=True,
)
st.markdown("### Save All Files to Folder")
target_folder = st.text_input("Enter target folder path:", key=f"save_folder_path_input_{unique_key}")
if st.button("Save Files", key=f"save_files_button_{unique_key}"):
if target_folder.strip() == "":
st.warning("Please enter a valid folder path.")
else:
target_folder_path = Path(target_folder)
target_folder_path.mkdir(parents=True, exist_ok=True)
for filename, content in data.file_dict.items():
save_path = target_folder_path / filename
save_path.parent.mkdir(parents=True, exist_ok=True)
save_path.write_text(content, encoding="utf-8")
st.success(f"All files saved to: {target_folder}")
else:
st.markdown(f"No files in :blue[{replace_ep_path(data.workspace_path)}]")
# Helper functions
def show_text(text, lang=None):
"""显示文本代码块"""
if lang:
st.code(text, language=lang, wrap_lines=True)
elif "\n" in text:
st.code(text, language="python", wrap_lines=True)
else:
st.code(text, language="html", wrap_lines=True)
def highlight_prompts_uri(uri):
"""高亮 URI 的格式"""
parts = uri.split(":")
return f"**{parts[0]}:**:green[**{parts[1]}**]"
def llm_log_win(llm_d: list):
for d in llm_d:
if "debug_tpl" in d["tag"]:
uri = d["obj"]["uri"]
tpl = d["obj"]["template"]
cxt = d["obj"]["context"]
rd = d["obj"]["rendered"]
with st.expander(highlight_prompts_uri(uri), expanded=False, icon="⚙️"):
t1, t2, t3 = st.tabs([":green[**Rendered**]", ":blue[**Template**]", ":orange[**Context**]"])
with t1:
show_text(rd)
with t2:
show_text(tpl, lang="django")
with t3:
st.json(cxt)
elif "debug_llm" in d["tag"]:
system = d["obj"].get("system", None)
user = d["obj"]["user"]
resp = d["obj"]["resp"]
with st.expander(f"**LLM**", expanded=False, icon="🤖"):
t1, t2, t3 = st.tabs([":green[**Response**]", ":blue[**User**]", ":orange[**System**]"])
with t1:
try:
rdict = json.loads(resp)
if "code" in rdict:
code = rdict["code"]
st.markdown(":red[**Code in response dict:**]")
st.code(code, language="python", wrap_lines=True, line_numbers=True)
rdict.pop("code")
elif "spec" in rdict:
spec = rdict["spec"]
st.markdown(":red[**Spec in response dict:**]")
st.markdown(spec)
rdict.pop("spec")
else:
# show model codes
showed_keys = []
for k, v in rdict.items():
if k.startswith("model_") and k.endswith(".py"):
st.markdown(f":red[**{k}**]")
st.code(v, language="python", wrap_lines=True, line_numbers=True)
showed_keys.append(k)
for k in showed_keys:
rdict.pop(k)
st.write(":red[**Other parts (except for the code or spec) in response dict:**]")
st.json(rdict)
except:
st.json(resp)
with t2:
show_text(user)
with t3:
show_text(system or "No system prompt available")
def hypothesis_win(data):
st.code(str(data).replace("\n", "\n\n"), wrap_lines=True)
def exp_gen_win(data, llm_data=None):
st.header("Exp Gen", divider="blue", anchor="exp-gen")
if state.show_llm_log:
llm_log_win(llm_data["no_tag"])
st.subheader("Hypothesis")
hypothesis_win(data["no_tag"].hypothesis)
st.subheader("pending_tasks")
for tasks in data["no_tag"].pending_tasks_list:
task_win(tasks[0])
st.subheader("Exp Workspace")
workspace_win(data["no_tag"].experiment_workspace)
def evolving_win(data, key, llm_data=None):
with st.container(border=True):
if len(data) > 1:
evo_id = st.slider("Evolving", 0, len(data) - 1, 0, key=key)
elif len(data) == 1:
evo_id = 0
else:
st.markdown("No evolving.")
return
if evo_id in data:
if state.show_llm_log:
llm_log_win(llm_data[evo_id])
if data[evo_id]["evolving code"][0] is not None:
st.subheader("codes")
workspace_win(data[evo_id]["evolving code"][0], instance_id=key)
fb = data[evo_id]["evolving feedback"][0]
st.subheader("evolving feedback" + ("" if bool(fb) else ""))
f1, f2, f3 = st.tabs(["execution", "return_checking", "code"])
f1.code(fb.execution, wrap_lines=True)
f2.code(fb.return_checking, wrap_lines=True)
f3.code(fb.code, wrap_lines=True)
else:
st.write("data[evo_id]['evolving code'][0] is None.")
st.write(data[evo_id])
else:
st.markdown("No evolving.")
def coding_win(data, llm_data: dict | None = None):
st.header("Coding", divider="blue", anchor="coding")
if llm_data is not None:
common_llm_data = llm_data.pop("no_tag", [])
evolving_data = {k: v for k, v in data.items() if isinstance(k, int)}
task_set = set()
for v in evolving_data.values():
for t in v:
if "Task" in t.split(".")[0]:
task_set.add(t.split(".")[0])
if task_set:
# 新版存Task tag的Trace
for task in task_set:
st.subheader(task)
task_data = {k: {a.split(".")[1]: b for a, b in v.items() if task in a} for k, v in evolving_data.items()}
evolving_win(task_data, key=task, llm_data=llm_data if llm_data else None)
else:
# 旧版未存Task tag的Trace
evolving_win(evolving_data, key="coding", llm_data=llm_data if llm_data else None)
if state.show_llm_log:
llm_log_win(common_llm_data)
if "no_tag" in data:
st.subheader("Exp Workspace (coding final)")
workspace_win(data["no_tag"].experiment_workspace, instance_id="coding_dump")
def running_win(data, mle_score, llm_data=None):
st.header("Running", divider="blue", anchor="running")
if llm_data is not None:
common_llm_data = llm_data.pop("no_tag", [])
evolving_win(
{k: v for k, v in data.items() if isinstance(k, int)}, key="running", llm_data=llm_data if llm_data else None
)
if state.show_llm_log:
llm_log_win(common_llm_data)
if "no_tag" in data:
st.subheader("Exp Workspace (running final)")
workspace_win(data["no_tag"].experiment_workspace, instance_id="running_dump")
st.subheader("Result")
st.write(data["no_tag"].result)
st.subheader("MLE Submission Score" + ("" if (isinstance(mle_score, dict) and mle_score["score"]) else ""))
if isinstance(mle_score, dict):
st.json(mle_score)
else:
st.code(mle_score, wrap_lines=True)
def feedback_win(data, llm_data=None):
data = data["no_tag"]
st.header("Feedback" + ("" if bool(data) else ""), divider="orange", anchor="feedback")
if state.show_llm_log and llm_data is not None:
llm_log_win(llm_data["no_tag"])
st.code(str(data).replace("\n", "\n\n"), wrap_lines=True)
if data.exception is not None:
st.markdown(f"**:red[Exception]**: {data.exception}")
def sota_win(data):
st.header("SOTA Experiment", divider="rainbow", anchor="sota-exp")
if data:
st.markdown(f"**SOTA Exp Hypothesis**")
hypothesis_win(data.hypothesis)
st.markdown("**Exp Workspace**")
workspace_win(data.experiment_workspace, instance_id="sota")
else:
st.markdown("No SOTA experiment.")
def main_win(data, llm_data=None):
exp_gen_win(data["direct_exp_gen"], llm_data["direct_exp_gen"] if llm_data else None)
if "coding" in data:
coding_win(data["coding"], llm_data["coding"] if llm_data else None)
if "running" in data:
running_win(
data["running"],
data.get("mle_score", "no submission to score"),
llm_data=llm_data["running"] if llm_data else None,
)
if "feedback" in data:
feedback_win(data["feedback"], llm_data.get("feedback", None) if llm_data else None)
if "record" in data and "SOTA experiment" in data["record"]:
sota_win(data["record"]["SOTA experiment"])
def replace_ep_path(p: Path):
# 替换workspace path为对应ep机器mount在ep03的path
# TODO: FIXME: 使用配置项来处理
match = re.search(r"ep\d+", str(state.log_folder))
if match:
ep = match.group(0)
return Path(
str(p).replace("repos/RD-Agent-Exp", f"repos/batch_ctrl/all_projects/{ep}").replace("/Data", "/data")
)
return p
def summarize_data():
st.header("Summary", divider="rainbow")
with st.container(border=True):
df = pd.DataFrame(
columns=[
"Component",
"Running Score",
"Feedback",
"e-loops",
"Time",
"Coding",
"Running",
"Start Time (UTC+8)",
"End Time (UTC+8)",
],
index=range(len(state.data) - 1),
)
for loop in range(len(state.data) - 1):
loop_data = state.data[loop]
df.loc[loop, "Component"] = loop_data["direct_exp_gen"]["no_tag"].hypothesis.component
if state.times[loop]:
df.loc[loop, "Time"] = str(sum((i.end - i.start for i in state.times[loop]), timedelta())).split(".")[0]
coding_time = state.times[loop][1].end - state.times[loop][1].start
df.loc[loop, "Coding"] = str(coding_time).split(".")[0]
if len(state.times[loop]) > 2:
running_time = state.times[loop][2].end - state.times[loop][2].start
df.loc[loop, "Running"] = str(running_time).split(".")[0]
df.loc[loop, "Start Time (UTC+8)"] = state.times[loop][0].start + timedelta(hours=8)
df.loc[loop, "End Time (UTC+8)"] = state.times[loop][-1].end + timedelta(hours=8)
if "running" in loop_data and "no_tag" in loop_data["running"]:
if "mle_score" not in state.data[loop]:
if "mle_score" in loop_data["running"]:
mle_score_txt = loop_data["running"]["mle_score"]
state.data[loop]["mle_score"] = extract_mle_json(mle_score_txt)
if state.data[loop]["mle_score"]["score"] is not None:
df.loc[loop, "Running Score"] = str(state.data[loop]["mle_score"]["score"])
else:
state.data[loop]["mle_score"] = mle_score_txt
df.loc[loop, "Running Score"] = ""
else:
mle_score_path = (
replace_ep_path(loop_data["running"]["no_tag"].experiment_workspace.workspace_path)
/ "mle_score.txt"
)
try:
mle_score_txt = mle_score_path.read_text()
state.data[loop]["mle_score"] = extract_mle_json(mle_score_txt)
if state.data[loop]["mle_score"]["score"] is not None:
df.loc[loop, "Running Score"] = str(state.data[loop]["mle_score"]["score"])
else:
state.data[loop]["mle_score"] = mle_score_txt
df.loc[loop, "Running Score"] = ""
except Exception as e:
state.data[loop]["mle_score"] = str(e)
df.loc[loop, "Running Score"] = ""
else:
if isinstance(state.data[loop]["mle_score"], dict):
df.loc[loop, "Running Score"] = str(state.data[loop]["mle_score"]["score"])
else:
df.loc[loop, "Running Score"] = ""
else:
df.loc[loop, "Running Score"] = "N/A"
if "coding" in loop_data:
df.loc[loop, "e-loops"] = max(i for i in loop_data["coding"].keys() if isinstance(i, int)) + 1
if "feedback" in loop_data:
df.loc[loop, "Feedback"] = "" if bool(loop_data["feedback"]["no_tag"]) else ""
else:
df.loc[loop, "Feedback"] = "N/A"
st.dataframe(df)
def comp_stat_func(x: pd.DataFrame):
total_num = x.shape[0]
valid_num = x[x["Running Score"] != "N/A"].shape[0]
avg_e_loops = x["e-loops"].mean()
return pd.Series(
{
"Loop Num": total_num,
"Valid Loop": valid_num,
"Valid Rate": round(valid_num / total_num * 100, 2),
"Avg e-loops": round(avg_e_loops, 2),
}
)
st1, st2 = st.columns([1, 1])
# component statistics
comp_df = df.loc[:, ["Component", "Running Score", "e-loops"]].groupby("Component").apply(comp_stat_func)
comp_df.loc["Total"] = comp_df.sum()
comp_df.loc["Total", "Valid Rate"] = round(
comp_df.loc["Total", "Valid Loop"] / comp_df.loc["Total", "Loop Num"] * 100, 2
)
comp_df["Valid Rate"] = comp_df["Valid Rate"].apply(lambda x: f"{x}%")
comp_df.loc["Total", "Avg e-loops"] = round(df["e-loops"].mean(), 2)
st2.markdown("### Component Statistics")
st2.dataframe(comp_df)
# component time statistics
time_df = df.loc[:, ["Component", "Time", "Coding", "Running"]]
time_df = time_df.astype({"Time": "timedelta64[ns]", "Coding": "timedelta64[ns]", "Running": "timedelta64[ns]"})
st1.markdown("### Time Statistics")
time_stat_df = time_df.groupby("Component").sum()
time_stat_df.loc["Total"] = time_stat_df.sum()
time_stat_df.loc[:, "Coding(%)"] = time_stat_df["Coding"] / time_stat_df["Time"] * 100
time_stat_df.loc[:, "Running(%)"] = time_stat_df["Running"] / time_stat_df["Time"] * 100
time_stat_df = time_stat_df.map(lambda x: str(x).split(".")[0] if pd.notnull(x) else "0:00:00")
st1.dataframe(time_stat_df)
def stdout_win(loop_id: int):
stdout = load_stdout(state.log_folder / f"{state.log_path}.stdout")
if stdout.startswith("Please Set"):
st.toast(stdout, icon="🟡")
return
start_index = stdout.find(f"Start Loop {loop_id}")
end_index = stdout.find(f"Start Loop {loop_id + 1}")
loop_stdout = remove_ansi_codes(stdout[start_index:end_index])
with st.container(border=True):
st.subheader(f"Loop {loop_id} stdout")
pattern = f"Start Loop {loop_id}, " + r"Step \d+: \w+"
matches = re.finditer(pattern, loop_stdout)
step_stdouts = {}
for match in matches:
step = match.group(0)
si = match.start()
ei = loop_stdout.find(f"Start Loop {loop_id}", match.end())
step_stdouts[step] = loop_stdout[si:ei].strip()
for k, v in step_stdouts.items():
with st.expander(k, expanded=False):
st.code(v, language="log", wrap_lines=True)
def get_folders_sorted(log_path):
"""缓存并返回排序后的文件夹列表,并加入进度打印"""
if not log_path.exists():
st.toast(f"Path {log_path} does not exist!")
return []
with st.spinner("正在加载文件夹列表..."):
folders = sorted(
(folder for folder in log_path.iterdir() if is_valid_session(folder)),
key=lambda folder: folder.stat().st_mtime,
reverse=True,
)
st.write(f"找到 {len(folders)} 个文件夹")
return [folder.name for folder in folders]
# UI - Sidebar
with st.sidebar:
# TODO: 只是临时的功能
if any("log.srv" in folder for folder in state.log_folders):
day_map = {"srv": "最近(srv)", "srv2": "上一批(srv2)", "srv3": "上上批(srv3)"}
day_srv = st.radio("选择批次", ["srv", "srv2", "srv3"], format_func=lambda x: day_map[x], horizontal=True)
if day_srv == "srv":
state.log_folders = [re.sub(r"log\.srv\d*", "log.srv", folder) for folder in state.log_folders]
elif day_srv == "srv2":
state.log_folders = [re.sub(r"log\.srv\d*", "log.srv2", folder) for folder in state.log_folders]
elif day_srv == "srv3":
state.log_folders = [re.sub(r"log\.srv\d*", "log.srv3", folder) for folder in state.log_folders]
state.log_folder = Path(st.radio(f"Select :blue[**one log folder**]", state.log_folders))
if not state.log_folder.exists():
st.warning(f"Path {state.log_folder} does not exist!")
else:
folders = get_folders_sorted(state.log_folder)
st.selectbox(f"Select from :blue[**{state.log_folder.absolute()}**]", folders, key="log_path")
if st.button("Refresh Data"):
if state.log_path is None:
st.toast("Please select a log path first!", icon="🟡")
st.stop()
load_times(state.log_folder / state.log_path)
state.data, state.llm_data = load_data(state.log_folder / state.log_path)
st.rerun()
st.toggle("Show LLM Log", key="show_llm_log")
st.toggle("Show stdout", key="show_stdout")
st.markdown(
f"""
- [Exp Gen](#exp-gen)
- [Coding](#coding)
- [Running](#running)
- [Feedback](#feedback)
- [SOTA Experiment](#sota-exp)
"""
)
# UI - Main
if state.data["competition"]:
st.title(state.data["competition"])
summarize_data()
if len(state.data) > 2:
loop_id = st.slider("Loop", 0, len(state.data) - 2, 0)
else:
loop_id = 0
if state.show_stdout:
stdout_win(loop_id)
main_win(state.data[loop_id], state.llm_data[loop_id])
+14 -732
View File
@@ -1,751 +1,33 @@
import hashlib
import math
import re
from collections import defaultdict
from datetime import timedelta
from pathlib import Path
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import streamlit as st
from streamlit import session_state as state
from rdagent.app.data_science.loop import DataScienceRDLoop
from rdagent.log.mle_summary import extract_mle_json, is_valid_session
from rdagent.log.storage import FileStorage
from rdagent.log.ui.conf import UI_SETTING
from rdagent.utils import remove_ansi_codes
st.set_page_config(layout="wide", page_title="RD-Agent", page_icon="🎓", initial_sidebar_state="expanded")
# 设置主日志路径
if "log_folder" not in state:
state.log_folder = Path("./log")
if "log_folders" not in state:
state.log_folders = UI_SETTING.default_log_folders
if "log_path" not in state:
state.log_path = None
if "show_all_summary" not in state:
state.show_all_summary = True
if "show_stdout" not in state:
state.show_stdout = False
def load_stdout():
# FIXME: TODO: 使用配置项来指定stdout文件名
stdout_path = state.log_folder / f"{state.log_path}.stdout"
if stdout_path.exists():
stdout = stdout_path.read_text()
else:
stdout = f"Please Set: {stdout_path}"
return stdout
def extract_loopid_func_name(tag):
"""提取 Loop ID 和函数名称"""
match = re.search(r"Loop_(\d+)\.([^.]+)", tag)
return match.groups() if match else (None, None)
def extract_evoid(tag):
"""提取 EVO ID"""
match = re.search(r"\.evo_loop_(\d+)\.", tag)
return match.group(1) if match else None
# @st.cache_data
def load_data(log_path: Path):
state.data = defaultdict(lambda: defaultdict(dict))
state.times = defaultdict(lambda: defaultdict(dict))
for msg in FileStorage(log_path).iter_msg():
if msg.tag and "llm" not in msg.tag and "session" not in msg.tag:
if msg.tag == "competition":
state.data["competition"] = msg.content
continue
li, fn = extract_loopid_func_name(msg.tag)
li = int(li)
# read times
loop_obj_path = log_path / "__session__" / f"{li}" / "4_record"
if loop_obj_path.exists():
try:
state.times[li] = DataScienceRDLoop.load(loop_obj_path, do_truncate=False).loop_trace[li]
except Exception as e:
pass
ei = extract_evoid(msg.tag)
msg.tag = re.sub(r"\.evo_loop_\d+", "", msg.tag)
msg.tag = re.sub(r"Loop_\d+\.[^.]+\.?", "", msg.tag)
msg.tag = msg.tag.strip()
if ei:
if int(ei) not in state.data[li][fn]:
state.data[li][fn][int(ei)] = {}
state.data[li][fn][int(ei)][msg.tag] = msg.content
else:
if msg.tag:
state.data[li][fn][msg.tag] = msg.content
else:
if not isinstance(msg.content, str):
state.data[li][fn]["no_tag"] = msg.content
# @st.cache_data
def get_folders_sorted(log_path):
"""缓存并返回排序后的文件夹列表,并加入进度打印"""
if not log_path.exists():
st.toast(f"Path {log_path} does not exist!")
return []
with st.spinner("正在加载文件夹列表..."):
folders = sorted(
(folder for folder in log_path.iterdir() if is_valid_session(folder)),
key=lambda folder: folder.stat().st_mtime,
reverse=True,
)
st.write(f"找到 {len(folders)} 个文件夹")
return [folder.name for folder in folders]
summary_page = st.Page("ds_summary.py", title="Summary", icon="📊")
trace_page = st.Page("ds_trace.py", title="Trace", icon="📈")
st.set_page_config(layout="wide", page_title="RD-Agent", page_icon="🎓", initial_sidebar_state="expanded")
st.navigation([summary_page, trace_page]).run()
# UI - Sidebar
with st.sidebar:
log_folder_str = st.text_area(
"**Log Folders**(split by ';')", placeholder=state.log_folder, value=";".join(state.log_folders)
)
state.log_folders = [folder.strip() for folder in log_folder_str.split(";") if folder.strip()]
st.subheader("Pages", divider="rainbow")
st.page_link(summary_page, icon="📊")
st.page_link(trace_page, icon="📈")
# # TODO: 只是临时的功能
day_map = {"srv": "最近(srv)", "srv2": "上一批(srv2)", "srv3": "上上批(srv3)"}
day_srv = st.radio("选择批次", ["srv", "srv2", "srv3"], format_func=lambda x: day_map[x], horizontal=True)
if day_srv == "srv":
state.log_folders = [re.sub(r"log\.srv\d*", "log.srv", folder) for folder in state.log_folders]
elif day_srv == "srv2":
state.log_folders = [re.sub(r"log\.srv\d*", "log.srv2", folder) for folder in state.log_folders]
elif day_srv == "srv3":
state.log_folders = [re.sub(r"log\.srv\d*", "log.srv3", folder) for folder in state.log_folders]
state.log_folder = Path(st.radio(f"Select :blue[**one log folder**]", state.log_folders))
if not state.log_folder.exists():
st.warning(f"Path {state.log_folder} does not exist!")
else:
folders = get_folders_sorted(state.log_folder)
st.selectbox(f"Select from :blue[**{state.log_folder.absolute()}**]", folders, key="log_path")
if st.button("Refresh Data"):
if state.log_path is None:
st.toast("Please select a log path first!", icon="🟡")
st.stop()
load_data(state.log_folder / state.log_path)
st.toggle("One Trace / Log Folder Summary", key="show_all_summary")
st.toggle("Show stdout", key="show_stdout")
# UI windows
def task_win(data):
with st.container(border=True):
st.markdown(f"**:violet[{data.name}]**")
st.markdown(data.description)
if hasattr(data, "architecture"): # model task
st.markdown(
f"""
| Model_type | Architecture | hyperparameters |
|------------|--------------|-----------------|
| {data.model_type} | {data.architecture} | {data.hyperparameters} |
"""
)
def workspace_win(data, instance_id=None):
show_files = {k: v for k, v in data.file_dict.items() if "test" not in k}
base_key = str(data.workspace_path)
if instance_id is not None:
base_key += f"_{instance_id}"
unique_key = hashlib.md5(base_key.encode()).hexdigest()
if len(show_files) > 0:
with st.expander(f"Files in :blue[{replace_ep_path(data.workspace_path)}]"):
code_tabs = st.tabs(show_files.keys())
for ct, codename in zip(code_tabs, show_files.keys()):
with ct:
st.code(
show_files[codename],
language=("python" if codename.endswith(".py") else "markdown"),
wrap_lines=True,
line_numbers=True,
)
st.markdown("### Save All Files to Folder")
target_folder = st.text_input("Enter target folder path:", key=f"save_folder_path_input_{unique_key}")
if st.button("Save Files", key=f"save_files_button_{unique_key}"):
if target_folder.strip() == "":
st.warning("Please enter a valid folder path.")
else:
target_folder_path = Path(target_folder)
target_folder_path.mkdir(parents=True, exist_ok=True)
for filename, content in data.file_dict.items():
save_path = target_folder_path / filename
save_path.parent.mkdir(parents=True, exist_ok=True)
save_path.write_text(content, encoding="utf-8")
st.success(f"All files saved to: {target_folder}")
else:
st.markdown(f"No files in :blue[{replace_ep_path(data.workspace_path)}]")
def hypothesis_win(data):
st.code(str(data).replace("\n", "\n\n"), wrap_lines=True)
def exp_gen_win(data):
st.header("Exp Gen", divider="blue", anchor="exp-gen")
st.subheader("Hypothesis")
hypothesis_win(data["no_tag"].hypothesis)
st.subheader("pending_tasks")
for tasks in data["no_tag"].pending_tasks_list:
task_win(tasks[0])
st.subheader("Exp Workspace")
workspace_win(data["no_tag"].experiment_workspace, instance_id="exp_gen")
def evolving_win(data, key):
with st.container(border=True):
if len(data) > 1:
evo_id = st.slider("Evolving", 0, len(data) - 1, 0, key=key)
elif len(data) == 1:
evo_id = 0
else:
st.markdown("No evolving.")
return
if evo_id in data:
if data[evo_id]["evolving code"][0] is not None:
st.subheader("codes")
workspace_win(data[evo_id]["evolving code"][0], instance_id=key)
fb = data[evo_id]["evolving feedback"][0]
st.subheader("evolving feedback" + ("" if bool(fb) else ""))
f1, f2, f3 = st.tabs(["execution", "return_checking", "code"])
f1.code(fb.execution, wrap_lines=True)
f2.code(fb.return_checking, wrap_lines=True)
f3.code(fb.code, wrap_lines=True)
else:
st.write("data[evo_id]['evolving code'][0] is None.")
st.write(data[evo_id])
else:
st.markdown("No evolving.")
def coding_win(data):
st.header("Coding", divider="blue", anchor="coding")
evolving_data = {k: v for k, v in data.items() if isinstance(k, int)}
task_set = set()
for v in evolving_data.values():
for t in v:
if "Task" in t.split(".")[0]:
task_set.add(t.split(".")[0])
if task_set:
# 新版存Task tag的Trace
for task in task_set:
st.subheader(task)
task_data = {k: {a.split(".")[1]: b for a, b in v.items() if task in a} for k, v in evolving_data.items()}
evolving_win(task_data, key=task)
else:
# 旧版未存Task tag的Trace
evolving_win(evolving_data, key="coding")
if "no_tag" in data:
st.subheader("Exp Workspace (coding final)")
workspace_win(data["no_tag"].experiment_workspace, instance_id="coding")
def running_win(data, mle_score):
st.header("Running", divider="blue", anchor="running")
evolving_win({k: v for k, v in data.items() if isinstance(k, int)}, key="running")
if "no_tag" in data:
st.subheader("Exp Workspace (running final)")
workspace_win(data["no_tag"].experiment_workspace, instance_id="running_dump")
st.subheader("Result")
st.write(data["no_tag"].result)
st.subheader("MLE Submission Score" + ("" if (isinstance(mle_score, dict) and mle_score["score"]) else ""))
if isinstance(mle_score, dict):
st.json(mle_score)
else:
st.code(mle_score, wrap_lines=True)
def feedback_win(data):
data = data["no_tag"]
st.header("Feedback" + ("" if bool(data) else ""), divider="orange", anchor="feedback")
st.code(str(data).replace("\n", "\n\n"), wrap_lines=True)
if data.exception is not None:
st.markdown(f"**:red[Exception]**: {data.exception}")
def sota_win(data):
st.header("SOTA Experiment", divider="rainbow", anchor="sota-exp")
if data:
st.markdown(f"**SOTA Exp Hypothesis**")
hypothesis_win(data.hypothesis)
st.markdown("**Exp Workspace**")
workspace_win(data.experiment_workspace, instance_id="sota")
else:
st.markdown("No SOTA experiment.")
def main_win(data):
exp_gen_win(data["direct_exp_gen"])
if "coding" in data:
coding_win(data["coding"])
if "running" in data:
running_win(data["running"], data["mle_score"])
if "feedback" in data:
feedback_win(data["feedback"])
if "record" in data and "SOTA experiment" in data["record"]:
sota_win(data["record"]["SOTA experiment"])
with st.sidebar:
st.markdown(
f"""
- [Exp Gen](#exp-gen)
- [Coding](#coding)
- [Running](#running)
- [Feedback](#feedback)
- [SOTA Experiment](#sota-exp)
"""
st.subheader("Settings", divider="rainbow")
with st.form("log_folder_form", border=False):
log_folder_str = st.text_area(
"**Log Folders**(split by ';')", placeholder=state.log_folder, value=";".join(state.log_folders)
)
def replace_ep_path(p: Path):
# 替换workspace path为对应ep机器mount在ep03的path
# TODO: FIXME: 使用配置项来处理
match = re.search(r"ep\d+", str(state.log_folder))
if match:
ep = match.group(0)
return Path(
str(p).replace("repos/RD-Agent-Exp", f"repos/batch_ctrl/all_projects/{ep}").replace("/Data", "/data")
)
return p
def summarize_data():
st.header("Summary", divider="rainbow")
df = pd.DataFrame(
columns=["Component", "Running Score", "Feedback", "e-loops", "Time", "Start Time (UTC+8)", "End Time (UTC+8)"],
index=range(len(state.data) - 1),
)
for loop in range(len(state.data) - 1):
loop_data = state.data[loop]
df.loc[loop, "Component"] = loop_data["direct_exp_gen"]["no_tag"].hypothesis.component
if state.times[loop]:
df.loc[loop, "Time"] = str(sum((i.end - i.start for i in state.times[loop]), timedelta())).split(".")[0]
df.loc[loop, "Start Time (UTC+8)"] = state.times[loop][0].start + timedelta(hours=8)
df.loc[loop, "End Time (UTC+8)"] = state.times[loop][-1].end + timedelta(hours=8)
if "running" in loop_data and "no_tag" in loop_data["running"]:
if "mle_score" not in state.data[loop]:
if "mle_score" in loop_data["running"]:
mle_score_txt = loop_data["running"]["mle_score"]
state.data[loop]["mle_score"] = extract_mle_json(mle_score_txt)
if state.data[loop]["mle_score"]["score"] is not None:
df.loc[loop, "Running Score"] = str(state.data[loop]["mle_score"]["score"])
else:
state.data[loop]["mle_score"] = mle_score_txt
df.loc[loop, "Running Score"] = ""
else:
mle_score_path = (
replace_ep_path(loop_data["running"]["no_tag"].experiment_workspace.workspace_path)
/ "mle_score.txt"
)
try:
mle_score_txt = mle_score_path.read_text()
state.data[loop]["mle_score"] = extract_mle_json(mle_score_txt)
if state.data[loop]["mle_score"]["score"] is not None:
df.loc[loop, "Running Score"] = str(state.data[loop]["mle_score"]["score"])
else:
state.data[loop]["mle_score"] = mle_score_txt
df.loc[loop, "Running Score"] = ""
except Exception as e:
state.data[loop]["mle_score"] = str(e)
df.loc[loop, "Running Score"] = ""
else:
if isinstance(state.data[loop]["mle_score"], dict):
df.loc[loop, "Running Score"] = str(state.data[loop]["mle_score"]["score"])
else:
df.loc[loop, "Running Score"] = ""
else:
df.loc[loop, "Running Score"] = "N/A"
if "coding" in loop_data:
df.loc[loop, "e-loops"] = max(i for i in loop_data["coding"].keys() if isinstance(i, int)) + 1
if "feedback" in loop_data:
df.loc[loop, "Feedback"] = "" if bool(loop_data["feedback"]["no_tag"]) else ""
else:
df.loc[loop, "Feedback"] = "N/A"
stat_t0, stat_t1 = st.columns(2)
stat_t0.dataframe(df)
def comp_stat_func(x: pd.DataFrame):
total_num = x.shape[0]
valid_num = x[x["Running Score"] != "N/A"].shape[0]
avg_e_loops = x["e-loops"].mean()
return pd.Series(
{
"Total": total_num,
"Valid": valid_num,
"Valid Rate": round(valid_num / total_num * 100, 2),
"Avg e-loops": round(avg_e_loops, 2),
}
)
comp_df = df.loc[:, ["Component", "Running Score", "e-loops"]].groupby("Component").apply(comp_stat_func)
comp_df.loc["Total"] = comp_df.sum()
comp_df.loc["Total", "Valid Rate"] = round(comp_df.loc["Total", "Valid"] / comp_df.loc["Total", "Total"] * 100, 2)
comp_df["Valid Rate"] = comp_df["Valid Rate"].apply(lambda x: f"{x}%")
comp_df.loc["Total", "Avg e-loops"] = round(df["e-loops"].mean(), 2)
stat_t1.dataframe(comp_df)
def get_summary_df(log_folders: list[str]) -> tuple[dict, pd.DataFrame]:
summarys = {}
for lf in log_folders:
if not (Path(lf) / "summary.pkl").exists():
st.warning(
f"No summary file found in **{lf}**\n\nRun:`dotenv run -- python rdagent/log/mle_summary.py grade_summary --log_folder={lf}`"
)
else:
summarys[lf] = pd.read_pickle(Path(lf) / "summary.pkl")
if len(summarys) == 0:
return {}, pd.DataFrame()
summary = {}
for lf, s in summarys.items():
for k, v in s.items():
stdout_p = Path(lf) / f"{k}.stdout"
v["stdout"] = []
if stdout_p.exists():
# stdout = stdout_p.read_text()
stdout = ""
if "Retrying" in stdout:
v["stdout"].append("LLM Retry")
if "Traceback (most recent call last):" in stdout[-10000:]:
v["stdout"].append("Code Error")
v["stdout"] = ", ".join([i for i in v["stdout"] if i])
# 调整实验名字
if "amlt" in lf:
summary[f"{lf[lf.rfind('amlt')+5:].split('/')[0]} - {k}"] = v
elif "ep" in lf:
summary[f"{lf[lf.rfind('ep'):]} - {k}"] = v
else:
summary[f"{lf} - {k}"] = v
summary = {k: v for k, v in summary.items() if "competition" in v}
base_df = pd.DataFrame(
columns=[
"Competition",
"Total Loops",
"Successful Final Decision",
"Made Submission",
"Valid Submission",
"V/M",
"Above Median",
"Bronze",
"Silver",
"Gold",
"Any Medal",
"Best Medal",
"SOTA Exp",
"Ours - Base",
"Ours vs Base",
"SOTA Exp Score",
"Baseline Score",
"Bronze Threshold",
"Silver Threshold",
"Gold Threshold",
"Medium Threshold",
"stdout",
],
index=summary.keys(),
)
# Read baseline results
baseline_result_path = UI_SETTING.baseline_result_path
if Path(baseline_result_path).exists():
baseline_df = pd.read_csv(baseline_result_path)
for k, v in summary.items():
loop_num = v["loop_num"]
base_df.loc[k, "Competition"] = v["competition"]
base_df.loc[k, "Total Loops"] = loop_num
if loop_num == 0:
base_df.loc[k] = "N/A"
else:
base_df.loc[k, "Successful Final Decision"] = v["success_loop_num"]
base_df.loc[k, "Made Submission"] = v["made_submission_num"]
base_df.loc[k, "Valid Submission"] = v["valid_submission_num"]
base_df.loc[k, "Above Median"] = v["above_median_num"]
base_df.loc[k, "Bronze"] = v["bronze_num"]
if v["bronze_num"] > 0:
base_df.loc[k, "Best Medal"] = "bronze"
base_df.loc[k, "Silver"] = v["silver_num"]
if v["silver_num"] > 0:
base_df.loc[k, "Best Medal"] = "silver"
base_df.loc[k, "Gold"] = v["gold_num"]
if v["gold_num"] > 0:
base_df.loc[k, "Best Medal"] = "gold"
base_df.loc[k, "Any Medal"] = v["get_medal_num"]
baseline_score = None
if Path(baseline_result_path).exists():
baseline_score = baseline_df.loc[baseline_df["competition_id"] == v["competition"], "score"].item()
base_df.loc[k, "SOTA Exp"] = v.get("sota_exp_stat", None)
if baseline_score is not None and v.get("sota_exp_score", None) is not None:
base_df.loc[k, "Ours - Base"] = v["sota_exp_score"] - baseline_score
try:
base_df.loc[k, "Ours vs Base"] = math.exp(
abs(math.log(v["sota_exp_score"] / baseline_score))
) # exp^|ln(a/b)|
except Exception as e:
base_df.loc[k, "Ours vs Base"] = None
base_df.loc[k, "SOTA Exp Score"] = v.get("sota_exp_score", None)
base_df.loc[k, "Baseline Score"] = baseline_score
base_df.loc[k, "Bronze Threshold"] = v.get("bronze_threshold", None)
base_df.loc[k, "Silver Threshold"] = v.get("silver_threshold", None)
base_df.loc[k, "Gold Threshold"] = v.get("gold_threshold", None)
base_df.loc[k, "Medium Threshold"] = v.get("median_threshold", None)
base_df.loc[k, "stdout"] = v["stdout"]
base_df["SOTA Exp"].replace("", pd.NA, inplace=True)
base_df = base_df.astype(
{
"Total Loops": int,
"Successful Final Decision": int,
"Made Submission": int,
"Valid Submission": int,
"Above Median": int,
"Bronze": int,
"Silver": int,
"Gold": int,
"Any Medal": int,
"Ours - Base": float,
"Ours vs Base": float,
"SOTA Exp Score": float,
"Baseline Score": float,
"Bronze Threshold": float,
"Silver Threshold": float,
"Gold Threshold": float,
"Medium Threshold": float,
}
)
return summary, base_df
def num2percent(num: int, total: int, show_origin=True) -> str:
if show_origin:
return f"{num} ({round(num / total * 100, 2)}%)"
return f"{round(num / total * 100, 2)}%"
def percent_df(df: pd.DataFrame, show_origin=True) -> pd.DataFrame:
base_df = df.astype("object", copy=True)
for k in base_df.index:
loop_num = int(base_df.loc[k, "Total Loops"])
if loop_num != 0:
base_df.loc[k, "Successful Final Decision"] = num2percent(
base_df.loc[k, "Successful Final Decision"], loop_num, show_origin
)
if base_df.loc[k, "Made Submission"] != 0:
base_df.loc[k, "V/M"] = (
f"{round(base_df.loc[k, 'Valid Submission'] / base_df.loc[k, 'Made Submission'] * 100, 2)}%"
)
else:
base_df.loc[k, "V/M"] = "N/A"
base_df.loc[k, "Made Submission"] = num2percent(base_df.loc[k, "Made Submission"], loop_num, show_origin)
base_df.loc[k, "Valid Submission"] = num2percent(base_df.loc[k, "Valid Submission"], loop_num, show_origin)
base_df.loc[k, "Above Median"] = num2percent(base_df.loc[k, "Above Median"], loop_num, show_origin)
base_df.loc[k, "Bronze"] = num2percent(base_df.loc[k, "Bronze"], loop_num, show_origin)
base_df.loc[k, "Silver"] = num2percent(base_df.loc[k, "Silver"], loop_num, show_origin)
base_df.loc[k, "Gold"] = num2percent(base_df.loc[k, "Gold"], loop_num, show_origin)
base_df.loc[k, "Any Medal"] = num2percent(base_df.loc[k, "Any Medal"], loop_num, show_origin)
return base_df
def days_summarize_win():
lfs1 = [re.sub(r"log\.srv\d*", "log.srv", folder) for folder in state.log_folders]
lfs2 = [re.sub(r"log\.srv\d*", "log.srv2", folder) for folder in state.log_folders]
lfs3 = [re.sub(r"log\.srv\d*", "log.srv3", folder) for folder in state.log_folders]
_, df1 = get_summary_df(lfs1)
_, df2 = get_summary_df(lfs2)
_, df3 = get_summary_df(lfs3)
df = pd.concat([df1, df2, df3], axis=0)
def mean_func(x: pd.DataFrame):
numeric_cols = x.select_dtypes(include=["int", "float"]).mean()
string_cols = x.select_dtypes(include=["object"]).agg(lambda col: ", ".join(col.fillna("none").astype(str)))
return pd.concat([numeric_cols, string_cols], axis=0).reindex(x.columns).drop("Competition")
df = df.groupby("Competition").apply(mean_func)
if st.toggle("Show Percent", key="show_percent"):
st.dataframe(percent_df(df, show_origin=False))
else:
st.dataframe(df)
def all_summarize_win():
def shorten_folder_name(folder: str) -> str:
if "amlt" in folder:
return folder[folder.rfind("amlt") + 5 :].split("/")[0]
if "ep" in folder:
return folder[folder.rfind("ep") :]
return folder
selected_folders = st.multiselect(
"Show these folders", state.log_folders, state.log_folders, format_func=shorten_folder_name
)
summary, base_df = get_summary_df(selected_folders)
if not summary:
return
base_df = percent_df(base_df)
st.dataframe(base_df)
st.markdown("Ours vs Base: `math.exp(abs(math.log(sota_exp_score / baseline_score)))`")
st.markdown(f"**统计的比赛数目: :red[{base_df.shape[0]}]**")
total_stat = (
base_df[
[
"Made Submission",
"Valid Submission",
"Above Median",
"Bronze",
"Silver",
"Gold",
"Any Medal",
]
]
!= "0 (0.0%)"
).sum()
total_stat.name = "总体统计(%)"
total_stat.loc["Bronze"] = base_df["Best Medal"].value_counts().get("bronze", 0)
total_stat.loc["Silver"] = base_df["Best Medal"].value_counts().get("silver", 0)
total_stat.loc["Gold"] = base_df["Best Medal"].value_counts().get("gold", 0)
total_stat = total_stat / base_df.shape[0] * 100
# SOTA Exp 统计
se_counts = base_df["SOTA Exp"].value_counts(dropna=True)
se_counts.loc["made_submission"] = se_counts.sum()
se_counts.loc["Any Medal"] = se_counts.get("gold", 0) + se_counts.get("silver", 0) + se_counts.get("bronze", 0)
se_counts.loc["above_median"] = se_counts.get("above_median", 0) + se_counts.get("Any Medal", 0)
se_counts.loc["valid_submission"] = se_counts.get("valid_submission", 0) + se_counts.get("above_median", 0)
sota_exp_stat = pd.Series(index=total_stat.index, dtype=int, name="SOTA Exp 统计(%)")
sota_exp_stat.loc["Made Submission"] = se_counts.get("made_submission", 0)
sota_exp_stat.loc["Valid Submission"] = se_counts.get("valid_submission", 0)
sota_exp_stat.loc["Above Median"] = se_counts.get("above_median", 0)
sota_exp_stat.loc["Bronze"] = se_counts.get("bronze", 0)
sota_exp_stat.loc["Silver"] = se_counts.get("silver", 0)
sota_exp_stat.loc["Gold"] = se_counts.get("gold", 0)
sota_exp_stat.loc["Any Medal"] = se_counts.get("Any Medal", 0)
sota_exp_stat = sota_exp_stat / base_df.shape[0] * 100
stat_df = pd.concat([total_stat, sota_exp_stat], axis=1)
stat_t0, stat_t1 = st.columns(2)
with stat_t0:
st.dataframe(stat_df.round(2))
markdown_table = f"""
| xxx | {stat_df.iloc[0,1]:.1f} | {stat_df.iloc[1,1]:.1f} | {stat_df.iloc[2,1]:.1f} | {stat_df.iloc[3,1]:.1f} | {stat_df.iloc[4,1]:.1f} | {stat_df.iloc[5,1]:.1f} | {stat_df.iloc[6,1]:.1f} |
"""
st.text(markdown_table)
with stat_t1:
Loop_counts = base_df["Total Loops"]
fig = px.histogram(Loop_counts, nbins=10, title="Total Loops Histogram (nbins=10)")
mean_value = Loop_counts.mean()
median_value = Loop_counts.median()
fig.add_vline(
x=mean_value, line_color="orange", annotation_text="Mean", annotation_position="top right", line_width=3
)
fig.add_vline(
x=median_value, line_color="red", annotation_text="Median", annotation_position="top right", line_width=3
)
st.plotly_chart(fig)
# write curve
for k, v in summary.items():
with st.container(border=True):
st.markdown(f"**:blue[{k}] - :violet[{v['competition']}]**")
fc1, fc2 = st.columns(2)
tscores = {f"loop {k-1}": v for k, v in v["test_scores"].items()}
tdf = pd.Series(tscores, name="score")
f2 = px.line(tdf, markers=True, title="Test scores")
fc2.plotly_chart(f2, key=k)
try:
vscores = {k: v.iloc[:, 0] for k, v in v["valid_scores"].items()}
if len(vscores) > 0:
metric_name = list(vscores.values())[0].name
else:
metric_name = "None"
vdf = pd.DataFrame(vscores)
vdf.columns = [f"loop {i}" for i in vdf.columns]
f1 = px.line(vdf.T, markers=True, title=f"Valid scores (metric: {metric_name})")
fc1.plotly_chart(f1, key=f"{k}_v")
except Exception as e:
import traceback
st.markdown("- Error: " + str(e))
st.code(traceback.format_exc())
st.markdown("- Valid Scores: ")
# st.write({k: type(v) for k, v in v["valid_scores"].items()})
st.json(v["valid_scores"])
def stdout_win(loop_id: int):
stdout = load_stdout()
if stdout.startswith("Please Set"):
st.toast(stdout, icon="🟡")
return
start_index = stdout.find(f"Start Loop {loop_id}")
end_index = stdout.find(f"Start Loop {loop_id + 1}")
loop_stdout = remove_ansi_codes(stdout[start_index:end_index])
with st.container(border=True):
st.subheader(f"Loop {loop_id} stdout")
pattern = f"Start Loop {loop_id}, " + r"Step \d+: \w+"
matches = re.finditer(pattern, loop_stdout)
step_stdouts = {}
for match in matches:
step = match.group(0)
si = match.start()
ei = loop_stdout.find(f"Start Loop {loop_id}", match.end())
step_stdouts[step] = loop_stdout[si:ei].strip()
for k, v in step_stdouts.items():
with st.expander(k, expanded=False):
st.code(v, language="log", wrap_lines=True)
# UI - Main
if state.show_all_summary:
with st.container(border=True):
if st.toggle("近3天平均", key="show_3days"):
days_summarize_win()
with st.container(border=True):
all_summarize_win()
elif "data" in state:
st.title(state.data["competition"])
summarize_data()
if len(state.data) > 2:
loop_id = st.slider("Loop", 0, len(state.data) - 2, 0)
else:
loop_id = 0
if state.show_stdout:
stdout_win(loop_id)
main_win(state.data[loop_id])
if st.form_submit_button("Confirm"):
state.log_folders = [folder.strip() for folder in log_folder_str.split(";") if folder.strip()]
st.rerun()
+1 -1
View File
@@ -26,7 +26,7 @@ class TestRDAgentImports(unittest.TestCase):
if "llm_st" in fstr:
continue
if (
fstr.endswith("rdagent/log/ui/app.py")
"rdagent/log/ui/" in fstr
or fstr.endswith("rdagent/app/cli.py")
or fstr.endswith("rdagent/app/CI/run.py")
or fstr.endswith("rdagent/app/utils/ape.py")