CoolFace
Apppublic

Solanabean/meta-ai

sourceHugging Facemitupdated 6mo agoView on Hugging Face
1likes
App README

Forensic Financial Investigation — OpenEnv Environment

An RL environment where AI agents investigate synthetically generated transaction networks to uncover financial fraud. Built on the OpenEnv framework.

Hackathon / Phase 1: Deploy to a Hugging Face Space (Docker SDK), add tag `openenv`, then verify with python scripts/verify_hf_space.py <SPACE_URL> — details below.

Motivation

Financial institutions spend billions annually on Anti-Money Laundering (AML) compliance, and an estimated $2 trillion is laundered globally each year. This environment models the investigative workflow of a forensic financial analyst: receive an alert, explore a transaction network, identify suspicious patterns using graph analysis, and submit a report.

Every episode generates a fresh, randomized scenario with planted fraud patterns hidden among legitimate transactions. The agent must navigate the graph efficiently under a step budget, using tools like temporal burst detection, network centrality analysis, and cycle detection — mirroring real-world forensic techniques.

Architecture

┌──────────────────────────────────────────────────────┐
│  AGENT (LLM or policy)                               │
│  Observes case summary, entities, transactions       │
│  Takes actions: query / trace / flag / analyze /     │
│                 view / submit                        │
└────────────────────┬─────────────────────────────────┘
                     │  WebSocket (step / reset / state)
┌────────────────────▼─────────────────────────────────┐
│  FFI ENVIRONMENT (Docker / HF Space)                 │
│  ┌─────────────────────────────────────────────────┐ │
│  │  Scenario Generator                             │ │
│  │  Faker entities + Transaction graph + Fraud     │ │
│  ├─────────────────────────────────────────────────┤ │
│  │  Environment Logic (reset / step / state)       │ │
│  │  Investigation state, reward shaping, grading   │ │
│  ├─────────────────────────────────────────────────┤ │
│  │  NetworkX Graph Analysis                        │ │
│  │  Centrality, cycles, temporal, anomaly          │ │
│  ├─────────────────────────────────────────────────┤ │
│  │  Grader — F1-based + quality bonus, [0.0, 1.0] │ │
│  └─────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘

Tasks

#Task IDDifficultyDescriptionMax Steps
1structuringEasyOne entity splits a large sum into multiple transactions each just under the $10,000 CTR reporting threshold, sent to different recipients within a short window.15
2layeringMediumMoney flows through a chain of 3-5 offshore shell companies, with decreasing amounts and different dates, to obscure its origin.25
3fraud_ringHard10+ entities form a complex network with circular money flows, parallel layering paths, split transfers across multiple channels, and heavy legitimate camouflage.40

Action Space

Every step, the agent submits one action:

ActionParametersEffect
query_entityentity_idLook up an entity — reveals their profile, risk indicators, financial summary, and all connected transactions.
trace_transactiontransaction_idGet full transaction details (amount, channel, currency) and reveal both endpoint entities.
flag_suspicioustransaction_idMark a transaction as fraudulent. Correct flag = +0.1 reward. False positive = -0.05 penalty.
view_network—Summary of all discovered entities and visible connections with flag markers.
analyze_patternanalysis_typeGraph-theoretic analysis using NetworkX. Types: temporal (burst detection, amount clustering), centrality (degree/betweenness centrality), cycles (circular flow detection), anomaly (unusual amounts, cross-jurisdiction transfers).
submit_report—End episode. Final grade computed via F1 score on flagged vs ground-truth, plus investigation quality bonus.

Observation Space

After each step, the agent receives:

FieldTypeDescription
case_summarystringThe initial alert / case briefing
entities_discoveredlist[dict]Entities uncovered (name, type, jurisdiction, risk indicators, financial stats)
transactions_visiblelist[dict]Transactions visible (id, from, to, amount, date, description, currency, channel)
flags_placedlist[str]Transaction IDs already flagged
investigation_loglist[str]Chronological log of agent actions
step_number / max_stepsintProgress tracking
messagestringFeedback from the environment
cumulative_rewardfloatRunning total of all rewards accumulated this episode
network_statsdictGraph-level statistics (total entities, transactions, truncation info)
doneboolWhether the episode is over
rewardfloatStep reward (partial credit throughout)

Observation lists are capped (25 entities, 60 transactions) to keep LLM token usage bounded. The network_stats field reports total counts and truncation status.

Reward Function

The reward provides signal throughout the trajectory, not just at episode end:

  • —Correct flag (flag_suspicious on a fraudulent transaction): +0.1
  • —False positive (flag_suspicious on a legitimate transaction): -0.05
  • —Discovering a fraud-linked entity (query_entity): +0.02
  • —Tracing a fraudulent transaction (trace_transaction): +0.03
  • —Productive pattern analysis (analyze_pattern revealing relevant patterns): +0.01 to +0.02
  • —Submitting report: F1 score as final reward + investigation quality bonus
  • —Running out of steps: -0.1 penalty

Grading

Each task grader returns a deterministic score in [0.0, 1.0]:

score = F1(flagged, ground_truth) - false_positive_penalty + investigation_quality_bonus

Where:

  • —false_positive_penalty = min(0.2, num_false_positives × 0.05)
  • —investigation_quality_bonus = (fraud_entities_discovered / total_fraud_entities) × 0.05

The quality bonus rewards agents that thoroughly investigate the network, not just flag transactions blindly.

Key Design Decisions

  • —No ground truth leakage: Flag messages are uniform regardless of whether the flag is correct, preventing agents from gaming the system.
  • —Thread-safe scenario generation: A lock ensures deterministic ID generation even with concurrent requests.
  • —Transaction metadata: Each transaction includes currency and channel (wire, ACH, SWIFT, crypto, cash_deposit), mirroring real financial data.
  • —Graph-theoretic analysis: NetworkX powers the analyze_pattern action, providing real forensic investigation tools: betweenness centrality to find intermediaries, cycle detection for circular flows, and temporal clustering for structuring patterns.
  • —Observation capping: Large observations are truncated with summary statistics to prevent token overflow in LLM agents.

Setup

Local Development

bash
cd forensic-financial-investigation
pip install -r requirements.txt

# Run server
uvicorn server.app:app --host 0.0.0.0 --port 8000 --reload

# Test health
curl http://localhost:8000/health

# View tasks
curl http://localhost:8000/tasks

# Run heuristic baseline (no API key needed)
curl -X POST http://localhost:8000/baseline

Docker

bash
docker build -t ffi-env .
docker run -d -p 8000:8000 ffi-env

Official submission inference (inference.py)

Hackathon validators run `inference.py` in the repo root. It uses only the OpenAI Python client and these required environment variables:

VariableDescription
API_BASE_URLOpenAI-compatible API base URL. Groq: https://api.groq.com/openai/v1. OpenAI: https://api.openai.com/v1. If unset, defaults to https://router.huggingface.co/v1.
MODEL_NAMEModel id for chat.completions. Optional: if unset, defaults to meta-llama/Meta-Llama-3.1-8B-Instruct (HF Inference–friendly). Override for Groq/OpenAI/other providers.
API_KEYPreferred. Injected automatically during official evaluation (LiteLLM). For local dev, you may use `HF_TOKEN` or `GROQ_API_KEY` instead (same api_key passed to the OpenAI client).

Optional: FFI_SERVER_URL (default http://localhost:8000), INFERENCE_BUDGET_S (default 1080 = 18 minutes wall time, under the 20 minute runtime cap).

Example (Groq — OpenAI-compatible):

bash
export API_BASE_URL="https://api.groq.com/openai/v1"
export MODEL_NAME="llama-3.3-70b-versatile"
export API_KEY="gsk_..."          # or HF_TOKEN / GROQ_API_KEY for local only
export FFI_SERVER_URL="http://localhost:8000"   # or your HF Space URL

python inference.py

Example (OpenAI):

bash
export API_BASE_URL="https://api.openai.com/v1"
export MODEL_NAME="gpt-4o-mini"
export API_KEY="sk-..."
export FFI_SERVER_URL="http://localhost:8000"
python inference.py

Stdout (for automated checks): only `[START]`, `[STEP]`, and `[END]` lines, each [KIND] + single JSON object (inference_log.emit_structured). The `[END]` line includes scores, average, model, and api_base_url. The same result object is duplicated on stderr as machine_result {...} for humans. All other logs use stderr.

Infra: Designed to finish within 20 minutes on 2 vCPU / 8 GB RAM (tight token limits per step, optional time budget).

Copy `.env.example` and fill in values locally (do not commit secrets).

Pre-submission checklist (all must pass)

CheckHow to verify
HF Space deploysSpace URL returns 200 on /health; POST /reset returns an observation — use scripts/verify_hf_space.py.
OpenEnv specopenenv validate
Dockerdocker build -t ffi-env . && docker run -p 8000:8000 ffi-env
Inferencepython inference.py completes with scores (env running + valid API credentials).
3+ tasks + gradersGET /tasks; POST /grader with flagged ids; scores strictly in (0, 1) (not 0.0 or 1.0)

Run the organizer’s pre-submission validation script (if provided) before uploading.

Baseline Scores

Heuristic baseline (/baseline endpoint, task-aware rules):

TaskDifficultyScoreStrategy
structuringEasy~0.9999Flags sub-$10k outbound from alert entity
layeringMedium~0.76Traces chains through shell companies
fraud_ringHard~0.24Flags inter-shell-entity transactions

Grades are mapped to the open interval (0, 1) for validator compatibility. The clear progression (near-perfect → medium → lower) still demonstrates difficulty scaling. A capable LLM agent can improve on medium/hard tasks by using multi-step graph traversal, analyze_pattern for cycle detection, and pattern recognition.

API Endpoints

EndpointMethodDescription
/healthGETHealth check
/docsGETOpenAPI documentation
/wsWebSocketEnvironment session (reset/step/state)
/resetPOST onlyReset — GET returns 405 (browser bar is GET). Body e.g. {"task_id":"structuring","seed":42}.
/stepPOST onlyStep — GET returns 405. Body e.g. {"action":{"action_type":"view_network"}}.
/stateGETReturns state of a fresh disposable env, not the last /reset. For a real episode use `/ws` or inference.py.
/tasksGETList tasks and action schema
/graderPOSTScore flagged transaction IDs (JSON array body). Query: task_id, seed; optional discovered_entity_ids (comma-separated) for the same entity-coverage bonus as the live environment grader.
/baselinePOSTRun heuristic baseline on all 3 tasks

Trying /reset and /step in the browser

Paste-only URLs are GET requests. These routes are POST with a JSON body. Use `/docs` → Try it out, or:

bash
curl -s -X POST "https://YOUR_SPACE.hf.space/reset" -H "Content-Type: application/json" -d "{\"task_id\":\"structuring\",\"seed\":42}"

Why GET /state looks empty

OpenEnv’s HTTP GET /state builds a new environment instance and returns its default state (episode_id null, step_count 0). It is not linked to a previous POST /reset. Multi-step runs use the WebSocket FFIEnvClient (/ws) — as in client.py and inference.py.

Hugging Face Space (Phase 1 submission)

  1. 1.Create a Space at huggingface.co/new-space with SDK: Docker (matches this repo’s Dockerfile).
  2. 2.Include `openenv` under `tags:` in the YAML block at the top of README.md (root of the repo). Hugging Face reads Space tags from that README card metadata — there is no separate tags control under Space Settings.
  3. 3.Push this repository to the Space (git remote add / git push to the Space clone URL), or connect your GitHub repo if the UI allows.
  4. 4.Wait for the Docker build in Logs (often 5–15 minutes).
  5. 5.Verify:
bash
   python scripts/verify_hf_space.py https://YOUR_USERNAME-YOUR_SPACE.hf.space

Expect: GET /health → 200, POST /reset → JSON with observation.

Troubleshooting: Build failures → check requirements.txt pins; 502 → cold start, retry; OOM → set WORKERS=1 in Space variables. Port must stay 8000 (app_port in frontmatter + Dockerfile EXPOSE). If opening the Space URL shows `{"detail":"Not Found"}` on /, use `/docs`, `/health`, or `/openapi.json` — the app is API-only unless you deploy a build that redirects / to /docs (current server.app does).