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-03-12 11:36:28 +08:00
from rdagent.components.coder.data_science.conf import (
DSCoderCoSTEERSettings ,
get_ds_env ,
)
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
from rdagent.oai.llm_utils import APIBackend
from rdagent.utils.agent.tpl import T
from rdagent.utils.agent.workflow import build_cls_from_json_with_retry
2025-03-12 11:36:28 +08:00
from rdagent.utils.env import DockerEnv , MLEBDockerConf
2025-02-11 20:50:19 +08:00
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-03-12 11:36:28 +08:00
env = get_ds_env ()
env . conf . extra_volumes = { f " { DS_RD_SETTING . local_data_path } / { self . scen . competition } " : "/kaggle/input" }
2025-03-14 17:56:41 +08:00
env . conf . running_timeout_period = DS_RD_SETTING . full_timeout
2025-02-11 20:50:19 +08:00
stdout = implementation . execute (
2025-03-12 11:36:28 +08:00
env = env , entry = f "rm submission.csv scores.csv"
2025-02-11 20:50:19 +08:00
) # Remove previous submission and scores files generated by worklfow.
# execute workflow
2025-03-19 19:00:53 +08:00
stdout = implementation . execute ( env = env , entry = "python -m coverage run main.py" )
2025-02-26 22:35:44 +08:00
stdout = re . sub ( r "=== Start of EDA part ===(.*)=== End of EDA part ===" , "" , stdout )
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-03-17 23:12:59 +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
# 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
mde = get_ds_env ( "mlebench" )
mde . conf . extra_volumes = {
f " { DS_RD_SETTING . local_data_path } /zip_files" : "/mle/data" ,
}
mde . prepare ()
# MLEBench Check
mle_check_code = (
( Path ( __file__ ) . absolute () . resolve () . parent / "eval_tests" / "mle_submission_format_test.txt" )
. read_text ()
. replace ( "<competition_id>" , self . scen . competition )
)
implementation . inject_files ( ** { "test/mle_submission_format_test.py" : mle_check_code })
submission_check_out , submission_ret_code = implementation . execute_ret_code (
env = mde , entry = "python test/mle_submission_format_test.py"
)
stdout += f " \n MLEBench submission check: \n { submission_check_out } "
2025-02-11 20:50:19 +08:00
system_prompt = T ( ".prompts:DSCoSTEER_eval.system" ) . r (
scenario = self . scen . get_scenario_all_desc (),
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
if feedback :
# 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
if submission_ret_code != 0 :
feedback . final_decision = False
feedback . return_checking += " \n Submission file check failed."
2025-02-19 22:03:39 +08:00
return feedback