File size: 6,646 Bytes
c17c7eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""
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()