fix: refine details (#979)

* feat: add parquet preview and extract common DataFrame preview logic

* refactor: improve error messages, prompts, regex, and session loading

* lint
This commit is contained in:
you-n-g
2025-06-23 10:38:27 +08:00
committed by GitHub
parent 0859b9f3a2
commit 46367e15f5
8 changed files with 62 additions and 26 deletions
@@ -74,13 +74,13 @@ class PipelineCoSTEEREvaluator(CoSTEEREvaluator):
# Check model names (index)
if not score_df.index.is_unique:
score_check_text += "\n[Error] The score dataframe contains duplicate model names."
score_check_text += "\n[Error] The file 'scores.csv' contains duplicate model names."
score_ret_code = 1
if "ensemble" not in model_set_in_scores:
score_check_text += "\n[Error] The score dataframe doesn't contain the ensemble model."
score_check_text += "\n[Error] The file 'scores.csv' doesn't contain the ensemble model."
score_ret_code = 1
if score_ret_code != 0:
score_check_text += f"The score_df is:\n{score_df}"
score_check_text += f"The dataframe in file 'scores.csv' is:\n{score_df}"
# Check metric name (columns)
if score_df.columns.tolist() != [self.scen.metric_name]:
@@ -125,10 +125,6 @@ class PipelineCoSTEEREvaluator(CoSTEEREvaluator):
)
stdout += "\n" + submission_check_out
eda_output = implementation.file_dict.get("EDA.md", None)
eda_output = implementation.file_dict.get("EDA.md", None)
if not isinstance(implementation, FBWorkspace):
eda_output = None
else:
@@ -114,8 +114,10 @@ class DSCoSTEERRunner(CoSTEER):
exp.sub_tasks = [
CoSTEERTask(
name="Debug running solution",
description=f"The whole workflow of the solution has finished with some execution error, please check the error message and debug the whole code repo.\nCurrent code repo md5: {md5_hash(exp.experiment_workspace.all_codes)}",
)
description=f"You'll be provided with the source code and the running and testing stdout. "
"Please check the error messages and debug the source code if any errors occur.\n"
f"Current code repo md5: {md5_hash(exp.experiment_workspace.all_codes)}",
),
]
exp = super().develop(exp) # run strategy(code implementation & evaluation loops)
exp.sub_tasks = bak_sub_tasks
@@ -60,7 +60,7 @@ class DSCoSTEERCoSTEEREvaluator(CoSTEEREvaluator):
eda_output = "No EDA output."
implementation.inject_files(**{"EDA.md": eda_output})
stdout = remove_eda_part(stdout)
stdout += f"The code executed {'successfully' if execute_ret_code == 0 else 'failed'}. {'EDA output is emmitted. ' if eda_output else ''}"
stdout += f"The code executed {'successfully' if execute_ret_code == 0 else 'failed'}. {'The EDA output is removed from the stdout. ' if eda_output else ''}"
# Check score file
score_fp = implementation.workspace_path / "scores.csv"
@@ -82,13 +82,13 @@ class DSCoSTEERCoSTEEREvaluator(CoSTEEREvaluator):
# in Pipeline task, we only check ensemble in scores.csv
if DS_RD_SETTING.coder_on_whole_pipeline:
if not score_df.index.is_unique:
score_check_text += "\n[Error] The score dataframe contains duplicate model names."
score_check_text += "\n[Error] The file 'scores.csv' contains duplicate model names."
score_ret_code = 1
if "ensemble" not in model_set_in_scores:
score_check_text += "\n[Error] The score dataframe doesn't contain the ensemble model."
score_check_text += "\n[Error] The file 'scores.csv' doesn't contain the ensemble model."
score_ret_code = 1
if score_ret_code != 0:
score_check_text += f"The score_df is:\n{score_df}"
score_check_text += f"The dataframe in file 'scores.csv' is:\n{score_df}"
else:
if model_set_in_scores != model_set_in_folder.union({"ensemble"}):
score_check_text += f"\n[Error] The scores dataframe does not contain the correct model names as index.\ncorrect model names are: {model_set_in_folder.union({'ensemble'})}\nscore_df is:\n{score_df}"
@@ -49,7 +49,7 @@ DSCoSTEER_eval:
user: |-
--------- code base ---------
{{ code }}
--------- test stdout ---------
--------- the stdout of code execution and testing ---------
{{ stdout }}
DSCoSTEER_debugger:
+30 -10
View File
@@ -306,19 +306,17 @@ def _walk(path: Path):
yield p
def preview_csv(p: Path, file_name: str, simple=True, show_nan_columns=False) -> str:
"""Generate a textual preview of a csv file
def preview_df(df: pd.DataFrame, file_name: str, simple=True, show_nan_columns=False) -> str:
"""Generate a textual preview of a dataframe
Args:
p (Path): the path to the csv file
df (pd.DataFrame): the dataframe to preview
file_name (str): the file name to use in the preview
simple (bool, optional): whether to use a simplified version of the preview. Defaults to True.
Returns:
str: the textual preview
"""
df = pd.read_csv(p)
out = []
out.append(f"-> {file_name} has {df.shape[0]} rows and {df.shape[1]} columns.")
@@ -358,6 +356,18 @@ def preview_csv(p: Path, file_name: str, simple=True, show_nan_columns=False) ->
return "\n".join(out)
def preview_csv(p: Path, file_name: str, simple=True, show_nan_columns=False) -> str:
"""Generate a textual preview of a csv file"""
df = pd.read_csv(p)
return preview_df(df, file_name, simple=simple, show_nan_columns=show_nan_columns)
def preview_parquet(p: Path, file_name: str, simple=True, show_nan_columns=False) -> str:
"""Generate a textual preview of a parquet file"""
df = pd.read_parquet(p)
return preview_df(df, file_name, simple=simple, show_nan_columns=show_nan_columns)
def preview_json(p: Path, file_name: str):
"""Generate a textual preview of a json file using a generated json schema"""
builder = SchemaBuilder()
@@ -388,7 +398,9 @@ def preview_json(p: Path, file_name: str):
return f"-> {file_name} has auto-generated json schema:\n" + builder.to_json(indent=2)
def describe_data_folder_v2(base_path, include_file_details=True, simple=False, show_nan_columns=False):
def describe_data_folder_v2(
base_path, include_file_details=True, simple=False, show_nan_columns=False, max_length: int = 6_000
):
"""
Generate a textual preview of a directory, including an overview of the directory
structure and previews of individual files
@@ -404,6 +416,8 @@ def describe_data_folder_v2(base_path, include_file_details=True, simple=False,
out.append(preview_csv(fn, file_name, simple=simple, show_nan_columns=show_nan_columns))
elif fn.suffix == ".json":
out.append(preview_json(fn, file_name))
elif fn.suffix == ".parquet":
out.append(preview_parquet(fn, file_name, simple=simple, show_nan_columns=show_nan_columns))
elif fn.suffix in plaintext_files:
if get_file_len_size(fn)[0] < 30:
with open(fn) as f:
@@ -411,16 +425,22 @@ def describe_data_folder_v2(base_path, include_file_details=True, simple=False,
if fn.suffix in code_files:
content = f"```\n{content}\n```"
out.append(f"-> {file_name} has content:\n\n{content}")
if len("\n\n".join(out)) > max_length:
break
result = "\n\n".join(out)
# if the result is very long we generate a simpler version
if len(result) > 6_000 and not simple:
if len(result) > max_length and not simple:
return describe_data_folder_v2(
base_path, include_file_details=include_file_details, simple=True, show_nan_columns=show_nan_columns
base_path,
include_file_details=include_file_details,
simple=True,
show_nan_columns=show_nan_columns,
max_length=max_length,
)
# if still too long, we truncate
if len(result) > 6_000 and simple:
return result[:6_000] + "\n... (truncated)"
if len(result) > max_length and simple:
return result[:max_length] + "\n... (truncated)"
return result
+2 -1
View File
@@ -31,7 +31,8 @@ class PythonAgentOut(AgentOut):
@classmethod
def extract_output(cls, resp: str):
match = re.search(r".*```[Pp]ython\n(.*)\n```.*", resp, re.DOTALL)
# We use lazy mode (.*?) to only extract the first code block in the response.
match = re.search(r".*```[Pp]ython\n(.*?)\n```.*", resp, re.DOTALL)
if match:
code = match.group(1)
code = re.sub(r"</?code>", "", code, flags=re.IGNORECASE)
+1
View File
@@ -116,6 +116,7 @@ def pull_image_with_progress(image: str) -> None:
class EnvConf(ExtendedBaseSettings):
# TODO: add prefix ....
default_entry: str
extra_volumes: dict = {}
running_timeout_period: int = 3600 # 10 minutes
+17 -1
View File
@@ -250,7 +250,11 @@ class LoopBase:
current_step = self.step_idx[li]
self.pbar.n = current_step
next_step = self.step_idx[li] % len(self.steps)
self.pbar.set_postfix(loop_index=li, step_index=next_step, step_name=self.steps[next_step])
self.pbar.set_postfix(
loop_index=li + next_step_idx // len(self.steps),
step_index=next_step,
step_name=self.steps[next_step],
)
self._check_exit_conditions_on_step()
else:
logger.warning(f"Step forward {si} of loop {li} is skipped.")
@@ -411,6 +415,18 @@ class LoopBase:
An instance of LoopBase with the loaded session.
"""
path = Path(path)
# if the path is a directory, load the latest session
if path.is_dir():
if path.name != "__session__":
path = path / "__session__"
if not path.exists():
raise FileNotFoundError(f"No session file found in {path}")
# iterate the dump steps in increasing order
files = sorted(path.glob("*/*_*"), key=lambda f: (int(f.parent.name), int(f.name.split("_")[0])))
path = files[-1]
logger.info(f"Loading latest session from {path}")
with path.open("rb") as f:
session = cast(LoopBase, pickle.load(f))