2025-02-11 20:50:19 +08:00
import json
2025-02-26 22:35:44 +08:00
import re
2025-02-11 20:50:19 +08:00
from pathlib import Path
2025-03-17 23:12:59 +08:00
import pandas as pd
2025-02-11 20:50:19 +08:00
from rdagent.app.data_science.conf import DS_RD_SETTING
from rdagent.components.coder.CoSTEER.evaluators import (
CoSTEEREvaluator ,
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-02-11 20:50:19 +08:00
from rdagent.core.evolving_framework import QueriedKnowledge
from rdagent.core.experiment import FBWorkspace , Task
from rdagent.log import rdagent_logger as logger
2025-05-06 16:00:13 +08:00
from rdagent.scenarios.data_science.test_eval import (
MLETestEval ,
NoTestEvalError ,
get_test_eval ,
)
2025-02-11 20:50:19 +08:00
from rdagent.utils.agent.tpl import T
from rdagent.utils.agent.workflow import build_cls_from_json_with_retry
from rdagent.utils.fmt import shrink_text
DIRNAME = Path ( __file__ ) . absolute () . resolve () . parent
DSCoSTEEREvalFeedback = CoSTEERSingleFeedback
class DSCoSTEERCoSTEEREvaluator ( CoSTEEREvaluator ):
def evaluate (
self ,
target_task : Task ,
implementation : FBWorkspace ,
gt_implementation : FBWorkspace ,
queried_knowledge : QueriedKnowledge = None ,
** kwargs ,
) -> DSCoSTEEREvalFeedback :
2025-04-10 17:56:57 +08:00
env = get_ds_env (
2025-04-16 18:11:46 +08:00
extra_volumes = {
f " { DS_RD_SETTING . local_data_path } / { self . scen . competition } " : T (
"scenarios.data_science.share:scen.input_path"
) . r ()
},
2025-04-10 17:56:57 +08:00
running_timeout_period = DS_RD_SETTING . full_timeout ,
)
2025-02-11 20:50:19 +08:00
stdout = implementation . execute (
2025-04-09 23:24:12 +08:00
env = env , entry = get_clear_ws_cmd ()
2025-02-11 20:50:19 +08:00
) # Remove previous submission and scores files generated by worklfow.
# execute workflow
2025-04-08 03:27:21 -06:00
stdout , execute_ret_code = implementation . execute_ret_code ( env = env , entry = "python -m coverage run main.py" )
2025-04-03 13:00:11 +08:00
match = re . search ( r "(.*?)=== Start of EDA part ===(.*)=== End of EDA part ===" , stdout , re . DOTALL )
eda_output = match . groups ()[ 1 ] if match else None
2025-04-17 16:19:26 +08:00
if eda_output is None :
eda_output = "No EDA output."
implementation . inject_files ( ** { "EDA.md" : eda_output })
stdout = remove_eda_part ( stdout )
2025-05-06 16:00:13 +08:00
stdout += f "The code executed { 'successfully' if execute_ret_code == 0 else 'failed' } . { 'EDA output is emmitted. ' if eda_output else '' } "
2025-02-11 20:50:19 +08:00
2025-03-17 23:12:59 +08:00
# Check score file
2025-02-11 20:50:19 +08:00
score_fp = implementation . workspace_path / "scores.csv"
2025-03-17 23:12:59 +08:00
score_ret_code = 0
score_check_text = ""
2025-02-11 20:50:19 +08:00
if not score_fp . exists ():
2025-03-17 23:12:59 +08:00
logger . warning ( "Metrics file (scores.csv) is not generated!" )
score_check_text = "[Error] Metrics file (scores.csv) is not generated!"
score_ret_code = 1
2025-02-11 20:50:19 +08:00
else :
2025-03-17 23:12:59 +08:00
try :
score_df = pd . read_csv ( score_fp , index_col = 0 )
model_set_in_scores = set ( score_df . index )
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-04-03 13:00:11 +08:00
# in Pipeline task, we only check ensemble in scores.csv
if DS_RD_SETTING . coder_on_whole_pipeline :
2025-04-07 19:06:42 +08:00
if not score_df . index . is_unique :
score_check_text += " \n [Error] The score dataframe contains duplicate model names."
score_ret_code = 1
2025-04-03 13:00:11 +08:00
if "ensemble" not in model_set_in_scores :
2025-04-07 19:06:42 +08:00
score_check_text += " \n [Error] The score dataframe doesn't contain the ensemble model."
2025-04-03 13:00:11 +08:00
score_ret_code = 1
2025-04-07 19:06:42 +08:00
if score_ret_code != 0 :
score_check_text += f "The score_df is: \n { score_df } "
2025-04-03 13:00:11 +08:00
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. \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
# Check metric name (columns)
if score_df . columns . tolist () != [ self . scen . metric_name ]:
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-03-17 23:12:59 +08:00
except Exception as e :
logger . error ( f "Error in checking the scores.csv file: { e } " )
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 -----"
score_ret_code = 1
2025-02-11 20:50:19 +08:00
2025-03-17 23:12:59 +08:00
# DockerEnv for MLEBench submission validation
2025-04-09 13:34:48 +08:00
submission_check_out = ""
2025-05-06 16:00:13 +08:00
submission_ret_code = 0
test_eval = get_test_eval ()
2025-04-09 13:34:48 +08:00
2025-05-06 16:00:13 +08:00
if test_eval . enabled ( self . scen . competition ):
submission_check_out , submission_ret_code = test_eval . valid ( self . scen . competition , implementation )
stdout += f " \n Submission check: \n { submission_check_out } \n If Submission check returns a 'Submission is valid' or similar message, despite some warning messages, you should still consider the submission as valid and give a positive final decision. "
2025-04-08 03:27:21 -06:00
if DS_RD_SETTING . rule_base_eval :
2025-04-09 13:34:48 +08:00
if DS_RD_SETTING . if_using_mle_data :
score_check_text = score_check_text + " \n " + submission_check_out
if (
execute_ret_code == 0
and score_ret_code == 0
and ( not DS_RD_SETTING . if_using_mle_data or submission_ret_code == 0 )
):
2025-04-08 03:27:21 -06:00
return DSCoSTEEREvalFeedback (
execution = stdout ,
2025-04-09 13:34:48 +08:00
return_checking = score_check_text ,
2025-04-08 03:27:21 -06:00
code = "Code evaluation is not available." ,
final_decision = True ,
)
else :
return DSCoSTEEREvalFeedback (
execution = stdout ,
2025-04-09 13:34:48 +08:00
return_checking = score_check_text ,
2025-04-08 03:27:21 -06:00
code = "Code evaluation is not available." ,
final_decision = False ,
)
2025-02-11 20:50:19 +08:00
system_prompt = T ( ".prompts:DSCoSTEER_eval.system" ) . r (
2025-04-09 09:42:30 +08:00
scenario = self . scen . get_scenario_all_desc ( eda_output = implementation . file_dict . get ( "EDA.md" , None )),
2025-05-06 16:00:13 +08:00
is_sub_enabled = test_eval . is_sub_enabled ( self . scen . competition ),
2025-02-11 20:50:19 +08:00
task_desc = target_task . get_task_information (),
)
user_prompt = T ( ".prompts:DSCoSTEER_eval.user" ) . r (
code = implementation . all_codes ,
stdout = shrink_text ( stdout ),
)
2025-02-19 22:03:39 +08:00
feedback = build_cls_from_json_with_retry (
2025-03-17 19:53:09 +08:00
DSCoSTEEREvalFeedback ,
system_prompt = system_prompt ,
user_prompt = user_prompt ,
init_kwargs_update_func = DSCoSTEEREvalFeedback . val_and_update_init_dict ,
2025-02-11 20:50:19 +08:00
)
2025-02-19 22:03:39 +08:00
2025-04-03 13:00:11 +08:00
if feedback and not DS_RD_SETTING . coder_on_whole_pipeline :
2025-02-19 22:03:39 +08:00
# remove unused files
2025-03-19 19:00:53 +08:00
implementation . execute ( env = env , entry = "python -m coverage json -o coverage.json" )
2025-03-17 23:12:59 +08:00
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 } " )
use_one_model = False
for f in used_files :
if f . startswith ( "model_" ) and "test" not in f :
use_one_model = True
break
if not use_one_model :
feedback . final_decision = False
logger . warning ( "No model script is used in `main.py`." )
feedback . code += " \n [Error] No model script is used in `main.py`."
all_python_files = set ( Path ( implementation . workspace_path ) . rglob ( "*.py" ))
must_have_files = [ "load_data.py" , "feature.py" , "ensemble.py" ]
unused_files = [
py_file . name
for py_file in all_python_files
if not ( py_file . name in used_files or py_file . name . endswith ( "test.py" ))
]
if unused_files :
logger . warning ( f "Unused scripts: { unused_files } " )
error_files = set ( unused_files ) . intersection ( set ( must_have_files ))
if error_files :
feedback . final_decision = False
logger . warning ( f " { error_files } must be used in `main.py`." )
feedback . code += f " \n [Error] { error_files } must be used in `main.py`."
elif use_one_model :
logger . info ( "Remove unused scripts." )
implementation . inject_files ( ** { file : implementation . DEL_KEY for file in unused_files })
2025-02-19 22:03:39 +08:00
2025-03-17 23:12:59 +08:00
if score_ret_code != 0 :
feedback . final_decision = False
feedback . return_checking += " \n " + score_check_text
2025-05-06 16:00:13 +08:00
if submission_ret_code != 0 :
2025-03-17 23:12:59 +08:00
feedback . final_decision = False
feedback . return_checking += " \n Submission file check failed."
2025-02-19 22:03:39 +08:00
return feedback