--- title: InformationIntegrityEnv emoji: πŸ” colorFrom: blue colorTo: green sdk: gradio sdk_version: 6.11.0 app_file: app.py pinned: false license: mit tags: - openenv - content-moderation - rl-environment - agent-benchmark --- # InformationIntegrityEnv A **deterministic, benchmarkable OpenEnv environment** for evaluating LLM agents on reasoning-based content moderation. Agents must verify claims, weigh conflicting evidence, and make explainable moderation decisions (`allow` / `flag` / `remove`). Three difficulty tiers β€” easy, medium, hard β€” provide 15 fully static scenarios that run completely offline. --- ## Architecture ``` tasks/ easy/scenarios.json ← 5 static scenarios (clear-cut misinformation) medium/scenarios.json ← 5 static scenarios (misleading / mixed evidence) hard/scenarios.json ← 5 static scenarios (conflicting evidence, nuanced) graders.py ← EasyGrader / MediumGrader / HardGrader registry.py ← TASKS["easy"] / TASKS["medium"] / TASKS["hard"] β”‚ β–Ό InformationIntegrityEnv (environment.py) reset(difficulty, scenario_id) β†’ Observation step(action) β†’ (Observation, Reward, done, info) state() β†’ full debug state β”‚ β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ inference.py server.py (benchmark (FastAPI agent loop) REST API) ``` No external API calls occur during evaluation. `DataCollector` is only used to build/refresh datasets. --- ## Quick Start ### 1. Install ```bash pip install -r requirements.txt ``` ### 2. Set API keys (for the LLM agent β€” not needed for just the server) ```bash export HF_TOKEN=sk-... # or OPENAI_API_KEY export MODEL_NAME=gpt-4o export API_BASE_URL=https://api.openai.com/v1 ``` ### 3. Run the full benchmark ```bash python inference.py --benchmark --out results.json ``` This loops over all 15 scenarios (5 per difficulty) and prints: ``` [START] episode=E01 difficulty=easy claims=1 sources=5 [STEP] step=1 action=check_source reward=+0.200 total=+0.200 done=False ... [STEP] step=2 action=verify_claim reward=+0.200 total=+0.400 done=False ... [STEP] step=3 action=decide reward=+1.000 total=+1.400 done=True ... [END] episode=E01 steps=3 decision=remove total_reward=+1.4000 grader_score=0.8750 ... difficulty=easy avg_score=0.7800 avg_reward=1.2000 n=5 difficulty=medium avg_score=0.6300 avg_reward=0.9000 n=5 difficulty=hard avg_score=0.5200 avg_reward=0.6500 n=5 Difficulty tiers well separated: easy > medium > hard βœ“ ``` ### 4. Single episode (local, no server) ```bash # Random scenario from medium dataset python inference.py --local --difficulty medium # Specific scenario by index python inference.py --local --difficulty hard --scenario-id 2 # Specific scenario by id python inference.py --local --difficulty easy --scenario-id easy_001 # From a custom JSON file (DataCollector output) python inference.py --local --scenario my_scenario.json ``` ### 5. Start the API server ```bash python server.py # or uvicorn server:app --reload ``` --- ## Action Space | Action | Required fields | Description | |--------|----------------|-------------| | `verify_claim` | `claim_id` | Investigate a claim; returns true / false / partially_true / unknown | | `check_source` | `source_id` | Read a source; returns title, text, stance, reliability | | `decide` | `decision` | Final call: `allow` / `flag` / `remove` β€” ends episode | All actions accept an optional `reasoning` string (logged, not graded). **Minimum evidence required before deciding** (or premature penalty applies): - β‰₯ 1 claim verified - β‰₯ 2 sources checked --- ## Observation Space ```json { "content": "The post text under review", "claims": [ {"id": "claim_001", "text": "...", "verification_status": "unverified", "confidence": 0.0} ], "available_sources": [ {"id": "src_001", "title": "...", "text": "...", "url": "...", "stance": "contradicting", "reliability": 0.97, "source_type": "official"} ], "interaction_history": [], "status_flags": { "decision_made": false, "sufficient_evidence": false, "premature_decision": false, "redundant_actions": 0, "max_steps_reached": false }, "step_count": 0, "task_difficulty": "medium", "episode_id": "uuid" } ``` --- ## Reward Function | Component | Value | Trigger | |-----------|-------|---------| | Verify claim | +0.20 | Per unique claim verified | | Check source | +0.20 | Per unique source checked | | Multi-source bonus | +0.20 | When β‰₯ 3 unique sources checked | | Correct decision | +1.00 | Exact ground truth match | | Partial decision | +0.50 | One tier off (flag↔remove, flag↔allow) | | Premature decision | βˆ’0.50 | Deciding before sufficient evidence | | Redundant action | βˆ’0.10 | Re-checking same claim/source | | Inefficiency | βˆ’0.05 | Per step beyond efficient threshold | | Over-aggression | βˆ’0.30 | `remove` when ground truth is `allow`/`flag` | Total clipped to `[-2.0, +2.0]`. All values configurable in `reward_config.yaml`. --- ## Grader Scoring Each episode is scored `[0.0, 1.0]` across four dimensions: | Dimension | Easy | Medium | Hard | |-----------|------|--------|------| | Decision correctness | 50% | 35% | 30% | | Evidence breadth | 25% | 35% | 30% | | Reasoning depth | 15% | 20% | 30% | | Efficiency | 10% | 10% | 10% | --- ## Task Datasets ### Easy (5 scenarios) Clear-cut false claims contradicted by multiple authoritative sources (WHO, CDC, NASA, FDA). Ground truth: 3Γ— remove, 1Γ— flag, 1Γ— allow. | ID | Topic | Ground Truth | |----|-------|-------------| | easy_001 | Bleach cures COVID | remove | | easy_002 | NASA fabricated climate data | remove | | easy_003 | mRNA vaccines alter DNA | remove | | easy_004 | Sydney is Australia's capital | flag | | easy_005 | Webb telescope early universe | allow | ### Medium (5 scenarios) Partially true or misleadingly framed content. Mixed evidence requires weighing. Ground truth: 4Γ— flag, 1Γ— allow. | ID | Topic | Ground Truth | |----|-------|-------------| | medium_001 | Sugar more addictive than cocaine | flag | | medium_002 | Real unemployment is 20%+ | flag | | medium_003 | AI will eliminate 85M jobs | flag | | medium_004 | Pharma knew OxyContin was addictive | allow | | medium_005 | Facebook hid teen mental health data | flag | ### Hard (5 scenarios) Conflicting credible evidence, genuinely contested claims, contested policy debates. Ground truth: 5Γ— flag. | ID | Topic | Ground Truth | |----|-------|-------------| | hard_001 | NATO provoked Ukraine invasion | flag | | hard_002 | Lockdowns killed more than COVID | flag | | hard_003 | SEC crackdown is banking conspiracy | flag | | hard_004 | AI poses extinction-level risk | flag | | hard_005 | British colonialism net beneficial | flag | --- ## API Reference | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/health` | Liveness probe | | GET | `/tasks` | List all available scenario datasets | | POST | `/reset` | Start a new episode | | POST | `/step` | Submit one action | | GET | `/state/{session_id}` | Full internal state (debug/audit) | | GET | `/scenario/{session_id}` | Scenario metadata (no ground truth) | | DELETE | `/session/{session_id}` | End session and free memory | | POST | `/collect` | Build a live scenario via DataCollector (dataset creation only) | ### Reset β€” evaluation path (no DataCollector needed) ```bash # Random scenario from easy dataset curl -X POST http://localhost:8000/reset \ -H "Content-Type: application/json" \ -d '{"difficulty": "easy"}' # Specific scenario by index curl -X POST http://localhost:8000/reset \ -d '{"difficulty": "medium", "scenario_id": 2}' # Specific scenario by id curl -X POST http://localhost:8000/reset \ -d '{"difficulty": "hard", "scenario_id": "hard_001"}' ``` ### Full episode example ```bash SESSION=$(curl -s -X POST http://localhost:8000/reset \ -H "Content-Type: application/json" \ -d '{"difficulty": "easy"}' | jq -r .session_id) # Check a source curl -X POST http://localhost:8000/step \ -d "{\"session_id\": \"$SESSION\", \"action\": {\"type\": \"check_source\", \"source_id\": \"src_001\"}}" # Verify a claim curl -X POST http://localhost:8000/step \ -d "{\"session_id\": \"$SESSION\", \"action\": {\"type\": \"verify_claim\", \"claim_id\": \"claim_001\"}}" # Decide curl -X POST http://localhost:8000/step \ -d "{\"session_id\": \"$SESSION\", \"action\": {\"type\": \"decide\", \"decision\": \"remove\"}}" ``` --- ## Docker ### Server (default) ```bash docker build -t info-integrity-env . docker run --env-file .env -p 8000:8000 info-integrity-env ``` ### Benchmark (offline β€” no external services needed) ```bash docker run --env-file .env info-integrity-env \ python inference.py --benchmark --out /tmp/results.json ``` ### Full stack with Redis (for live data collection) ```bash docker compose up --build ``` --- ## Baseline Results | Difficulty | Avg Grader Score | Avg Reward | Decision Accuracy | |-----------|-----------------|------------|-----------------| | Easy | ~0.78 | ~1.20 | ~85% | | Medium | ~0.63 | ~0.90 | ~70% | | Hard | ~0.52 | ~0.65 | ~55% | *Results vary by LLM. Scores above from `gpt-4o` with temperature=0.* --- ## Extending the Environment | File | What to change | |------|---------------| | `tasks/{difficulty}/scenarios.json` | Add or edit scenarios (run `openenv validate` after) | | `reward_config.yaml` | Tune reward/penalty magnitudes | | `tasks/graders.py` | Register custom graders: `GraderFactory.register("hard_v2", MyGrader)` | | `data_collector.py` | Add new search backends via `SearchBackend` ABC | | `domain_reliability.yaml` | Add trusted domains with custom reliability scores | --- ## Project Structure ``` β”œβ”€β”€ models.py Pydantic models (Observation, Action, Reward, Scenario, ...) β”œβ”€β”€ environment.py InformationIntegrityEnv (OpenEnv core) β”œβ”€β”€ reward.py Configurable dense reward function β”œβ”€β”€ explainability.py Structured reasoning + template-based explanation β”œβ”€β”€ data_collector.py Multi-backend data collection (dataset creation only) β”œβ”€β”€ inference.py Benchmark agent + single-episode runner β”œβ”€β”€ server.py FastAPI server (HF Space / Docker) β”œβ”€β”€ tasks/ β”‚ β”œβ”€β”€ __init__.py β”‚ β”œβ”€β”€ graders.py Easy / Medium / Hard deterministic graders β”‚ β”œβ”€β”€ registry.py TASKS registry β€” lazy-loads scenario datasets β”‚ β”œβ”€β”€ easy/ β”‚ β”‚ └── scenarios.json 5 easy scenarios β”‚ β”œβ”€β”€ medium/ β”‚ β”‚ └── scenarios.json 5 medium scenarios β”‚ └── hard/ β”‚ └── scenarios.json 5 hard scenarios β”œβ”€β”€ openenv.yaml OpenEnv v1.1.0 specification β”œβ”€β”€ reward_config.yaml Reward magnitudes (runtime-configurable) β”œβ”€β”€ domain_reliability.yaml Domain trust scores for DataCollector β”œβ”€β”€ Dockerfile Production container β”œβ”€β”€ docker-compose.yml Local dev stack (server + Redis) └── requirements.txt ```