Armaansaini20 commited on
Commit
c17c7eb
Β·
0 Parent(s):

Meta OpenEnv Submission: AegisGym v1.0.0

Browse files
Dockerfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
6
+
7
+ COPY requirements.txt .
8
+ RUN pip install --no-cache-dir -r requirements.txt
9
+
10
+ COPY . .
11
+
12
+ EXPOSE 8000
13
+
14
+ # Start OpenEnv API server
15
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AegisGym: Financial Compliance & AML Sandbox
2
+
3
+ AegisGym is a reinforcement learning sandbox for training and evaluating autonomous financial auditors. It simulates real-world banking compliance tasks, including Sanction Checks, Anti-Money Laundering (AML) detection, and Regulatory Alignment.
4
+
5
+ ## 🏦 Motivation
6
+ Financial institutions process millions of transactions daily. Human auditors often struggle with "smurfing" hidden in noise or complex regulatory clauses across jurisdictions. AegisGym provides a rigorous environment to train LLM-based agents to detect financial crime with high explainability and logical reasoning.
7
+
8
+ ## πŸ› οΈ Environment Specification
9
+
10
+ ### πŸ“ Action Space
11
+ The agent must provide an `AuditAction`:
12
+ - `action_type`: `APPROVE`, `FLAG`, or `REQUEST_INFO`.
13
+ - `target_id`: The ID of the account or transaction.
14
+ - `regulation_citation`: A string citing the relevant regulation (e.g., "BSA-31-USC-5318").
15
+
16
+ ### πŸ‘οΈ Observation Space
17
+ The agent receives an `AuditObservation`:
18
+ - `transactions`: A list of recent transaction dictionaries.
19
+ - `account_metadata`: Details about the account age, tier, and history.
20
+ - `retrieved_regs`: Relevant regulatory guidelines fetched via RAG.
21
+
22
+ ### 🎯 Tasks & Difficulty
23
+ | Task ID | Name | Difficulty | Description |
24
+ |---------|------|------------|-------------|
25
+ | `easy_audit` | Sanction Check | Easy | Identify accounts on a blocklist. |
26
+ | `medium_audit` | Smurfing Detection | Medium | Detect structuring patterns under withdrawal limits. |
27
+ | `hard_audit` | Regulatory Alignment | Hard | Accurately cite complex regulations for high-risk tx. |
28
+
29
+ ## πŸš€ Setup & Usage
30
+
31
+ ### Prerequisites
32
+ - Python 3.10+
33
+ - `openenv-core`
34
+ - Hugging Face Space (for deployment)
35
+
36
+ ### Installation
37
+ ```bash
38
+ pip install -r requirements.txt
39
+ ```
40
+
41
+ ### Running Locally
42
+ ```bash
43
+ uvicorn app:app --host 0.0.0.0 --port 7860
44
+ ```
45
+
46
+ ### Baseline Inference
47
+ ```bash
48
+ export API_BASE_URL="https://api.openai.com/v1"
49
+ export MODEL_NAME="gpt-4o"
50
+ export OPENAI_API_KEY="your_key"
51
+ python inference.py
52
+ ```
53
+
54
+ ## πŸ‹ Deployment
55
+ This environment is designed for Hugging Face Spaces. Use the provided `Dockerfile`.
56
+ - **Public URL:** [armaan020/AegisGym](https://huggingface.co/spaces/armaan020/AegisGym)
__pycache__/app.cpython-313.pyc ADDED
Binary file (364 Bytes). View file
 
__pycache__/client_env.cpython-313.pyc ADDED
Binary file (3.8 kB). View file
 
__pycache__/grader.cpython-313.pyc ADDED
Binary file (2.75 kB). View file
 
__pycache__/models.cpython-313.pyc ADDED
Binary file (2.09 kB). View file
 
__pycache__/server.cpython-313.pyc ADDED
Binary file (5.46 kB). View file
 
__pycache__/train.cpython-313.pyc ADDED
Binary file (6.93 kB). View file
 
app.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from openenv.core.env_server import create_fastapi_app
2
+ from server import AegisGymEnv
3
+ from models import AuditAction, AuditObservation
4
+
5
+ app = create_fastapi_app(AegisGymEnv, AuditAction, AuditObservation)
audit_logs.txt ADDED
Binary file (2.66 kB). View file
 
audit_logs_full.txt ADDED
Binary file (4.33 kB). View file
 
baseline_inference.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from server import AegisGymEnv
3
+ from models import AuditAction
4
+
5
+ def main():
6
+ print("--- AegisGym Final Baseline Inference Simulation (Official API) ---")
7
+
8
+ env = AegisGymEnv()
9
+ obs = env.reset()
10
+
11
+ total_score = 0.0
12
+ episodes = 10
13
+
14
+ for i in range(episodes):
15
+ tier = env.state.current_tier
16
+ print(f"\nEpisode {i+1} | Tier: {tier.upper()}")
17
+
18
+ # Mocking an agent's decision based on the tier
19
+ if tier == "easy":
20
+ action = AuditAction(
21
+ action_type="FLAG",
22
+ target_id="ACC-BL-001",
23
+ regulation_citation="Sanctions List Match"
24
+ )
25
+ elif tier == "medium":
26
+ action = AuditAction(
27
+ action_type="FLAG",
28
+ target_id="ACC-SMURF-99",
29
+ regulation_citation="Structuring/Smurfing 9000-10000 range"
30
+ )
31
+ else:
32
+ action = AuditAction(
33
+ action_type="FLAG",
34
+ target_id="ACC-REG-VIOLATOR",
35
+ regulation_citation="EU-AI-Act-Art-57"
36
+ )
37
+
38
+ # step() returns an AuditObservation instance only
39
+ obs = env.step(action)
40
+ print(f"Action Taken: {action.action_type} for {action.target_id}")
41
+ print(f"Reward: {obs.reward} | Done: {obs.done}")
42
+
43
+ total_score += obs.reward
44
+ if obs.done:
45
+ break
46
+
47
+ print(f"\n--- Reproducibility Report ---")
48
+ print(f"Total Episodes: {min(episodes, env.state.step_count)}")
49
+ print(f"Custom Agent Mean Score (Reward): {total_score / min(episodes, env.state.step_count)}")
50
+
51
+ if __name__ == "__main__":
52
+ main()
check_consistency.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ def check():
4
+ path = "audit_logs.txt"
5
+ if not os.path.exists(path):
6
+ print("Logs not found.")
7
+ return
8
+
9
+ try:
10
+ # PowerShell results might be in UTF-16
11
+ with open(path, "rb") as f:
12
+ raw = f.read()
13
+ content = raw.decode("utf-16", "ignore")
14
+ except:
15
+ content = open(path, "r", errors="ignore").read()
16
+
17
+ questions = [
18
+ "What are the requirements to be listed on the Nasdaq?",
19
+ "When was Rule. 2010"
20
+ ]
21
+
22
+ print("=== Consistency Check ===")
23
+ for q in questions:
24
+ found = q.lower() in content.lower()
25
+ print(f"Found '{q}': {found}")
26
+
27
+ if __name__ == "__main__":
28
+ check()
client.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openenv.core.env_client import EnvClient
2
+ from models import ComplianceAction
3
+ from server import ComplianceEnv
4
+ import json
5
+
6
+ def run_test():
7
+ print("Initializing test environment...")
8
+
9
+ # In a real setup, client talks to a served environment via EnvClient
10
+ # For local demonstration, we instantiate the Env directly
11
+ env = ComplianceEnv()
12
+
13
+ obs = env.reset()
14
+ print("Initial Observation:")
15
+ print(obs.model_dump_json(indent=2))
16
+
17
+ # Simulate a bad action (approve a sanctioned entity)
18
+ # Let's force it to be a sanctioned entity for testing
19
+ env.db.current_entity = "EvilCorp"
20
+
21
+ action1 = ComplianceAction(
22
+ decision="APPROVE",
23
+ reasoning="The transaction seems fine.",
24
+ cited_regulation_id="NONE"
25
+ )
26
+
27
+ new_obs, reward, done, info = env.step(action1)
28
+ print("\nAction 1: APPROVE EvilCorp")
29
+ print(f"Reward: {reward} (Expect negative)")
30
+ print(f"Trace info: {info}")
31
+
32
+ # Simulate a good action
33
+ env.db.current_entity = "EvilCorp"
34
+ action2 = ComplianceAction(
35
+ decision="BLOCK",
36
+ reasoning="Entity is on the sanctions list. Blocking as per AML regulations.",
37
+ cited_regulation_id="EU-AML-01"
38
+ )
39
+
40
+ new_obs, reward, done, info = env.step(action2)
41
+ print("\nAction 2: BLOCK EvilCorp with good reasoning")
42
+ print(f"Reward: {reward} (Expect positive)")
43
+ print(f"Trace info: {info}")
44
+
45
+ if __name__ == "__main__":
46
+ run_test()
client_env.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AegisGym WebSocket client β€” concrete subclass of openenv EnvClient.
3
+
4
+ Usage:
5
+ client = AegisGymWsClient()
6
+ sync_client = client.sync()
7
+ obs = sync_client.reset()
8
+ obs = sync_client.step({...})
9
+ """
10
+ from typing import Any, Dict
11
+ from openenv.core.env_client import EnvClient
12
+ from openenv.core.sync_client import SyncEnvClient
13
+ from models import AuditAction, AuditObservation
14
+
15
+ HF_SPACE_WSS = "wss://armaan020-aegisgym.hf.space"
16
+
17
+
18
+ class AegisGymWsClient(EnvClient):
19
+ """Concrete EnvClient implementation for the AegisGym HF Space."""
20
+
21
+ def _step_payload(self, action: Dict[str, Any]) -> Dict[str, Any]:
22
+ """Convert an action dict into the WS step payload."""
23
+ return action if isinstance(action, dict) else action.model_dump()
24
+
25
+ def _parse_result(self, payload: Dict[str, Any]) -> Any:
26
+ """Parse reset/step response from the server into usable result."""
27
+ return payload # keep as dict; training code accesses .observation, .reward, .done
28
+
29
+ def _parse_state(self, payload: Dict[str, Any]) -> Any:
30
+ """Parse the state endpoint response."""
31
+ return payload
32
+
33
+
34
+ def get_sync_client(ws_url: str = HF_SPACE_WSS) -> SyncEnvClient:
35
+ """Return a synchronous wrapper over the WebSocket client."""
36
+ return AegisGymWsClient(base_url=ws_url).sync()
37
+
38
+
39
+ def test_live_env():
40
+ print("=== Testing Live AegisGym Space (wss) ===")
41
+ client = get_sync_client()
42
+
43
+ print("reset() ...")
44
+ result = client.reset()
45
+ print(f" result type: {type(result)}")
46
+ print(f" keys: {list(result.keys()) if isinstance(result, dict) else dir(result)}")
47
+
48
+ action = AuditAction(
49
+ action_type="FLAG",
50
+ target_id="ACC-BL-001",
51
+ regulation_citation="EU-AI-Act-Art-57"
52
+ ).model_dump()
53
+
54
+ print("step(FLAG ACC-BL-001) ...")
55
+ result = client.step(action)
56
+ print(f" reward={result.get('reward') if isinstance(result, dict) else getattr(result,'reward',None)}")
57
+ print(f" done={result.get('done') if isinstance(result, dict) else getattr(result,'done',None)}")
58
+
59
+ print("state() ...")
60
+ s = client.state()
61
+ print(f" State: {s}")
62
+
63
+ print("\n=== Live environment OK! ===")
64
+
65
+
66
+ if __name__ == "__main__":
67
+ test_live_env()
deploy_hf.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import subprocess
3
+ import sys
4
+ import os
5
+
6
+ def check_huggingface_login():
7
+ """Verify the user is logged into Hugging Face CLI."""
8
+ try:
9
+ from huggingface_hub import HfApi
10
+ api = HfApi()
11
+ api.whoami()
12
+ return True
13
+ except Exception:
14
+ return False
15
+
16
+ def init_space(space_name: str, private: bool = True):
17
+ """Initialize a Hugging Face space for the environment."""
18
+ print(f"--- Initializing Hugging Face Space: {space_name} ---")
19
+
20
+ if not check_huggingface_login():
21
+ print("Error: You are not logged in to Hugging Face CLI. Please run 'huggingface-cli login' first.")
22
+ sys.exit(1)
23
+
24
+ try:
25
+ from huggingface_hub import HfApi
26
+ api = HfApi()
27
+ api.create_repo(repo_id=space_name, repo_type="space", space_sdk="docker", private=private, exist_ok=True)
28
+ print("Space created or verified successfully!")
29
+ except Exception as e:
30
+ print(f"Failed to create space: {e}")
31
+ print("Note: If it already exists, deployment will proceed.")
32
+
33
+ def deploy_to_space(space_name: str):
34
+ """Deploy the current directory to the Hugging Face Space."""
35
+ print(f"\n--- Deploying AegisGym to {space_name} ---")
36
+
37
+ # Normally this would involve git add/commit/push to the HF remote
38
+ # or using the huggingface_hub Python library to upload the folder seamlessly.
39
+ try:
40
+ from huggingface_hub import HfApi
41
+ api = HfApi()
42
+ print("Uploading files to Hub...")
43
+
44
+ # Don't upload the cache or local virtual environments
45
+ ignore_patterns = ["__pycache__/*", "*.git*", ".env", "venv/*"]
46
+
47
+ url = api.upload_folder(
48
+ folder_path=".",
49
+ repo_id=space_name,
50
+ repo_type="space",
51
+ ignore_patterns=ignore_patterns
52
+ )
53
+ print(f"Deployment successful! Your environment is live at: {url}")
54
+
55
+ except ImportError:
56
+ print("Error: 'huggingface_hub' is not installed. Run 'pip install huggingface_hub'.")
57
+ sys.exit(1)
58
+ except Exception as e:
59
+ print(f"Deployment failed: {e}")
60
+
61
+ if __name__ == "__main__":
62
+ parser = argparse.ArgumentParser(description="Deploy AegisGym to Hugging Face Spaces.")
63
+ parser.add_argument("space_name", help="The name of your huggingface space (e.g. username/aegisgym)")
64
+ parser.add_argument("--public", action="store_true", help="Make the space public (default is private)")
65
+
66
+ args = parser.parse_args()
67
+
68
+ init_space(args.space_name, private=not args.public)
69
+ deploy_to_space(args.space_name)
70
+
71
+ print("\n--- Next Steps ---")
72
+ print("1. Your Space is building the Docker container using your OpenEnv configurations.")
73
+ print(f"2. You can access it directly via: https://huggingface.co/spaces/{args.space_name}")
74
+ print("3. In your training script, switch your Env URL to match your Space.")
generate_matching_report.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ def generate_report():
4
+ with open("synced_report.json", "r") as f:
5
+ data = json.load(f)
6
+
7
+ with open("matching_dataset_logs.md", "w", encoding="utf-8") as f:
8
+ f.write("# Synchronized Audit & Dataset Report\n\n")
9
+ f.write("This report pairs each simulation episode with the exact dataset entry that triggered it.\n\n")
10
+
11
+ for e in data:
12
+ f.write(f"## Episode {e['episode']}\n")
13
+ f.write(f"### πŸ“Š Dataset Entry\n")
14
+ f.write(f"**Question:** {e['dataset_question']}\n\n")
15
+ f.write(f"**Context (Answer):** {e['dataset_answer']}\n\n")
16
+ f.write(f"### πŸ“„ Audit Log\n")
17
+ f.write(f"**LLM Reasoning:**\n```json\n{e['llm_reasoning']}\n```\n")
18
+ f.write(f"**Action:** {e['action']['action_type']} on {e['action']['target_id']}\n\n")
19
+ f.write(f"**Reward:** {e['reward']}\n\n")
20
+ f.write("---\n\n")
21
+
22
+ if __name__ == "__main__":
23
+ generate_report()
grader.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from typing import Dict, Any, List
3
+
4
+ class Grader:
5
+ def __init__(self):
6
+ # Dummy blacklist for Easy task
7
+ self.sanctioned_ids = ["ACC-BL-001", "ACC-BL-002", "ACC-BL-999"]
8
+
9
+ def grade_easy(self, target_id: str, is_flagged: bool) -> float:
10
+ """
11
+ Sanction Hit: Binary match of AccountID vs Blacklist.
12
+ Returns identification accuracy (Pid)
13
+ """
14
+ is_sanctioned = target_id in self.sanctioned_ids
15
+ if is_sanctioned and is_flagged:
16
+ return 1.0
17
+ elif not is_sanctioned and not is_flagged:
18
+ return 1.0
19
+ return 0.0
20
+
21
+ def grade_medium(self, transactions: List[Dict[str, Any]], target_id: str, is_flagged: bool) -> float:
22
+ """
23
+ Smurfing Detection: Temporal window analysis of related transactions.
24
+ Checks for multiple transactions just under $10,000.
25
+ """
26
+ smurf_count = sum(1 for tx in transactions if 9000 <= tx.get("amount", 0) < 10000)
27
+ has_smurfing = smurf_count >= 3
28
+
29
+ if has_smurfing and is_flagged:
30
+ return 1.0
31
+ elif not has_smurfing and not is_flagged:
32
+ return 1.0
33
+ return 0.0
34
+
35
+ def grade_hard(self, citation: str) -> float:
36
+ """
37
+ Regulatory Mapping: LLM-as-a-judge + Cosine similarity (mocked here).
38
+ Returns citation accuracy (Pcit) based on simulated semantic match.
39
+ """
40
+ if not citation:
41
+ return 0.0
42
+
43
+ # Simplified mock evaluation
44
+ valid_citations = ["EU-AI-Act-Art-57", "BSA-31-USC-5318"]
45
+ if any(v in citation for v in valid_citations):
46
+ return 1.0
47
+ elif len(citation) > 10:
48
+ return 0.5 # Partial credit for trying to cite something substantive
49
+ return 0.0
inference.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AegisGym Baseline Inference Script
3
+ Requirement: Must use OpenAI client and env vars for credentials.
4
+ """
5
+ import os
6
+ import json
7
+ from openai import OpenAI
8
+ from client_env import get_sync_client
9
+ from models import AuditAction
10
+
11
+ # ─── Config (Required by Meta OpenEnv) ──────────────────────────────────────
12
+ API_BASE_URL = os.getenv("API_BASE_URL", "https://api.openai.com/v1")
13
+ MODEL_NAME = os.getenv("MODEL_NAME", "gpt-4o")
14
+ HF_TOKEN = os.getenv("HF_TOKEN", "") # Used if hitting HF Inference Endpoints
15
+ ENV_URL = os.getenv("ENV_URL", "https://armaan020-aegisgym.hf.space")
16
+
17
+ client = OpenAI(api_key=HF_TOKEN or os.getenv("OPENAI_API_KEY"), base_url=API_BASE_URL)
18
+
19
+ SYSTEM_PROMPT = """You are a financial compliance auditor AI.
20
+ Respond ONLY with a JSON object:
21
+ {"action_type": "FLAG|APPROVE|REQUEST_INFO", "target_id": "<id>", "regulation_citation": "<cite>"}"""
22
+
23
+ def run_baseline(num_steps=3):
24
+ print(f"=== AegisGym Baseline Inference ===")
25
+ print(f"Model: {MODEL_NAME} | Env: {ENV_URL}")
26
+
27
+ env = get_sync_client(ENV_URL)
28
+
29
+ for i in range(num_steps):
30
+ print(f"\n--- Step {i+1} ---")
31
+ obs_payload = env.reset()
32
+ obs = obs_payload.get("observation", {})
33
+
34
+ user_msg = (
35
+ f"Transactions: {obs.get('transactions', [])}\n"
36
+ f"Context: {obs.get('retrieved_regs', '')}\n"
37
+ f"Account: {obs.get('account_metadata', {})}"
38
+ )
39
+
40
+ # OpenAI API Call
41
+ response = client.chat.completions.create(
42
+ model=MODEL_NAME,
43
+ messages=[
44
+ {"role": "system", "content": SYSTEM_PROMPT},
45
+ {"role": "user", "content": user_msg}
46
+ ],
47
+ response_format={"type": "json_object"}
48
+ )
49
+
50
+ content = response.choices[0].message.content
51
+ print(f"LLM Response: {content}")
52
+
53
+ # Parse and Step
54
+ try:
55
+ action_data = json.loads(content)
56
+ result = env.step(action_data)
57
+ print(f"Reward: {result.get('reward')} | Done: {result.get('done')}")
58
+ except Exception as e:
59
+ print(f"Parse Error: {e}")
60
+
61
+ if __name__ == "__main__":
62
+ run_baseline()
matching_dataset_logs.md ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Synchronized Audit & Dataset Report
2
+
3
+ This report pairs each simulation episode with the exact dataset entry that triggered it.
4
+
5
+ ## Episode 1
6
+ ### πŸ“Š Dataset Entry
7
+ **Question:** When was Rule. 2010 Standards of Commercial Honor and Principles of Trade Adopted?
8
+
9
+ **Context (Answer):** 2009-05-29 00:00:00
10
+
11
+ ### πŸ“„ Audit Log
12
+ **LLM Reasoning:**
13
+ ```json
14
+ {"action_type": "FLAG", "target_id": "ACC-BL-001", "regulation_citation": "EU-AI-Act-Art-57"}
15
+ ```
16
+ **Action:** FLAG on ACC-BL-001
17
+
18
+ **Reward:** 0.5
19
+
20
+ ---
21
+
22
+ ## Episode 2
23
+ ### πŸ“Š Dataset Entry
24
+ **Question:** When was Rule Rule 2020. Use of Manipulative, Deceptive or Other Fraudulent Devices adopted
25
+
26
+ **Context (Answer):** 2009-05-29 00:00:00
27
+
28
+ ### πŸ“„ Audit Log
29
+ **LLM Reasoning:**
30
+ ```json
31
+ {
32
+ "action_type": "FLAG",
33
+ "target_id": "ACC-CLEAN-01",
34
+ "regulation_citation": "EU-AI-Act-Art-57"
35
+ }
36
+ ```
37
+ **Action:** FLAG on ACC-CLEAN-01
38
+
39
+ **Reward:** -1.0
40
+
41
+ ---
42
+
43
+ ## Episode 3
44
+ ### πŸ“Š Dataset Entry
45
+ **Question:** Which countries currently have bilateral FTAs in effect with the U.S.?
46
+
47
+
48
+
49
+ **Context (Answer):** "The U.S. currently has bilateral FTAs in effect with Australia, Bahrain, Chile, Israel, Jordan, Morocco, Oman, Singapore, Peru, Korea, and Columbia.
50
+
51
+ ### πŸ“„ Audit Log
52
+ **LLM Reasoning:**
53
+ ```json
54
+ {"action_type": "FLAG", "target_id": "ACC-BL-001", "regulation_citation": "EU-AI-Act-Art-57: Sandboxes required for high risk."}
55
+ ```
56
+ **Action:** FLAG on ACC-BL-001
57
+
58
+ **Reward:** 0.5
59
+
60
+ ---
61
+
models.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openenv.core.env_server import Action, Observation, State
2
+ from pydantic import Field
3
+ from typing import List, Dict, Any
4
+
5
+ class AuditAction(Action):
6
+ action_type: str = Field(description="One of: ['APPROVE', 'FLAG', 'REQUEST_INFO']")
7
+ target_id: str = Field(description="The ID of the account or transaction being evaluated.")
8
+ regulation_citation: str = Field(description="The specific regulation clause cited.")
9
+
10
+ class AuditObservation(Observation):
11
+ transactions: List[Dict[str, Any]] = Field(description="List of transaction dicts")
12
+ account_metadata: Dict[str, Any] = Field(description="Account metadata")
13
+ retrieved_regs: str = Field(description="RAG-retrieved sections of guidelines")
14
+
15
+ # Official OpenEnv returns these properties directly on the Observation
16
+ reward: float = Field(default=0.0, description="Reward gained in the step")
17
+ done: bool = Field(default=False, description="Whether the episode is complete")
18
+
19
+ class AuditState(State):
20
+ step_count: int = Field(description="Current step of the episode")
21
+ current_tier: str = Field(description="The compliance tier active for the episode")
22
+
23
+ # Required by base State (depending on library version, might require extra fields, but standard is dict-like or these fields)
openenv.yaml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id: aegis_gym
2
+ version: "1.0.0"
3
+ name: "AegisGym: Financial Compliance & AML Sandbox"
4
+ description: "A sandbox for training agents to perform banking audits, sanction checks, and anti-money laundering (AML) detection."
5
+ author: "Armaan"
6
+ tags: ["finance", "compliance", "aml", "audit", "openenv"]
7
+ tasks:
8
+ - id: easy_audit
9
+ name: "Sanction Check"
10
+ description: "Evaluate a single account against a known sanctions list. Identify 'ACC-BL-*' accounts as high-risk."
11
+ difficulty: "easy"
12
+ - id: medium_audit
13
+ name: "Smurfing Detection"
14
+ description: "Analyze a stream of transactions to detect 'smurfing' (structuring) patterns where multiple deposits just under $10,000 are made."
15
+ difficulty: "medium"
16
+ - id: hard_audit
17
+ name: "Regulatory Alignment"
18
+ description: "Audit high-value transactions and cite the specific regulatory framework (e.g., EU AI Act, BSA) correctly."
19
+ difficulty: "hard"
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ openenv-core @ git+https://github.com/meta-pytorch/OpenEnv.git
2
+ pydantic
3
+ uvicorn
4
+ openai
sample_dataset.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datasets import load_dataset
2
+
3
+ def sample_dataset():
4
+ print("Loading SecureFinAI-Lab/Regulations_QA...")
5
+ try:
6
+ # We'll use a streaming load or just take the first few examples
7
+ ds = load_dataset("SecureFinAI-Lab/Regulations_QA", split="train", streaming=True)
8
+ iterator = iter(ds)
9
+ print("\n--- Example 1 ---")
10
+ item1 = next(iterator)
11
+ print(item1)
12
+ print("\n--- Example 2 ---")
13
+ item2 = next(iterator)
14
+ print(item2)
15
+ except Exception as e:
16
+ print(f"Error loading dataset: {e}")
17
+
18
+ if __name__ == "__main__":
19
+ sample_dataset()
server.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from typing import Dict, Any, Tuple
3
+ from openenv.core.env_server import Environment
4
+ from models import AuditAction, AuditObservation, AuditState
5
+ from grader import Grader
6
+
7
+ class AegisGymEnv(Environment):
8
+ def __init__(self):
9
+ super().__init__()
10
+ self.grader = Grader()
11
+ self.step_count = 0
12
+ self.current_tier = "easy"
13
+ self.max_steps = 100
14
+
15
+ # Test Data
16
+ self.current_target_id = "ACC-BL-001"
17
+ self.current_transactions = []
18
+
19
+ def reset(self) -> AuditObservation:
20
+ self.step_count = 0
21
+ self._set_next_scenario()
22
+ return self._create_observation(0.0, False)
23
+
24
+ @property
25
+ def state(self) -> AuditState:
26
+ return AuditState(
27
+ step_count=self.step_count,
28
+ current_tier=self.current_tier
29
+ )
30
+
31
+ def _create_observation(self, reward: float, done: bool) -> AuditObservation:
32
+ return AuditObservation(
33
+ transactions=self.current_transactions,
34
+ account_metadata={"age_days": 120, "tier": "standard", "target_id": self.current_target_id},
35
+ retrieved_regs="EU-AI-Act-Art-57: Sandboxes required for high risk. BSA-31-USC-5318: AML programs.",
36
+ reward=reward,
37
+ done=done
38
+ )
39
+
40
+ def step(self, action: AuditAction) -> AuditObservation:
41
+ self.step_count += 1
42
+
43
+ is_flagged = action.action_type in ["FLAG", "BLOCK", "REQUEST_INFO"]
44
+
45
+ p_id, p_cit = 0.0, 0.0
46
+ f_p, f_n = 0.0, 0.0
47
+
48
+ if self.current_tier == "easy":
49
+ p_id = self.grader.grade_easy(action.target_id, is_flagged)
50
+ is_sanctioned = action.target_id in self.grader.sanctioned_ids
51
+ if not is_sanctioned and is_flagged: f_p = 1.0
52
+ elif is_sanctioned and not is_flagged: f_n = 5.0
53
+
54
+ elif self.current_tier == "medium":
55
+ p_id = self.grader.grade_medium(self.current_transactions, action.target_id, is_flagged)
56
+ smurf_count = sum(1 for tx in self.current_transactions if 9000 <= tx.get("amount", 0) < 10000)
57
+ has_smurfing = smurf_count >= 3
58
+ if not has_smurfing and is_flagged: f_p = 1.0
59
+ elif has_smurfing and not is_flagged: f_n = 5.0
60
+
61
+ elif self.current_tier == "hard":
62
+ p_id = 1.0 if is_flagged else 0.0
63
+ p_cit = self.grader.grade_hard(action.regulation_citation)
64
+ if not is_flagged: f_n = 5.0
65
+
66
+ reward = (0.5 * p_id) + (0.5 * p_cit) - (1.0 * f_p) - (1.0 * f_n)
67
+ done = self.step_count >= self.max_steps
68
+
69
+ obs = self._create_observation(reward, done)
70
+ self._set_next_scenario()
71
+ return obs
72
+
73
+ def _set_next_scenario(self):
74
+ tiers = ["easy", "medium", "hard"]
75
+ self.current_tier = tiers[self.step_count % 3]
76
+
77
+ if self.current_tier == "easy":
78
+ self.current_target_id = random.choice(["ACC-BL-001", "ACC-CLEAN-01"])
79
+ self.current_transactions = [{"amount": 500, "currency": "USD"}]
80
+ elif self.current_tier == "medium":
81
+ self.current_target_id = "ACC-SMURF-99"
82
+ if random.random() > 0.5:
83
+ self.current_transactions = [{"amount": 9500, "currency": "USD"} for _ in range(4)]
84
+ else:
85
+ self.current_transactions = [{"amount": 5000, "currency": "USD"}, {"amount": 200, "currency": "USD"}]
86
+ elif self.current_tier == "hard":
87
+ self.current_target_id = "ACC-REG-VIOLATOR"
88
+ self.current_transactions = [{"amount": 50000, "currency": "USD", "note": "High risk AI deployment without sandbox"}]
simulation.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AegisGym Simulation Script
3
+ Runs multiple audit episodes using the LLM for inference (CPU-friendly).
4
+ This generates the logs and metrics to analyze the system's performance.
5
+ """
6
+ import os
7
+ import torch
8
+ import json
9
+ from transformers import AutoTokenizer, AutoModelForCausalLM
10
+ from client_env import get_sync_client
11
+ from train import parse_action, SYSTEM_PROMPT
12
+
13
+ MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
14
+ ENV_URL = "https://armaan020-aegisgym.hf.space"
15
+
16
+ def run_simulation(num_episodes=5):
17
+ print(f"=== Starting AegisGym Simulation (Inference Only) ===")
18
+ print(f"Model: {MODEL_NAME}")
19
+ print(f"Env: {ENV_URL}\n")
20
+
21
+ print(f"Loading model on CPU...")
22
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
23
+ model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, torch_dtype="auto", device_map="cpu")
24
+ print("Model loaded.\n")
25
+
26
+ env = get_sync_client(ENV_URL)
27
+
28
+ total_reward = 0
29
+ results = []
30
+
31
+ for i in range(num_episodes):
32
+ print(f"--- Episode {i+1} ---")
33
+ result = env.reset()
34
+ obs_dict = result.get("observation", {})
35
+
36
+ state = env.state()
37
+ tier = state.get("current_tier", "easy")
38
+
39
+ user_msg = (
40
+ f"Audit the following transaction.\n\n"
41
+ f"Tier: {tier.upper()}\n"
42
+ f"Transactions: {obs_dict.get('transactions', [])}\n"
43
+ f"Context: {obs_dict.get('retrieved_regs', [])}\n"
44
+ f"Account: {obs_dict.get('account_metadata', {})}"
45
+ )
46
+ messages = [
47
+ {"role": "system", "content": SYSTEM_PROMPT},
48
+ {"role": "user", "content": user_msg},
49
+ ]
50
+ prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
51
+
52
+ print(f"[Audit Prompt]:\n{user_msg}")
53
+
54
+ # Inference
55
+ inputs = tokenizer(prompt, return_tensors="pt")
56
+ with torch.no_grad():
57
+ outputs = model.generate(**inputs, max_new_tokens=128)
58
+
59
+ completion = tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
60
+ print(f"[Model Reasoning]:\n{completion}")
61
+
62
+ action = parse_action(completion)
63
+ print(f"[Action]: {action.action_type} on {action.target_id}")
64
+
65
+ step_result = env.step(action.model_dump())
66
+ reward = step_result.get("reward", 0.0)
67
+ done = step_result.get("done", False)
68
+
69
+ print(f"[Reward]: {reward} | [Done]: {done}\n")
70
+ total_reward += reward
71
+ results.append({
72
+ "episode": i+1,
73
+ "tier": tier,
74
+ "action": action.action_type,
75
+ "reward": reward
76
+ })
77
+
78
+ print(f"=== Simulation Complete ===")
79
+ print(f"Average Reward: {total_reward / num_episodes}")
80
+
81
+ if __name__ == "__main__":
82
+ run_simulation(num_episodes=3)
synced_report.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "episode": 1,
4
+ "dataset_question": "When was Rule. 2010 Standards of Commercial Honor and Principles of Trade Adopted?",
5
+ "dataset_answer": "2009-05-29 00:00:00",
6
+ "tier": "easy",
7
+ "llm_reasoning": "{\"action_type\": \"FLAG\", \"target_id\": \"ACC-BL-001\", \"regulation_citation\": \"EU-AI-Act-Art-57\"}",
8
+ "action": {
9
+ "metadata": {},
10
+ "action_type": "FLAG",
11
+ "target_id": "ACC-BL-001",
12
+ "regulation_citation": "EU-AI-Act-Art-57"
13
+ },
14
+ "reward": 0.5
15
+ },
16
+ {
17
+ "episode": 2,
18
+ "dataset_question": "When was Rule Rule 2020. Use of Manipulative, Deceptive or Other Fraudulent Devices adopted",
19
+ "dataset_answer": "2009-05-29 00:00:00",
20
+ "tier": "easy",
21
+ "llm_reasoning": "{\n \"action_type\": \"FLAG\",\n \"target_id\": \"ACC-CLEAN-01\",\n \"regulation_citation\": \"EU-AI-Act-Art-57\"\n}",
22
+ "action": {
23
+ "metadata": {},
24
+ "action_type": "FLAG",
25
+ "target_id": "ACC-CLEAN-01",
26
+ "regulation_citation": "EU-AI-Act-Art-57"
27
+ },
28
+ "reward": -1.0
29
+ },
30
+ {
31
+ "episode": 3,
32
+ "dataset_question": "Which countries currently have bilateral FTAs in effect with the U.S.?\n\n",
33
+ "dataset_answer": "\"The U.S. currently has bilateral FTAs in effect with Australia, Bahrain, Chile, Israel, Jordan, Morocco, Oman, Singapore, Peru, Korea, and Columbia.",
34
+ "tier": "easy",
35
+ "llm_reasoning": "{\"action_type\": \"FLAG\", \"target_id\": \"ACC-BL-001\", \"regulation_citation\": \"EU-AI-Act-Art-57: Sandboxes required for high risk.\"}",
36
+ "action": {
37
+ "metadata": {},
38
+ "action_type": "FLAG",
39
+ "target_id": "ACC-BL-001",
40
+ "regulation_citation": "EU-AI-Act-Art-57: Sandboxes required for high risk."
41
+ },
42
+ "reward": 0.5
43
+ }
44
+ ]
synced_simulation.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AegisGym Synchronized Simulation
3
+ Saves exact dataset entries alongside the audit logs they generated.
4
+ """
5
+ import torch
6
+ import json
7
+ from transformers import AutoTokenizer, AutoModelForCausalLM
8
+ from datasets import load_dataset
9
+ from client_env import get_sync_client
10
+ from train import parse_action, SYSTEM_PROMPT
11
+
12
+ MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
13
+ ENV_URL = "https://armaan020-aegisgym.hf.space"
14
+
15
+ def run_synced_simulation(num_episodes=3):
16
+ print(f"Loading model on CPU...")
17
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
18
+ model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, torch_dtype="auto", device_map="cpu")
19
+
20
+ print("Loading dataset...")
21
+ ds = load_dataset("SecureFinAI-Lab/Regulations_QA", split="train", streaming=True)
22
+ it = iter(ds)
23
+
24
+ env = get_sync_client(ENV_URL)
25
+ full_report = []
26
+
27
+ for i in range(num_episodes):
28
+ print(f"--- Episode {i+1} ---")
29
+ item = next(it)
30
+ result = env.reset()
31
+ obs_dict = result.get("observation", {})
32
+ state = env.state()
33
+ tier = state.get("current_tier", "easy")
34
+
35
+ custom_prompt = item.get("question", "Audit the following transaction.")
36
+ dataset_answer = item.get("answer", "No specific guidance provided.")
37
+
38
+ user_msg = (
39
+ f"{custom_prompt}\n\n"
40
+ f"Tier: {tier.upper()}\n"
41
+ f"Transactions: {obs_dict.get('transactions', [])}\n"
42
+ f"Context: {obs_dict.get('retrieved_regs', [])}\n"
43
+ f"Regulatory Hint: {dataset_answer}\n"
44
+ f"Account: {obs_dict.get('account_metadata', {})}"
45
+ )
46
+ messages = [
47
+ {"role": "system", "content": SYSTEM_PROMPT},
48
+ {"role": "user", "content": user_msg},
49
+ ]
50
+ prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
51
+
52
+ inputs = tokenizer(prompt, return_tensors="pt")
53
+ with torch.no_grad():
54
+ outputs = model.generate(**inputs, max_new_tokens=128)
55
+
56
+ completion = tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
57
+ action = parse_action(completion)
58
+ step_result = env.step(action.model_dump())
59
+
60
+ full_report.append({
61
+ "episode": i+1,
62
+ "dataset_question": custom_prompt,
63
+ "dataset_answer": dataset_answer,
64
+ "tier": tier,
65
+ "llm_reasoning": completion,
66
+ "action": action.model_dump(),
67
+ "reward": step_result.get("reward", 0.0)
68
+ })
69
+
70
+ with open("synced_report.json", "w") as f:
71
+ json.dump(full_report, f, indent=2)
72
+ print("\nSaved synced_report.json")
73
+
74
+ if __name__ == "__main__":
75
+ run_synced_simulation()
train.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AegisGym GRPO Training Script
3
+ Connects to the live HF Space for environment rollouts.
4
+ Run: python train.py
5
+ """
6
+ import os
7
+ import json
8
+ from datasets import Dataset
9
+ from trl import GRPOConfig, GRPOTrainer
10
+ from transformers import AutoTokenizer
11
+ from client_env import get_sync_client
12
+ from models import AuditAction
13
+
14
+ # ─── Config ──────────────────────────────────────────────────────────────────
15
+ ENV_URL = os.getenv("AEGISGYM_URL", "https://armaan020-aegisgym.hf.space")
16
+ MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-0.5B-Instruct")
17
+ OUTPUT_DIR = "aegisgym-grpo-agent"
18
+
19
+ SYSTEM_PROMPT = """You are a financial compliance auditor AI.
20
+ Given a transaction scenario and regulatory context, respond with a JSON object:
21
+ {"action_type": "FLAG|APPROVE|REQUEST_INFO", "target_id": "<account_id>", "regulation_citation": "<regulation>"}
22
+ Be precise and concise."""
23
+
24
+ # ─── Action parser ────────────────────────────────────────────────────────────
25
+ def parse_action(text: str) -> AuditAction:
26
+ try:
27
+ start = text.find("{")
28
+ end = text.rfind("}") + 1
29
+ if start >= 0 and end > start:
30
+ data = json.loads(text[start:end])
31
+ return AuditAction(**data)
32
+ except Exception:
33
+ pass
34
+ return AuditAction(action_type="REQUEST_INFO", target_id="UNKNOWN", regulation_citation="parse_error")
35
+
36
+ # ─── Dataset Loading ──────────────────────────────────────────────────────────
37
+ from datasets import load_dataset
38
+ import itertools
39
+
40
+ print("Loading SecureFinAI-Lab/Regulations_QA dataset...")
41
+ try:
42
+ # Use a streaming dataset for efficiency
43
+ raw_dataset = load_dataset("SecureFinAI-Lab/Regulations_QA", split="train", streaming=True)
44
+ dataset_iterator = itertools.cycle(iter(raw_dataset))
45
+ print("Dataset loaded successfully.")
46
+ except Exception as e:
47
+ print(f"Warning: Failed to load dataset: {e}. Falling back to default prompts.")
48
+ dataset_iterator = None
49
+
50
+ # ─── Rollout function ─────────────────────────────────────────────────────────
51
+ def rollout_func(trainer, prompts, tokenizer):
52
+ """One rollout episode connecting to the live AegisGym Space with dataset augmentation."""
53
+ from trl.experimental.openenv import generate_rollout_completions
54
+
55
+ env = get_sync_client(ENV_URL)
56
+ result = env.reset()
57
+ obs_dict = result.get("observation", {})
58
+
59
+ # Sample from dataset if available
60
+ dataset_context = ""
61
+ custom_prompt = "Audit the following transaction."
62
+ if dataset_iterator:
63
+ item = next(dataset_iterator)
64
+ custom_prompt = item.get("question", custom_prompt)
65
+ dataset_context = f"\nRegulatory Context: {item.get('answer', '')}"
66
+
67
+ all_prompt_ids, all_completion_ids, all_logprobs, rewards = [], [], [], []
68
+
69
+ state = env.state()
70
+ tier = state.get("current_tier", "easy")
71
+
72
+ # Combine dataset context with environment context
73
+ regs = obs_dict.get('retrieved_regs', "")
74
+ retrieved_regs = [regs] if isinstance(regs, str) else list(regs)
75
+ if dataset_context:
76
+ retrieved_regs.append(dataset_context)
77
+
78
+ user_msg = (
79
+ f"{custom_prompt}\n\n"
80
+ f"Tier: {tier.upper()}\n"
81
+ f"Transactions: {obs_dict.get('transactions', [])}\n"
82
+ f"Context: {retrieved_regs}\n"
83
+ f"Account: {obs_dict.get('account_metadata', {})}"
84
+ )
85
+ messages = [
86
+ {"role": "system", "content": SYSTEM_PROMPT},
87
+ {"role": "user", "content": user_msg},
88
+ ]
89
+ prompt_text = tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
90
+
91
+ out = generate_rollout_completions(trainer, [prompt_text])[0]
92
+ all_prompt_ids.extend(out["prompt_ids"])
93
+ all_completion_ids.extend(out["completion_ids"])
94
+ all_logprobs.extend(out["logprobs"])
95
+
96
+ completion_text = out.get("text") or tokenizer.decode(out["completion_ids"], skip_special_tokens=True)
97
+ action = parse_action(completion_text)
98
+ result = env.step(action.model_dump())
99
+ rewards.append(float(result.get("reward", 0.0)))
100
+
101
+ return {
102
+ "prompt_ids": all_prompt_ids,
103
+ "completion_ids": all_completion_ids,
104
+ "logprobs": all_logprobs,
105
+ "env_reward": rewards[-1] if rewards else 0.0,
106
+ }
107
+
108
+ # ─── Reward shim ─────────────────────────────────────────────────────────────
109
+ def reward_compliance(completions, **kwargs):
110
+ rewards = kwargs.get("env_reward", [])
111
+ if not rewards:
112
+ return [0.0] * len(completions)
113
+ return [float(r) for r in rewards]
114
+
115
+ # ─── Main ─────────────────────────────────────────────────────────────────────
116
+ def main():
117
+ print(f"Model : {MODEL_NAME}")
118
+ print(f"Env : {ENV_URL}")
119
+
120
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
121
+ tokenizer.pad_token = tokenizer.eos_token
122
+
123
+ dataset = Dataset.from_dict({"prompt": ["Audit the following transaction."] * 200})
124
+
125
+ config = GRPOConfig(
126
+ num_train_epochs = 1,
127
+ learning_rate = 5e-6,
128
+ per_device_train_batch_size = 1,
129
+ gradient_accumulation_steps = 4,
130
+ warmup_steps = 10,
131
+ num_generations = 2,
132
+ max_completion_length = 256,
133
+ use_vllm = False,
134
+ output_dir = OUTPUT_DIR,
135
+ logging_steps = 1,
136
+ save_steps = 25,
137
+ gradient_checkpointing = True,
138
+ )
139
+
140
+ trainer = GRPOTrainer(
141
+ model = MODEL_NAME,
142
+ processing_class = tokenizer,
143
+ reward_funcs = [reward_compliance],
144
+ train_dataset = dataset,
145
+ args = config,
146
+ rollout_func = rollout_func,
147
+ )
148
+
149
+ print("\n=== Starting GRPO Training ===")
150
+ trainer.train()
151
+ trainer.save_model(OUTPUT_DIR)
152
+ print(f"\nModel saved to {OUTPUT_DIR}/")
153
+
154
+ if __name__ == "__main__":
155
+ main()
verify_consistency.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datasets import load_dataset
2
+ import os
3
+
4
+ def verify():
5
+ log_path = "audit_logs.txt"
6
+ if not os.path.exists(log_path):
7
+ print("Logs not found.")
8
+ return
9
+
10
+ # Read logs (UTF-16 from PowerShell redirection)
11
+ with open(log_path, "rb") as f:
12
+ content = f.read().decode("utf-16", "ignore")
13
+
14
+ print("--- Loading Dataset ---")
15
+ ds = load_dataset("SecureFinAI-Lab/Regulations_QA", split="train", streaming=True)
16
+ it = iter(ds)
17
+
18
+ print("--- Comparing Samples ---")
19
+ for i in range(5):
20
+ sample = next(it)
21
+ q = sample["question"]
22
+ found = q in content
23
+ print(f"Sample {i+1}: '{q[:50]}...' Found: {found}")
24
+
25
+ if __name__ == "__main__":
26
+ verify()
verify_dataset_rollout.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Verification script for dataset-augmented rollout function.
3
+ """
4
+ from transformers import AutoTokenizer
5
+ from train import rollout_func, dataset_iterator
6
+ import trl.experimental.openenv
7
+
8
+ def verify():
9
+ print("=== Verifying Dataset-Augmented Rollout ===")
10
+ model_name = "Qwen/Qwen2.5-0.5B-Instruct"
11
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
12
+
13
+ # Mock generation
14
+ def mock_gen(trainer, prompts):
15
+ print(f"\n[Generated Prompt Sample]:\n{prompts[0][:500]}...\n")
16
+ fake_completion = '{"action_type": "APPROVE", "target_id": "ACC-001", "regulation_citation": "EU-Regulation"}'
17
+ return [{"prompt_ids": [1], "completion_ids": [1], "logprobs": [0.0], "text": fake_completion}]
18
+
19
+ trl.experimental.openenv.generate_rollout_completions = mock_gen
20
+
21
+ print("Running rollout_func...")
22
+ result = rollout_func(None, ["prompt"], tokenizer)
23
+ print(f"\nRollout Finished!")
24
+ print(f"Reward: {result['env_reward']}")
25
+
26
+ if __name__ == "__main__":
27
+ verify()