Pandago/graphstrike-model-training
<br>
<p align="center"> <img src="assets/logo.png" width="600"/> </p>
<br>
<p align="center"> <img src="https://img.shields.io/badge/Hugging%20Face-FFD21E?style=for-the-badge&logo=huggingface&logoColor=black"/> <img src="https://img.shields.io/badge/HF%20Spaces-FFBF00?style=for-the-badge&logo=huggingface&logoColor=black"/> <img src="https://img.shields.io/badge/FastAPI-009688?style=for-the-badge&logo=fastapi&logoColor=white"/> <img src="https://img.shields.io/badge/Docker-2496ED?style=for-the-badge&logo=docker&logoColor=white"/> <img src="https://img.shields.io/badge/Gradio-F97316?style=for-the-badge&logo=gradio&logoColor=white"/> <img src="https://img.shields.io/badge/OpenEnv-4B5563?style=for-the-badge&logo=envato&logoColor=white"/> <img src="https://img.shields.io/badge/Amazon%20Bedrock-FF9900?style=for-the-badge&logo=amazonaws&logoColor=white"/> </p> <br>
<h1 align="center"> </h1> <p align="center"> An OpenEnv-compatible reinforcement learning environment where an LLM agent must identify all 10 members of a coordinated fake account network hidden inside a synthetic social network. The agent learns via Reflexion and a dynamic hybrid rule/LLM policy , not via gradient updates or fine-tuning. <br /> </p> </p>
<br>
Round 2 — Platform-Adaptive Trust & Safety
Round 2 makes detection platform-aware end-to-end. Each episode runs on a named platform (Instagram, Snapchat, X, LinkedIn, Reddit, …); a PlatformPolicy is compiled offline from real transparency-report text and cached per platform; the high-signal account fields start hidden and are revealed only by explicit tool actions; and a shared evaluation runner consults the LLM at exactly two decision points per suspicious account.
How the policy lives end-to-end
transparency reports per-episode runtime
──────────────────── ───────────────────
Tavily search ──► Groq Llama-3.1 client.reset(task, seed)
extracts {π, fn_cost, fp_cost, │
harm_weight, primary_signal} env loads policy_cache/{platform}.json
│ │
▼ GET_POLICY (step 0, +0.20 bonus)
sanitize_pi() clamp π to [5e-4, 0.05] │
compute_threshold: DP1 (LLM): pick tool
θ_raw = C_fn·π / [C_fn·π + C_fp·(1−π)] reverse_image_search / analyze_bio
θ* = clamp(θ_raw / harm_weight, .01, .95) / check_ip / done
fp_penalty_weight = C_fp │
│ DP2 (LLM): flag / skip
▼ │
sanity_check_policy() warns on outliers SUBMIT
│ reward = tp − fp·C_fp − fn·0.3 + bonuses
policy_cache/{platform}.json (30-day TTL) decision_package + grader_score returnedThe threshold θ* is read by the LLM in DP1/DP2 prompts; the FP-penalty C_fp is paid in the terminal reward at SUBMIT. Both come from the same compile-time computation — they cannot drift apart.
What's new in this Round 2 cut
- 9 actions including
get_policy,reverse_image_search,analyze_bio,check_ip.openenv.yamlaction_schema mirrors all nine. - Per-step reward delta is returned on every
/step(not only at SUBMIT), so per-action shaping like the GET_POLICY bonus and tool penalties are immediately visible. - `visible_accounts` is populated for every visible id at reset, with hidden signals at
0.0 / ""until tools reveal them. - `StepResponse` carries top-level `decision_package` and `grader_score` after SUBMIT — callers no longer need to grep the message.
- Blind FLAG is denied at flag time with `−0.15` when the agent has neither inspected the account nor used any tool on it.
- Generic platform support: the policy compiler uses the platform-agnostic Tavily query
"{platform} fake account content policy enforcement 2024 2025"and falls back to a generic policy when no hardcoded entry exists. - Two-decision-point eval runner (
eval-models/_round2_runner.py) drives episodes deterministically and exposes the LLM only at DP1 (tool pick) and DP2 (flag/skip). Six thin model shims (qwen, gemma, deepseek, llama, mistral, nvidia) plug in HF-router or Bedrock backends through a shared_llm_adapters.py. - `bash check.sh` runs a 12-step system check covering health, all 9 actions, reward shaping, and the decision package.
Full architecture, formula audit, end-to-end policy lifecycle, sanity rules, scoring math, tool contracts, eval-runner internals, and quickstart commands live in [`reference.md`](reference.md) (single source of truth).
Theme
SUPPORT
Customer Service Agents
Complex environment where agents resolve multi-step queries using external tools and APIs.
Problem Statement
The task: A social network contains fake accounts organised into a single coordinated ring of 10. The ring behaves in a coordinated way — same posting hour, same IP subnet, stolen celebrity photos, copy-paste bios. The agent must find all 10 by navigating a limited step budget, inspecting accounts, and flagging suspects.
Proposed Solution
An OpenEnv-compatible reinforcement learning environment where an LLM agent must identify all 10 members of a coordinated fake account ring hidden inside a synthetic social network. The agent learns via Reflexion and a dynamic hybrid rule/LLM policy — not via gradient updates or fine-tuning.
Novelty Highlights
- Adaptive Hybrid Intelligence (Rules + LLM): Unlike static ensembles, GraphStrike dynamically blends deterministic rules and LLM reasoning using a trust gate, shifting control as performance improves.
- Learning Without Fine-Tuning: Instead of updating model weights, the agent learns through Reflexion lessons and best-trajectory memory injected into future prompts.
- Graph-First Detection Pipeline: Detection is not account-by-account only; it uses cascade effects, neighbor propagation, and multi-hop graph expansion to uncover coordinated rings.
- Math-Grounded Decision Control: Risk composition, trust calibration, and grader alignment are formula-driven, making behavior interpretable and reproducible.
- Adversarial Evasion Benchmarking: Hard-mode includes timed evasion events, so success reflects robustness under disruption rather than overfitting to static patterns.
- Safety-Net by Design: High-confidence rule overrides prevent catastrophic LLM errors while preserving LLM flexibility for strategic exploration. ---
Performance Summary
We evaluate GraphStrike's hybrid rule/LLM policy across multiple frontier models to measure how well each model handles the investigation task. All runs use the same inference pipeline (`inference.py`) with identical system prompts and structured logging. Each model ran: (1) seed=0 on all 3 tasks, and (2) seeds 0-2 on all 3 tasks for variance measurement.
Seed=0 scores (single episode per task):
<p align="center"> <img src="images/table1.png" alt="Model Performance Table" width="1600"/> </p> <br>
3-seed variance scores (mean across seeds 0, 1, 2):
<p align="center"> <img src="images/table2.png" alt="Model Performance Table" width="1600"/> </p> <br>
Rule-Based Baseline (no LLM, deterministic)
<p align="center"> <img src="images/table3.png" alt="Model Performance Table" width="1600"/> </p> <br>
Table of Contents
- What This Is
- The Problem: How Fake Detection Actually Works
- Synthetic Data Generation
- Data Model
- The RL Environment
- Risk Scoring Mathematics
- The LLM Policy (Qwen3 via Bedrock)
- Reflexion — How the Agent Learns
- Hybrid Policy — The Novel Contribution
- Training Loop End-to-End
- API Reference
- Docker Deployment
- Submission Requirements
- Verification & Validation
1. What is this !?
This is an OpenEnv hackathon submission. OpenEnv is a framework for building RL environments with a standard microservice interface (/reset, /step, /state) so that any agent implementation can plug in.
What makes this non-trivial:
- The network is large (50–1000 accounts depending on difficulty).
- Fake accounts are mixed with innocent high-signal "decoy" accounts.
- In hard mode, the gang actively evades — dropping intra-gang follows, renaming profiles — while the agent is mid-investigation.
- The agent cannot see the full network upfront: it must explore via INSPECT and INVESTIGATE_NETWORK actions, spending steps to reveal information.
What makes the learning novel:
- The LLM (inference via AWS Bedrock) cannot be fine-tuned — it is a black-box API.
- The agent learns via Reflexion: post-episode lessons are written back into memory and injected into every future prompt.
- A dynamic hybrid policy (α-weighted) blends the LLM with a deterministic rule engine, with the blend weight α updating based on recent win rate. Rules dominate early; the LLM takes over as it proves itself.
System Architecture
2. The Problem: How Fake Detection Actually Works
A real-world fake account detector does not read post content. Detection relies on three categories of signals computed from metadata:
Signal Hierarchy (Node -> Behavioral -> Graph)
- Node signals (offline): content fingerprints like photo reuse, bio-template similarity, and comment repetition provide the first suspicion layer.
- Behavioral signals (temporal/device): coordinated posting hour, account-age clustering, and shared IP subnet add stronger gang-level evidence.
- Graph signals (live at INSPECT): mutual follows, flagged-neighbor growth, and cluster alignment are hardest to evade, so they carry the highest weight in risk scoring.
- False-positive control: high-legitimacy hubs (for example celebrities) are down-weighted through hub-legitimacy discounting.
3. Synthetic Data Generation
File: server/generator.py
Episodes are generated deterministically by seed. 150 episodes are pre-generated (50 per task) and cached as JSON files in episodes/.
Network Composition
- Gang accounts: All 10 share
base_age(same creation week), tightly clusteredavg_post_hour, highphoto_reuse_score/bio_template_score,comment_repeat_scorein [0.60, 0.90],ip_cluster_id = "ip_gang_{seed}", and dense intra-gang follow edges (density 0.60–0.80). - Real accounts: Log-normal follower distributions, unique IP clusters, low fake scores.
- Decoy accounts (medium/hard): Real accounts with elevated fraud scores (0.20–0.40 range) — they look suspicious but are NOT gang members and penalise reckless flagging.
- Celebrity accounts (2 per episode): 100k–5M followers, very low fake scores, high
hub_legitimacy_score. - Zero-edge isolates (2 per episode): No edges — test whether the agent wastes steps on disconnected nodes.
4. Data Model
File: models.py
ActionType
AccountProfile — key fields
FakeGangObservation — what the agent sees each step
done, reward, visible_accounts, visible_account_ids, flagged_ids, inspected_ids, suspect_ids, graph_edges, steps_remaining, evasion_triggered, evasion_count, task, message
5. The RL Environment
File: server/environment.py
Episode Lifecycle & Action Mechanics
FLAG cascade (dual): When FLAG(X) is called — (1) every visible account that X follows becomes SUSPECT via the follow-graph, and (2) every visible account sharing X's ip_cluster_id becomes SUSPECT. Gang members share ip_gang_{seed}; real accounts have unique IPs → zero false positives.
Reward Function
base_reward = tp×1.0 − fp×0.5 − fn×0.3
Win condition:
easy/medium: recall ≥ 0.8 AND precision ≥ 0.7
hard: recall ≥ 0.9 AND precision ≥ 0.8
Bonuses:
+5.0 full win
+3.0 perfect recall
+2.0 partial win (high recall, low precision)
+1.0 efficiency (SUBMIT with ≥50% steps remaining)
−1.0 per evasion event (hard mode)
−2.0 forced submit (ran out of steps)Evasion (hard mode)
- `unfollow_intragang`: 30% of intra-gang edges randomly removed mid-investigation — destroys graph signal. Fires 4 times (steps 15, 30, 45, 60).
- `rename_count`: Random gang members get
name_change_count += 1— a visual evasion signal.
6. Risk Scoring Mathematics
File: server/scoring.py — all functions are stateless and deterministic.
8. The LLM Policy (Qwen3 via Bedrock)
File: agent/policy.py
Model: qwen.qwen3-next-80b-a3b via AWS Bedrock Converse API (maxTokens=512, temperature=0.4)
Prompt Structure
Every step, the policy builds a prompt from three components:
[reflections from past episodes] ← grows richer every episode
[best trajectory few-shot example] ← best win ever, showing the full action log
━━━ CURRENT CASE ━━━
[formatted observation] ← status badges, risk scores, suspect list
What is your next action?Accounts in the observation are sorted by `fake_risk_score` descending, with status badges prepended. fnbr=N(!) highlights when flagged_neighbor_count > 0; [HUB?] warns the LLM not to flag high-legitimacy accounts.
Required Response Format
<thinking>
Reasoning — which account is most suspicious and why.
</thinking>
<action>
INSPECT acc_0041
</action>If parsing fails, a heuristic fallback inspects the highest-scored uninspected account. Retries use exponential backoff (1s, 2s, 4s) up to 3 attempts.
9. Reflexion — How the Agent Learns
Files: agent/reflection.py, agent/memory.py
The agent cannot update Qwen3's weights — Bedrock is a black-box API. Instead, it learns via Reflexion: post-episode lessons are written as text and injected into future prompts.
Reflexion Learning Loop
Episode N:
1. LLM acts using: system_prompt + reflections[last 4] + best_trajectory
2. Episode ends → WIN or LOSS
3. Post-episode:
LOSS → generate_reflection(action_log, outcome) → lesson stored
WIN → save trajectory if better reward + generate_success_reflection
Episode N+1:
→ last 4 reflections + best win trajectory injected into prompt
→ LLM has learned from its pastExample generated reflection:
"The starting accounts were all real; I wasted 8 steps inspecting low-signal nodes before pivoting. When photo_reuse and bio_template are both below 0.3 after 3 inspections, immediately use INVESTIGATE_NETWORK to jump to a different graph region."
All memory persists in a Docker volume (memory/) across container restarts — reflections, best trajectories, win history, and α values per task.
10. Hybrid Policy — The Novel Contribution
File: agent/hybrid_policy.py
Key insight: A new LLM agent starts dumb but improves over time. A rule engine is always consistent but cannot adapt. The hybrid policy exploits both — rules provide a safety net early while the LLM builds its track record; once the LLM proves itself, rules step back.
Architecture
Alpha (α): The Trust Weight
α is a per-task value in [0.20, cap] representing current trust in the LLM:
reflection_factor = min(1.0, n_reflections / 4.0)
raw = 0.20 + reflection_factor × (0.80 × recent_win_rate + 0.12)
α = clamp(raw, 0.20, cap)Alpha trajectory over training (easy task, cap=0.50):
<br>
Rule Confidence Levels
At α=0.20 (early): rules dominate (~90% of decisions). At α=0.50 (moderate): LLM controls exploration; rules control safety. At α=0.85 (high): LLM controls most decisions; rules only override forced submits and uninspected suspects.
α is saved to memory/alpha_{task}.json and persists across Docker restarts — the agent doesn't reset to 0.20 every time.
11. Training Loop End-to-End
File: train.py
Curriculum
Seeds rotate deterministically: seed = (episode_num + task_offset) % 50
Per-Episode Flow
for ep in range(n_episodes):
1. DETERMINE TASK curriculum_task(ep) or fixed task
2. COMPUTE ALPHA compute_alpha(win_rate, n_reflections, task)
3. LOAD CONTEXT last 4 reflections + best win trajectory
4. RUN EPISODE while not obs.done:
blend(rule_action, llm_action, rule_conf, α)
→ obs = env.step(final)
5. POST-EPISODE record_win → update α → generate reflection
6. LOG task | win/loss | reward | recall | precision | α | modesEpisode metrics (flushed to runs/metrics.jsonl every 5 episodes) include: episode, task, won, reward, recall, precision, steps_used, alpha_used, mode_agree, mode_rule, mode_llm, n_reflections_used.
You can watch the transition: early episodes have high rule counts; later episodes have high agree counts (LLM learned to make the same decisions as the rules, but also brings strategic reasoning the rules can't).
12. API Reference
File: server/app.py
Baseline performance:
13. Docker Deployment
# Build
docker build -f server/Dockerfile -t graphstrike .
# Run
docker run -it \
-e AWS_ACCESS_KEY_ID=your_key \
-e AWS_SECRET_ACCESS_KEY=your_secret \
-v $(pwd)/memory:/app/memory \
-v $(pwd)/runs:/app/runs \
-p 8000:8000 \
graphstrikeThe memory/ and runs/ volumes preserve all learning between container restarts.
Environment Variables
Startup Sequence (run.sh)
1. Validate AWS credentials
2. python server/generator.py → generates 150 episode JSON files
3. uvicorn server.app:app → starts the environment server
4. Health check polling → waits until /health responds
5. python train.py → runs the full training loopFull HTTP validation
python3 -m uvicorn server.app:app --port 8001 &
sleep 3
python3 validate.py --url http://localhost:8001
# Expected: Results: 24/24 passed — all OKDeployed Endpoint Verification
curl https://pandago-graphstrike.hf.space/health
# → {"status": "healthy"}
curl https://pandago-graphstrike.hf.space/tasks
# → {"tasks": ["easy","medium","hard"], "action_schema": {...}, "score_range": [0.0, 1.0]}
curl -X POST https://pandago-graphstrike.hf.space/baseline
# → {"scores": {"easy": 0.91, "medium": 0.906, "hard": 0.9038}, "agent": "rule_based"}