# LLM Punishment Treatment Changes This document summarizes the changes made to add ChatGPT/LLM-based punishment treatments while preserving the four original oTree treatments. ## Summary Two new treatments were added in `settings.py`: - `AI_judge_llm` - `humanAI_judge_llm` The existing treatments remain available and keep their original logic: - `no_judge` - `human_judge` - `AI_judge` - `humanAI_judge` The new LLM treatments are selectable from the oTree session configuration list, so the experimenter can choose among six treatments when creating a session. ## Changed Files - `settings.py` - `requirements.txt` - `base/__init__.py` - `base/templates/base/AIProcessing.html` - `base/templates/base/Intro.html` - `base/templates/base/Instructions.html` - `base/templates/base/Judge.html` ## New Session Configurations `settings.py` now contains: - `AI_judge_llm`: AI-only punishment using an LLM. - `humanAI_judge_llm`: LLM suggestions shown as editable prefilled values for the human controller. Each LLM treatment includes: ```python ai_punishment_model=environ.get('OPENAI_MODEL', 'gpt-4o-mini') show_ai_reasoning_to_judge=False ``` `OPENAI_MODEL` can be set as an environment variable. If it is not set, the code uses `gpt-4o-mini`. `show_ai_reasoning_to_judge` is currently `False`, so judges do not see the LLM reasoning. If this is changed to `True`, `Judge.html` is already prepared to show the AI suggestion and reasoning. ## New Stored Fields The `Player` model now stores the AI suggestion separately from the final punishment: - `ai_punishment_decision`: AI's suggested punishment, stored as text. - `ai_punishment_reasoning`: AI's reasoning. - `ai_punishment_raw_response`: raw JSON response or fallback information. - `ai_punishment_model`: model used for the request. - `ai_punishment_status`: `success` or `fallback`. - `ai_punishment_fallback_used`: `True` if the old formula was used because the LLM path failed. The existing field `punishment_received` remains the final applied punishment. This means the data export can distinguish: - what the AI suggested, - why the AI suggested it, - whether fallback was used, - what punishment was finally applied. ## LLM Prompt Inputs For each 4-person PGG group, the LLM prompt includes: - round number, - endowment, - MPCR, - PGG group ID, - each player's `id_in_subsession`, - each player's contribution, - total contribution to the public pool, - public return after the multiplier. The LLM is asked to return JSON with one punishment decision and one reasoning string per player. ## JSON Format The API call requests structured JSON in this shape: ```json { "players": [ { "id_in_subsession": 3, "punishment": 0, "reasoning": "Short explanation." } ] } ``` Punishment values are forced into the range `0` to `5`. ## Flow By Treatment ### `AI_judge` Unchanged behavior. The old formula is used: ```python min(5, max(0, round((average_contribution - player_contribution) / 2))) ``` The result is written directly to `punishment_received`. ### `humanAI_judge` Unchanged behavior. The old formula pre-fills the human judge form. The human judge can edit the values. The submitted human values become `punishment_received`. ### `AI_judge_llm` 1. Contributions are collected. 2. Pre-punishment payoffs are calculated. 3. `AIProcessing` runs an async `live_method`. 4. The LLM suggestion and reasoning are stored in the new AI fields. 5. At the next wait page, `ai_punishment_decision` is copied into `punishment_received`. 6. Final payoffs are calculated from `punishment_received`. This follows the requested logic: first store AI punishment and reasoning, then load the AI punishment into the active punishment variable at the next step. ### `humanAI_judge_llm` 1. Contributions are collected. 2. Pre-punishment payoffs are calculated. 3. `AIProcessing` runs an async `live_method`. 4. The LLM suggestion and reasoning are stored in the new AI fields. 5. The human judge form is prefilled from `ai_punishment_decision`. 6. The judge can accept or edit each value. 7. The submitted human values become `punishment_received`. This preserves both records: - AI suggestion and reasoning, - final human decision. ## Reliability And Fallback The LLM path is designed to fail safely. If any of these happens: - no `OPENAI_API_KEY`, - missing `openai` package, - API timeout, - API error, - invalid JSON, - missing player in the JSON response, then the code falls back to the original formula and records: ```python ai_punishment_fallback_used = True ai_punishment_status = 'fallback' ``` The experiment continues instead of blocking the session. ## Async Processing The new `AIProcessing` page uses oTree 6 async live methods. To reduce race risk, only one designated participant triggers LLM processing: - in `AI_judge_llm`, the lowest-id normal player triggers it, - in `humanAI_judge_llm`, the lowest-id judge triggers it. Other participants wait on the same page until the stored AI decisions are complete. ## Environment Variables The LLM API key is not stored in code. The deployed app must have: ```text OPENAI_API_KEY ``` Optional: ```text OPENAI_MODEL ``` For Heroku, these should be added as Config Vars. ## Dependency Change `requirements.txt` now includes: ```text openai>=1.40.0 ``` Install dependencies before local testing: ```bash /Users/z133668/myvenv/bin/python -m pip install -r requirements.txt ``` ## Validation Performed Local validation used `/Users/z133668/myvenv`. Results: - Python import check passed: `import settings, base`. - Python syntax check passed for `base/__init__.py` and `settings.py`. - `otree devserver` loaded the app. - `otree check` is not available in this oTree CLI version. - `otree test` is not usable because the repo does not contain `base.tests`. ## Operational Notes Because new model fields were added, old local or deployed databases may need a reset before starting a fresh test session. Do not reset a production database containing real participant data unless the data has been exported and the reset is intentional.