2024-08-19 10:58:28 +08:00
import json
2025-02-28 15:13:43 +08:00
from typing import Dict
2024-08-19 10:58:28 +08:00
2024-09-11 15:26:52 +08:00
import pandas as pd
2024-08-19 10:58:28 +08:00
2024-11-15 15:40:22 +08:00
from rdagent.components.knowledge_management.graph import UndirectedNode
2024-08-19 10:58:28 +08:00
from rdagent.core.experiment import Experiment
2025-05-29 16:16:51 +08:00
from rdagent.core.proposal import Experiment2Feedback , HypothesisFeedback , Trace
2024-08-19 10:58:28 +08:00
from rdagent.log import rdagent_logger as logger
from rdagent.oai.llm_utils import APIBackend
2024-11-15 15:40:22 +08:00
from rdagent.scenarios.kaggle.experiment.kaggle_experiment import KG_SELECT_MAPPING
2024-08-19 10:58:28 +08:00
from rdagent.utils import convert2bool
2025-05-29 16:16:51 +08:00
from rdagent.utils.agent.tpl import T
2024-08-19 10:58:28 +08:00
2025-01-17 22:53:05 +08:00
class KGExperiment2Feedback ( Experiment2Feedback ):
2024-09-26 16:15:47 +08:00
def process_results ( self , current_result , sota_result ):
# Convert the results to dataframes
current_df = pd . DataFrame ( current_result )
sota_df = pd . DataFrame ( sota_result )
# Combine the dataframes on the Metric index
combined_df = pd . concat ([ current_df , sota_df ], axis = 1 )
combined_df . columns = [ "current_df" , "sota_df" ]
# combined_df["the largest"] = combined_df.apply(
# lambda row: "sota_df"
# if row["sota_df"] > row["current_df"]
# else ("Equal" if row["sota_df"] == row["current_df"] else "current_df"),
# axis=1,
# )
# Add a note about metric direction
evaluation_direction = "higher" if self . scen . evaluation_metric_direction else "lower"
2024-09-27 00:17:28 +08:00
evaluation_description = f "Direction of improvement (higher/lower is better) should be judged per metric. Here ' { evaluation_direction } ' is better for the metrics."
combined_df [ "Note" ] = evaluation_description
2024-09-26 16:15:47 +08:00
2024-09-27 00:17:28 +08:00
return combined_df , evaluation_description
2024-09-26 16:15:47 +08:00
2025-01-17 22:53:05 +08:00
def generate_feedback ( self , exp : Experiment , trace : Trace ) -> HypothesisFeedback :
2024-08-19 10:58:28 +08:00
"""
The `ti` should be executed and the results should be included, as well as the comparison between previous results (done by LLM).
For example: `mlflow` of Qlib will be included.
"""
2024-09-11 15:26:52 +08:00
"""
Generate feedback for the given experiment and hypothesis.
Args:
exp: The experiment to generate feedback for.
hypothesis: The hypothesis to generate feedback for.
trace: The trace of the experiment.
Returns:
Any: The feedback generated for the given experiment and hypothesis.
"""
2025-01-17 22:53:05 +08:00
hypothesis = exp . hypothesis
2024-08-19 10:58:28 +08:00
logger . info ( "Generating feedback..." )
2024-09-11 15:26:52 +08:00
current_result = exp . result
2024-08-19 10:58:28 +08:00
2024-09-27 00:17:28 +08:00
evaluation_description = None
2024-09-11 15:26:52 +08:00
# Check if there are any based experiments
if exp . based_experiments :
sota_result = exp . based_experiments [ - 1 ] . result
# Process the results to filter important metrics
2024-09-27 00:17:28 +08:00
combined_result , evaluation_description = self . process_results ( current_result , sota_result )
2024-09-11 15:26:52 +08:00
else :
# If there are no based experiments, we'll only use the current result
2024-09-27 00:17:28 +08:00
combined_result , evaluation_description = self . process_results (
current_result , current_result
) # Compare with itself
2024-09-11 15:26:52 +08:00
print ( "Warning: No previous experiments to compare against. Using current result as baseline." )
2024-08-19 10:58:28 +08:00
2024-09-20 16:06:49 +08:00
# Generate the user prompt based on the action type
if hypothesis . action == "Model tuning" :
prompt_key = "model_tuning_feedback_generation"
elif hypothesis . action == "Model feature selection" :
prompt_key = "feature_selection_feedback_generation"
else :
prompt_key = "factor_feedback_generation"
2024-09-11 15:26:52 +08:00
# Generate the system prompt
2025-05-29 16:16:51 +08:00
sys_prompt = T ( f "scenarios.kaggle.prompts: { prompt_key } .system" ) . r (
scenario = self . scen . get_scenario_all_desc ( filtered_tag = "feedback" )
2024-09-11 15:26:52 +08:00
)
2024-11-15 15:40:22 +08:00
sota_exp = exp . based_experiments [ - 1 ] if exp . based_experiments else None
assert sota_exp is not None
sota_features = str ( exp . based_experiments [ - 1 ] . experiment_workspace . data_description )
sota_models = json . dumps ( exp . based_experiments [ - 1 ] . experiment_workspace . model_description , indent = 2 )
sota_result = exp . based_experiments [ - 1 ] . result
sota_sub_results = exp . based_experiments [ - 1 ] . sub_results
current_hypothesis = hypothesis . hypothesis
current_hypothesis_reason = hypothesis . reason
current_target_action = hypothesis . action
current_sub_exps_to_code = {}
if hypothesis . action == "Model tuning" :
2025-02-18 13:38:13 +08:00
current_sub_exps_to_code [ exp . sub_tasks [ 0 ] . get_task_information ()] = exp . sub_workspace_list [ 0 ] . all_codes
2024-11-15 15:40:22 +08:00
elif hypothesis . action == "Model feature selection" :
2025-01-17 22:53:05 +08:00
current_sub_exps_to_code [ exp . sub_tasks [ 0 ] . get_task_information ()] = exp . experiment_workspace . file_dict [
2024-11-15 15:40:22 +08:00
KG_SELECT_MAPPING [ exp . sub_tasks [ 0 ] . model_type ]
]
else :
current_sub_exps_to_code = {
2025-01-17 22:53:05 +08:00
sub_ws . target_task . get_task_information (): sub_ws . all_codes for sub_ws in exp . sub_workspace_list
2024-11-15 15:40:22 +08:00
}
current_sub_exps_to_code_str = json . dumps ( current_sub_exps_to_code , indent = 2 )
current_result = exp . result
current_sub_results = exp . sub_results
last_hypothesis_and_feedback = None
if trace . hist and len ( trace . hist ) > 0 :
2025-01-17 22:53:05 +08:00
last_hypothesis_and_feedback = ( trace . hist [ - 1 ][ 0 ] . hypothesis , trace . hist [ - 1 ][ 1 ])
2024-09-22 23:12:29 +08:00
2024-09-20 16:06:49 +08:00
# Prepare render dictionary
render_dict = {
2024-11-15 15:40:22 +08:00
"sota_features" : sota_features ,
"sota_models" : sota_models ,
"sota_result" : sota_result ,
"sota_sub_results" : sota_sub_results ,
"current_hypothesis" : current_hypothesis ,
"current_hypothesis_reason" : current_hypothesis_reason ,
"current_target_action" : current_target_action ,
"current_sub_exps_to_code" : current_sub_exps_to_code_str ,
"current_result" : current_result ,
"current_sub_results" : current_sub_results ,
"combined_result" : combined_result ,
2024-09-27 00:17:28 +08:00
"evaluation_description" : evaluation_description ,
2024-11-15 15:40:22 +08:00
"last_hypothesis_and_feedback" : last_hypothesis_and_feedback ,
2024-09-20 16:06:49 +08:00
}
2024-09-11 15:26:52 +08:00
2025-05-29 16:16:51 +08:00
usr_prompt = T ( f "scenarios.kaggle.prompts:kg_feedback_generation_user" ) . r ( ** render_dict )
2024-08-19 10:58:28 +08:00
2024-09-11 15:26:52 +08:00
response = APIBackend () . build_messages_and_create_chat_completion (
user_prompt = usr_prompt ,
system_prompt = sys_prompt ,
2024-08-19 10:58:28 +08:00
json_mode = True ,
2025-02-28 15:13:43 +08:00
json_target_type = Dict [ str , str | bool | int ],
2024-08-19 10:58:28 +08:00
)
2024-09-11 15:26:52 +08:00
response_json = json . loads ( response )
observations = response_json . get ( "Observations" , "No observations provided" )
hypothesis_evaluation = response_json . get ( "Feedback for Hypothesis" , "No feedback provided" )
new_hypothesis = response_json . get ( "New Hypothesis" , "No new hypothesis provided" )
reason = response_json . get ( "Reasoning" , "No reasoning provided" )
decision = convert2bool ( response_json . get ( "Replace Best Result" , "no" ))
2025-01-17 22:53:05 +08:00
# leaderboard = self.scen.leaderboard
# current_score = current_result.iloc[0]
# sorted_scores = sorted(leaderboard, reverse=True)
# import bisect
# if self.scen.evaluation_metric_direction:
# insert_position = bisect.bisect_right([-score for score in sorted_scores], -current_score)
# else:
# insert_position = bisect.bisect_left(sorted_scores, current_score, lo=0, hi=len(sorted_scores))
# percentile_ranking = (insert_position) / (len(sorted_scores)) * 100
2024-09-11 15:26:52 +08:00
2024-09-19 11:33:33 +08:00
experiment_feedback = {
2024-11-15 15:40:22 +08:00
"hypothesis_text" : current_hypothesis ,
"tasks_factors" : current_sub_exps_to_code ,
2024-09-19 11:33:33 +08:00
"current_result" : current_result ,
}
2024-09-27 00:17:28 +08:00
if self . scen . if_using_vector_rag :
2024-11-15 15:40:22 +08:00
raise NotImplementedError ( "Vector RAG is not implemented yet since there are plenty bugs!" )
2024-09-27 00:17:28 +08:00
self . scen . vector_base . add_experience_to_vector_base ( experiment_feedback )
2024-09-28 23:43:11 -04:00
self . scen . vector_base . dump ()
2024-09-27 00:17:28 +08:00
elif self . scen . if_using_graph_rag :
2024-11-15 15:40:22 +08:00
competition_node = UndirectedNode ( content = self . scen . get_competition_full_desc (), label = "competition" )
hypothesis_node = UndirectedNode ( content = hypothesis . hypothesis , label = hypothesis . action )
exp_code_nodes = []
for exp , code in current_sub_exps_to_code . items ():
exp_code_nodes . append ( UndirectedNode ( content = exp , label = "experiments" ))
if code != "" :
exp_code_nodes . append ( UndirectedNode ( content = code , label = "code" ))
conclusion_node = UndirectedNode ( content = response , label = "conclusion" )
all_nodes = [ competition_node , hypothesis_node , * exp_code_nodes , conclusion_node ]
all_nodes = trace . knowledge_base . batch_embedding ( all_nodes )
for node in all_nodes :
if node is not competition_node :
trace . knowledge_base . add_node ( node , competition_node )
2024-09-19 11:33:33 +08:00
2024-09-30 12:30:42 +08:00
if self . scen . if_action_choosing_based_on_UCB :
self . scen . action_counts [ hypothesis . action ] += 1
2024-08-19 10:58:28 +08:00
return HypothesisFeedback (
2024-09-11 15:26:52 +08:00
observations = observations ,
hypothesis_evaluation = hypothesis_evaluation ,
new_hypothesis = new_hypothesis ,
reason = reason ,
decision = decision ,
2024-08-19 10:58:28 +08:00
)