Siddh12334 commited on
Commit
9cd90d3
Β·
1 Parent(s): 6801c6a

chore: add CLAUDE.md, README, update gitignore

Browse files
Files changed (3) hide show
  1. .gitignore +11 -11
  2. CLAUDE.md +440 -0
  3. README.md +0 -0
.gitignore CHANGED
@@ -1,11 +1,11 @@
1
- venv/
2
- __pycache__/
3
- *.pyc
4
- *.pyo
5
- .env
6
- *.egg-info/
7
- dist/
8
- build/
9
- .DS_Store
10
- data/facts.json
11
- assets/*.png
 
1
+ +venv/
2
+ +__pycache__/
3
+ +*.pyc
4
+ +*.pyo
5
+ +.env
6
+ +*.egg-info/
7
+ +dist/
8
+ +build/
9
+ +.DS_Store
10
+ +data/facts.json
11
+ +assets/*.png
CLAUDE.md ADDED
@@ -0,0 +1,440 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ContextCorruption-Env β€” Engineering Spec
2
+ > OpenEnv Hackathon | Meta Γ— HuggingFace Γ— PyTorch
3
+ > Team: Siddh + Teammate | Deadline: Tomorrow 5pm | Budget: $60 HF Credits
4
+
5
+ ---
6
+
7
+ ## Division of Ownership
8
+
9
+ | Owner | Scope |
10
+ |---|---|
11
+ | **Siddh** | `environment/` (env, reward, actions, server) + `training/` |
12
+ | **Teammate** | `data/` (loader, corruption, generator, facts.json) |
13
+ | **Both** | Integration smoke test (Tomorrow 9am), Docker + HF deploy (Tomorrow 11am) |
14
+
15
+ ---
16
+
17
+ ## Repo Layout
18
+
19
+ ```
20
+ context-corruption-env/
21
+ β”œβ”€β”€ CLAUDE.md
22
+ β”œβ”€β”€ README.md
23
+ β”œβ”€β”€ openenv.yaml
24
+ β”œβ”€β”€ Dockerfile
25
+ β”œβ”€β”€ requirements.txt
26
+ β”œβ”€β”€ environment/ # Siddh
27
+ β”‚ β”œβ”€β”€ __init__.py
28
+ β”‚ β”œβ”€β”€ actions.py # Pydantic schemas
29
+ β”‚ β”œβ”€β”€ reward.py # scoring logic
30
+ β”‚ β”œβ”€β”€ env.py # OpenEnv Environment subclass
31
+ β”‚ └── server.py # FastAPI app via OpenEnv helper
32
+ β”œβ”€β”€ data/ # Teammate
33
+ β”‚ β”œβ”€β”€ loader.py
34
+ β”‚ β”œβ”€β”€ corruption.py
35
+ β”‚ β”œβ”€β”€ generator.py
36
+ β”‚ └── facts.json # generated artifact, 500+ QA pairs
37
+ β”œβ”€β”€ training/
38
+ β”‚ β”œβ”€β”€ train_grpo.py
39
+ β”‚ └── ContextCorruption_GRPO.ipynb
40
+ β”œβ”€β”€ eval/
41
+ β”‚ └── baseline_eval.py
42
+ └── assets/
43
+ β”œβ”€β”€ reward_curve.png
44
+ └── loss_curve.png
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Bootstrap (both, do this first)
50
+
51
+ ```bash
52
+ # Create repo on GitHub: Public, Python .gitignore, MIT license
53
+ git clone https://github.com/YOUR_USERNAME/context-corruption-env.git
54
+ cd context-corruption-env
55
+
56
+ mkdir -p environment data training eval assets
57
+ touch environment/__init__.py
58
+ touch environment/{actions,reward,env,server}.py
59
+ touch data/{loader,corruption,generator}.py
60
+ touch training/train_grpo.py eval/baseline_eval.py
61
+ touch requirements.txt openenv.yaml Dockerfile
62
+
63
+ git add . && git commit -m "feat: initial structure" && git push origin main
64
+
65
+ # Siddh's branch
66
+ git checkout -b feat/environment && git push origin feat/environment
67
+
68
+ # Teammate's branch
69
+ git checkout -b feat/data-pipeline && git push origin feat/data-pipeline
70
+
71
+ # Shared venv
72
+ python -m venv venv && source venv/bin/activate
73
+ pip install openenv fastapi uvicorn websockets pydantic \
74
+ datasets transformers torch trl unsloth \
75
+ wandb faker python-dotenv
76
+ pip freeze > requirements.txt
77
+ ```
78
+
79
+ ---
80
+
81
+ ## environment/actions.py
82
+
83
+ **Purpose:** Define the shared data contracts. Everything else imports from here.
84
+
85
+ **ActionType enum** β€” four string-valued variants:
86
+ - `read_doc` β€” read a document by index (costs budget, no state change)
87
+ - `flag_suspicious` β€” mark a doc as potentially corrupted
88
+ - `unflag_doc` β€” remove a flag
89
+ - `submit_answer` β€” end the episode with a final answer
90
+
91
+ **ContextCorruptionAction (Pydantic BaseModel):**
92
+ - `action_type: ActionType`
93
+ - `doc_id: Optional[int]` β€” 0-indexed, only used for doc actions
94
+ - `answer: Optional[str]` β€” only used on submit
95
+ - `confidence: Optional[float]` β€” 0.0–1.0, only used on submit; validate range
96
+
97
+ **Document (Pydantic BaseModel):**
98
+ - `id: int`, `title: str`, `content: str`
99
+ - `is_flagged: bool = False` β€” this is the *agent's* flag, not ground truth
100
+
101
+ **EpisodeObservation (Pydantic BaseModel):**
102
+ - `question: str`
103
+ - `documents: list[Document]`
104
+ - `flagged_ids: list[int]`
105
+ - `budget_remaining: int`
106
+ - `turn: int`
107
+ - `episode_done: bool = False`
108
+ - `reward: Optional[float]` β€” only populated after SUBMIT_ANSWER or budget exhaustion
109
+ - `message: Optional[str]` β€” optional human-readable status
110
+
111
+ ---
112
+
113
+ ## environment/reward.py
114
+
115
+ **Purpose:** Score a completed episode. No LLM judge β€” fully deterministic.
116
+
117
+ **Function signature:**
118
+ ```
119
+ compute_reward(submitted_answer, ground_truth_answer, flagged_ids,
120
+ corrupt_ids, confidence, budget_used, max_budget)
121
+ -> tuple[float, dict]
122
+ ```
123
+
124
+ **Scoring breakdown (weights sum to ~1.05 max, floor ~-0.5):**
125
+
126
+ | Component | Logic | Weight |
127
+ |---|---|---|
128
+ | Answer correctness | normalize both strings (lowercase, strip punct, collapse whitespace), exact match | +0.4 |
129
+ | Flag recall | `len(true_positives) / len(corrupt_ids)` β€” fraction of corrupt docs caught | +0.3 |
130
+ | Precision (no false flags) | start at +0.2, subtract 0.1 per false positive, floor at 0 | +0.2 |
131
+ | Confidence calibration | if correct: `+0.1 Γ— confidence`; if wrong: `-0.2 Γ— confidence` | Β±0.1 |
132
+ | Efficiency | `0.05 Γ— (1 - budget_used / max_budget)` β€” small bonus, don't over-optimise | +0.05 |
133
+
134
+ Return `(round(total, 4), breakdown_dict)`. The breakdown dict must include all component keys plus `"total"`.
135
+
136
+ **Private helper:** `_normalize(text: str) -> str` β€” lowercase, strip non-word chars, collapse whitespace.
137
+
138
+ ---
139
+
140
+ ## environment/env.py
141
+
142
+ **Purpose:** The stateful RL environment. Subclass `openenv.Environment`.
143
+
144
+ **Class constants:**
145
+ - `MAX_BUDGET = 12`
146
+ - `NUM_DOCS = 8`
147
+ - `DIFFICULTY_LEVELS = [1, 2, 3, 4]` (number of corrupt docs per episode)
148
+
149
+ **`__init__(self, difficulty=None)`**
150
+ - Store difficulty (None = random per episode)
151
+ - Load `data/facts.json` from `Path(__file__).parent.parent / "data" / "facts.json"`
152
+ - If the file doesn't exist, fall back to a hardcoded single-item list so the env still imports cleanly
153
+ - Call `_reset_state()`
154
+
155
+ **`reset() -> EpisodeObservation`**
156
+ - Call `_reset_state()`
157
+ - Sample a random fact
158
+ - Pick `n_corrupt`: use `self.difficulty` if set, else `random.choice(DIFFICULTY_LEVELS)`
159
+ - Sample `n_corrupt` positions from `range(NUM_DOCS)` without replacement β†’ store as `self._corrupt_ids`
160
+ - Call `data.generator.generate_documents(fact, num_docs=NUM_DOCS, corrupt_positions=self._corrupt_ids)`
161
+ - Store question, ground truth answer, documents on self
162
+ - Return `_build_observation()`
163
+
164
+ **`step(action: ContextCorruptionAction) -> EpisodeObservation`**
165
+ - If `self._done`, return observation with message "Episode already done."
166
+ - Increment `self._turn` and `self._budget_used`
167
+ - Dispatch on `action.action_type`:
168
+ - `READ_DOC` β€” no-op (document content is already in the observation; the budget cost is the point)
169
+ - `FLAG_SUSPICIOUS` β€” append `action.doc_id` to `self._flagged_ids` if not already there
170
+ - `UNFLAG_DOC` β€” remove `action.doc_id` from `self._flagged_ids` if present
171
+ - `SUBMIT_ANSWER` β€” call `compute_reward(...)`, set `self._done = True`, store reward + breakdown
172
+ - After dispatch, check if `budget_used >= MAX_BUDGET` and not yet done β†’ force submission with empty answer, confidence 0.0
173
+ - Return `_build_observation(reward=reward)`
174
+
175
+ **`state() -> dict`** β€” return all internal state including ground truth (for logging/debug only, never surfaced to the agent via the observation)
176
+
177
+ **`_reset_state()`** β€” zero/clear all instance variables
178
+
179
+ **`_build_observation(reward=None, message=None) -> EpisodeObservation`** β€” build the Pydantic observation from current state; set `is_flagged` on each Document based on `self._flagged_ids`
180
+
181
+ ---
182
+
183
+ ## environment/server.py
184
+
185
+ **Purpose:** Expose the env over HTTP/WebSocket for TRL to connect to.
186
+
187
+ Use OpenEnv's `create_app` helper β€” it handles session management and WebSocket routing automatically. You only need to pass:
188
+ - `env_factory`: a zero-arg callable that returns a fresh `ContextCorruptionEnv()`
189
+ - `action_model`: `ContextCorruptionAction`
190
+ - `observation_model`: `EpisodeObservation`
191
+ - `max_concurrent_envs`: 64
192
+
193
+ Assign the result to `app` so uvicorn can find it. Add a `__main__` guard that runs uvicorn on `0.0.0.0:8000`.
194
+
195
+ > **Note:** If the `create_app` helper name differs in the installed version, check `meta-pytorch/OpenEnv` GitHub for the current API surface.
196
+
197
+ ---
198
+
199
+ ## data/loader.py
200
+
201
+ **Purpose:** Pull QA facts from three sources, merge, shuffle, write `facts.json`.
202
+
203
+ **Three loaders (implement as separate functions, called by `build_fact_database`):**
204
+
205
+ 1. **`load_natural_questions(n=300)`**
206
+ - Dataset: `google-research-datasets/natural_questions`, `train` split, streaming=True
207
+ - Filter: only rows where `annotations.short_answers[0].text` exists and has ≀5 words
208
+ - Shape each fact as `{question, answer, source: "natural_questions", conflict_type: "entity"}`
209
+
210
+ 2. **`load_popqa(n=150)`**
211
+ - Dataset: `akariasai/PopQA`, `test` split
212
+ - Filter: rows where `possible_answers` is non-empty
213
+ - Shape: `{question, answer: possible_answers[0], source: "popqa", conflict_type: "entity", entity, relation}`
214
+
215
+ 3. **`load_faitheval_counterfactual(n=100)`**
216
+ - Source: raw JSON from the SalesforceAIResearch/FaithEval GitHub repo (`data/counterfactual.json`)
217
+ - Fetch with `urllib.request`; wrap in try/except and return `[]` on any failure (URL may be down)
218
+ - Shape: `{question, answer, source: "faitheval", conflict_type: "counterfactual", provided_context}`
219
+
220
+ **`build_fact_database()`** β€” call all three, concatenate, shuffle, write to `data/facts.json`. Also callable as `__main__`.
221
+
222
+ ---
223
+
224
+ ## data/corruption.py
225
+
226
+ **Purpose:** Four escalating corruption functions. This is the creative heart of the dataset.
227
+
228
+ **Level 1 β€” `corrupt_number(text, answer)`**
229
+ - Find all integers in text with regex `\b\d{4}\b|\b\d+\b`
230
+ - If the number looks like a year (4 digits, 1900–2030), shift by a random offset (Β±5, Β±10, Β±20)
231
+ - Otherwise multiply by a random factor (0.5Γ—, 2Γ—, 3Γ—, 5Γ—, 10Γ—)
232
+ - Fallback if no numbers found: append a note claiming the figure was revised
233
+
234
+ **Level 2 β€” `corrupt_entity(text, answer)`**
235
+ - Maintain pools of plausible substitutes by category: countries, cities, person names (use Faker), organizations
236
+ - If the answer appears in text, replace it with a different member of the most-fitting pool
237
+ - Fallback: append a sentence attributing the fact to a Faker-generated person name
238
+
239
+ **Level 3 β€” `corrupt_inversion(text, answer)`**
240
+ - Maintain a hardcoded antonym map: largest↔smallest, first↔last, highest↔lowest, won↔lost, north↔south, etc.
241
+ - Case-preserving replacement (preserve UPPER/Title/lower casing of the matched word)
242
+ - Fallback: append a sentence saying this contradicts earlier scholarly consensus
243
+
244
+ **Level 4 β€” `corrupt_coherent(text, answer)`**
245
+ - Generate a plausible wrong answer via `_generate_wrong_answer`:
246
+ - If answer contains digits β†’ mutate a number (Β±1, Β±2, Β±5)
247
+ - If single capitalised word β†’ use `fake.last_name()`
248
+ - Multi-word β†’ shuffle words
249
+ - Insert the wrong answer into the text *and* wrap it in an authoritative-sounding template citing a fake source + fake org + plausible year
250
+ - This level should read as convincing to a careful human reader
251
+
252
+ **Dispatcher:** `corrupt_text(text, answer, level: int) -> str` β€” route to the right function, catch all exceptions and return a safe fallback.
253
+
254
+ ---
255
+
256
+ ## data/generator.py
257
+
258
+ **Purpose:** Wrap a raw QA fact into a list of 8 document dicts, some corrupted.
259
+
260
+ **`generate_documents(fact, num_docs=8, corrupt_positions=None) -> list[dict]`**
261
+
262
+ For each document index:
263
+ - Pick a random source name (e.g. "Encyclopedia Britannica", "Reuters Fact Check", etc. β€” maintain a pool of ~10)
264
+ - Pick a random sentence template that incorporates `{source}`, `{question}`, and `{answer}`
265
+ - If the index is in `corrupt_positions`, apply `corrupt_text` at an escalating level (first corrupt doc = level 1, second = level 2, etc., capping at 4)
266
+ - Return a dict with `id`, `title`, `content`, `is_corrupt` (ground truth β€” never shown to the agent)
267
+
268
+ ---
269
+
270
+ ## Integration Smoke Test (Tomorrow 9am β€” run together)
271
+
272
+ ```python
273
+ from environment.env import ContextCorruptionEnv
274
+ from environment.actions import ContextCorruptionAction, ActionType
275
+
276
+ env = ContextCorruptionEnv(difficulty=2)
277
+ obs = env.reset()
278
+ assert len(obs.documents) == 8
279
+ assert obs.budget_remaining == 12
280
+
281
+ obs = env.step(ContextCorruptionAction(action_type=ActionType.READ_DOC, doc_id=0))
282
+ assert obs.budget_remaining == 11
283
+
284
+ obs = env.step(ContextCorruptionAction(action_type=ActionType.FLAG_SUSPICIOUS, doc_id=0))
285
+ assert 0 in obs.flagged_ids
286
+
287
+ obs = env.step(ContextCorruptionAction(
288
+ action_type=ActionType.SUBMIT_ANSWER, answer="test", confidence=0.8))
289
+ assert obs.episode_done
290
+ assert -0.5 <= obs.reward <= 1.05
291
+ print("Smoke test passed. State:", env.state())
292
+ ```
293
+
294
+ This must run without errors before starting training.
295
+
296
+ ---
297
+
298
+ ## training/train_grpo.py
299
+
300
+ **Purpose:** Fine-tune Qwen2-1.5B-Instruct with GRPO against the live env server.
301
+
302
+ **Steps to implement:**
303
+
304
+ 1. Init WandB: `wandb.init(project="context-corruption-env", name="qwen-1.5b-grpo-run1")`
305
+
306
+ 2. Load model with Unsloth:
307
+ - `unsloth/Qwen2-1.5B-Instruct`, `max_seq_length=2048`, `load_in_4bit=True`
308
+ - Apply LoRA: `r=16`, target `q/k/v/o_proj`, no dropout, `use_gradient_checkpointing="unsloth"`
309
+
310
+ 3. Configure GRPO:
311
+ - `num_train_epochs=3`
312
+ - `per_device_train_batch_size=4`, `gradient_accumulation_steps=4`
313
+ - `learning_rate=5e-5`, `max_completion_length=512`
314
+ - `num_generations=8` (GRPO group size)
315
+ - `report_to="wandb"`, `logging_steps=10`, `save_steps=50`
316
+
317
+ 4. System prompt for the agent β€” describe the task, list the three tools (`read_doc`, `flag_suspicious`, `submit_answer`), and give the strategy: cross-reference claims, flag inconsistencies, trust parametric knowledge when docs conflict.
318
+
319
+ 5. `env_factory = lambda: EnvClient(base_url=ENV_URL).sync()` β€” fill `ENV_URL` after HF Space is live.
320
+
321
+ 6. Construct `GRPOTrainer(model, config, environment_factory, tokenizer)` and call `.train()`.
322
+
323
+ 7. After training: `wandb.finish()`. Download reward + loss plots from WandB and save as `assets/reward_curve.png` and `assets/loss_curve.png`. Commit them.
324
+
325
+ **Budget guidance:** ~200 steps target; minimum 50 steps with visible upward trend is acceptable. Reserve ~$20 of credits for reruns.
326
+
327
+ ---
328
+
329
+ ## eval/baseline_eval.py
330
+
331
+ **Purpose:** Establish a pre-training baseline to make the improvement curves meaningful.
332
+
333
+ Run 100 episodes with a random agent (randomly flags 0–4 docs, submits "unknown" with 0.5 confidence). Print avg/min/max reward and write to `eval/baseline_results.json`. Run this *before* training.
334
+
335
+ ---
336
+
337
+ ## Dockerfile
338
+
339
+ ```dockerfile
340
+ FROM python:3.11-slim
341
+ WORKDIR /app
342
+ COPY requirements.txt .
343
+ RUN pip install --no-cache-dir -r requirements.txt
344
+ COPY . .
345
+ RUN python -c "from data.loader import build_fact_database; build_fact_database()" || true
346
+ EXPOSE 7860
347
+ CMD ["uvicorn", "environment.server:app", "--host", "0.0.0.0", "--port", "7860"]
348
+ ```
349
+
350
+ ---
351
+
352
+ ## openenv.yaml
353
+
354
+ ```yaml
355
+ name: context-corruption-env
356
+ version: "1.0.0"
357
+ description: >
358
+ OpenEnv environment for training epistemic robustness in LLMs.
359
+ Agents identify correct answers and flag corrupted documents
360
+ in a multi-doc QA setting with verifiable, objective rewards.
361
+ author: "Your Team Name"
362
+ license: MIT
363
+
364
+ environment:
365
+ entrypoint: "environment.server:app"
366
+ action_schema: "environment.actions.ContextCorruptionAction"
367
+ observation_schema: "environment.actions.EpisodeObservation"
368
+ max_concurrent_sessions: 64
369
+
370
+ reward:
371
+ type: "objective"
372
+ range: [-0.5, 1.05]
373
+ components:
374
+ - {name: answer_correctness, weight: 0.4}
375
+ - {name: corruption_detection, weight: 0.3}
376
+ - {name: false_positive_penalty, weight: 0.2}
377
+ - {name: confidence_calibration, weight: 0.1}
378
+
379
+ datasets:
380
+ - {name: Natural Questions, url: "https://huggingface.co/datasets/google-research-datasets/natural_questions"}
381
+ - {name: PopQA, url: "https://huggingface.co/datasets/akariasai/PopQA"}
382
+ - {name: FaithEval, url: "https://github.com/SalesforceAIResearch/FaithEval"}
383
+
384
+ citation: |
385
+ @misc{contextcorruption2026,
386
+ title={ContextCorruption-Env: Training Epistemic Robustness in LLMs},
387
+ year={2026},
388
+ note={OpenEnv Hackathon Submission}
389
+ }
390
+ ```
391
+
392
+ ---
393
+
394
+ ## HuggingFace Deployment
395
+
396
+ ```bash
397
+ pip install huggingface_hub
398
+ huggingface-cli login
399
+ huggingface-cli repo create context-corruption-env --type space --space_sdk docker
400
+ git remote add space https://huggingface.co/spaces/YOUR_HF_USERNAME/context-corruption-env
401
+ git push space main
402
+ ```
403
+
404
+ Emergency fallback if Space deploy fails: `pip install pyngrok && ngrok http 8000`
405
+
406
+ ---
407
+
408
+ ## Sync Schedule
409
+
410
+ | Time | Checkpoint |
411
+ |------|------------|
412
+ | Tonight 12am | Siddh: `env.py` skeleton runs. Teammate: `facts.json` with 100+ entries. |
413
+ | Tonight 3am | Full integration attempt β€” `env.reset()` works with real data. |
414
+ | Tomorrow 9am | Smoke test passes. Both push to main. |
415
+ | Tomorrow 11am | HF Space live. Training starts. |
416
+ | Tomorrow 2pm | Training done. Plots committed. README filled in. |
417
+ | Tomorrow 4pm | Final submission check. All links in README. |
418
+
419
+ ---
420
+
421
+ ## Emergency Fallbacks
422
+
423
+ - **FaithEval URL down:** Skip it β€” NQ + PopQA gives 450+ facts, enough.
424
+ - **Training too slow:** 50 steps with visible upward trend beats no training evidence.
425
+ - **OpenEnv API changed:** Check `meta-pytorch/OpenEnv` GitHub for the current `create_app` / `Environment` import paths.
426
+
427
+ ---
428
+
429
+ ## README Template (fill in after training)
430
+
431
+ Key sections judges want to see:
432
+ 1. **The Problem** β€” LLMs defer to wrong retrieved docs even when they know the answer. Standard RLHF makes this worse (cite ClashEval NeurIPS 2024, CANOE May 2025, Knowledgeable-R1 June 2025).
433
+ 2. **What We Built** β€” OpenEnv RL environment, objective reward, no LLM judge.
434
+ 3. **Results table** β€” Random baseline vs trained model: avg reward, answer accuracy, corruption detection rate.
435
+ 4. Reward + loss curve images.
436
+ 5. Links: HF Space, Colab, blog post, WandB run.
437
+
438
+ ---
439
+
440
+ *OpenEnv Hackathon, April 2026*
README.md ADDED
File without changes