AegisGym / scripts /train.py
Armaansaini20
Refactor AegisGym to compliant structure, fix metadata 500 error, and improve grader logic.
8533d4f
Raw
History Blame Contribute Delete
6.65 kB
"""
AegisGym GRPO Training Script
Connects to the live HF Space for environment rollouts.
Run: python train.py
"""
import os
import json
from datasets import Dataset
from trl import GRPOConfig, GRPOTrainer
from transformers import AutoTokenizer
from client_env import get_sync_client
from models import AuditAction
# ─── Config ──────────────────────────────────────────────────────────────────
ENV_URL = os.getenv("AEGISGYM_URL", "https://armaan020-aegisgym.hf.space")
MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-0.5B-Instruct")
OUTPUT_DIR = "aegisgym-grpo-agent"
SYSTEM_PROMPT = """You are a financial compliance auditor AI.
Given a transaction scenario and regulatory context, respond with a JSON object:
{"action_type": "FLAG|APPROVE|REQUEST_INFO", "target_id": "<account_id>", "regulation_citation": "<regulation>"}
Be precise and concise."""
# ─── Action parser ────────────────────────────────────────────────────────────
def parse_action(text: str) -> AuditAction:
try:
start = text.find("{")
end = text.rfind("}") + 1
if start >= 0 and end > start:
data = json.loads(text[start:end])
return AuditAction(**data)
except Exception:
pass
return AuditAction(action_type="REQUEST_INFO", target_id="UNKNOWN", regulation_citation="parse_error")
# ─── Dataset Loading ──────────────────────────────────────────────────────────
from datasets import load_dataset
import itertools
print("Loading SecureFinAI-Lab/Regulations_QA dataset...")
try:
# Use a streaming dataset for efficiency
raw_dataset = load_dataset("SecureFinAI-Lab/Regulations_QA", split="train", streaming=True)
dataset_iterator = itertools.cycle(iter(raw_dataset))
print("Dataset loaded successfully.")
except Exception as e:
print(f"Warning: Failed to load dataset: {e}. Falling back to default prompts.")
dataset_iterator = None
# ─── Rollout function ─────────────────────────────────────────────────────────
def rollout_func(trainer, prompts, tokenizer):
"""One rollout episode connecting to the live AegisGym Space with dataset augmentation."""
from trl.experimental.openenv import generate_rollout_completions
env = get_sync_client(ENV_URL)
result = env.reset()
obs_dict = result.get("observation", {})
# Sample from dataset if available
dataset_context = ""
custom_prompt = "Audit the following transaction."
if dataset_iterator:
item = next(dataset_iterator)
custom_prompt = item.get("question", custom_prompt)
dataset_context = f"\nRegulatory Context: {item.get('answer', '')}"
all_prompt_ids, all_completion_ids, all_logprobs, rewards = [], [], [], []
state = env.state()
tier = state.get("current_tier", "easy")
# Combine dataset context with environment context
regs = obs_dict.get('retrieved_regs', "")
retrieved_regs = [regs] if isinstance(regs, str) else list(regs)
if dataset_context:
retrieved_regs.append(dataset_context)
user_msg = (
f"{custom_prompt}\n\n"
f"Tier: {tier.upper()}\n"
f"Transactions: {obs_dict.get('transactions', [])}\n"
f"Context: {retrieved_regs}\n"
f"Account: {obs_dict.get('account_metadata', {})}"
)
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_msg},
]
prompt_text = tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
out = generate_rollout_completions(trainer, [prompt_text])[0]
all_prompt_ids.extend(out["prompt_ids"])
all_completion_ids.extend(out["completion_ids"])
all_logprobs.extend(out["logprobs"])
completion_text = out.get("text") or tokenizer.decode(out["completion_ids"], skip_special_tokens=True)
action = parse_action(completion_text)
result = env.step(action.model_dump())
rewards.append(float(result.get("reward", 0.0)))
return {
"prompt_ids": all_prompt_ids,
"completion_ids": all_completion_ids,
"logprobs": all_logprobs,
"env_reward": rewards[-1] if rewards else 0.0,
}
# ─── Reward shim ─────────────────────────────────────────────────────────────
def reward_compliance(completions, **kwargs):
rewards = kwargs.get("env_reward", [])
if not rewards:
return [0.0] * len(completions)
return [float(r) for r in rewards]
# ─── Main ─────────────────────────────────────────────────────────────────────
def main():
print(f"Model : {MODEL_NAME}")
print(f"Env : {ENV_URL}")
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
tokenizer.pad_token = tokenizer.eos_token
dataset = Dataset.from_dict({"prompt": ["Audit the following transaction."] * 200})
config = GRPOConfig(
num_train_epochs = 1,
learning_rate = 5e-6,
per_device_train_batch_size = 1,
gradient_accumulation_steps = 4,
warmup_steps = 10,
num_generations = 2,
max_completion_length = 256,
use_vllm = False,
output_dir = OUTPUT_DIR,
logging_steps = 1,
save_steps = 25,
gradient_checkpointing = True,
)
trainer = GRPOTrainer(
model = MODEL_NAME,
processing_class = tokenizer,
reward_funcs = [reward_compliance],
train_dataset = dataset,
args = config,
rollout_func = rollout_func,
)
print("\n=== Starting GRPO Training ===")
trainer.train()
trainer.save_model(OUTPUT_DIR)
print(f"\nModel saved to {OUTPUT_DIR}/")
if __name__ == "__main__":
main()