2025-01-17 22:53:05 +08:00
import json
import re
from pathlib import Path
import pandas as pd
from rdagent.app.data_science.conf import DS_RD_SETTING
from rdagent.components.coder.CoSTEER.evaluators import (
CoSTEEREvaluator ,
CoSTEERMultiFeedback ,
CoSTEERSingleFeedback ,
)
2025-04-09 23:24:12 +08:00
from rdagent.components.coder.data_science.conf import get_clear_ws_cmd , get_ds_env
2025-04-17 16:19:26 +08:00
from rdagent.components.coder.data_science.utils import remove_eda_part
2025-01-17 22:53:05 +08:00
from rdagent.core.evolving_framework import QueriedKnowledge
from rdagent.core.experiment import FBWorkspace , Task
2025-04-04 23:54:54 +08:00
from rdagent.log import rdagent_logger as logger
2025-01-17 22:53:05 +08:00
from rdagent.utils.agent.tpl import T
2025-01-27 20:19:11 +08:00
from rdagent.utils.agent.workflow import build_cls_from_json_with_retry
2025-01-17 22:53:05 +08:00
DIRNAME = Path ( __file__ ) . absolute () . resolve () . parent
WorkflowSingleFeedback = CoSTEERSingleFeedback
WorkflowMultiFeedback = CoSTEERMultiFeedback
class WorkflowGeneralCaseSpecEvaluator ( CoSTEEREvaluator ):
"""
Motivation case:
- Simplest case, we already split the data into train_data, valid_data, and test_data. We require the model to learn (optionally validate on valid data), and infer on test data.
Test workflow:
- Build train, valid, and test data to run it, and test the output (e.g., shape, etc.)
"""
def evaluate (
self ,
target_task : Task ,
implementation : FBWorkspace ,
gt_implementation : FBWorkspace ,
queried_knowledge : QueriedKnowledge = None ,
** kwargs ,
2025-01-27 20:19:11 +08:00
) -> CoSTEERSingleFeedback :
2025-01-17 22:53:05 +08:00
target_task_information = target_task . get_task_information ()
if (
queried_knowledge is not None
and target_task_information in queried_knowledge . success_task_to_knowledge_dict
):
return queried_knowledge . success_task_to_knowledge_dict [ target_task_information ] . feedback
elif queried_knowledge is not None and target_task_information in queried_knowledge . failed_task_info_set :
return WorkflowSingleFeedback (
execution = "This task has failed too many times, skip implementation." ,
return_checking = "This task has failed too many times, skip implementation." ,
code = "This task has failed too many times, skip implementation." ,
final_decision = False ,
)
2025-01-27 00:41:35 +08:00
2025-07-29 14:59:38 +08:00
env = get_ds_env (
extra_volumes = { self . scen . debug_path : T ( "scenarios.data_science.share:scen.input_path" ) . r ()},
running_timeout_period = self . scen . real_debug_timeout (),
)
2025-01-27 00:41:35 +08:00
2025-02-06 15:37:41 +08:00
# # DockerEnv for MLEBench submission validation
# mle_de_conf = MLEBDockerConf()
# mle_de_conf.extra_volumes = {
# f"{DS_RD_SETTING.local_data_path}/zip_files": "/mle/data",
# }
# mde = DockerEnv(conf=mle_de_conf)
# mde.prepare()
2025-01-27 00:41:35 +08:00
2025-01-22 21:51:34 +08:00
# Clean the scores.csv & submission.csv.
2025-04-09 23:24:12 +08:00
implementation . execute ( env = env , entry = get_clear_ws_cmd ())
2025-01-22 21:51:34 +08:00
2025-04-04 12:07:46 +08:00
stdout = implementation . execute ( env = env , entry = f "python -m coverage run main.py" )
2025-03-27 19:36:57 +08:00
# remove EDA part
2025-04-17 16:19:26 +08:00
stdout = remove_eda_part ( stdout )
2025-01-17 22:53:05 +08:00
# Check score file
score_fp = implementation . workspace_path / "scores.csv"
2025-02-20 15:42:17 +08:00
score_ret_code = 0
2025-02-27 22:07:20 +08:00
score_check_text = ""
2025-01-17 22:53:05 +08:00
if not score_fp . exists ():
2025-02-27 22:07:20 +08:00
score_check_text = "[Error] Metrics file (scores.csv) is not generated!"
2025-02-20 15:42:17 +08:00
score_ret_code = 1
2025-04-04 12:07:46 +08:00
implementation . execute ( env = env , entry = "python -m coverage json -o coverage.json" )
coverage_report_path = implementation . workspace_path / "coverage.json"
if coverage_report_path . exists ():
used_files = set ( json . loads ( coverage_report_path . read_text ())[ "files" ] . keys ())
coverage_report_path . unlink ()
logger . info ( f "All used scripts: { used_files } " )
if len ( used_files ) == 1 :
score_check_text += f " \n [Error] The only used script is { used_files } . \n Please check if you have implemented entry point in 'main.py'."
2025-01-17 22:53:05 +08:00
else :
2025-02-08 19:03:29 +08:00
try :
score_df = pd . read_csv ( score_fp , index_col = 0 )
model_set_in_scores = set ( score_df . index )
2025-03-17 21:23:57 +08:00
# We assume that model names in `score_df` are stored without the '.py' file extension.
2025-02-08 19:03:29 +08:00
model_set_in_folder = set (
f [: - 3 ] for f in implementation . file_dict . keys () if re . match ( r "^model_(?!test)\w+\.py$" , f )
)
2025-03-27 19:36:57 +08:00
# Check model names (index)
2025-03-14 17:40:06 +08:00
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. \n correct model names are: { model_set_in_folder . union ({ 'ensemble' }) } \n score_df is: \n { score_df } "
score_ret_code = 1
2025-03-27 19:36:57 +08:00
2025-08-06 20:54:08 +08:00
# Check metric name (columns) - case insensitive
if [ col . lower () for col in score_df . columns . tolist ()] != [ self . scen . metric_name . lower ()]:
2025-03-27 19:36:57 +08:00
score_check_text += f " \n [Error] The scores dataframe does not contain the correct column names. \n Correct columns is: [' { self . scen . metric_name } '] \n But got: { score_df . columns . tolist () } "
score_ret_code = 1
2025-04-04 22:44:55 +08:00
# Check if scores contain NaN (values)
if score_df . isnull () . values . any ():
nan_locations = score_df [ score_df . isnull () . any ( axis = 1 )]
score_check_text += f " \n [Error] The scores dataframe contains NaN values at the following locations: \n { nan_locations } "
score_ret_code = 1
2025-02-08 19:03:29 +08:00
except Exception as e :
2025-03-14 17:40:06 +08:00
score_check_text += f " \n [Error] in checking the scores.csv file: { e } \n scores.csv's content: \n ----- \n { score_fp . read_text () } \n -----"
2025-02-20 15:42:17 +08:00
score_ret_code = 1
2025-01-17 22:53:05 +08:00
# Check submission file
2025-04-16 18:11:46 +08:00
base_check_code = T ( ".eval_tests.submission_format_test" , ftype = "txt" ) . r ()
2025-02-20 15:42:17 +08:00
implementation . inject_files ( ** { "test/submission_format_test.py" : base_check_code })
# stdout += "----Submission Check 1-----\n"
2025-07-02 15:11:18 +08:00
submission_result = implementation . run ( env = env , entry = "python test/submission_format_test.py" )
2025-09-10 10:02:49 +08:00
submission_check_out = submission_result . get_truncated_stdout ()
2025-07-02 23:31:29 +08:00
submission_ret_code = submission_result . exit_code
2025-02-27 22:07:20 +08:00
stdout += " \n " + submission_check_out
2025-02-20 15:42:17 +08:00
2025-01-17 22:53:05 +08:00
system_prompt = T ( ".prompts:workflow_eval.system" ) . r (
2025-04-09 09:42:30 +08:00
# here we pass `None` to `eda_output` because we do not have nor need EDA output for workflow.
scenario = self . scen . get_scenario_all_desc ( eda_output = None ),
2025-01-17 22:53:05 +08:00
task_desc = target_task . get_task_information (),
2025-03-27 17:17:41 +08:00
spec = (
implementation . file_dict [ "spec/workflow.md" ]
if DS_RD_SETTING . spec_enabled
else T ( "scenarios.data_science.share:component_spec.Workflow" ) . r ()
),
2025-01-17 22:53:05 +08:00
)
user_prompt = T ( ".prompts:workflow_eval.user" ) . r (
stdout = stdout . strip (),
code = implementation . file_dict [ "main.py" ],
)
2025-02-20 15:42:17 +08:00
wfb = build_cls_from_json_with_retry (
2025-03-17 19:53:09 +08:00
WorkflowSingleFeedback ,
system_prompt = system_prompt ,
user_prompt = user_prompt ,
init_kwargs_update_func = WorkflowSingleFeedback . val_and_update_init_dict ,
2025-01-27 20:19:11 +08:00
)
2025-02-27 22:07:20 +08:00
if score_ret_code != 0 :
wfb . final_decision = False
2025-03-17 23:12:59 +08:00
wfb . return_checking += " \n " + score_check_text
2025-02-27 22:07:20 +08:00
if submission_ret_code != 0 :
wfb . final_decision = False
2025-03-17 23:12:59 +08:00
wfb . return_checking += " \n Submission file check failed."
2025-02-20 15:42:17 +08:00
return wfb