#!/usr/bin/env python3 """Baseline agent for KaggleSimEnv v3. Structured multi-phase approach: Phase 1: Inspect hints (1-2 hints) Phase 2: Diagnose dataset (shift detection, cleaning) Phase 3: Set CV + feature engineering Phase 4: Train model + handle imbalance Phase 5: Ensemble + regularize + postprocess Phase 6: Submit Usage: python -m baseline.run_baseline --mode local python -m baseline.run_baseline --mode api --base-url http://localhost:7860 """ from __future__ import annotations import argparse import json import os import sys from typing import Any import requests from openai import OpenAI from kaggle_sim_env.environment import KaggleSimEnv from kaggle_sim_env.grader import Grader from kaggle_sim_env.models import Action from kaggle_sim_env.tasks import TASK_REGISTRY, get_task SYSTEM_PROMPT = """\ You are an expert Kaggle Grand Master agent. You work in structured phases. ## PHASES (follow in order) 1. INSPECT: Use inspect_top_solution to get 1-2 hints before acting. 2. DIAGNOSE: Check dataset properties. If has_time_column → detect shift. If columns look leaky → clean data. 3. CV: Choose CV based on dataset properties (group/time/standard). 4. FEATURES: Engineer domain-appropriate features only. Don't use image/spatial features on tabular data. 5. TRAIN: Pick the right model family for the domain. 6. TUNE: Handle imbalance, tune loss if needed. 7. STABILIZE: Regularize + ensemble (keep it to 1-2 techniques). 8. POSTPROCESS: Only if domain-relevant (TTA for images, physics for trajectories). 9. SUBMIT: When confident or running low on steps. ## HIERARCHICAL ACTIONS (use category field!) Example: {"action_type": "feature_engineering", "parameters": {"category": "distribution", "technique": "log_transform"}} Categories per action type: set_cv: standard(kfold,repeated_kfold) | group(group_kfold,stratified_group_kfold) | temporal(time_split,combined_group_time) feature_engineering: distribution(log_transform,normalize,quantile_features) | interaction(interaction_terms,domain_ratios) | encoding(sin_cos_encoding,target_encoding,spatial_encoding,tfidf_features) | spatial(relative_coordinates,distance_features) | signal(frequency_features,multi_layer_features,fourier_resampling) detect_shift: detection(adversarial_validation,feature_importance_shift) | mitigation(remove_identifiers,domain_invariant_features) train_model: tree(xgboost,lightgbm,catboost,random_forest) | linear(linear) | neural(neural_network,pretrained_backbone,temporal_cnn,transformer_encoder) handle_imbalance: weighting(scale_pos_weight,class_weighted_loss) | calibration(calibrate_probabilities,optimize_threshold) | hierarchy(hierarchical_labels,lower_thresholds_recall) clean_data: removal(remove_corrupted,remove_outliers,remove_leaky_features) | reconstruction(analytical_reconstruction,nan_native_model,domain_augmentation,clean_subset_training) augmentation: geometric(geometric,rotation_invariant,image_rectification) | color(color_transform,clahe) | noise(gaussian_noise,robustness_augmentation) | domain(camera_simulation,temporal_augmentation,symmetry_augmentation,multi_view_processing) ensemble: averaging(weighted_average,multi_seed_averaging,swa) | stacking(stacking) | diversity(diverse_features,heterogeneous) postprocess: calibration(bias_correction,prediction_shrinkage,per_group_calibration) | domain(domain_rules,physics_constraints) | inference(tta) tune_loss: asymmetric(asymmetric_loss,epsilon_insensitive) | uncertainty(gaussian_nll) | multi_objective(multi_task,interval_regression,quantile_regression) | weighting(sample_weighted,auxiliary_physics_loss) regularize: weight(strong_regularization,ema,dropout) | transfer(freeze_backbone) Also available: {"action_type": "pseudo_label", "parameters": {"iterations": 1}} {"action_type": "inspect_top_solution", "parameters": {}} {"action_type": "submit", "parameters": {}} ## FAILURE MODE AWARENESS - Using kfold on temporal/grouped data → CV inflated, test destroyed - Image augmentation on tabular data → wasted step + penalty - Target encoding without group CV → leakage trap - Training without addressing shift → model memorises wrong distribution - Using tree models on image/trajectory data → poor fit ## CRITICAL RULES - Start with 1-2 hints to understand the dataset - NEVER use more than 8-10 substantive actions before submitting - Only use strategies relevant to THIS dataset - Respond with ONLY valid JSON, nothing else """ def build_user_message(observation: dict[str, Any]) -> str: meta = observation["dataset_metadata"] return ( f"Step {observation['step_count']}/{observation['max_steps']} " f"CV: {observation['current_cv_score']:.4f} " f"Rank: {observation['leaderboard_rank']}\n" f"Applied: {observation['applied_strategies']}\n" f"Message: {observation.get('message', '')}\n\n" f"Dataset: rows={meta['num_rows']} features={meta['num_features']} " f"target={meta['target_column']} type={meta['task_type']}\n" f" has_time={meta.get('has_time_column',False)} " f"has_group={meta.get('has_group_column',False)} " f"has_image={meta.get('has_image_data',False)} " f"has_spatial={meta.get('has_spatial_data',False)}\n" f" target_dist={meta.get('target_distribution','normal')} " f"class_balance={meta.get('class_balance',{})}\n" f" columns: {', '.join(c['name']+'('+c['dtype']+',miss='+str(c['missing_pct'])+'%)' for c in meta['columns'][:8])}" f"{'...' if len(meta['columns'])>8 else ''}\n\n" "Choose your next action (JSON only):" ) def parse_llm_action(text: str) -> dict[str, Any]: text = text.strip() if text.startswith("```"): lines = text.split("\n") lines = [l for l in lines if not l.strip().startswith("```")] text = "\n".join(lines).strip() return json.loads(text) def run_local(client: OpenAI, task_id: str) -> dict[str, Any]: env = KaggleSimEnv() grader_inst = Grader() task = get_task(task_id) obs = env.reset(task_id=task_id) obs_dict = obs.model_dump() messages: list[dict[str, str]] = [{"role": "system", "content": SYSTEM_PROMPT}] actions_log: list[dict[str, Any]] = [] while not obs_dict.get("done", False): messages.append({"role": "user", "content": build_user_message(obs_dict)}) response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, temperature=0.0, max_tokens=256, ) raw = response.choices[0].message.content or "{}" messages.append({"role": "assistant", "content": raw}) try: action_dict = parse_llm_action(raw) action = Action(**action_dict) except Exception as exc: print(f" [!] Parse error: {exc}. Submitting.") action = Action(action_type="submit", parameters={}) step_result = env.step(action) obs_dict = step_result.observation.model_dump() actions_log.append({ "action": action.model_dump(), "cv_score": step_result.observation.current_cv_score, "reward": step_result.reward.total, }) trap_info = "" if step_result.info.get("traps"): trap_info = f" TRAP!" combo_info = "" if step_result.info.get("combos_completed"): combo_info = f" COMBO: {step_result.info['combos_completed']}" print( f" Step {obs_dict['step_count']:2d}: {action.full_tag():50s} " f"CV={obs_dict['current_cv_score']:.4f} R={step_result.reward.total:+.4f}" f"{trap_info}{combo_info}" ) state = env.state() grade = grader_inst.grade(state, task) return {"task_id": task_id, "actions": actions_log, "grade": grade.model_dump()} def run_api(client: OpenAI, task_id: str, base_url: str) -> dict[str, Any]: resp = requests.post(f"{base_url}/reset", json={"task_id": task_id}, timeout=30) resp.raise_for_status() obs_dict = resp.json() messages: list[dict[str, str]] = [{"role": "system", "content": SYSTEM_PROMPT}] actions_log: list[dict[str, Any]] = [] while not obs_dict.get("done", False): messages.append({"role": "user", "content": build_user_message(obs_dict)}) response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, temperature=0.0, max_tokens=256, ) raw = response.choices[0].message.content or "{}" messages.append({"role": "assistant", "content": raw}) try: action_dict = parse_llm_action(raw) except Exception: action_dict = {"action_type": "submit", "parameters": {}} resp = requests.post(f"{base_url}/step", json=action_dict, timeout=30) resp.raise_for_status() step_data = resp.json() obs_dict = step_data["observation"] actions_log.append({ "action": action_dict, "cv_score": obs_dict["current_cv_score"], "reward": step_data["reward"]["total"], }) resp = requests.post(f"{base_url}/grader", timeout=30) resp.raise_for_status() return {"task_id": task_id, "actions": actions_log, "grade": resp.json()} def main() -> None: parser = argparse.ArgumentParser(description="KaggleSimEnv baseline agent") parser.add_argument("--mode", choices=["local", "api"], default="local") parser.add_argument("--base-url", default="http://localhost:7860") parser.add_argument("--tasks", nargs="*", default=list(TASK_REGISTRY.keys())) args = parser.parse_args() api_key = os.environ.get("OPENAI_API_KEY") if not api_key: print("Error: OPENAI_API_KEY not set.", file=sys.stderr) sys.exit(1) client = OpenAI(api_key=api_key) results: list[dict[str, Any]] = [] for task_id in args.tasks: print(f"\n{'='*70}") print(f" Task: {task_id}") print(f"{'='*70}") result = run_local(client, task_id) if args.mode == "local" else run_api(client, task_id, args.base_url) results.append(result) g = result["grade"] print(f"\n Grade: perf={g['performance_score']:.4f} strat={g['strategy_score']:.4f} " f"combo={g['combo_score']:.4f} trap={g['trap_score']:.4f} final={g['final_score']:.4f}") print(f"\n{'='*70}") print(" SUMMARY") print(f"{'='*70}") for r in results: g = r["grade"] print(f" {r['task_id']:25s} final={g['final_score']:.4f}") avg = sum(r["grade"]["final_score"] for r in results) / max(len(results), 1) print(f"\n Average: {avg:.4f}") if __name__ == "__main__": main()