Upload trained LoRA adapter from Kaggle
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +7 -0
- .gitattributes +10 -10
- .gitignore +6 -0
- Dockerfile +14 -0
- GUIDE.md +245 -0
- README.md +283 -52
- SETUP.md +266 -0
- client.py +134 -0
- data/project_data.json +231 -0
- data/sprint_data.json +65 -0
- evaluate.py +203 -0
- evaluate_r2.py +457 -0
- huggingface_tokenizers_cache/CACHEDIR.TAG +4 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/.no_exist/3d254dbee5e3beae81bb8a717ad3a03427a09d26/chat_template.jinja +0 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/31349551d90c7606f325fe0f11bbb8bd5fa0d7c7 +0 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/4783fe10ac3adce15ac8f358ef5462739852c569 +0 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/482ced4679301bf287ebb310bdd1790eb4514232 +24 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/76ac30de06e0158be805f42a4e63934bedb5a169 +58 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/9b8043f10c758210957b050c77f14d6282f33a52 +31 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/9c5ae00e602b8860cbd784ba82a8aa14e8feecec692e7076590d014d7b7fdafa +3 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/a5de2eb6cac65b89846ac1bcfb837a3d83459f40 +209 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/refs/main +1 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/added_tokens.json +24 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/config.json +58 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/merges.txt +0 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/special_tokens_map.json +31 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/tokenizer.json +3 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/tokenizer_config.json +209 -0
- huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/vocab.json +0 -0
- inference.py +220 -0
- inference_r2.py +855 -0
- openenv.yaml +88 -0
- plot_results.py +421 -0
- project_client.py +289 -0
- pyproject.toml +24 -0
- requirements.env +0 -0
- requirements.txt +0 -0
- results/phase1/README.md +73 -0
- results/phase1/adapter_config.json +50 -0
- results/phase1/adapter_model.safetensors +3 -0
- results/phase1/chat_template.jinja +54 -0
- results/phase1/checkpoint-100/README.md +210 -0
- results/phase1/checkpoint-100/adapter_config.json +50 -0
- results/phase1/checkpoint-100/adapter_model.safetensors +3 -0
- results/phase1/checkpoint-100/chat_template.jinja +54 -0
- results/phase1/checkpoint-100/optimizer.pt +3 -0
- results/phase1/checkpoint-100/rng_state.pth +3 -0
- results/phase1/checkpoint-100/scaler.pt +3 -0
- results/phase1/checkpoint-100/scheduler.pt +3 -0
- results/phase1/checkpoint-100/tokenizer.json +3 -0
.dockerignore
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/
|
| 2 |
+
**/__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
.env
|
| 5 |
+
*.log
|
| 6 |
+
.git/
|
| 7 |
+
results/
|
.gitattributes
CHANGED
|
@@ -33,13 +33,13 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
checkpoint-
|
| 41 |
-
checkpoint-
|
| 42 |
-
checkpoint-
|
| 43 |
-
checkpoint-
|
| 44 |
-
checkpoint-50/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 45 |
-
tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.sh text eol=lf
|
| 37 |
+
*.lock text eol=lf
|
| 38 |
+
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/9c5ae00e602b8860cbd784ba82a8aa14e8feecec692e7076590d014d7b7fdafa filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
results/phase1/checkpoint-100/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
results/phase1/checkpoint-150/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
results/phase1/checkpoint-200/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
results/phase1/checkpoint-222/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
results/phase1/checkpoint-50/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
results/phase1/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
.env
|
| 5 |
+
*.log
|
| 6 |
+
.DS_Store
|
Dockerfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY . .
|
| 9 |
+
|
| 10 |
+
EXPOSE 7860
|
| 11 |
+
EXPOSE 8000
|
| 12 |
+
|
| 13 |
+
# Startup script runs both services
|
| 14 |
+
CMD ["sh", "start.sh"]
|
GUIDE.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 📖 Complete Guide — AI Sprint Manager OpenEnv
|
| 2 |
+
|
| 3 |
+
**What we built, how to test it, how to demo it, and how to explain it.**
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 🤔 What Are We Building — Plain English
|
| 8 |
+
|
| 9 |
+
Imagine you're the Tech Lead of a software company. Every 2 weeks (called a "sprint"), your team gets a list of tasks — new features, bug fixes, tech debt. Your job is to decide:
|
| 10 |
+
|
| 11 |
+
- Which developer gets which task?
|
| 12 |
+
- What's most urgent when a new bug appears?
|
| 13 |
+
- What do you do when someone calls in sick?
|
| 14 |
+
- How do you avoid burning out your best developer?
|
| 15 |
+
|
| 16 |
+
**We built a simulation of this scenario** so an AI agent can practice making these decisions thousands of times and learn to get better — just like how AlphaGo learned to play Go by playing millions of games against itself.
|
| 17 |
+
|
| 18 |
+
The AI plays the role of the Tech Lead. It looks at the sprint state and decides what to do. The environment tells it how well it did (reward). Over time, it learns better strategies.
|
| 19 |
+
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
## 🏗️ How It's Built — Layer by Layer
|
| 23 |
+
|
| 24 |
+
```
|
| 25 |
+
YOU / AI AGENT
|
| 26 |
+
↓ makes decisions (assign, skip, reprioritize...)
|
| 27 |
+
FASTAPI SERVER (ui.py / server/app.py)
|
| 28 |
+
↓ receives actions, returns results
|
| 29 |
+
SPRINT ENVIRONMENT (sprint_env/environment.py)
|
| 30 |
+
↓ core logic: tracks tasks, devs, days, rewards
|
| 31 |
+
DATA (data/sprint_data.json)
|
| 32 |
+
↓ tasks and developers — fully customizable
|
| 33 |
+
GRADIO UI (ui.py)
|
| 34 |
+
visual sprint board, charts, controls
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## 📁 What Each File Does
|
| 40 |
+
|
| 41 |
+
| File | Purpose | Change it to... |
|
| 42 |
+
|------|---------|----------------|
|
| 43 |
+
| `data/sprint_data.json` | All scenario data | Add your own tasks/devs |
|
| 44 |
+
| `sprint_env/models.py` | Data contracts (Action/Observation/State) | Add new fields |
|
| 45 |
+
| `sprint_env/tasks.py` | Task & Developer classes | Add new task types |
|
| 46 |
+
| `sprint_env/environment.py` | Core RL logic | Change simulation rules |
|
| 47 |
+
| `sprint_env/graders.py` | Scoring (easy/medium/hard) | Change scoring weights |
|
| 48 |
+
| `sprint_env/data_loader.py` | Loads JSON data with caching | Point to custom data |
|
| 49 |
+
| `server/app.py` | OpenEnv HTTP API entry point | Add new endpoints |
|
| 50 |
+
| `client.py` | Typed Python client for RL training | Use in training scripts |
|
| 51 |
+
| `ui.py` | Gradio UI + combined server | Change UI layout |
|
| 52 |
+
| `inference.py` | Baseline LLM agent | Change model/strategy |
|
| 53 |
+
| `openenv.yaml` | OpenEnv spec metadata | Update task list |
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## 🔄 What Happens Each Step
|
| 58 |
+
|
| 59 |
+
```
|
| 60 |
+
Day 1 → Day 2 → Day 3 → ... → Day 10 → DONE
|
| 61 |
+
↑ ↑ ↑
|
| 62 |
+
agent agent agent
|
| 63 |
+
acts acts acts
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
**One step = one day in the sprint:**
|
| 67 |
+
|
| 68 |
+
1. Agent receives observation (all tasks, all devs, current day)
|
| 69 |
+
2. Agent picks an action (e.g. "assign T1 to dev1")
|
| 70 |
+
3. Environment validates the action
|
| 71 |
+
4. Developers work on assigned tasks — progress increases
|
| 72 |
+
5. Random events fire (dev goes sick, new bug appears)
|
| 73 |
+
6. Reward is calculated and returned
|
| 74 |
+
7. Repeat until Day 10 or all tasks resolved
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
## 💰 Reward Design — Why It Works for RL
|
| 79 |
+
|
| 80 |
+
The reward function is **shaped** (signal at every step) not **sparse** (only at the end):
|
| 81 |
+
|
| 82 |
+
```
|
| 83 |
+
Good actions → positive reward immediately
|
| 84 |
+
Bad actions → negative reward immediately
|
| 85 |
+
Task done on time → bonus
|
| 86 |
+
Task missed deadline → penalty
|
| 87 |
+
Sprint ends → final_score × 10 bonus
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
This means a learning agent gets feedback on every single decision — critical for efficient RL training with GRPO, PPO, or any policy gradient algorithm.
|
| 91 |
+
|
| 92 |
+
---
|
| 93 |
+
|
| 94 |
+
## ✅ How To Know Everything Is Working
|
| 95 |
+
|
| 96 |
+
### Quick 60-second check
|
| 97 |
+
|
| 98 |
+
```bash
|
| 99 |
+
# 1. Start server
|
| 100 |
+
python ui.py
|
| 101 |
+
|
| 102 |
+
# 2. In another terminal — health check
|
| 103 |
+
curl http://localhost:7860/health
|
| 104 |
+
# Expected: {"status":"ok","env":"ai-sprint-manager"}
|
| 105 |
+
|
| 106 |
+
# 3. Reset
|
| 107 |
+
curl -X POST http://localhost:7860/reset \
|
| 108 |
+
-H "Content-Type: application/json" \
|
| 109 |
+
-d '{"task_name":"easy_sprint","seed":42}'
|
| 110 |
+
# Expected: JSON with current_day=1, 5 tasks in backlog
|
| 111 |
+
|
| 112 |
+
# 4. Step
|
| 113 |
+
curl -X POST http://localhost:7860/step \
|
| 114 |
+
-H "Content-Type: application/json" \
|
| 115 |
+
-d '{"action":{"action_type":"assign","task_id":"T1","dev_id":"dev1"}}'
|
| 116 |
+
# Expected: reward around +1.2, T1 now in_progress
|
| 117 |
+
|
| 118 |
+
# 5. Validate
|
| 119 |
+
openenv validate
|
| 120 |
+
# Expected: [OK] ai-sprint-manager: Ready for multi-mode deployment
|
| 121 |
+
|
| 122 |
+
# 6. Run inference
|
| 123 |
+
python inference.py
|
| 124 |
+
# Expected: [START]/[STEP]/[END] lines, scores for all 3 tasks
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
### Full test checklist
|
| 128 |
+
|
| 129 |
+
| # | Test | How | Pass Condition |
|
| 130 |
+
|---|------|-----|---------------|
|
| 131 |
+
| 1 | Server health | `GET /health` | `{"status":"ok"}` |
|
| 132 |
+
| 2 | Reset works | `POST /reset` | day=1, tasks in backlog |
|
| 133 |
+
| 3 | Assign works | `POST /step` assign T1→dev1 | reward +1.2, T1 in_progress |
|
| 134 |
+
| 4 | Skill mismatch rejected | Assign backend task to frontend dev | reward -0.15, error message |
|
| 135 |
+
| 5 | Sprint ends | 10 skip steps | `done: true` |
|
| 136 |
+
| 6 | Grader runs | Check final_score in info | value between 0.0 and 1.0 |
|
| 137 |
+
| 7 | OpenEnv valid | `openenv validate` | `[OK]` message |
|
| 138 |
+
| 8 | Inference output | `python inference.py` | `[START]` `[STEP]` `[END]` lines |
|
| 139 |
+
| 9 | Docker build | `docker build .` | Exit code 0 |
|
| 140 |
+
| 10 | Docker run | `docker run -p 7860:7860 ...` then health | `{"status":"ok"}` |
|
| 141 |
+
| 11 | Live Space | `curl https://sejal-k-ai-sprint-manager.hf.space/health` | `{"status":"ok"}` |
|
| 142 |
+
| 12 | UI loads | Open http://localhost:7860 | Gradio UI visible |
|
| 143 |
+
| 13 | UI reset | Click Reset Sprint | Sprint board populates |
|
| 144 |
+
| 14 | Auto-assign | Click Auto-Assign All | Tasks move to in_progress |
|
| 145 |
+
| 15 | Reward chart | Take 3+ actions | Sparkline appears |
|
| 146 |
+
|
| 147 |
+
---
|
| 148 |
+
|
| 149 |
+
## 🎤 Project Demo Script (10 minutes)
|
| 150 |
+
|
| 151 |
+
### Before Demo
|
| 152 |
+
```bash
|
| 153 |
+
python ui.py
|
| 154 |
+
# Open http://localhost:7860 in browser — full screen
|
| 155 |
+
# Have terminal with inference.py output ready
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
### [0:00 — 1:30] The Problem
|
| 159 |
+
> "Software teams waste hours every sprint on planning. Which developer gets which task? What happens when someone goes sick? What if a critical bug appears on day 5? These decisions directly affect delivery speed and developer burnout."
|
| 160 |
+
|
| 161 |
+
> "We built an RL environment that simulates exactly this — so an AI agent can learn to make these decisions better."
|
| 162 |
+
|
| 163 |
+
### [1:30 — 3:00] Show the UI
|
| 164 |
+
- Select `easy_sprint` → **🔄 Reset Sprint**
|
| 165 |
+
- Point to sprint board: *"5 tasks in backlog, 3 developers, 10 day sprint"*
|
| 166 |
+
- Point to Skill Guide: *"This tells you which dev is right for which task. Backend tasks need Alice, frontend tasks need Bob."*
|
| 167 |
+
|
| 168 |
+
### [3:00 — 4:30] Manual Play — Good vs Bad Decision
|
| 169 |
+
- Assign T3 (frontend) → dev1 (backend): *"Wrong skill — negative reward, task rejected"*
|
| 170 |
+
- Assign T3 (frontend) → dev2 (frontend): *"Correct match — positive reward, task starts"*
|
| 171 |
+
- Point to reward chart: *"See the reward signal? This is exactly what the AI learns from."*
|
| 172 |
+
|
| 173 |
+
### [4:30 — 6:00] Auto-Assign
|
| 174 |
+
- Click **🤖 Auto-Assign All**
|
| 175 |
+
- *"Rule-based auto-assign picks the best skill match for every task."*
|
| 176 |
+
- Click **▶️ Take Action** (skip) a few times
|
| 177 |
+
- *"Each day the sprint advances, tasks progress, deadlines approach."*
|
| 178 |
+
|
| 179 |
+
### [6:00 — 7:30] Hard Sprint
|
| 180 |
+
- Reset with `hard_sprint`
|
| 181 |
+
- *"12 tasks, 5 developers, random events — developers go sick, urgent bugs appear mid-sprint."*
|
| 182 |
+
- Auto-assign, then skip a few times
|
| 183 |
+
- When a 🚨 event fires: *"There — urgent bug on day 4. A trained agent needs to react and reassign resources."*
|
| 184 |
+
|
| 185 |
+
### [7:30 — 9:00] Inference Output
|
| 186 |
+
- Show terminal with inference.py output
|
| 187 |
+
- *"This is our Llama 3.1 baseline running against all 3 scenarios automatically."*
|
| 188 |
+
- Point to structured output: *"`[START]` `[STEP]` `[END]` — machine-parseable format the judges require."*
|
| 189 |
+
- Point to scores: *"Easy sprint perfect score of 1.0 — validates the environment works. Hard sprint 0.0 — shows it genuinely challenges frontier models."*
|
| 190 |
+
|
| 191 |
+
### [9:00 — 10:00] Technical Highlights
|
| 192 |
+
> "What makes this submission stand out:"
|
| 193 |
+
|
| 194 |
+
- **Real-world domain** — not CartPole, not a game — actual software engineering
|
| 195 |
+
- **External data file** — `data/sprint_data.json` — anyone can plug in their own team
|
| 196 |
+
- **Typed Python client** — `client.py` makes it plug-and-play with TRL, Stable-Baselines3
|
| 197 |
+
- **OpenEnv compliant** — passes all 3 validation checks
|
| 198 |
+
- **Shaped rewards** — signal at every step, enables efficient RL training
|
| 199 |
+
|
| 200 |
+
---
|
| 201 |
+
|
| 202 |
+
## 🐛 Common Issues & Fixes
|
| 203 |
+
|
| 204 |
+
| Problem | Cause | Fix |
|
| 205 |
+
|---------|-------|-----|
|
| 206 |
+
| `ModuleNotFoundError` | Missing package | `pip install -r requirements.txt` |
|
| 207 |
+
| Port 7860 in use | Other process | Kill it or change port in ui.py |
|
| 208 |
+
| `401 Unauthorized` in inference | Bad HF token | Regenerate at hf.co/settings/tokens |
|
| 209 |
+
| Validate step 3 fails | openenv not in PATH | Activate venv before running script |
|
| 210 |
+
| Tasks not progressing | No devs assigned | Auto-Assign or assign manually |
|
| 211 |
+
| Score always 0.0 | All tasks missed | Assign earlier, prioritize urgent tasks |
|
| 212 |
+
| Docker timeout | venv in context | Check `.dockerignore` has `venv/` |
|
| 213 |
+
|
| 214 |
+
---
|
| 215 |
+
|
| 216 |
+
## 🔬 Is This a Real RL Environment?
|
| 217 |
+
|
| 218 |
+
| Criterion | Our Environment |
|
| 219 |
+
|-----------|----------------|
|
| 220 |
+
| Sequential decisions | ✅ Each day depends on previous assignments |
|
| 221 |
+
| Large state space | ✅ Tasks × Developers × Day — combinatorial |
|
| 222 |
+
| Non-trivial action space | ✅ 5 types × 12 tasks × 5 devs |
|
| 223 |
+
| Shaped reward | ✅ Signal every step, not just episode end |
|
| 224 |
+
| Stochastic transitions | ✅ Random dev absences, mid-sprint bugs |
|
| 225 |
+
| Clean episode boundaries | ✅ reset() gives fresh state every time |
|
| 226 |
+
| Partial observability | ✅ Agent can't predict future events |
|
| 227 |
+
| Trainable with RL | ✅ GRPO / PPO / any policy gradient |
|
| 228 |
+
|
| 229 |
+
**A trained RL agent (not zero-shot LLM) should score 0.7+ on medium and 0.4+ on hard** after sufficient training — currently it scores 0.42 and 0.0 with baseline Llama 3.1. That's the gap RL training is meant to close.
|
| 230 |
+
|
| 231 |
+
---
|
| 232 |
+
|
| 233 |
+
## 📊 Baseline Score Interpretation
|
| 234 |
+
|
| 235 |
+
```
|
| 236 |
+
easy_sprint: 1.00 ← LLM figured out skill matching perfectly
|
| 237 |
+
medium_sprint: 0.42 ← Partial success, random events hurt performance
|
| 238 |
+
hard_sprint: 0.00 ← Cascade failures overwhelm baseline LLM
|
| 239 |
+
average: 0.47
|
| 240 |
+
```
|
| 241 |
+
|
| 242 |
+
These scores are intentional and show the difficulty curve works correctly:
|
| 243 |
+
- Easy solvable by any agent → environment is correct
|
| 244 |
+
- Medium shows partial success → reward shaping works
|
| 245 |
+
- Hard challenges frontier models → difficulty is genuine
|
README.md
CHANGED
|
@@ -1,73 +1,304 @@
|
|
| 1 |
---
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
-
|
| 9 |
-
- transformers
|
| 10 |
-
- trl
|
| 11 |
-
- unsloth
|
| 12 |
-
licence: license
|
| 13 |
-
pipeline_tag: text-generation
|
| 14 |
---
|
| 15 |
|
| 16 |
-
#
|
| 17 |
|
| 18 |
-
|
| 19 |
-
It has been trained using [TRL](https://github.com/huggingface/trl).
|
| 20 |
|
| 21 |
-
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
-
question = "If you had a time machine, but could only go to the past or the future once and never return, which would you choose and why?"
|
| 27 |
-
generator = pipeline("text-generation", model="None", device="cuda")
|
| 28 |
-
output = generator([{"role": "user", "content": question}], max_new_tokens=128, return_full_text=False)[0]
|
| 29 |
-
print(output["generated_text"])
|
| 30 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
-
##
|
| 33 |
|
| 34 |
-
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
-
|
| 38 |
|
| 39 |
-
##
|
| 40 |
|
| 41 |
-
|
| 42 |
-
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
|
| 48 |
-
##
|
| 49 |
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
```
|
| 61 |
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
}
|
| 73 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: AI Sprint Manager
|
| 3 |
+
emoji: 🤖
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
tags: [openenv, reinforcement-learning, agile, sprint-management, fastapi, gradio]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# 🤖 AI Sprint Manager — OpenEnv
|
| 12 |
|
| 13 |
+
> **A reinforcement learning environment where an AI agent acts as a Tech Lead managing agile software sprints.**
|
|
|
|
| 14 |
|
| 15 |
+
---
|
| 16 |
|
| 17 |
+
## 🎯 What Is This?
|
| 18 |
+
|
| 19 |
+
Modern software teams spend enormous time on sprint planning decisions:
|
| 20 |
+
- Which developer gets which task?
|
| 21 |
+
- What do you do when someone goes sick mid-sprint?
|
| 22 |
+
- How do you handle an urgent production bug that appears on day 5?
|
| 23 |
+
|
| 24 |
+
This environment simulates these real-world decisions so an AI agent can **learn optimal sprint management strategies** through reinforcement learning.
|
| 25 |
+
|
| 26 |
+
The agent plays the role of a Tech Lead. Each step it observes the full sprint state (tasks, developers, workloads, deadlines) and takes an action. The environment responds with a reward signal that guides learning.
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
## 🏗️ Architecture
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
```
|
| 33 |
+
┌─────────────────────────────────────────┐
|
| 34 |
+
│ RL Agent / LLM / Training Loop │
|
| 35 |
+
│ (uses client.py) │
|
| 36 |
+
└──────────────────┬──────────────────────┘
|
| 37 |
+
│ HTTP reset / step / state
|
| 38 |
+
▼
|
| 39 |
+
┌─────────────────────────────────────────┐
|
| 40 |
+
│ FastAPI Server (port 7860) │
|
| 41 |
+
│ /reset /step /state /health │
|
| 42 |
+
└──────────────────┬──────────────────────┘
|
| 43 |
+
│
|
| 44 |
+
▼
|
| 45 |
+
┌─────────────────────────────────────────┐
|
| 46 |
+
│ Sprint Environment (core logic) │
|
| 47 |
+
│ • Task/developer simulation │
|
| 48 |
+
│ • Reward calculation │
|
| 49 |
+
│ • Random events (bugs, absences) │
|
| 50 |
+
│ • 3 graders: easy / medium / hard │
|
| 51 |
+
└──────────────────┬──────────────────────┘
|
| 52 |
+
│ data loaded from
|
| 53 |
+
▼
|
| 54 |
+
┌─────────────────────────────────────────┐
|
| 55 |
+
│ data/sprint_data.json │
|
| 56 |
+
│ (customizable — bring your own data!) │
|
| 57 |
+
└─────────────────────────────────────────┘
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
|
| 62 |
+
## 🎮 Live Demo
|
| 63 |
|
|
|
|
| 64 |
|
| 65 |
+
1. Select a sprint scenario (easy / medium / hard)
|
| 66 |
+
2. Click **🔄 Reset Sprint**
|
| 67 |
+
3. Use the **Skill → Dev Guide** to assign tasks correctly
|
| 68 |
+
4. Or click **🤖 Auto-Assign All** to let the system decide
|
| 69 |
+
5. Watch the reward history and task status update in real time
|
| 70 |
|
| 71 |
+
---
|
| 72 |
|
| 73 |
+
## 📐 Action Space
|
| 74 |
|
| 75 |
+
| Field | Type | Values |
|
| 76 |
+
|---|---|---|
|
| 77 |
+
| `action_type` | string | `assign`, `reassign`, `reprioritize`, `unblock`, `skip` |
|
| 78 |
+
| `task_id` | string | Task ID e.g. `"T1"`, `"T6"` |
|
| 79 |
+
| `dev_id` | string | Developer ID e.g. `"dev1"`, `"dev3"` |
|
| 80 |
+
| `new_priority` | int | 1–5 (1=highest, for reprioritize only) |
|
| 81 |
|
| 82 |
+
## 📊 Observation Space
|
| 83 |
|
| 84 |
+
| Field | Type | Description |
|
| 85 |
+
|---|---|---|
|
| 86 |
+
| `current_day` | int | Day in sprint (1–10) |
|
| 87 |
+
| `sprint_length` | int | Total sprint length |
|
| 88 |
+
| `developers` | list | Each dev's skill, capacity, load, tasks, availability |
|
| 89 |
+
| `tasks` | list | Each task's type, priority, effort, deadline, status, progress |
|
| 90 |
+
| `reward` | float | Step reward |
|
| 91 |
+
| `cumulative_reward` | float | Total reward this episode |
|
| 92 |
+
| `tasks_completed/missed/in_progress/backlog` | int | Status counts |
|
| 93 |
+
| `workload_balance_score` | float | 0=unbalanced, 1=perfect |
|
| 94 |
+
| `events` | list | Events that just happened (completions, misses, absences) |
|
| 95 |
+
| `done` | bool | Whether episode is complete |
|
| 96 |
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
## 🎯 Tasks (Scenarios)
|
| 100 |
+
|
| 101 |
+
| ID | Difficulty | Devs | Tasks | Random Events |
|
| 102 |
+
|---|---|---|---|---|
|
| 103 |
+
| `easy_sprint` | 🟢 Easy | 3 | 5 | None |
|
| 104 |
+
| `medium_sprint` | 🟡 Medium | 4 | 8 | Dev absences, bugs expire |
|
| 105 |
+
| `hard_sprint` | 🔴 Hard | 5 | 12 | Urgent bugs mid-sprint, cascading failures |
|
| 106 |
+
|
| 107 |
+
### Baseline Scores (meta-llama/Llama-3.1-8B-Instruct)
|
| 108 |
+
|
| 109 |
+
| Task | Score |
|
| 110 |
+
|---|---|
|
| 111 |
+
| `easy_sprint` | 0.01 |
|
| 112 |
+
| `medium_sprint` | 0.46 ████████ |
|
| 113 |
+
| `hard_sprint` | 0.01 |
|
| 114 |
+
| **Average** | **0.16** |
|
| 115 |
+
|
| 116 |
+
---
|
| 117 |
|
| 118 |
+
## 💰 Reward Function
|
| 119 |
+
|
| 120 |
+
| Event | Reward |
|
| 121 |
+
|---|---|
|
| 122 |
+
| Assign task (skill match) | +0.8 to +1.3 |
|
| 123 |
+
| Assign task (skill mismatch penalty) | +0.1 to +0.6 |
|
| 124 |
+
| Wrong skill / over capacity | -0.15 |
|
| 125 |
+
| Task completed on time | +0.5 to +2.5 |
|
| 126 |
+
| Task completed late | +0.1 |
|
| 127 |
+
| Task missed deadline | -0.3 to -1.5 |
|
| 128 |
+
| Urgent bug missed | -0.25 extra |
|
| 129 |
+
| Skip (no action) | -0.05 |
|
| 130 |
+
| Final score bonus | score × 10.0 |
|
| 131 |
+
|
| 132 |
+
---
|
| 133 |
+
|
| 134 |
+
## 🔌 API Reference
|
| 135 |
+
|
| 136 |
+
```bash
|
| 137 |
+
# Health check
|
| 138 |
+
GET /health → {"status": "ok", "env": "ai-sprint-manager"}
|
| 139 |
+
|
| 140 |
+
# Start new episode
|
| 141 |
+
POST /reset
|
| 142 |
+
Body: {"task_name": "easy_sprint", "seed": 42}
|
| 143 |
+
|
| 144 |
+
# Take one action
|
| 145 |
+
POST /step
|
| 146 |
+
Body: {"action": {"action_type": "assign", "task_id": "T1", "dev_id": "dev1"}}
|
| 147 |
+
|
| 148 |
+
# Get full state
|
| 149 |
+
GET /state
|
| 150 |
+
|
| 151 |
+
# List scenarios
|
| 152 |
+
GET /tasks
|
| 153 |
```
|
| 154 |
|
| 155 |
+
---
|
| 156 |
+
|
| 157 |
+
## 🐍 Python Client Usage
|
| 158 |
+
|
| 159 |
+
```python
|
| 160 |
+
from client import SprintEnvClient
|
| 161 |
+
from sprint_env.models import SprintAction
|
| 162 |
+
|
| 163 |
+
# Connect to live Space
|
| 164 |
+
with SprintEnvClient(base_url="https://sejal-k-ai-sprint-manager.hf.space") as env:
|
| 165 |
+
# Reset
|
| 166 |
+
obs = env.reset(task_name="medium_sprint", seed=42)
|
| 167 |
+
|
| 168 |
+
# Agent loop
|
| 169 |
+
while not obs["done"]:
|
| 170 |
+
action = SprintAction(
|
| 171 |
+
action_type="assign",
|
| 172 |
+
task_id="T1",
|
| 173 |
+
dev_id="dev1",
|
| 174 |
+
)
|
| 175 |
+
result = env.step(action)
|
| 176 |
+
print(result) # StepResult(reward=+1.20, done=False, day=2, completed=0)
|
| 177 |
+
obs = result.observation
|
| 178 |
+
```
|
| 179 |
+
|
| 180 |
+
---
|
| 181 |
+
|
| 182 |
+
## 🗂️ Project Structure
|
| 183 |
+
|
| 184 |
+
```
|
| 185 |
+
ai-sprint-manager-openenv/
|
| 186 |
+
├── openenv.yaml # OpenEnv spec metadata
|
| 187 |
+
├── pyproject.toml # Project dependencies
|
| 188 |
+
├── Dockerfile # Container definition
|
| 189 |
+
├── requirements.txt # Python dependencies
|
| 190 |
+
├── inference.py # Baseline LLM agent script
|
| 191 |
+
├── client.py # Typed Python client (for RL training)
|
| 192 |
+
├── ui.py # Gradio UI + FastAPI combined server
|
| 193 |
+
├── start.sh # Container startup script
|
| 194 |
+
│
|
| 195 |
+
├── data/
|
| 196 |
+
│ └── sprint_data.json # All scenario data (customizable!)
|
| 197 |
+
│
|
| 198 |
+
├── sprint_env/
|
| 199 |
+
│ ├── __init__.py
|
| 200 |
+
│ ├── models.py # Pydantic Action/Observation/State
|
| 201 |
+
│ ├── tasks.py # Task & Developer dataclasses
|
| 202 |
+
│ ├── environment.py # Core RL environment logic
|
| 203 |
+
│ ├── graders.py # Scoring functions (easy/medium/hard)
|
| 204 |
+
│ └── data_loader.py # JSON data loader with caching
|
| 205 |
+
│
|
| 206 |
+
└── server/
|
| 207 |
+
├── __init__.py
|
| 208 |
+
└── app.py # OpenEnv-compliant FastAPI server entry
|
| 209 |
+
```
|
| 210 |
+
|
| 211 |
+
---
|
| 212 |
+
|
| 213 |
+
## 🔧 Bring Your Own Data
|
| 214 |
+
|
| 215 |
+
Don't want to use our sample scenarios? Edit `data/sprint_data.json`:
|
| 216 |
+
|
| 217 |
+
```json
|
| 218 |
+
{
|
| 219 |
+
"scenarios": {
|
| 220 |
+
"my_custom_sprint": {
|
| 221 |
+
"description": "My team's actual sprint",
|
| 222 |
+
"difficulty": "medium",
|
| 223 |
+
"developers": [
|
| 224 |
+
{"id": "dev1", "name": "Your Name", "skill": "backend", "capacity": 5, "productivity": 1.0}
|
| 225 |
+
],
|
| 226 |
+
"tasks": [
|
| 227 |
+
{"id": "T1", "name": "Your Task", "task_type": "feature", "priority": 1,
|
| 228 |
+
"effort": 3, "deadline": 5, "required_skill": "backend"}
|
| 229 |
+
]
|
| 230 |
+
}
|
| 231 |
+
}
|
| 232 |
}
|
| 233 |
+
```
|
| 234 |
+
|
| 235 |
+
Or point to your own file:
|
| 236 |
+
```bash
|
| 237 |
+
export SPRINT_DATA_PATH=/path/to/your/data.json
|
| 238 |
+
python ui.py
|
| 239 |
+
```
|
| 240 |
+
|
| 241 |
+
---
|
| 242 |
+
|
| 243 |
+
## 🚀 Setup & Run
|
| 244 |
+
|
| 245 |
+
```bash
|
| 246 |
+
# Clone
|
| 247 |
+
git clone https://github.com/sejalsksagar/ai-sprint-manager-openenv.git
|
| 248 |
+
cd ai-sprint-manager-openenv
|
| 249 |
+
|
| 250 |
+
# Install
|
| 251 |
+
python -m venv venv
|
| 252 |
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
| 253 |
+
pip install -r requirements.txt
|
| 254 |
+
|
| 255 |
+
# Configure
|
| 256 |
+
cp .env.example .env
|
| 257 |
+
# Edit .env with your HF_TOKEN
|
| 258 |
+
|
| 259 |
+
# Run locally
|
| 260 |
+
python ui.py
|
| 261 |
+
# Open http://localhost:7860
|
| 262 |
+
|
| 263 |
+
# Docker
|
| 264 |
+
docker build -t ai-sprint-manager .
|
| 265 |
+
docker run -p 7860:7860 ai-sprint-manager
|
| 266 |
+
|
| 267 |
+
# Run inference
|
| 268 |
+
python inference.py
|
| 269 |
+
```
|
| 270 |
+
|
| 271 |
+
---
|
| 272 |
+
|
| 273 |
+
## 🤖 Can an RL Agent Learn From This?
|
| 274 |
+
|
| 275 |
+
Yes. The environment is designed for policy gradient training (GRPO, PPO):
|
| 276 |
+
|
| 277 |
+
```python
|
| 278 |
+
# Example training loop skeleton (TRL/GRPO compatible)
|
| 279 |
+
from client import SprintEnvClient
|
| 280 |
+
from sprint_env.models import SprintAction
|
| 281 |
+
|
| 282 |
+
env = SprintEnvClient(base_url="http://localhost:7860")
|
| 283 |
+
|
| 284 |
+
for episode in range(1000):
|
| 285 |
+
obs = env.reset(task_name="medium_sprint")
|
| 286 |
+
trajectory = []
|
| 287 |
+
|
| 288 |
+
while not obs["done"]:
|
| 289 |
+
action = policy.sample(obs) # your policy here
|
| 290 |
+
result = env.step(action)
|
| 291 |
+
trajectory.append((obs, action, result.reward))
|
| 292 |
+
obs = result.observation
|
| 293 |
+
|
| 294 |
+
policy.update(trajectory) # GRPO/PPO update
|
| 295 |
+
```
|
| 296 |
+
|
| 297 |
+
The shaped reward function provides learning signal at every step — not just at episode end — which is critical for efficient RL training.
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
---
|
| 301 |
+
|
| 302 |
+
## 👥 Team
|
| 303 |
+
|
| 304 |
+
Built for the **Meta PyTorch OpenEnv Hackathon x SST | India AI Hackathon '26**
|
SETUP.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🛠️ Team Setup Guide — AI Sprint Manager OpenEnv
|
| 2 |
+
|
| 3 |
+
Complete setup instructions for Windows and Mac teammates.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 📋 Prerequisites
|
| 8 |
+
|
| 9 |
+
### Windows
|
| 10 |
+
|
| 11 |
+
1. **Install Python 3.11**
|
| 12 |
+
- Download from https://www.python.org/downloads/
|
| 13 |
+
- ✅ Check **"Add Python to PATH"** during install
|
| 14 |
+
- Verify: open PowerShell and run `python --version`
|
| 15 |
+
|
| 16 |
+
2. **Install Git**
|
| 17 |
+
- Download from https://git-scm.com/download/win
|
| 18 |
+
- Use default options during install
|
| 19 |
+
- Verify: `git --version`
|
| 20 |
+
|
| 21 |
+
3. **Install Docker Desktop**
|
| 22 |
+
- Download from https://www.docker.com/products/docker-desktop/
|
| 23 |
+
- Requires Windows 10/11 with WSL2 enabled
|
| 24 |
+
- After install, open Docker Desktop and wait for it to start
|
| 25 |
+
- Verify: `docker --version`
|
| 26 |
+
|
| 27 |
+
4. **Install VS Code** (recommended)
|
| 28 |
+
- Download from https://code.visualstudio.com/
|
| 29 |
+
|
| 30 |
+
### Mac
|
| 31 |
+
|
| 32 |
+
1. **Install Homebrew** (package manager)
|
| 33 |
+
```bash
|
| 34 |
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
2. **Install Python 3.11**
|
| 38 |
+
```bash
|
| 39 |
+
brew install python@3.11
|
| 40 |
+
```
|
| 41 |
+
Verify: `python3.11 --version`
|
| 42 |
+
|
| 43 |
+
3. **Install Git**
|
| 44 |
+
```bash
|
| 45 |
+
brew install git
|
| 46 |
+
```
|
| 47 |
+
Verify: `git --version`
|
| 48 |
+
|
| 49 |
+
4. **Install Docker Desktop**
|
| 50 |
+
- Download from https://www.docker.com/products/docker-desktop/
|
| 51 |
+
- Choose Apple Silicon or Intel depending on your Mac
|
| 52 |
+
- Open Docker Desktop and wait for it to start
|
| 53 |
+
- Verify: `docker --version`
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## 📥 Clone the GitHub Repo
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
# Both Windows (PowerShell) and Mac (Terminal)
|
| 61 |
+
git clone https://github.com/YOUR_GITHUB_USERNAME/ai-sprint-manager-openenv.git
|
| 62 |
+
cd ai-sprint-manager-openenv
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
## 🌿 Create Your Own Branch
|
| 68 |
+
|
| 69 |
+
```bash
|
| 70 |
+
# Replace "yourname" with your actual name
|
| 71 |
+
git checkout -b feature/yourname-improvements
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
Verify you're on your branch:
|
| 75 |
+
```bash
|
| 76 |
+
git branch
|
| 77 |
+
# Should show * feature/yourname-improvements
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
|
| 82 |
+
## 🐍 Set Up Virtual Environment
|
| 83 |
+
|
| 84 |
+
### Windows
|
| 85 |
+
```powershell
|
| 86 |
+
python -m venv venv
|
| 87 |
+
venv\Scripts\activate
|
| 88 |
+
pip install --upgrade pip
|
| 89 |
+
pip install -r requirements.txt
|
| 90 |
+
pip install python-dotenv
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
### Mac
|
| 94 |
+
```bash
|
| 95 |
+
python3 -m venv venv
|
| 96 |
+
source venv/bin/activate
|
| 97 |
+
pip install --upgrade pip
|
| 98 |
+
pip install -r requirements.txt
|
| 99 |
+
pip install python-dotenv
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
---
|
| 103 |
+
|
| 104 |
+
## ⚙️ Create Your `.env` File
|
| 105 |
+
|
| 106 |
+
Create a file called `.env` in the project root (never commit this!):
|
| 107 |
+
|
| 108 |
+
```
|
| 109 |
+
HF_TOKEN=hf_your_token_here
|
| 110 |
+
API_BASE_URL=https://router.huggingface.co/v1
|
| 111 |
+
MODEL_NAME=meta-llama/Llama-3.1-8B-Instruct
|
| 112 |
+
ENV_BASE_URL=http://localhost:7860
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
We'll get the HF_TOKEN in the next section.
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## 🤗 Create Hugging Face Account & Token
|
| 120 |
+
|
| 121 |
+
1. Go to https://huggingface.co and sign up
|
| 122 |
+
2. Go to https://huggingface.co/settings/tokens
|
| 123 |
+
3. Click **New token**
|
| 124 |
+
4. Name: `sprint-manager-token`
|
| 125 |
+
5. Type: **Read**
|
| 126 |
+
6. Click **Create token** and copy it
|
| 127 |
+
7. Paste it into your `.env` file as `HF_TOKEN=hf_...`
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## 🚀 Run Locally & Test
|
| 132 |
+
|
| 133 |
+
### Terminal 1 — Start the API + UI server
|
| 134 |
+
#### Windows
|
| 135 |
+
```powershell
|
| 136 |
+
python ui.py
|
| 137 |
+
```
|
| 138 |
+
#### Mac
|
| 139 |
+
```bash
|
| 140 |
+
python ui.py
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
### Terminal 2 — Test the API
|
| 144 |
+
#### Windows
|
| 145 |
+
```powershell
|
| 146 |
+
Invoke-WebRequest -Uri http://localhost:7860/health -Method GET
|
| 147 |
+
```
|
| 148 |
+
#### Mac
|
| 149 |
+
```bash
|
| 150 |
+
curl http://localhost:7860/health
|
| 151 |
+
# Expected: {"status":"ok","env":"ai-sprint-manager"}
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
### Test via Browser
|
| 155 |
+
Open http://localhost:7860 — you should see the Gradio sprint board UI.
|
| 156 |
+
|
| 157 |
+
Try:
|
| 158 |
+
1. Select `easy_sprint`, click **🔄 Reset Sprint**
|
| 159 |
+
2. Set Action=`assign`, Task ID=`T1`, Dev ID=`dev1`
|
| 160 |
+
3. Click **▶️ Take Action**
|
| 161 |
+
4. You should see a positive reward and T1 assigned to Alice
|
| 162 |
+
|
| 163 |
+
### Run Inference Locally
|
| 164 |
+
```bash
|
| 165 |
+
# Make sure .env file has your HF_TOKEN
|
| 166 |
+
python inference.py
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
---
|
| 170 |
+
|
| 171 |
+
## 🐳 Test with Docker
|
| 172 |
+
|
| 173 |
+
```bash
|
| 174 |
+
docker build -t ai-sprint-manager .
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
### Windows
|
| 178 |
+
```powershell
|
| 179 |
+
docker run -p 7860:7860 ai-sprint-manager
|
| 180 |
+
```
|
| 181 |
+
### Mac
|
| 182 |
+
```bash
|
| 183 |
+
docker run -p 7860:7860 ai-sprint-manager
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
Open http://localhost:7860 to verify.
|
| 187 |
+
|
| 188 |
+
---
|
| 189 |
+
|
| 190 |
+
## ☁️ Create Your Own HF Space & Deploy
|
| 191 |
+
|
| 192 |
+
1. Go to https://huggingface.co/new-space
|
| 193 |
+
2. Fill in:
|
| 194 |
+
- **Space name:** `ai-sprint-manager-yourname`
|
| 195 |
+
- **SDK:** Docker
|
| 196 |
+
- **Visibility:** Public
|
| 197 |
+
3. Click **Create Space**
|
| 198 |
+
|
| 199 |
+
4. Add your HF token as a Secret:
|
| 200 |
+
- Go to Space → **Settings** → **Variables and secrets**
|
| 201 |
+
- Add secret: Name=`HF_TOKEN`, Value=your token
|
| 202 |
+
|
| 203 |
+
5. Add HF as a git remote and push your branch:
|
| 204 |
+
```bash
|
| 205 |
+
git remote add myspace https://huggingface.co/spaces/YOUR_HF_USERNAME/ai-sprint-manager-yourname
|
| 206 |
+
git push myspace feature/yourname-improvements:main
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
6. Wait 2-3 minutes for build. Test your live Space:
|
| 210 |
+
|
| 211 |
+
### Windows
|
| 212 |
+
```powershell
|
| 213 |
+
Invoke-WebRequest -Uri "https://YOUR_HF_USERNAME-ai-sprint-manager-yourname.hf.space/health" -Method GET
|
| 214 |
+
```
|
| 215 |
+
### Mac
|
| 216 |
+
```bash
|
| 217 |
+
curl https://YOUR_HF_USERNAME-ai-sprint-manager-yourname.hf.space/health
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
---
|
| 221 |
+
|
| 222 |
+
## 🔧 Run OpenEnv Validation
|
| 223 |
+
|
| 224 |
+
```bash
|
| 225 |
+
pip install openenv-core uv
|
| 226 |
+
uv lock
|
| 227 |
+
openenv validate
|
| 228 |
+
# Expected: [OK] ai-sprint-manager: Ready for multi-mode deployment
|
| 229 |
+
```
|
| 230 |
+
|
| 231 |
+
---
|
| 232 |
+
|
| 233 |
+
## 💡 Suggested Improvements for Teammates
|
| 234 |
+
|
| 235 |
+
### 🟢 Easy (Good for getting started)
|
| 236 |
+
|
| 237 |
+
1. **Better skill matching UI** — Show a skill-to-dev mapping table in the Gradio UI so users know which dev to pick
|
| 238 |
+
2. **Sprint history chart** — Add a reward-over-time line chart using `gr.Plot`
|
| 239 |
+
3. **Add more tasks** — Expand `tasks.py` with more realistic task names and types
|
| 240 |
+
4. **Auto-assign button** — Add a Gradio button that automatically assigns all backlog tasks using a simple rule (best skill match)
|
| 241 |
+
|
| 242 |
+
### 🟡 Medium
|
| 243 |
+
|
| 244 |
+
5. **Session isolation** — Currently all users share one env instance. Add session IDs so multiple users can use the UI simultaneously
|
| 245 |
+
6. **Sprint replay** — Save the full episode history and add a "replay" feature to the UI
|
| 246 |
+
7. **Better reward visualization** — Add a bar chart showing per-task completion status
|
| 247 |
+
8. **Configurable sprint length** — Let users set sprint length (5/10/15 days) in the UI
|
| 248 |
+
|
| 249 |
+
### 🔴 Hard (Advanced)
|
| 250 |
+
|
| 251 |
+
9. **Real RL training loop** — Add a `train.py` script using Stable-Baselines3 or TRL+GRPO to actually train a policy
|
| 252 |
+
10. **Multi-agent support** — Let multiple AI agents collaborate on sprint management
|
| 253 |
+
11. **WebSocket support** — Upgrade from HTTP to WebSocket for real-time updates per OpenEnv spec
|
| 254 |
+
12. **Custom sprint builder** — Let users define their own tasks and team in the UI
|
| 255 |
+
|
| 256 |
+
---
|
| 257 |
+
|
| 258 |
+
## 📤 Submit Your Changes
|
| 259 |
+
|
| 260 |
+
```bash
|
| 261 |
+
git add .
|
| 262 |
+
git commit -m "feat: your improvement description"
|
| 263 |
+
git push origin feature/yourname-improvements
|
| 264 |
+
```
|
| 265 |
+
|
| 266 |
+
Then open a Pull Request on GitHub to merge into `main`.
|
client.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Sprint Manager — OpenEnv Client
|
| 3 |
+
|
| 4 |
+
This is what RL researchers import in their training code.
|
| 5 |
+
It provides a clean typed interface to the environment server.
|
| 6 |
+
|
| 7 |
+
Usage:
|
| 8 |
+
import requests
|
| 9 |
+
from client import SprintEnvClient, SprintAction
|
| 10 |
+
|
| 11 |
+
client = SprintEnvClient(base_url="https://sejal-k-ai-sprint-manager.hf.space")
|
| 12 |
+
obs = client.reset(task_name="easy_sprint")
|
| 13 |
+
result = client.step(SprintAction(action_type="assign", task_id="T1", dev_id="dev1"))
|
| 14 |
+
state = client.state()
|
| 15 |
+
client.close()
|
| 16 |
+
|
| 17 |
+
# Or as context manager:
|
| 18 |
+
with SprintEnvClient(base_url="http://localhost:7860") as client:
|
| 19 |
+
obs = client.reset(task_name="medium_sprint", seed=42)
|
| 20 |
+
while not obs["done"]:
|
| 21 |
+
result = client.step(SprintAction(action_type="skip"))
|
| 22 |
+
obs = result["observation"]
|
| 23 |
+
"""
|
| 24 |
+
from __future__ import annotations
|
| 25 |
+
import requests
|
| 26 |
+
from typing import Optional, Any
|
| 27 |
+
from sprint_env.models import SprintAction
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class StepResult:
|
| 31 |
+
"""Typed result from a step() call."""
|
| 32 |
+
def __init__(self, payload: dict):
|
| 33 |
+
self.observation: dict = payload["observation"]
|
| 34 |
+
self.reward: float = payload["reward"]
|
| 35 |
+
self.done: bool = payload["done"]
|
| 36 |
+
self.info: dict = payload.get("info", {})
|
| 37 |
+
|
| 38 |
+
def __repr__(self):
|
| 39 |
+
return (
|
| 40 |
+
f"StepResult(reward={self.reward:+.2f}, done={self.done}, "
|
| 41 |
+
f"day={self.observation.get('current_day')}, "
|
| 42 |
+
f"completed={self.observation.get('tasks_completed')})"
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class SprintEnvClient:
|
| 47 |
+
"""
|
| 48 |
+
HTTP client for the Sprint Manager OpenEnv environment.
|
| 49 |
+
|
| 50 |
+
Wraps the REST API into a clean Python interface.
|
| 51 |
+
Use this in RL training loops, notebooks, or evaluation scripts.
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
def __init__(self, base_url: str = "http://localhost:7860", timeout: int = 30):
|
| 55 |
+
self.base_url = base_url.rstrip("/")
|
| 56 |
+
self.timeout = timeout
|
| 57 |
+
self._session = requests.Session()
|
| 58 |
+
|
| 59 |
+
def reset(
|
| 60 |
+
self,
|
| 61 |
+
task_name: str = "easy_sprint",
|
| 62 |
+
seed: Optional[int] = None,
|
| 63 |
+
episode_id: Optional[str] = None,
|
| 64 |
+
) -> dict:
|
| 65 |
+
"""
|
| 66 |
+
Reset the environment and return initial observation.
|
| 67 |
+
|
| 68 |
+
Args:
|
| 69 |
+
task_name: One of "easy_sprint", "medium_sprint", "hard_sprint"
|
| 70 |
+
seed: Random seed for reproducibility
|
| 71 |
+
episode_id: Optional episode identifier
|
| 72 |
+
|
| 73 |
+
Returns:
|
| 74 |
+
Observation dict
|
| 75 |
+
"""
|
| 76 |
+
payload = {"task_name": task_name}
|
| 77 |
+
if seed is not None:
|
| 78 |
+
payload["seed"] = seed
|
| 79 |
+
if episode_id is not None:
|
| 80 |
+
payload["episode_id"] = episode_id
|
| 81 |
+
|
| 82 |
+
resp = self._session.post(
|
| 83 |
+
f"{self.base_url}/reset", json=payload, timeout=self.timeout
|
| 84 |
+
)
|
| 85 |
+
resp.raise_for_status()
|
| 86 |
+
return resp.json()
|
| 87 |
+
|
| 88 |
+
def step(self, action: SprintAction) -> StepResult:
|
| 89 |
+
"""
|
| 90 |
+
Take one action and advance the sprint by one day.
|
| 91 |
+
|
| 92 |
+
Args:
|
| 93 |
+
action: SprintAction with action_type, task_id, dev_id, new_priority
|
| 94 |
+
|
| 95 |
+
Returns:
|
| 96 |
+
StepResult with observation, reward, done, info
|
| 97 |
+
"""
|
| 98 |
+
payload = {"action": action.model_dump()}
|
| 99 |
+
resp = self._session.post(
|
| 100 |
+
f"{self.base_url}/step", json=payload, timeout=self.timeout
|
| 101 |
+
)
|
| 102 |
+
resp.raise_for_status()
|
| 103 |
+
return StepResult(resp.json())
|
| 104 |
+
|
| 105 |
+
def state(self) -> dict:
|
| 106 |
+
"""Return the full current environment state."""
|
| 107 |
+
resp = self._session.get(f"{self.base_url}/state", timeout=self.timeout)
|
| 108 |
+
resp.raise_for_status()
|
| 109 |
+
return resp.json()
|
| 110 |
+
|
| 111 |
+
def health(self) -> dict:
|
| 112 |
+
"""Check server health."""
|
| 113 |
+
resp = self._session.get(f"{self.base_url}/health", timeout=self.timeout)
|
| 114 |
+
resp.raise_for_status()
|
| 115 |
+
return resp.json()
|
| 116 |
+
|
| 117 |
+
def list_tasks(self) -> list[dict]:
|
| 118 |
+
"""List all available sprint scenarios."""
|
| 119 |
+
resp = self._session.get(f"{self.base_url}/tasks", timeout=self.timeout)
|
| 120 |
+
resp.raise_for_status()
|
| 121 |
+
return resp.json()["tasks"]
|
| 122 |
+
|
| 123 |
+
def close(self):
|
| 124 |
+
"""Close the HTTP session."""
|
| 125 |
+
self._session.close()
|
| 126 |
+
|
| 127 |
+
def __enter__(self):
|
| 128 |
+
return self
|
| 129 |
+
|
| 130 |
+
def __exit__(self, *args):
|
| 131 |
+
self.close()
|
| 132 |
+
|
| 133 |
+
def __repr__(self):
|
| 134 |
+
return f"SprintEnvClient(base_url='{self.base_url}')"
|
data/project_data.json
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenarios": {
|
| 3 |
+
"project_easy": {
|
| 4 |
+
"description": "Small stable team, clear backlog, no cascade failures. Good for baseline LLM testing on multi-sprint horizon.",
|
| 5 |
+
"difficulty": "easy",
|
| 6 |
+
"num_sprints": 6,
|
| 7 |
+
"days_per_sprint": 10,
|
| 8 |
+
"developers": [
|
| 9 |
+
{"id": "dev1", "name": "Alice", "skill": "backend", "capacity": 5, "productivity": 1.2},
|
| 10 |
+
{"id": "dev2", "name": "Bob", "skill": "frontend", "capacity": 4, "productivity": 1.0},
|
| 11 |
+
{"id": "dev3", "name": "Carol", "skill": "fullstack", "capacity": 6, "productivity": 1.1},
|
| 12 |
+
{"id": "dev4", "name": "Dave", "skill": "devops", "capacity": 4, "productivity": 1.0}
|
| 13 |
+
],
|
| 14 |
+
"tasks": [
|
| 15 |
+
{"id": "T01", "name": "User Auth API", "task_type": "feature", "priority": 1, "effort": 3, "sprint": 1, "deadline_day": 8, "required_skill": "backend", "depends_on": []},
|
| 16 |
+
{"id": "T02", "name": "Login UI", "task_type": "feature", "priority": 1, "effort": 3, "sprint": 1, "deadline_day": 9, "required_skill": "frontend", "depends_on": []},
|
| 17 |
+
{"id": "T03", "name": "CI/CD Setup", "task_type": "feature", "priority": 2, "effort": 3, "sprint": 1, "deadline_day": 10, "required_skill": "devops", "depends_on": []},
|
| 18 |
+
{"id": "T04", "name": "Database Schema v1", "task_type": "feature", "priority": 1, "effort": 4, "sprint": 1, "deadline_day": 9, "required_skill": "backend", "depends_on": []},
|
| 19 |
+
|
| 20 |
+
{"id": "T05", "name": "User Profile API", "task_type": "feature", "priority": 1, "effort": 4, "sprint": 2, "deadline_day": 18, "required_skill": "backend", "depends_on": ["T01"]},
|
| 21 |
+
{"id": "T06", "name": "Profile Page UI", "task_type": "feature", "priority": 2, "effort": 3, "sprint": 2, "deadline_day": 19, "required_skill": "frontend", "depends_on": ["T02"]},
|
| 22 |
+
{"id": "T07", "name": "Fix Login CSS Bug", "task_type": "bug", "priority": 1, "effort": 1, "sprint": 2, "deadline_day": 13, "required_skill": "frontend", "depends_on": []},
|
| 23 |
+
{"id": "T08", "name": "Docker Deployment", "task_type": "feature", "priority": 2, "effort": 3, "sprint": 2, "deadline_day": 20, "required_skill": "devops", "depends_on": ["T03"]},
|
| 24 |
+
|
| 25 |
+
{"id": "T09", "name": "Search API", "task_type": "feature", "priority": 2, "effort": 4, "sprint": 3, "deadline_day": 28, "required_skill": "backend", "depends_on": ["T04"]},
|
| 26 |
+
{"id": "T10", "name": "Search UI", "task_type": "feature", "priority": 2, "effort": 3, "sprint": 3, "deadline_day": 29, "required_skill": "frontend", "depends_on": []},
|
| 27 |
+
{"id": "T11", "name": "Email Notifications", "task_type": "feature", "priority": 3, "effort": 3, "sprint": 3, "deadline_day": 30, "required_skill": "backend", "depends_on": ["T05"]},
|
| 28 |
+
{"id": "T12", "name": "Staging Environment", "task_type": "feature", "priority": 2, "effort": 2, "sprint": 3, "deadline_day": 27, "required_skill": "devops", "depends_on": ["T08"]},
|
| 29 |
+
|
| 30 |
+
{"id": "T13", "name": "Payment Integration", "task_type": "feature", "priority": 1, "effort": 5, "sprint": 4, "deadline_day": 38, "required_skill": "backend", "depends_on": ["T05"]},
|
| 31 |
+
{"id": "T14", "name": "Checkout UI", "task_type": "feature", "priority": 1, "effort": 4, "sprint": 4, "deadline_day": 39, "required_skill": "frontend", "depends_on": ["T10"]},
|
| 32 |
+
{"id": "T15", "name": "API Rate Limiting", "task_type": "tech_debt", "priority": 3, "effort": 3, "sprint": 4, "deadline_day": 40, "required_skill": "backend", "depends_on": []},
|
| 33 |
+
{"id": "T16", "name": "Load Balancer Config", "task_type": "feature", "priority": 2, "effort": 3, "sprint": 4, "deadline_day": 38, "required_skill": "devops", "depends_on": ["T12"]},
|
| 34 |
+
|
| 35 |
+
{"id": "T17", "name": "Analytics Dashboard", "task_type": "feature", "priority": 2, "effort": 4, "sprint": 5, "deadline_day": 49, "required_skill": "fullstack", "depends_on": ["T13"]},
|
| 36 |
+
{"id": "T18", "name": "Accessibility Audit", "task_type": "tech_debt", "priority": 3, "effort": 3, "sprint": 5, "deadline_day": 50, "required_skill": "frontend", "depends_on": []},
|
| 37 |
+
{"id": "T19", "name": "Performance Optimisation", "task_type": "tech_debt", "priority": 3, "effort": 3, "sprint": 5, "deadline_day": 48, "required_skill": "backend", "depends_on": []},
|
| 38 |
+
{"id": "T20", "name": "Monitoring & Alerting", "task_type": "feature", "priority": 2, "effort": 3, "sprint": 5, "deadline_day": 50, "required_skill": "devops", "depends_on": ["T16"]},
|
| 39 |
+
|
| 40 |
+
{"id": "T21", "name": "Final Security Hardening", "task_type": "bug", "priority": 1, "effort": 4, "sprint": 6, "deadline_day": 58, "required_skill": "backend", "depends_on": ["T15"]},
|
| 41 |
+
{"id": "T22", "name": "Production Release UI", "task_type": "feature", "priority": 1, "effort": 3, "sprint": 6, "deadline_day": 59, "required_skill": "frontend", "depends_on": ["T14", "T18"]},
|
| 42 |
+
{"id": "T23", "name": "Runbook Documentation", "task_type": "tech_debt", "priority": 3, "effort": 2, "sprint": 6, "deadline_day": 60, "required_skill": "fullstack", "depends_on": []},
|
| 43 |
+
{"id": "T24", "name": "Production Deploy", "task_type": "feature", "priority": 1, "effort": 3, "sprint": 6, "deadline_day": 60, "required_skill": "devops", "depends_on": ["T20", "T21"]}
|
| 44 |
+
],
|
| 45 |
+
"instructions": [
|
| 46 |
+
{"id": "I01", "release_day": 1, "text": "Prioritise auth tasks above all others in sprint 1.", "target_sprint": 1, "affects_tasks": ["T01", "T02"]},
|
| 47 |
+
{"id": "I02", "release_day": 5, "text": "Ensure CI/CD is completed before sprint 1 ends.", "target_sprint": 1, "affects_tasks": ["T03"]},
|
| 48 |
+
{"id": "I03", "release_day": 11, "text": "The login CSS bug is blocking QA — resolve first in sprint 2.", "target_sprint": 2, "affects_tasks": ["T07"]},
|
| 49 |
+
{"id": "I04", "release_day": 14, "text": "Docker deployment must be validated by day 20.", "target_sprint": 2, "affects_tasks": ["T08"]},
|
| 50 |
+
{"id": "I05", "release_day": 21, "text": "Search must be demo-ready by end of sprint 3.", "target_sprint": 3, "affects_tasks": ["T09", "T10"]},
|
| 51 |
+
{"id": "I06", "release_day": 25, "text": "Email notifications are now a stakeholder requirement for sprint 3.", "target_sprint": 3, "affects_tasks": ["T11"]},
|
| 52 |
+
{"id": "I07", "release_day": 31, "text": "Payment integration is the top priority for sprint 4 — assign best backend dev.", "target_sprint": 4, "affects_tasks": ["T13"]},
|
| 53 |
+
{"id": "I08", "release_day": 35, "text": "API rate limiting must ship in sprint 4 before production scaling.", "target_sprint": 4, "affects_tasks": ["T15"]},
|
| 54 |
+
{"id": "I09", "release_day": 41, "text": "Analytics dashboard needs to be interactive — fullstack only.", "target_sprint": 5, "affects_tasks": ["T17"]},
|
| 55 |
+
{"id": "I10", "release_day": 45, "text": "Accessibility audit result must be attached to sprint 5 report.", "target_sprint": 5, "affects_tasks": ["T18"]},
|
| 56 |
+
{"id": "I11", "release_day": 51, "text": "Security hardening is a release gate — cannot ship without it.", "target_sprint": 6, "affects_tasks": ["T21"]},
|
| 57 |
+
{"id": "I12", "release_day": 55, "text": "Production deploy is on day 60 — no delays accepted.", "target_sprint": 6, "affects_tasks": ["T24"]}
|
| 58 |
+
]
|
| 59 |
+
},
|
| 60 |
+
|
| 61 |
+
"project_medium": {
|
| 62 |
+
"description": "Mid-size team with developer absences, mid-sprint urgent bugs, and instructions that conflict with prior plans.",
|
| 63 |
+
"difficulty": "medium",
|
| 64 |
+
"num_sprints": 6,
|
| 65 |
+
"days_per_sprint": 10,
|
| 66 |
+
"developers": [
|
| 67 |
+
{"id": "dev1", "name": "Alice", "skill": "backend", "capacity": 5, "productivity": 1.1},
|
| 68 |
+
{"id": "dev2", "name": "Bob", "skill": "frontend", "capacity": 4, "productivity": 0.9},
|
| 69 |
+
{"id": "dev3", "name": "Carol", "skill": "devops", "capacity": 5, "productivity": 1.0},
|
| 70 |
+
{"id": "dev4", "name": "Dave", "skill": "fullstack", "capacity": 6, "productivity": 1.2},
|
| 71 |
+
{"id": "dev5", "name": "Eve", "skill": "backend", "capacity": 5, "productivity": 1.0}
|
| 72 |
+
],
|
| 73 |
+
"absences": [
|
| 74 |
+
{"dev_id": "dev2", "day_start": 14, "day_end": 16, "reason": "sick leave"},
|
| 75 |
+
{"dev_id": "dev4", "day_start": 33, "day_end": 35, "reason": "conference"},
|
| 76 |
+
{"dev_id": "dev1", "day_start": 52, "day_end": 54, "reason": "vacation"}
|
| 77 |
+
],
|
| 78 |
+
"tasks": [
|
| 79 |
+
{"id": "T01", "name": "Auth Microservice", "task_type": "feature", "priority": 1, "effort": 5, "sprint": 1, "deadline_day": 8, "required_skill": "backend", "depends_on": []},
|
| 80 |
+
{"id": "T02", "name": "Login & Register UI", "task_type": "feature", "priority": 1, "effort": 4, "sprint": 1, "deadline_day": 9, "required_skill": "frontend", "depends_on": []},
|
| 81 |
+
{"id": "T03", "name": "Kubernetes Base Setup", "task_type": "feature", "priority": 2, "effort": 5, "sprint": 1, "deadline_day": 10, "required_skill": "devops", "depends_on": []},
|
| 82 |
+
{"id": "T04", "name": "DB Schema & Migrations", "task_type": "feature", "priority": 1, "effort": 4, "sprint": 1, "deadline_day": 9, "required_skill": "backend", "depends_on": []},
|
| 83 |
+
{"id": "T05", "name": "API Gateway Setup", "task_type": "feature", "priority": 2, "effort": 4, "sprint": 1, "deadline_day": 10, "required_skill": "fullstack", "depends_on": []},
|
| 84 |
+
|
| 85 |
+
{"id": "T06", "name": "User Profile Service", "task_type": "feature", "priority": 1, "effort": 5, "sprint": 2, "deadline_day": 18, "required_skill": "backend", "depends_on": ["T01"]},
|
| 86 |
+
{"id": "T07", "name": "Profile & Settings UI", "task_type": "feature", "priority": 2, "effort": 4, "sprint": 2, "deadline_day": 19, "required_skill": "frontend", "depends_on": ["T02"]},
|
| 87 |
+
{"id": "T08", "name": "Prod DB Crash Fix", "task_type": "urgent_bug", "priority": 1, "effort": 2, "sprint": 2, "deadline_day": 12, "required_skill": "backend", "depends_on": []},
|
| 88 |
+
{"id": "T09", "name": "Helm Chart Deployment", "task_type": "feature", "priority": 2, "effort": 4, "sprint": 2, "deadline_day": 20, "required_skill": "devops", "depends_on": ["T03"]},
|
| 89 |
+
{"id": "T10", "name": "Inter-Service Auth", "task_type": "feature", "priority": 1, "effort": 4, "sprint": 2, "deadline_day": 19, "required_skill": "fullstack", "depends_on": ["T05"]},
|
| 90 |
+
|
| 91 |
+
{"id": "T11", "name": "Notification Service", "task_type": "feature", "priority": 2, "effort": 5, "sprint": 3, "deadline_day": 28, "required_skill": "backend", "depends_on": ["T06"]},
|
| 92 |
+
{"id": "T12", "name": "Notification Preference UI", "task_type": "feature", "priority": 2, "effort": 3, "sprint": 3, "deadline_day": 29, "required_skill": "frontend", "depends_on": ["T07"]},
|
| 93 |
+
{"id": "T13", "name": "Payment Gateway API", "task_type": "feature", "priority": 1, "effort": 6, "sprint": 3, "deadline_day": 30, "required_skill": "backend", "depends_on": ["T04"]},
|
| 94 |
+
{"id": "T14", "name": "Service Mesh (Istio)", "task_type": "feature", "priority": 2, "effort": 5, "sprint": 3, "deadline_day": 28, "required_skill": "devops", "depends_on": ["T09"]},
|
| 95 |
+
{"id": "T15", "name": "Search & Filter API", "task_type": "feature", "priority": 2, "effort": 4, "sprint": 3, "deadline_day": 29, "required_skill": "fullstack", "depends_on": ["T10"]},
|
| 96 |
+
|
| 97 |
+
{"id": "T16", "name": "Checkout & Orders API", "task_type": "feature", "priority": 1, "effort": 5, "sprint": 4, "deadline_day": 38, "required_skill": "backend", "depends_on": ["T13"]},
|
| 98 |
+
{"id": "T17", "name": "Checkout Flow UI", "task_type": "feature", "priority": 1, "effort": 5, "sprint": 4, "deadline_day": 39, "required_skill": "frontend", "depends_on": ["T12"]},
|
| 99 |
+
{"id": "T18", "name": "Payment Security Audit", "task_type": "bug", "priority": 1, "effort": 3, "sprint": 4, "deadline_day": 36, "required_skill": "backend", "depends_on": ["T13"]},
|
| 100 |
+
{"id": "T19", "name": "Canary Deployment Setup", "task_type": "feature", "priority": 2, "effort": 4, "sprint": 4, "deadline_day": 39, "required_skill": "devops", "depends_on": ["T14"]},
|
| 101 |
+
{"id": "T20", "name": "Full-Text Search (ES)", "task_type": "feature", "priority": 3, "effort": 5, "sprint": 4, "deadline_day": 40, "required_skill": "fullstack", "depends_on": ["T15"]},
|
| 102 |
+
|
| 103 |
+
{"id": "T21", "name": "Reporting & Analytics API", "task_type": "feature", "priority": 2, "effort": 5, "sprint": 5, "deadline_day": 49, "required_skill": "backend", "depends_on": ["T16"]},
|
| 104 |
+
{"id": "T22", "name": "Analytics Dashboard UI", "task_type": "feature", "priority": 2, "effort": 4, "sprint": 5, "deadline_day": 49, "required_skill": "frontend", "depends_on": ["T17"]},
|
| 105 |
+
{"id": "T23", "name": "SLA & SLO Monitoring", "task_type": "tech_debt", "priority": 3, "effort": 3, "sprint": 5, "deadline_day": 50, "required_skill": "devops", "depends_on": ["T19"]},
|
| 106 |
+
{"id": "T24", "name": "Mobile API Optimisation", "task_type": "tech_debt", "priority": 3, "effort": 4, "sprint": 5, "deadline_day": 50, "required_skill": "fullstack", "depends_on": ["T20"]},
|
| 107 |
+
{"id": "T25", "name": "Data Privacy Compliance", "task_type": "tech_debt", "priority": 2, "effort": 3, "sprint": 5, "deadline_day": 48, "required_skill": "backend", "depends_on": []},
|
| 108 |
+
|
| 109 |
+
{"id": "T26", "name": "Pen Test & Remediation", "task_type": "bug", "priority": 1, "effort": 5, "sprint": 6, "deadline_day": 57, "required_skill": "backend", "depends_on": ["T18", "T25"]},
|
| 110 |
+
{"id": "T27", "name": "Final UI Polish & QA", "task_type": "feature", "priority": 1, "effort": 4, "sprint": 6, "deadline_day": 59, "required_skill": "frontend", "depends_on": ["T22"]},
|
| 111 |
+
{"id": "T28", "name": "Blue-Green Production Swap", "task_type": "feature", "priority": 1, "effort": 4, "sprint": 6, "deadline_day": 60, "required_skill": "devops", "depends_on": ["T23"]},
|
| 112 |
+
{"id": "T29", "name": "Runbooks & Postmortem Docs", "task_type": "tech_debt", "priority": 3, "effort": 3, "sprint": 6, "deadline_day": 60, "required_skill": "fullstack", "depends_on": []},
|
| 113 |
+
{"id": "T30", "name": "Stakeholder Demo Prep", "task_type": "feature", "priority": 2, "effort": 2, "sprint": 6, "deadline_day": 60, "required_skill": "fullstack", "depends_on": ["T21", "T22"]}
|
| 114 |
+
],
|
| 115 |
+
"instructions": [
|
| 116 |
+
{"id": "I01", "release_day": 1, "text": "Auth microservice is a hard dependency for all downstream services — finish before day 8.", "target_sprint": 1, "affects_tasks": ["T01"]},
|
| 117 |
+
{"id": "I02", "release_day": 3, "text": "API gateway must be validated with at least one downstream service by end of sprint 1.", "target_sprint": 1, "affects_tasks": ["T05"]},
|
| 118 |
+
{"id": "I03", "release_day": 7, "text": "Kubernetes base setup is blocking the DevOps track — escalate if behind.", "target_sprint": 1, "affects_tasks": ["T03"]},
|
| 119 |
+
{"id": "I04", "release_day": 11, "text": "Production DB crash is P0 — drop everything and assign a backend dev immediately.", "target_sprint": 2, "affects_tasks": ["T08"]},
|
| 120 |
+
{"id": "I05", "release_day": 15, "text": "Bob is on sick leave — redistribute frontend tasks for sprint 2.", "target_sprint": 2, "affects_tasks": ["T07"]},
|
| 121 |
+
{"id": "I06", "release_day": 18, "text": "Inter-service auth must be ready before notification service starts in sprint 3.", "target_sprint": 2, "affects_tasks": ["T10"]},
|
| 122 |
+
{"id": "I07", "release_day": 21, "text": "Payment gateway is now a sprint 3 commitment to the business — no slip.", "target_sprint": 3, "affects_tasks": ["T13"]},
|
| 123 |
+
{"id": "I08", "release_day": 24, "text": "Service mesh complexity is underestimated — pair Carol with Dave on Istio.", "target_sprint": 3, "affects_tasks": ["T14"]},
|
| 124 |
+
{"id": "I09", "release_day": 27, "text": "Add payment security audit as a sprint 3 exit gate before sprint 4 checkout work begins.", "target_sprint": 3, "affects_tasks": ["T18"]},
|
| 125 |
+
{"id": "I10", "release_day": 31, "text": "Checkout flow is top priority — nothing ships in sprint 4 before T16 and T17 are green.", "target_sprint": 4, "affects_tasks": ["T16", "T17"]},
|
| 126 |
+
{"id": "I11", "release_day": 33, "text": "Dave is at conference days 33-35 — replan sprint 4 fullstack tasks.", "target_sprint": 4, "affects_tasks": ["T20"]},
|
| 127 |
+
{"id": "I12", "release_day": 36, "text": "Canary deployment must be validated before blue-green swap in sprint 6.", "target_sprint": 4, "affects_tasks": ["T19"]},
|
| 128 |
+
{"id": "I13", "release_day": 41, "text": "Data privacy compliance is now a legal requirement — must complete sprint 5.", "target_sprint": 5, "affects_tasks": ["T25"]},
|
| 129 |
+
{"id": "I14", "release_day": 44, "text": "Analytics dashboard needs real-time updates — scope increase noted.", "target_sprint": 5, "affects_tasks": ["T22"]},
|
| 130 |
+
{"id": "I15", "release_day": 48, "text": "SLA monitoring must include payment service endpoints.", "target_sprint": 5, "affects_tasks": ["T23"]},
|
| 131 |
+
{"id": "I16", "release_day": 51, "text": "Alice is on vacation days 52-54 — replan sprint 6 backend tasks.", "target_sprint": 6, "affects_tasks": ["T26"]},
|
| 132 |
+
{"id": "I17", "release_day": 53, "text": "Pen test results are back — two critical findings must be fixed before blue-green swap.", "target_sprint": 6, "affects_tasks": ["T26", "T28"]},
|
| 133 |
+
{"id": "I18", "release_day": 56, "text": "Stakeholder demo is on day 60 — runbooks and demo prep are release gates.", "target_sprint": 6, "affects_tasks": ["T29", "T30"]}
|
| 134 |
+
]
|
| 135 |
+
},
|
| 136 |
+
|
| 137 |
+
"project_hard": {
|
| 138 |
+
"description": "Large team, cascading failures, urgent mid-project pivots, conflicting instructions, and hard delivery deadline with zero tolerance for missed dependencies.",
|
| 139 |
+
"difficulty": "hard",
|
| 140 |
+
"num_sprints": 6,
|
| 141 |
+
"days_per_sprint": 10,
|
| 142 |
+
"developers": [
|
| 143 |
+
{"id": "dev1", "name": "Alice", "skill": "backend", "capacity": 5, "productivity": 1.3},
|
| 144 |
+
{"id": "dev2", "name": "Bob", "skill": "frontend", "capacity": 4, "productivity": 0.8},
|
| 145 |
+
{"id": "dev3", "name": "Carol", "skill": "devops", "capacity": 5, "productivity": 1.0},
|
| 146 |
+
{"id": "dev4", "name": "Dave", "skill": "fullstack", "capacity": 7, "productivity": 1.1},
|
| 147 |
+
{"id": "dev5", "name": "Eve", "skill": "backend", "capacity": 5, "productivity": 0.9},
|
| 148 |
+
{"id": "dev6", "name": "Frank", "skill": "frontend", "capacity": 4, "productivity": 1.0},
|
| 149 |
+
{"id": "dev7", "name": "Grace", "skill": "devops", "capacity": 5, "productivity": 1.1}
|
| 150 |
+
],
|
| 151 |
+
"absences": [
|
| 152 |
+
{"dev_id": "dev2", "day_start": 12, "day_end": 15, "reason": "sick leave"},
|
| 153 |
+
{"dev_id": "dev5", "day_start": 23, "day_end": 26, "reason": "family emergency"},
|
| 154 |
+
{"dev_id": "dev4", "day_start": 33, "day_end": 37, "reason": "conference"},
|
| 155 |
+
{"dev_id": "dev1", "day_start": 51, "day_end": 55, "reason": "vacation"},
|
| 156 |
+
{"dev_id": "dev3", "day_start": 43, "day_end": 45, "reason": "sick leave"}
|
| 157 |
+
],
|
| 158 |
+
"tasks": [
|
| 159 |
+
{"id": "T01", "name": "Auth & IAM Service", "task_type": "feature", "priority": 1, "effort": 6, "sprint": 1, "deadline_day": 7, "required_skill": "backend", "depends_on": []},
|
| 160 |
+
{"id": "T02", "name": "API Gateway + Rate Limiting", "task_type": "feature", "priority": 1, "effort": 5, "sprint": 1, "deadline_day": 8, "required_skill": "fullstack", "depends_on": []},
|
| 161 |
+
{"id": "T03", "name": "Design System Setup", "task_type": "feature", "priority": 2, "effort": 4, "sprint": 1, "deadline_day": 9, "required_skill": "frontend", "depends_on": []},
|
| 162 |
+
{"id": "T04", "name": "Kubernetes + Helm Base", "task_type": "feature", "priority": 1, "effort": 6, "sprint": 1, "deadline_day": 9, "required_skill": "devops", "depends_on": []},
|
| 163 |
+
{"id": "T05", "name": "Core DB Schema & Seed Data", "task_type": "feature", "priority": 1, "effort": 4, "sprint": 1, "deadline_day": 8, "required_skill": "backend", "depends_on": []},
|
| 164 |
+
{"id": "T06", "name": "Service-to-Service Auth", "task_type": "feature", "priority": 1, "effort": 4, "sprint": 1, "deadline_day": 10, "required_skill": "fullstack", "depends_on": ["T01"]},
|
| 165 |
+
{"id": "T07", "name": "CI/CD Pipelines", "task_type": "feature", "priority": 2, "effort": 4, "sprint": 1, "deadline_day": 10, "required_skill": "devops", "depends_on": []},
|
| 166 |
+
|
| 167 |
+
{"id": "T08", "name": "User Management API", "task_type": "feature", "priority": 1, "effort": 5, "sprint": 2, "deadline_day": 17, "required_skill": "backend", "depends_on": ["T01", "T05"]},
|
| 168 |
+
{"id": "T09", "name": "User Management UI", "task_type": "feature", "priority": 1, "effort": 4, "sprint": 2, "deadline_day": 18, "required_skill": "frontend", "depends_on": ["T03"]},
|
| 169 |
+
{"id": "T10", "name": "Istio Service Mesh", "task_type": "feature", "priority": 2, "effort": 6, "sprint": 2, "deadline_day": 20, "required_skill": "devops", "depends_on": ["T04"]},
|
| 170 |
+
{"id": "T11", "name": "Event Bus (Kafka)", "task_type": "feature", "priority": 1, "effort": 6, "sprint": 2, "deadline_day": 19, "required_skill": "fullstack", "depends_on": ["T06"]},
|
| 171 |
+
{"id": "T12", "name": "Prod DB Failure — Hotfix", "task_type": "urgent_bug", "priority": 1, "effort": 3, "sprint": 2, "deadline_day": 12, "required_skill": "backend", "depends_on": []},
|
| 172 |
+
{"id": "T13", "name": "Payment Compliance Spike", "task_type": "tech_debt", "priority": 2, "effort": 3, "sprint": 2, "deadline_day": 20, "required_skill": "backend", "depends_on": []},
|
| 173 |
+
|
| 174 |
+
{"id": "T14", "name": "Payment Gateway Service", "task_type": "feature", "priority": 1, "effort": 7, "sprint": 3, "deadline_day": 28, "required_skill": "backend", "depends_on": ["T08", "T13"]},
|
| 175 |
+
{"id": "T15", "name": "Checkout UI — Multi-step", "task_type": "feature", "priority": 1, "effort": 6, "sprint": 3, "deadline_day": 29, "required_skill": "frontend", "depends_on": ["T09"]},
|
| 176 |
+
{"id": "T16", "name": "Canary + Rollback Automation", "task_type": "feature", "priority": 2, "effort": 5, "sprint": 3, "deadline_day": 30, "required_skill": "devops", "depends_on": ["T10"]},
|
| 177 |
+
{"id": "T17", "name": "Notification & Event Service", "task_type": "feature", "priority": 2, "effort": 5, "sprint": 3, "deadline_day": 29, "required_skill": "fullstack", "depends_on": ["T11"]},
|
| 178 |
+
{"id": "T18", "name": "Inventory & Orders API", "task_type": "feature", "priority": 1, "effort": 6, "sprint": 3, "deadline_day": 28, "required_skill": "backend", "depends_on": ["T05"]},
|
| 179 |
+
{"id": "T19", "name": "Payment Security Audit", "task_type": "bug", "priority": 1, "effort": 3, "sprint": 3, "deadline_day": 27, "required_skill": "backend", "depends_on": ["T14"]},
|
| 180 |
+
|
| 181 |
+
{"id": "T20", "name": "Order Management UI", "task_type": "feature", "priority": 1, "effort": 5, "sprint": 4, "deadline_day": 37, "required_skill": "frontend", "depends_on": ["T15", "T18"]},
|
| 182 |
+
{"id": "T21", "name": "Fraud Detection Service", "task_type": "feature", "priority": 1, "effort": 6, "sprint": 4, "deadline_day": 38, "required_skill": "fullstack", "depends_on": ["T14", "T17"]},
|
| 183 |
+
{"id": "T22", "name": "Full-Text Search (ES)", "task_type": "feature", "priority": 2, "effort": 5, "sprint": 4, "deadline_day": 39, "required_skill": "backend", "depends_on": ["T18"]},
|
| 184 |
+
{"id": "T23", "name": "Multi-Region Failover", "task_type": "feature", "priority": 2, "effort": 6, "sprint": 4, "deadline_day": 40, "required_skill": "devops", "depends_on": ["T16"]},
|
| 185 |
+
{"id": "T24", "name": "Mid-Sprint Scope Pivot — Recs", "task_type": "feature", "priority": 2, "effort": 5, "sprint": 4, "deadline_day": 39, "required_skill": "fullstack", "depends_on": []},
|
| 186 |
+
{"id": "T25", "name": "Payment Gateway Bug Surge", "task_type": "urgent_bug", "priority": 1, "effort": 4, "sprint": 4, "deadline_day": 34, "required_skill": "backend", "depends_on": []},
|
| 187 |
+
|
| 188 |
+
{"id": "T26", "name": "Reporting & BI API", "task_type": "feature", "priority": 2, "effort": 5, "sprint": 5, "deadline_day": 48, "required_skill": "backend", "depends_on": ["T22"]},
|
| 189 |
+
{"id": "T27", "name": "Executive Dashboard UI", "task_type": "feature", "priority": 2, "effort": 5, "sprint": 5, "deadline_day": 49, "required_skill": "frontend", "depends_on": ["T20"]},
|
| 190 |
+
{"id": "T28", "name": "SLO / SLA Monitoring", "task_type": "tech_debt", "priority": 2, "effort": 4, "sprint": 5, "deadline_day": 49, "required_skill": "devops", "depends_on": ["T23"]},
|
| 191 |
+
{"id": "T29", "name": "Recommendation Engine v1", "task_type": "feature", "priority": 2, "effort": 6, "sprint": 5, "deadline_day": 50, "required_skill": "fullstack", "depends_on": ["T24"]},
|
| 192 |
+
{"id": "T30", "name": "GDPR / Data Privacy Pass", "task_type": "tech_debt", "priority": 1, "effort": 4, "sprint": 5, "deadline_day": 48, "required_skill": "backend", "depends_on": []},
|
| 193 |
+
{"id": "T31", "name": "Mobile API Adaptation Layer", "task_type": "feature", "priority": 3, "effort": 4, "sprint": 5, "deadline_day": 50, "required_skill": "fullstack", "depends_on": ["T21"]},
|
| 194 |
+
|
| 195 |
+
{"id": "T32", "name": "Penetration Test & Fixes", "task_type": "bug", "priority": 1, "effort": 6, "sprint": 6, "deadline_day": 56, "required_skill": "backend", "depends_on": ["T19", "T30"]},
|
| 196 |
+
{"id": "T33", "name": "Final UI QA & Accessibility", "task_type": "bug", "priority": 1, "effort": 4, "sprint": 6, "deadline_day": 58, "required_skill": "frontend", "depends_on": ["T27"]},
|
| 197 |
+
{"id": "T34", "name": "Blue-Green Production Deploy", "task_type": "feature", "priority": 1, "effort": 5, "sprint": 6, "deadline_day": 60, "required_skill": "devops", "depends_on": ["T28", "T32"]},
|
| 198 |
+
{"id": "T35", "name": "Fraud Rule Tuning", "task_type": "tech_debt", "priority": 2, "effort": 3, "sprint": 6, "deadline_day": 58, "required_skill": "fullstack", "depends_on": ["T21"]},
|
| 199 |
+
{"id": "T36", "name": "Runbooks & DR Documentation", "task_type": "tech_debt", "priority": 2, "effort": 3, "sprint": 6, "deadline_day": 59, "required_skill": "fullstack", "depends_on": []},
|
| 200 |
+
{"id": "T37", "name": "Stakeholder Demo & Handoff", "task_type": "feature", "priority": 1, "effort": 2, "sprint": 6, "deadline_day": 60, "required_skill": "fullstack", "depends_on": ["T33", "T35", "T36"]}
|
| 201 |
+
],
|
| 202 |
+
"instructions": [
|
| 203 |
+
{"id": "I01", "release_day": 1, "text": "Auth & IAM must be done before day 7 — it gates all other services.", "target_sprint": 1, "affects_tasks": ["T01"]},
|
| 204 |
+
{"id": "I02", "release_day": 2, "text": "API gateway rate limiting is a launch requirement — not optional.", "target_sprint": 1, "affects_tasks": ["T02"]},
|
| 205 |
+
{"id": "I03", "release_day": 5, "text": "Service-to-service auth must be validated in sprint 1 before Kafka work begins.", "target_sprint": 1, "affects_tasks": ["T06"]},
|
| 206 |
+
{"id": "I04", "release_day": 8, "text": "Kubernetes must be ready for sprint 2 deployments — escalate any blockers.", "target_sprint": 1, "affects_tasks": ["T04"]},
|
| 207 |
+
{"id": "I05", "release_day": 11, "text": "Prod DB failure is P0 — assign Alice immediately, push T08 if needed.", "target_sprint": 2, "affects_tasks": ["T12"]},
|
| 208 |
+
{"id": "I06", "release_day": 12, "text": "Bob is on sick leave days 12-15 — redistribute T09 to Frank.", "target_sprint": 2, "affects_tasks": ["T09"]},
|
| 209 |
+
{"id": "I07", "release_day": 16, "text": "Kafka event bus is a hard dependency for notification service in sprint 3.", "target_sprint": 2, "affects_tasks": ["T11"]},
|
| 210 |
+
{"id": "I08", "release_day": 19, "text": "Payment compliance spike must close before sprint 3 payment work starts.", "target_sprint": 2, "affects_tasks": ["T13"]},
|
| 211 |
+
{"id": "I09", "release_day": 21, "text": "Payment gateway is the sprint 3 critical path — assign Eve and Alice.", "target_sprint": 3, "affects_tasks": ["T14"]},
|
| 212 |
+
{"id": "I10", "release_day": 23, "text": "Eve is out days 23-26 — replan sprint 3 backend with Alice carrying payment.", "target_sprint": 3, "affects_tasks": ["T14", "T18"]},
|
| 213 |
+
{"id": "I11", "release_day": 25, "text": "Payment security audit is a release gate — must pass before checkout UI is signed off.", "target_sprint": 3, "affects_tasks": ["T19"]},
|
| 214 |
+
{"id": "I12", "release_day": 28, "text": "Canary rollback automation must be validated before multi-region failover in sprint 4.", "target_sprint": 3, "affects_tasks": ["T16"]},
|
| 215 |
+
{"id": "I13", "release_day": 31, "text": "Fraud detection is now a sprint 4 commitment — do not descope.", "target_sprint": 4, "affects_tasks": ["T21"]},
|
| 216 |
+
{"id": "I14", "release_day": 33, "text": "Dave is at conference days 33-37 — all fullstack sprint 4 tasks need replan.", "target_sprint": 4, "affects_tasks": ["T21", "T24"]},
|
| 217 |
+
{"id": "I15", "release_day": 33, "text": "Payment bug surge T25 is P0 — assign best available backend dev.", "target_sprint": 4, "affects_tasks": ["T25"]},
|
| 218 |
+
{"id": "I16", "release_day": 36, "text": "Recommendation engine scope has been greenlit — T24 must ship in sprint 4.", "target_sprint": 4, "affects_tasks": ["T24"]},
|
| 219 |
+
{"id": "I17", "release_day": 39, "text": "Multi-region failover must be tested before sprint 6 production deploy.", "target_sprint": 4, "affects_tasks": ["T23"]},
|
| 220 |
+
{"id": "I18", "release_day": 41, "text": "GDPR pass is now a legal requirement — must complete in sprint 5 no exceptions.", "target_sprint": 5, "affects_tasks": ["T30"]},
|
| 221 |
+
{"id": "I19", "release_day": 43, "text": "Carol is sick days 43-45 — DevOps SLO monitoring may slip, flag risk.", "target_sprint": 5, "affects_tasks": ["T28"]},
|
| 222 |
+
{"id": "I20", "release_day": 45, "text": "Executive dashboard needs live data feed — scope increase, coordinate with backend.", "target_sprint": 5, "affects_tasks": ["T27", "T26"]},
|
| 223 |
+
{"id": "I21", "release_day": 47, "text": "Mobile API layer has been reprioritised by CPO — must not miss sprint 5.", "target_sprint": 5, "affects_tasks": ["T31"]},
|
| 224 |
+
{"id": "I22", "release_day": 51, "text": "Alice is on vacation days 51-55 — pen test fixes must be pre-assigned.", "target_sprint": 6, "affects_tasks": ["T32"]},
|
| 225 |
+
{"id": "I23", "release_day": 53, "text": "Pen test returned 3 critical CVEs — T32 scope has increased, adjust sprint 6 plan.", "target_sprint": 6, "affects_tasks": ["T32"]},
|
| 226 |
+
{"id": "I24", "release_day": 55, "text": "Blue-green deploy is hard-locked to day 60 — no rescheduling.", "target_sprint": 6, "affects_tasks": ["T34"]},
|
| 227 |
+
{"id": "I25", "release_day": 57, "text": "Stakeholder demo requires fraud rule tuning and runbooks to be complete first.", "target_sprint": 6, "affects_tasks": ["T35", "T36", "T37"]}
|
| 228 |
+
]
|
| 229 |
+
}
|
| 230 |
+
}
|
| 231 |
+
}
|
data/sprint_data.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenarios": {
|
| 3 |
+
"easy_sprint": {
|
| 4 |
+
"description": "Small team, clear tasks, no surprises. Good for baseline testing.",
|
| 5 |
+
"difficulty": "easy",
|
| 6 |
+
"developers": [
|
| 7 |
+
{"id": "dev1", "name": "Alice", "skill": "backend", "capacity": 5, "productivity": 1.2},
|
| 8 |
+
{"id": "dev2", "name": "Bob", "skill": "frontend", "capacity": 4, "productivity": 1.0},
|
| 9 |
+
{"id": "dev3", "name": "Carol", "skill": "fullstack","capacity": 6, "productivity": 1.1}
|
| 10 |
+
],
|
| 11 |
+
"tasks": [
|
| 12 |
+
{"id": "T1", "name": "User Login API", "task_type": "feature", "priority": 2, "effort": 3, "deadline": 5, "required_skill": "backend"},
|
| 13 |
+
{"id": "T2", "name": "Dashboard UI", "task_type": "feature", "priority": 2, "effort": 3, "deadline": 6, "required_skill": "frontend"},
|
| 14 |
+
{"id": "T3", "name": "Fix CSS Layout Bug", "task_type": "bug", "priority": 1, "effort": 1, "deadline": 3, "required_skill": "frontend"},
|
| 15 |
+
{"id": "T4", "name": "Database Migration", "task_type": "feature", "priority": 3, "effort": 4, "deadline": 7, "required_skill": "backend"},
|
| 16 |
+
{"id": "T5", "name": "Setup CI/CD Pipeline", "task_type": "feature", "priority": 3, "effort": 3, "deadline": 8, "required_skill": "devops"}
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
"medium_sprint": {
|
| 20 |
+
"description": "Mid-size team with bugs and random dev absences.",
|
| 21 |
+
"difficulty": "medium",
|
| 22 |
+
"developers": [
|
| 23 |
+
{"id": "dev1", "name": "Alice", "skill": "backend", "capacity": 5, "productivity": 1.1},
|
| 24 |
+
{"id": "dev2", "name": "Bob", "skill": "frontend", "capacity": 4, "productivity": 0.9},
|
| 25 |
+
{"id": "dev3", "name": "Carol", "skill": "devops", "capacity": 5, "productivity": 1.0},
|
| 26 |
+
{"id": "dev4", "name": "Dave", "skill": "fullstack","capacity": 6, "productivity": 1.2}
|
| 27 |
+
],
|
| 28 |
+
"tasks": [
|
| 29 |
+
{"id": "T1", "name": "Auth Service", "task_type": "feature", "priority": 1, "effort": 5, "deadline": 5, "required_skill": "backend"},
|
| 30 |
+
{"id": "T2", "name": "Profile Page UI", "task_type": "feature", "priority": 2, "effort": 4, "deadline": 6, "required_skill": "frontend"},
|
| 31 |
+
{"id": "T3", "name": "Payment Integration", "task_type": "feature", "priority": 1, "effort": 6, "deadline": 7, "required_skill": "backend"},
|
| 32 |
+
{"id": "T4", "name": "Docker Deployment", "task_type": "feature", "priority": 2, "effort": 3, "deadline": 5, "required_skill": "devops"},
|
| 33 |
+
{"id": "T5", "name": "Login Page", "task_type": "feature", "priority": 2, "effort": 3, "deadline": 5, "required_skill": "frontend"},
|
| 34 |
+
{"id": "T6", "name": "Prod DB Crash Fix", "task_type": "bug", "priority": 1, "effort": 2, "deadline": 3, "required_skill": "backend"},
|
| 35 |
+
{"id": "T7", "name": "API Rate Limiting", "task_type": "tech_debt", "priority": 4, "effort": 3, "deadline": 9, "required_skill": "backend"},
|
| 36 |
+
{"id": "T8", "name": "Mobile Layout Fix", "task_type": "bug", "priority": 2, "effort": 2, "deadline": 4, "required_skill": "frontend"}
|
| 37 |
+
]
|
| 38 |
+
},
|
| 39 |
+
"hard_sprint": {
|
| 40 |
+
"description": "Large team, many tasks, urgent bugs appear mid-sprint, dev absences cascade.",
|
| 41 |
+
"difficulty": "hard",
|
| 42 |
+
"developers": [
|
| 43 |
+
{"id": "dev1", "name": "Alice", "skill": "backend", "capacity": 5, "productivity": 1.3},
|
| 44 |
+
{"id": "dev2", "name": "Bob", "skill": "frontend", "capacity": 4, "productivity": 0.8},
|
| 45 |
+
{"id": "dev3", "name": "Carol", "skill": "devops", "capacity": 5, "productivity": 1.0},
|
| 46 |
+
{"id": "dev4", "name": "Dave", "skill": "fullstack","capacity": 7, "productivity": 1.1},
|
| 47 |
+
{"id": "dev5", "name": "Eve", "skill": "backend", "capacity": 5, "productivity": 0.9}
|
| 48 |
+
],
|
| 49 |
+
"tasks": [
|
| 50 |
+
{"id": "T1", "name": "Microservices Refactor", "task_type": "feature", "priority": 1, "effort": 8, "deadline": 5, "required_skill": "backend"},
|
| 51 |
+
{"id": "T2", "name": "Real-time Notifications", "task_type": "feature", "priority": 1, "effort": 6, "deadline": 5, "required_skill": "fullstack"},
|
| 52 |
+
{"id": "T3", "name": "Security Audit Fixes", "task_type": "bug", "priority": 1, "effort": 5, "deadline": 4, "required_skill": "backend"},
|
| 53 |
+
{"id": "T4", "name": "Kubernetes Migration", "task_type": "feature", "priority": 2, "effort": 7, "deadline": 6, "required_skill": "devops"},
|
| 54 |
+
{"id": "T5", "name": "Homepage Redesign", "task_type": "feature", "priority": 2, "effort": 5, "deadline": 6, "required_skill": "frontend"},
|
| 55 |
+
{"id": "T6", "name": "Payment Gateway Bug", "task_type": "urgent_bug", "priority": 1, "effort": 3, "deadline": 3, "required_skill": "backend"},
|
| 56 |
+
{"id": "T7", "name": "Search Performance", "task_type": "tech_debt", "priority": 3, "effort": 4, "deadline": 7, "required_skill": "backend"},
|
| 57 |
+
{"id": "T8", "name": "A/B Testing Framework", "task_type": "feature", "priority": 3, "effort": 5, "deadline": 8, "required_skill": "fullstack"},
|
| 58 |
+
{"id": "T9", "name": "CDN Integration", "task_type": "feature", "priority": 3, "effort": 3, "deadline": 7, "required_skill": "devops"},
|
| 59 |
+
{"id": "T10", "name": "OAuth2 Provider", "task_type": "feature", "priority": 2, "effort": 6, "deadline": 6, "required_skill": "backend"},
|
| 60 |
+
{"id": "T11", "name": "Analytics Dashboard", "task_type": "feature", "priority": 3, "effort": 5, "deadline": 8, "required_skill": "fullstack"},
|
| 61 |
+
{"id": "T12", "name": "Accessibility Audit", "task_type": "tech_debt", "priority": 4, "effort": 3, "deadline": 9, "required_skill": "frontend"}
|
| 62 |
+
]
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
}
|
evaluate.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
evaluate.py — Compare baseline vs trained RL policy
|
| 3 |
+
=====================================================
|
| 4 |
+
Run after train.py completes.
|
| 5 |
+
"""
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
import os
|
| 8 |
+
import json
|
| 9 |
+
import requests
|
| 10 |
+
from openai import OpenAI
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
|
| 13 |
+
load_dotenv()
|
| 14 |
+
|
| 15 |
+
ENV_URL = os.getenv("ENV_BASE_URL", "https://sejal-k-ai-sprint-manager.hf.space")
|
| 16 |
+
BASELINE_MODEL = os.getenv("MODEL_NAME", "meta-llama/Llama-3.1-8B-Instruct")
|
| 17 |
+
API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
|
| 18 |
+
API_KEY = os.getenv("HF_TOKEN", "dummy")
|
| 19 |
+
POLICY_PATH = "./results/best_policy.json"
|
| 20 |
+
TASKS = ["easy_sprint", "medium_sprint", "hard_sprint"]
|
| 21 |
+
N_SEEDS = 5
|
| 22 |
+
|
| 23 |
+
SYSTEM = """You are a Tech Lead. Output ONLY JSON.
|
| 24 |
+
{"action_type": "assign", "task_id": "T1", "dev_id": "dev1", "new_priority": null}
|
| 25 |
+
Only assign backlog tasks to available skill-matched developers."""
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# ── Shared helpers ────────────────────────────────────────────────────────────
|
| 29 |
+
|
| 30 |
+
def env_reset(task_name, seed=42):
|
| 31 |
+
r = requests.post(f"{ENV_URL}/reset",
|
| 32 |
+
json={"task_name": task_name, "seed": seed}, timeout=30)
|
| 33 |
+
r.raise_for_status()
|
| 34 |
+
return r.json()
|
| 35 |
+
|
| 36 |
+
def env_step(action):
|
| 37 |
+
r = requests.post(f"{ENV_URL}/step", json={"action": {
|
| 38 |
+
"action_type": action.get("action_type", "skip"),
|
| 39 |
+
"task_id": action.get("task_id"),
|
| 40 |
+
"dev_id": action.get("dev_id"),
|
| 41 |
+
"new_priority": action.get("new_priority"),
|
| 42 |
+
}}, timeout=30)
|
| 43 |
+
r.raise_for_status()
|
| 44 |
+
return r.json()
|
| 45 |
+
|
| 46 |
+
def run_episode(act_fn, task_name, seed=42):
|
| 47 |
+
obs = env_reset(task_name, seed)
|
| 48 |
+
result = {"info": {}}
|
| 49 |
+
for _ in range(12):
|
| 50 |
+
if obs.get("done"):
|
| 51 |
+
break
|
| 52 |
+
action = act_fn(obs)
|
| 53 |
+
result = env_step(action)
|
| 54 |
+
obs = result["observation"]
|
| 55 |
+
return max(0.01, min(0.99, result.get("info", {}).get("final_score", 0.01)))
|
| 56 |
+
|
| 57 |
+
def evaluate(act_fn, name):
|
| 58 |
+
print(f"\n {name}")
|
| 59 |
+
print(f" {'─'*45}")
|
| 60 |
+
scores = {}
|
| 61 |
+
for task in TASKS:
|
| 62 |
+
vals = []
|
| 63 |
+
for seed in range(N_SEEDS):
|
| 64 |
+
try:
|
| 65 |
+
vals.append(run_episode(act_fn, task, seed=seed*13+42))
|
| 66 |
+
except Exception:
|
| 67 |
+
vals.append(0.01)
|
| 68 |
+
avg = sum(vals) / len(vals)
|
| 69 |
+
scores[task] = round(avg, 4)
|
| 70 |
+
bar = "█" * int(avg * 20)
|
| 71 |
+
print(f" {task:<20} {avg:.4f} {bar}")
|
| 72 |
+
overall = sum(scores.values()) / len(scores)
|
| 73 |
+
scores["average"] = round(overall, 4)
|
| 74 |
+
print(f" {'AVERAGE':<20} {overall:.4f}")
|
| 75 |
+
return scores
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
# ── Trained policy agent ──────────────────────────────────────────────────────
|
| 79 |
+
|
| 80 |
+
class TrainedPolicy:
|
| 81 |
+
def __init__(self, path):
|
| 82 |
+
with open(path) as f:
|
| 83 |
+
w = json.load(f)
|
| 84 |
+
self.priority_weight = w["priority_weight"]
|
| 85 |
+
self.deadline_weight = w["deadline_weight"]
|
| 86 |
+
self.skill_weight = w["skill_weight"]
|
| 87 |
+
self.load_weight = w["load_weight"]
|
| 88 |
+
|
| 89 |
+
def act(self, obs):
|
| 90 |
+
day = obs.get("current_day", 1)
|
| 91 |
+
backlog = [t for t in obs["tasks"] if t["status"] == "backlog"]
|
| 92 |
+
avail = [d for d in obs["developers"]
|
| 93 |
+
if d["is_available"] and d["current_load"] < d["capacity"]]
|
| 94 |
+
if not backlog or not avail:
|
| 95 |
+
return {"action_type": "skip", "task_id": None,
|
| 96 |
+
"dev_id": None, "new_priority": None}
|
| 97 |
+
|
| 98 |
+
best, bt, bd = float("-inf"), None, None
|
| 99 |
+
for t in backlog:
|
| 100 |
+
for d in avail:
|
| 101 |
+
s = self.priority_weight * (6 - t["priority"])
|
| 102 |
+
s += self.deadline_weight * (10 / max(1, t["deadline"] - day))
|
| 103 |
+
if d["skill"] == t["required_skill"]:
|
| 104 |
+
s += self.skill_weight * 3
|
| 105 |
+
elif d["skill"] == "fullstack":
|
| 106 |
+
s += self.skill_weight * 2
|
| 107 |
+
else:
|
| 108 |
+
s -= self.skill_weight * 2
|
| 109 |
+
s -= self.load_weight * (d["current_load"] / max(d["capacity"], 1)) * 2
|
| 110 |
+
if s > best:
|
| 111 |
+
best, bt, bd = s, t, d
|
| 112 |
+
if bt and bd:
|
| 113 |
+
return {"action_type": "assign", "task_id": bt["id"],
|
| 114 |
+
"dev_id": bd["id"], "new_priority": None}
|
| 115 |
+
return {"action_type": "skip", "task_id": None,
|
| 116 |
+
"dev_id": None, "new_priority": None}
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# ── Baseline LLM agent ────────────────────────────────────────────────────────
|
| 120 |
+
|
| 121 |
+
def make_baseline_act():
|
| 122 |
+
client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
|
| 123 |
+
|
| 124 |
+
def act(obs):
|
| 125 |
+
backlog = sorted([t for t in obs["tasks"] if t["status"] == "backlog"],
|
| 126 |
+
key=lambda t: (t["priority"], t["deadline"]))
|
| 127 |
+
avail = [d for d in obs["developers"]
|
| 128 |
+
if d["is_available"] and d["current_load"] < d["capacity"]]
|
| 129 |
+
prompt = (
|
| 130 |
+
f"Day {obs['current_day']}/{obs['sprint_length']}\n"
|
| 131 |
+
f"Backlog: {[t['id']+':'+t['required_skill'] for t in backlog[:4]]}\n"
|
| 132 |
+
f"Devs: {[d['id']+':'+d['skill'] for d in avail]}\n"
|
| 133 |
+
f"JSON action:"
|
| 134 |
+
)
|
| 135 |
+
try:
|
| 136 |
+
resp = client.chat.completions.create(
|
| 137 |
+
model=BASELINE_MODEL,
|
| 138 |
+
messages=[
|
| 139 |
+
{"role": "system", "content": SYSTEM},
|
| 140 |
+
{"role": "user", "content": prompt},
|
| 141 |
+
],
|
| 142 |
+
temperature=0.1, max_tokens=80,
|
| 143 |
+
)
|
| 144 |
+
text = resp.choices[0].message.content or ""
|
| 145 |
+
a = json.loads(text.strip())
|
| 146 |
+
if a.get("action_type") in ("assign","reassign","skip","unblock"):
|
| 147 |
+
return a
|
| 148 |
+
except Exception:
|
| 149 |
+
pass
|
| 150 |
+
return {"action_type": "skip", "task_id": None,
|
| 151 |
+
"dev_id": None, "new_priority": None}
|
| 152 |
+
return act
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
# ── Main ──────────────────────────────────────────────────────────────────────
|
| 156 |
+
|
| 157 |
+
def main():
|
| 158 |
+
try:
|
| 159 |
+
r = requests.get(f"{ENV_URL}/health", timeout=10)
|
| 160 |
+
print(f"Server: {r.json()}")
|
| 161 |
+
except Exception:
|
| 162 |
+
print("ERROR: Start server first: python ui.py")
|
| 163 |
+
return
|
| 164 |
+
|
| 165 |
+
print("\n" + "="*55)
|
| 166 |
+
print(" EVALUATION: Baseline vs Trained RL Policy")
|
| 167 |
+
print("="*55)
|
| 168 |
+
|
| 169 |
+
results = {}
|
| 170 |
+
|
| 171 |
+
# Baseline
|
| 172 |
+
results["baseline"] = evaluate(make_baseline_act(), f"Baseline LLM ({BASELINE_MODEL})")
|
| 173 |
+
|
| 174 |
+
# Trained policy
|
| 175 |
+
if os.path.exists(POLICY_PATH):
|
| 176 |
+
policy = TrainedPolicy(POLICY_PATH)
|
| 177 |
+
results["trained"] = evaluate(policy.act, f"Trained RL Policy ({POLICY_PATH})")
|
| 178 |
+
|
| 179 |
+
# Comparison table
|
| 180 |
+
print(f"\n{'='*55}")
|
| 181 |
+
print(" IMPROVEMENT SUMMARY")
|
| 182 |
+
print(f"{'='*55}")
|
| 183 |
+
print(f" {'Task':<20} {'Baseline':>10} {'Trained':>10} {'Delta':>10}")
|
| 184 |
+
print(f" {'─'*48}")
|
| 185 |
+
for task in TASKS + ["average"]:
|
| 186 |
+
b = results["baseline"].get(task, 0)
|
| 187 |
+
t = results["trained"].get(task, 0)
|
| 188 |
+
delta = t - b
|
| 189 |
+
sign = "+" if delta >= 0 else ""
|
| 190 |
+
print(f" {task:<20} {b:>10.4f} {t:>10.4f} {sign}{delta:>9.4f}")
|
| 191 |
+
else:
|
| 192 |
+
print(f"\n No trained policy at {POLICY_PATH}")
|
| 193 |
+
print(" Run python train.py first.")
|
| 194 |
+
|
| 195 |
+
# Save
|
| 196 |
+
os.makedirs("results", exist_ok=True)
|
| 197 |
+
with open("results/evaluation.json", "w") as f:
|
| 198 |
+
json.dump(results, f, indent=2)
|
| 199 |
+
print(f"\n Saved → results/evaluation.json")
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
if __name__ == "__main__":
|
| 203 |
+
main()
|
evaluate_r2.py
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
evaluate_r2.py — Round 2 Evaluation Script
|
| 3 |
+
============================================
|
| 4 |
+
Compares baseline (rule-based) vs trained LLM on both R1 and R2 tasks.
|
| 5 |
+
Produces the before/after improvement table judges want to see.
|
| 6 |
+
Saves results to results/r2_evaluation.json.
|
| 7 |
+
|
| 8 |
+
Usage:
|
| 9 |
+
# Baseline only (rule-based, no model needed):
|
| 10 |
+
python evaluate_r2.py --baseline-only
|
| 11 |
+
|
| 12 |
+
# Full comparison (trained model vs baseline):
|
| 13 |
+
python evaluate_r2.py --model results/trained_model
|
| 14 |
+
|
| 15 |
+
# Quick 1-episode-per-task run:
|
| 16 |
+
python evaluate_r2.py --model results/trained_model --episodes 1
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import argparse
|
| 22 |
+
import json
|
| 23 |
+
import os
|
| 24 |
+
import sys
|
| 25 |
+
import time
|
| 26 |
+
from pathlib import Path
|
| 27 |
+
|
| 28 |
+
import requests
|
| 29 |
+
|
| 30 |
+
ENV_BASE_URL = os.getenv("ENV_BASE_URL", "https://sejal-k-ai-sprint-manager.hf.space")
|
| 31 |
+
HF_TOKEN = os.getenv("HF_TOKEN", "")
|
| 32 |
+
API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
|
| 33 |
+
# NOTE: MODEL_NAME here is for INFERENCE comparison only.
|
| 34 |
+
# For TRAINING, use Qwen/Qwen2.5-1.5B-Instruct (loaded locally in train_llm.py).
|
| 35 |
+
MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-1.5B-Instruct")
|
| 36 |
+
|
| 37 |
+
RESULTS_DIR = Path("results")
|
| 38 |
+
RESULTS_DIR.mkdir(exist_ok=True)
|
| 39 |
+
|
| 40 |
+
R1_TASKS = ["easy_sprint", "medium_sprint", "hard_sprint"]
|
| 41 |
+
R2_TASKS = ["project_easy", "project_medium", "project_hard"]
|
| 42 |
+
|
| 43 |
+
# ── Measured baselines (FINAL — do not change) ────────────────────────────────
|
| 44 |
+
# R1: Llama-3.1-8B zero-shot inference (inference.py), measured 2025-01
|
| 45 |
+
LLAMA_BASELINE_R1 = {
|
| 46 |
+
"easy_sprint": 0.0100,
|
| 47 |
+
"medium_sprint": 0.4583,
|
| 48 |
+
"hard_sprint": 0.0100,
|
| 49 |
+
"average": 0.1594,
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
# R2: Llama-3.1-8B zero-shot inference (inference_r2.py), measured 2025-01
|
| 53 |
+
LLAMA_BASELINE_R2 = {
|
| 54 |
+
"project_easy": 0.3198,
|
| 55 |
+
"project_medium": 0.2443,
|
| 56 |
+
"project_hard": 0.2520,
|
| 57 |
+
"average": 0.2720,
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
# Training model: Qwen/Qwen2.5-1.5B-Instruct (GRPO, local 4-bit QLoRA)
|
| 61 |
+
TRAINING_MODEL = "Qwen/Qwen2.5-1.5B-Instruct"
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
# ── Rule-based policies ───────────────────────────────────────────────────────
|
| 65 |
+
|
| 66 |
+
def rule_based_r1(obs: dict) -> dict:
|
| 67 |
+
tasks = obs.get("tasks", [])
|
| 68 |
+
devs = obs.get("developers", [])
|
| 69 |
+
avail = [d for d in devs if d["is_available"] and d["current_load"] < d["capacity"]]
|
| 70 |
+
backlog = sorted([t for t in tasks if t["status"] == "backlog"],
|
| 71 |
+
key=lambda t: (t["priority"], t["deadline"]))
|
| 72 |
+
for task in backlog:
|
| 73 |
+
match = [d for d in avail if d["skill"] == task.get("required_skill") or d["skill"] == "fullstack"]
|
| 74 |
+
dev = match[0] if match else (avail[0] if avail else None)
|
| 75 |
+
if dev:
|
| 76 |
+
return {"action_type": "assign", "task_id": task["id"],
|
| 77 |
+
"dev_id": dev["id"], "new_priority": None}
|
| 78 |
+
return {"action_type": "skip", "task_id": None, "dev_id": None, "new_priority": None}
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def rule_based_r2(obs: dict) -> dict:
|
| 82 |
+
tasks = obs.get("tasks", [])
|
| 83 |
+
devs = obs.get("developers", [])
|
| 84 |
+
done_ids = {t["id"] for t in tasks if t["status"] == "done"}
|
| 85 |
+
avail = [d for d in devs if d["is_available"] and d["current_load"] < d["capacity"] * 2]
|
| 86 |
+
|
| 87 |
+
def best_dev(task):
|
| 88 |
+
m = [d for d in avail if d["skill"] == task.get("required_skill") or d["skill"] == "fullstack"]
|
| 89 |
+
return m[0] if m else (avail[0] if avail else None)
|
| 90 |
+
|
| 91 |
+
for inst in [i for i in obs.get("instruction_queue", []) if not i.get("followed", False)]:
|
| 92 |
+
for tid in inst.get("affects_tasks", []):
|
| 93 |
+
t = next((t for t in tasks if t["id"] == tid and t["status"] == "backlog"), None)
|
| 94 |
+
if t and all(d in done_ids for d in t.get("metadata", {}).get("depends_on", [])):
|
| 95 |
+
dev = best_dev(t)
|
| 96 |
+
if dev:
|
| 97 |
+
return {"action_type": "assign", "task_id": t["id"],
|
| 98 |
+
"dev_id": dev["id"], "new_priority": None}
|
| 99 |
+
|
| 100 |
+
backlog = sorted([t for t in tasks if t["status"] == "backlog"],
|
| 101 |
+
key=lambda t: (t["priority"], t["deadline"]))
|
| 102 |
+
for t in backlog:
|
| 103 |
+
if all(d in done_ids for d in t.get("metadata", {}).get("depends_on", [])):
|
| 104 |
+
dev = best_dev(t)
|
| 105 |
+
if dev:
|
| 106 |
+
return {"action_type": "assign", "task_id": t["id"],
|
| 107 |
+
"dev_id": dev["id"], "new_priority": None}
|
| 108 |
+
|
| 109 |
+
return {"action_type": "skip", "task_id": None, "dev_id": None, "new_priority": None}
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
# ── Score calculators ─────────────────────────────────────────────────────────
|
| 113 |
+
|
| 114 |
+
def score_r1_obs(obs: dict) -> float:
|
| 115 |
+
"""Extract R1 final score from terminal observation."""
|
| 116 |
+
done = sum(1 for t in obs.get("tasks", []) if t["status"] == "done")
|
| 117 |
+
total = len(obs.get("tasks", [])) or 1
|
| 118 |
+
missed = sum(1 for t in obs.get("tasks", []) if t["status"] == "missed")
|
| 119 |
+
raw = done / total - missed / total * 0.3
|
| 120 |
+
return round(max(0.01, min(0.99, raw)), 4)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def score_r2_obs(obs: dict) -> float:
|
| 124 |
+
"""Compute R2 project score from terminal observation.
|
| 125 |
+
Formula: delivery×0.55 + instruction_following×0.30 + team_health×0.15
|
| 126 |
+
"""
|
| 127 |
+
tasks_total = len(obs.get("tasks", [])) or 1
|
| 128 |
+
tasks_done = obs.get("tasks_completed", 0)
|
| 129 |
+
inst_score = obs.get("instruction_following_score", 0.01)
|
| 130 |
+
delivery_rate = tasks_done / tasks_total
|
| 131 |
+
debt_count = len(obs.get("tech_debt", []))
|
| 132 |
+
team_health = max(0.01, 1.0 - debt_count * 0.02)
|
| 133 |
+
raw = delivery_rate * 0.55 + inst_score * 0.30 + team_health * 0.15
|
| 134 |
+
return round(max(0.01, min(0.99, raw)), 4)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
# ── Episode runners ───────────────────────────────────────────────────────────
|
| 138 |
+
|
| 139 |
+
def run_r1_episode(r1_client, task_name: str, policy_fn) -> dict:
|
| 140 |
+
"""Run one R1 episode. Calls /step directly as dict to avoid model_dump() issue."""
|
| 141 |
+
import requests as _req
|
| 142 |
+
obs = r1_client.reset(task_name=task_name, seed=42)
|
| 143 |
+
rewards, actions = [], []
|
| 144 |
+
base_url = r1_client.base_url
|
| 145 |
+
for _ in range(12):
|
| 146 |
+
if obs.get("done", False):
|
| 147 |
+
break
|
| 148 |
+
action = policy_fn(obs)
|
| 149 |
+
resp = _req.post(f"{base_url}/step", json={"action": action}, timeout=30)
|
| 150 |
+
resp.raise_for_status()
|
| 151 |
+
result = resp.json()
|
| 152 |
+
obs = result["observation"]
|
| 153 |
+
rewards.append(result["reward"])
|
| 154 |
+
actions.append(action["action_type"])
|
| 155 |
+
if result["done"]:
|
| 156 |
+
break
|
| 157 |
+
return {
|
| 158 |
+
"task": task_name,
|
| 159 |
+
"score": score_r1_obs(obs),
|
| 160 |
+
"cumulative_reward": round(sum(rewards), 4),
|
| 161 |
+
"steps": len(rewards),
|
| 162 |
+
"tasks_completed": obs.get("tasks_completed", 0),
|
| 163 |
+
"tasks_missed": obs.get("tasks_missed", 0),
|
| 164 |
+
"action_breakdown": {a: actions.count(a) for a in set(actions)},
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def run_r2_episode(r2_client, task_name: str, policy_fn) -> dict:
|
| 169 |
+
obs = r2_client.reset(task_name=task_name, seed=42)
|
| 170 |
+
rewards, actions, sprint_rewards = [], [], []
|
| 171 |
+
for _ in range(60):
|
| 172 |
+
if obs.get("done", False):
|
| 173 |
+
break
|
| 174 |
+
action = policy_fn(obs)
|
| 175 |
+
result = r2_client.step(action)
|
| 176 |
+
obs = result.observation if hasattr(result, "observation") else result["observation"]
|
| 177 |
+
rew = result.reward if hasattr(result, "reward") else result["reward"]
|
| 178 |
+
done = result.done if hasattr(result, "done") else result["done"]
|
| 179 |
+
rewards.append(rew)
|
| 180 |
+
actions.append(action["action_type"])
|
| 181 |
+
sprint_rewards = obs.get("sprint_rewards", [])
|
| 182 |
+
if done:
|
| 183 |
+
break
|
| 184 |
+
return {
|
| 185 |
+
"task": task_name,
|
| 186 |
+
"score": score_r2_obs(obs),
|
| 187 |
+
"cumulative_reward": round(sum(rewards), 4),
|
| 188 |
+
"steps": len(rewards),
|
| 189 |
+
"tasks_completed": obs.get("tasks_completed", 0),
|
| 190 |
+
"tasks_missed": obs.get("tasks_missed", 0),
|
| 191 |
+
"instruction_following_score": obs.get("instruction_following_score", 0.0),
|
| 192 |
+
"tech_debt_count": len(obs.get("tech_debt", [])),
|
| 193 |
+
"sprint_rewards": sprint_rewards,
|
| 194 |
+
"action_breakdown": {a: actions.count(a) for a in set(actions)},
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
# ── LLM policy builders ───────────────────────────────────────────────────────
|
| 199 |
+
|
| 200 |
+
def _build_api_policy(model_id: str, system_prompt: str):
|
| 201 |
+
"""Build an LLM policy that calls the HF router API."""
|
| 202 |
+
from openai import OpenAI
|
| 203 |
+
client = OpenAI(api_key=HF_TOKEN, base_url=API_BASE_URL)
|
| 204 |
+
|
| 205 |
+
def policy(obs: dict) -> dict:
|
| 206 |
+
import json as _json
|
| 207 |
+
user_msg = f"Current state:\n{_json.dumps(obs, indent=2)}\nOutput JSON action only."
|
| 208 |
+
try:
|
| 209 |
+
resp = client.chat.completions.create(
|
| 210 |
+
model=model_id,
|
| 211 |
+
messages=[{"role": "system", "content": system_prompt},
|
| 212 |
+
{"role": "user", "content": user_msg}],
|
| 213 |
+
max_tokens=60,
|
| 214 |
+
temperature=0.1,
|
| 215 |
+
)
|
| 216 |
+
raw = resp.choices[0].message.content.strip()
|
| 217 |
+
start = raw.find("{")
|
| 218 |
+
end = raw.rfind("}") + 1
|
| 219 |
+
if start >= 0 and end > start:
|
| 220 |
+
return _json.loads(raw[start:end])
|
| 221 |
+
except Exception:
|
| 222 |
+
pass
|
| 223 |
+
return {"action_type": "skip", "task_id": None, "dev_id": None, "new_priority": None}
|
| 224 |
+
|
| 225 |
+
return policy
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def build_llm_policy(model_path: str, system_prompt: str):
|
| 229 |
+
"""Build R2 LLM policy — tries local model first, falls back to API."""
|
| 230 |
+
# Try local model (after training)
|
| 231 |
+
local_path = Path(model_path)
|
| 232 |
+
if local_path.exists():
|
| 233 |
+
try:
|
| 234 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 235 |
+
import torch, json as _json
|
| 236 |
+
tokenizer = AutoTokenizer.from_pretrained(str(local_path))
|
| 237 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 238 |
+
str(local_path), torch_dtype=torch.float16, device_map="auto"
|
| 239 |
+
)
|
| 240 |
+
print(f"[INFO] Loaded local model from {local_path}", flush=True)
|
| 241 |
+
|
| 242 |
+
def local_policy(obs: dict) -> dict:
|
| 243 |
+
prompt = f"{system_prompt}\n\nState:\n{_json.dumps(obs)}\nAction:"
|
| 244 |
+
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
| 245 |
+
with torch.no_grad():
|
| 246 |
+
outputs = model.generate(**inputs, max_new_tokens=60, temperature=0.1, do_sample=True)
|
| 247 |
+
raw = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
|
| 248 |
+
try:
|
| 249 |
+
start = raw.find("{"); end = raw.rfind("}") + 1
|
| 250 |
+
if start >= 0 and end > start:
|
| 251 |
+
return _json.loads(raw[start:end])
|
| 252 |
+
except Exception:
|
| 253 |
+
pass
|
| 254 |
+
return {"action_type": "skip", "task_id": None, "dev_id": None, "new_priority": None}
|
| 255 |
+
|
| 256 |
+
return local_policy
|
| 257 |
+
except Exception as e:
|
| 258 |
+
print(f"[WARN] Could not load local model: {e}", flush=True)
|
| 259 |
+
|
| 260 |
+
# Fall back to HF API (model_path is an HF model ID like "sejal-k/ai-sprint-manager-trained")
|
| 261 |
+
print(f"[INFO] Using HF API for model {model_path}", flush=True)
|
| 262 |
+
return _build_api_policy(model_path, system_prompt)
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
# ── Main evaluation ───────────────────────────────────────────────────────────
|
| 266 |
+
|
| 267 |
+
def evaluate(model_path: str | None = None, n_episodes: int = 3, baseline_only: bool = False):
|
| 268 |
+
from client import SprintEnvClient
|
| 269 |
+
from project_client import ProjectEnvClient
|
| 270 |
+
|
| 271 |
+
print(f"\n{'='*60}", flush=True)
|
| 272 |
+
print(f" AI Sprint Manager — Evaluation", flush=True)
|
| 273 |
+
print(f" Env: {ENV_BASE_URL}", flush=True)
|
| 274 |
+
print(f" Model: {model_path or 'rule-based only'}", flush=True)
|
| 275 |
+
print(f" Training model: {TRAINING_MODEL}", flush=True)
|
| 276 |
+
print(f"{'='*60}", flush=True)
|
| 277 |
+
|
| 278 |
+
# Health check
|
| 279 |
+
try:
|
| 280 |
+
r = requests.get(f"{ENV_BASE_URL}/health", timeout=10)
|
| 281 |
+
r.raise_for_status()
|
| 282 |
+
r2 = requests.get(f"{ENV_BASE_URL}/project/health", timeout=10)
|
| 283 |
+
r2.raise_for_status()
|
| 284 |
+
print(f"[OK] Environment is live", flush=True)
|
| 285 |
+
except Exception as e:
|
| 286 |
+
print(f"[ERROR] Server unreachable: {e}", flush=True)
|
| 287 |
+
sys.exit(1)
|
| 288 |
+
|
| 289 |
+
results = {
|
| 290 |
+
"metadata": {
|
| 291 |
+
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
| 292 |
+
"model": model_path or "rule-based",
|
| 293 |
+
"training_model": TRAINING_MODEL,
|
| 294 |
+
"env_url": ENV_BASE_URL,
|
| 295 |
+
"n_episodes": n_episodes,
|
| 296 |
+
"baseline_only": baseline_only,
|
| 297 |
+
},
|
| 298 |
+
# Measured Llama-3.1-8B zero-shot baselines (FINAL)
|
| 299 |
+
"r1_llama_baseline": LLAMA_BASELINE_R1,
|
| 300 |
+
"r2_llama_baseline": LLAMA_BASELINE_R2,
|
| 301 |
+
# Live run results
|
| 302 |
+
"r1_rule_based": {},
|
| 303 |
+
"r1_llm": {},
|
| 304 |
+
"r2_rule_based": {},
|
| 305 |
+
"r2_llm": {},
|
| 306 |
+
"improvement": {},
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
r1_client = SprintEnvClient(base_url=ENV_BASE_URL)
|
| 310 |
+
r2_client = ProjectEnvClient(base_url=ENV_BASE_URL)
|
| 311 |
+
|
| 312 |
+
# ── R1 rule-based baseline ────────────────────────────────────────────────
|
| 313 |
+
print(f"\n{'─'*55}", flush=True)
|
| 314 |
+
print(f" R1 — Rule-based baseline", flush=True)
|
| 315 |
+
print(f"{'─'*55}", flush=True)
|
| 316 |
+
for task in R1_TASKS:
|
| 317 |
+
ep_results = []
|
| 318 |
+
for ep in range(n_episodes):
|
| 319 |
+
r = run_r1_episode(r1_client, task, rule_based_r1)
|
| 320 |
+
ep_results.append(r)
|
| 321 |
+
print(f" {task} ep{ep+1}: score={r['score']:.4f} "
|
| 322 |
+
f"done={r['tasks_completed']} reward={r['cumulative_reward']:.2f}", flush=True)
|
| 323 |
+
avg_score = sum(r["score"] for r in ep_results) / n_episodes
|
| 324 |
+
results["r1_rule_based"][task] = {
|
| 325 |
+
"avg_score": round(avg_score, 4),
|
| 326 |
+
"episodes": ep_results,
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
if not baseline_only and model_path:
|
| 330 |
+
from inference_r2 import R2_SYSTEM_PROMPT
|
| 331 |
+
|
| 332 |
+
R1_SYSTEM_PROMPT = (
|
| 333 |
+
"You are an expert Tech Lead managing an agile sprint. "
|
| 334 |
+
"Output a JSON action: {\"action_type\":\"<assign|reassign|reprioritize|unblock|skip>\","
|
| 335 |
+
"\"task_id\":\"<id or null>\",\"dev_id\":\"<id or null>\",\"new_priority\":<1-5 or null>}. "
|
| 336 |
+
"Only output JSON. Assign backlog tasks to available developers, skill match preferred."
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
llm_r1_policy = _build_api_policy(model_path, R1_SYSTEM_PROMPT)
|
| 340 |
+
llm_r2_policy = build_llm_policy(model_path, R2_SYSTEM_PROMPT)
|
| 341 |
+
|
| 342 |
+
# ── R1 LLM ───────────────────────────────────────────────────────────
|
| 343 |
+
print(f"\n{'─'*55}", flush=True)
|
| 344 |
+
print(f" R1 — LLM ({model_path})", flush=True)
|
| 345 |
+
print(f"{'─'*55}", flush=True)
|
| 346 |
+
for task in R1_TASKS:
|
| 347 |
+
ep_results = []
|
| 348 |
+
for ep in range(n_episodes):
|
| 349 |
+
r = run_r1_episode(r1_client, task, llm_r1_policy)
|
| 350 |
+
ep_results.append(r)
|
| 351 |
+
print(f" {task} ep{ep+1}: score={r['score']:.4f}", flush=True)
|
| 352 |
+
avg_score = sum(r["score"] for r in ep_results) / n_episodes
|
| 353 |
+
results["r1_llm"][task] = {
|
| 354 |
+
"avg_score": round(avg_score, 4),
|
| 355 |
+
"episodes": ep_results,
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
# ── R2 LLM ───────────────────────────────────────────────────────────
|
| 359 |
+
print(f"\n{'─'*55}", flush=True)
|
| 360 |
+
print(f" R2 — LLM ({model_path})", flush=True)
|
| 361 |
+
print(f"{'─'*55}", flush=True)
|
| 362 |
+
for task in R2_TASKS:
|
| 363 |
+
ep_results = []
|
| 364 |
+
for ep in range(n_episodes):
|
| 365 |
+
r = run_r2_episode(r2_client, task, llm_r2_policy)
|
| 366 |
+
ep_results.append(r)
|
| 367 |
+
print(f" {task} ep{ep+1}: score={r['score']:.4f} "
|
| 368 |
+
f"inst={r['instruction_following_score']:.2f}", flush=True)
|
| 369 |
+
avg_score = sum(r["score"] for r in ep_results) / n_episodes
|
| 370 |
+
results["r2_llm"][task] = {
|
| 371 |
+
"avg_score": round(avg_score, 4),
|
| 372 |
+
"episodes": ep_results,
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
# ── Improvement table ─────────────────────────────────────────────────
|
| 376 |
+
for task in R2_TASKS:
|
| 377 |
+
base_llama = LLAMA_BASELINE_R2.get(task, 0)
|
| 378 |
+
llm = results["r2_llm"].get(task, {}).get("avg_score", base_llama)
|
| 379 |
+
delta_vs_llama = round(llm - base_llama, 4)
|
| 380 |
+
results["improvement"][task] = {
|
| 381 |
+
"llama_baseline": base_llama,
|
| 382 |
+
"trained_llm": llm,
|
| 383 |
+
"delta_vs_llama": delta_vs_llama,
|
| 384 |
+
"pct_gain_vs_llama": round(delta_vs_llama / max(base_llama, 0.01) * 100, 1),
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
r1_client.close()
|
| 388 |
+
r2_client.close()
|
| 389 |
+
|
| 390 |
+
_print_summary(results, baseline_only)
|
| 391 |
+
|
| 392 |
+
out_path = RESULTS_DIR / "r2_evaluation.json"
|
| 393 |
+
with open(out_path, "w") as f:
|
| 394 |
+
json.dump(results, f, indent=2)
|
| 395 |
+
print(f"\n[INFO] Results saved to {out_path}", flush=True)
|
| 396 |
+
return results
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def _print_summary(results: dict, baseline_only: bool):
|
| 400 |
+
print(f"\n{'='*65}", flush=True)
|
| 401 |
+
print(f" EVALUATION SUMMARY", flush=True)
|
| 402 |
+
print(f"{'='*65}", flush=True)
|
| 403 |
+
|
| 404 |
+
print(f"\n{'R1 SCORES (Llama-3.1-8B zero-shot — measured baseline)':─<65}", flush=True)
|
| 405 |
+
print(f" {'Task':<22} {'Llama Baseline':>15} {'LLM Trained':>12}", flush=True)
|
| 406 |
+
for task in ["easy_sprint", "medium_sprint", "hard_sprint"]:
|
| 407 |
+
llama = results["r1_llama_baseline"].get(task, 0)
|
| 408 |
+
llm = results["r1_llm"].get(task, {}).get("avg_score", 0)
|
| 409 |
+
llm_s = f"{llm:.4f}" if llm else "—"
|
| 410 |
+
print(f" {task:<22} {llama:>15.4f} {llm_s:>12}", flush=True)
|
| 411 |
+
avg = results["r1_llama_baseline"].get("average", 0)
|
| 412 |
+
print(f" {'AVERAGE':<22} {avg:>15.4f}", flush=True)
|
| 413 |
+
|
| 414 |
+
print(f"\n{'R2 SCORES':─<65}", flush=True)
|
| 415 |
+
print(f" {'Task':<22} {'Llama Baseline':>15} {'LLM Trained':>12} {'Δ vs Llama':>10}", flush=True)
|
| 416 |
+
for task in ["project_easy", "project_medium", "project_hard"]:
|
| 417 |
+
llama = results["r2_llama_baseline"].get(task, 0)
|
| 418 |
+
llm = results["r2_llm"].get(task, {}).get("avg_score", 0)
|
| 419 |
+
imp = results["improvement"].get(task, {})
|
| 420 |
+
delta = imp.get("delta_vs_llama", 0)
|
| 421 |
+
llm_s = f"{llm:.4f}" if llm else "—"
|
| 422 |
+
delta_s = f"+{delta:.4f}" if delta > 0 else (f"{delta:.4f}" if delta else "—")
|
| 423 |
+
print(f" {task:<22} {llama:>15.4f} {llm_s:>12} {delta_s:>10}", flush=True)
|
| 424 |
+
avg_r2 = results["r2_llama_baseline"].get("average", 0)
|
| 425 |
+
print(f" {'AVERAGE':<22} {avg_r2:>15.4f}", flush=True)
|
| 426 |
+
|
| 427 |
+
print(f"\n{'='*65}", flush=True)
|
| 428 |
+
print(f" Training model: Qwen/Qwen2.5-1.5B-Instruct (GRPO, 4-bit QLoRA)", flush=True)
|
| 429 |
+
print(f" Baselines: Llama-3.1-8B zero-shot (via HF Router)", flush=True)
|
| 430 |
+
print(f"{'='*65}", flush=True)
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
# ── CLI ───────────────────────────────────────────────────────────────────────
|
| 434 |
+
|
| 435 |
+
def main():
|
| 436 |
+
parser = argparse.ArgumentParser(description="Evaluate R1+R2 before/after training")
|
| 437 |
+
parser.add_argument("--model", type=str, default=None,
|
| 438 |
+
help="Path to trained model dir or HF model ID")
|
| 439 |
+
parser.add_argument("--baseline-only", action="store_true",
|
| 440 |
+
help="Run rule-based baseline only (no model needed)")
|
| 441 |
+
parser.add_argument("--episodes", type=int, default=3,
|
| 442 |
+
help="Episodes per task (default: 3)")
|
| 443 |
+
args = parser.parse_args()
|
| 444 |
+
|
| 445 |
+
if not args.baseline_only and not args.model:
|
| 446 |
+
print("[INFO] No --model specified. Running baseline-only evaluation.", flush=True)
|
| 447 |
+
args.baseline_only = True
|
| 448 |
+
|
| 449 |
+
evaluate(
|
| 450 |
+
model_path=args.model,
|
| 451 |
+
n_episodes=args.episodes,
|
| 452 |
+
baseline_only=args.baseline_only,
|
| 453 |
+
)
|
| 454 |
+
|
| 455 |
+
|
| 456 |
+
if __name__ == "__main__":
|
| 457 |
+
main()
|
huggingface_tokenizers_cache/CACHEDIR.TAG
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Signature: 8a477f597d28d172789f06886806bc55
|
| 2 |
+
# This file is a cache directory tag created by huggingface_hub.
|
| 3 |
+
# For information about cache directory tags, see:
|
| 4 |
+
# https://bford.info/cachedir/
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/.no_exist/3d254dbee5e3beae81bb8a717ad3a03427a09d26/chat_template.jinja
ADDED
|
File without changes
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/31349551d90c7606f325fe0f11bbb8bd5fa0d7c7
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/4783fe10ac3adce15ac8f358ef5462739852c569
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/482ced4679301bf287ebb310bdd1790eb4514232
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"</tool_call>": 151658,
|
| 3 |
+
"<tool_call>": 151657,
|
| 4 |
+
"<|box_end|>": 151649,
|
| 5 |
+
"<|box_start|>": 151648,
|
| 6 |
+
"<|endoftext|>": 151643,
|
| 7 |
+
"<|file_sep|>": 151664,
|
| 8 |
+
"<|fim_middle|>": 151660,
|
| 9 |
+
"<|fim_pad|>": 151662,
|
| 10 |
+
"<|fim_prefix|>": 151659,
|
| 11 |
+
"<|fim_suffix|>": 151661,
|
| 12 |
+
"<|im_end|>": 151645,
|
| 13 |
+
"<|im_start|>": 151644,
|
| 14 |
+
"<|image_pad|>": 151655,
|
| 15 |
+
"<|object_ref_end|>": 151647,
|
| 16 |
+
"<|object_ref_start|>": 151646,
|
| 17 |
+
"<|quad_end|>": 151651,
|
| 18 |
+
"<|quad_start|>": 151650,
|
| 19 |
+
"<|repo_name|>": 151663,
|
| 20 |
+
"<|video_pad|>": 151656,
|
| 21 |
+
"<|vision_end|>": 151653,
|
| 22 |
+
"<|vision_pad|>": 151654,
|
| 23 |
+
"<|vision_start|>": 151652
|
| 24 |
+
}
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/76ac30de06e0158be805f42a4e63934bedb5a169
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_name_or_path": "Qwen/Qwen2.5-1.5B-Instruct",
|
| 3 |
+
"architectures": [
|
| 4 |
+
"Qwen2ForCausalLM"
|
| 5 |
+
],
|
| 6 |
+
"attention_dropout": 0.0,
|
| 7 |
+
"eos_token_id": 151645,
|
| 8 |
+
"hidden_act": "silu",
|
| 9 |
+
"hidden_size": 1536,
|
| 10 |
+
"initializer_range": 0.02,
|
| 11 |
+
"intermediate_size": 8960,
|
| 12 |
+
"max_position_embeddings": 32768,
|
| 13 |
+
"max_window_layers": 21,
|
| 14 |
+
"model_type": "qwen2",
|
| 15 |
+
"num_attention_heads": 12,
|
| 16 |
+
"num_hidden_layers": 28,
|
| 17 |
+
"num_key_value_heads": 2,
|
| 18 |
+
"pad_token_id": 151654,
|
| 19 |
+
"quantization_config": {
|
| 20 |
+
"_load_in_4bit": true,
|
| 21 |
+
"_load_in_8bit": false,
|
| 22 |
+
"bnb_4bit_compute_dtype": "bfloat16",
|
| 23 |
+
"bnb_4bit_quant_storage": "uint8",
|
| 24 |
+
"bnb_4bit_quant_type": "nf4",
|
| 25 |
+
"bnb_4bit_use_double_quant": true,
|
| 26 |
+
"llm_int8_enable_fp32_cpu_offload": false,
|
| 27 |
+
"llm_int8_has_fp16_weight": false,
|
| 28 |
+
"llm_int8_skip_modules": [
|
| 29 |
+
"lm_head",
|
| 30 |
+
"multi_modal_projector",
|
| 31 |
+
"merger",
|
| 32 |
+
"modality_projection",
|
| 33 |
+
"model.layers.0.self_attn",
|
| 34 |
+
"model.layers.1.mlp",
|
| 35 |
+
"model.layers.2.mlp",
|
| 36 |
+
"model.layers.3.mlp",
|
| 37 |
+
"model.layers.7.mlp",
|
| 38 |
+
"model.layers.24.mlp",
|
| 39 |
+
"model.layers.26.mlp",
|
| 40 |
+
"model.layers.15.self_attn"
|
| 41 |
+
],
|
| 42 |
+
"llm_int8_threshold": 6.0,
|
| 43 |
+
"load_in_4bit": true,
|
| 44 |
+
"load_in_8bit": false,
|
| 45 |
+
"quant_method": "bitsandbytes"
|
| 46 |
+
},
|
| 47 |
+
"rms_norm_eps": 1e-06,
|
| 48 |
+
"rope_scaling": null,
|
| 49 |
+
"rope_theta": 1000000.0,
|
| 50 |
+
"sliding_window": null,
|
| 51 |
+
"tie_word_embeddings": true,
|
| 52 |
+
"torch_dtype": "bfloat16",
|
| 53 |
+
"transformers_version": "4.49.0.dev0",
|
| 54 |
+
"unsloth_fixed": true,
|
| 55 |
+
"use_cache": true,
|
| 56 |
+
"use_sliding_window": false,
|
| 57 |
+
"vocab_size": 151936
|
| 58 |
+
}
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/9b8043f10c758210957b050c77f14d6282f33a52
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"additional_special_tokens": [
|
| 3 |
+
"<|im_start|>",
|
| 4 |
+
"<|im_end|>",
|
| 5 |
+
"<|object_ref_start|>",
|
| 6 |
+
"<|object_ref_end|>",
|
| 7 |
+
"<|box_start|>",
|
| 8 |
+
"<|box_end|>",
|
| 9 |
+
"<|quad_start|>",
|
| 10 |
+
"<|quad_end|>",
|
| 11 |
+
"<|vision_start|>",
|
| 12 |
+
"<|vision_end|>",
|
| 13 |
+
"<|vision_pad|>",
|
| 14 |
+
"<|image_pad|>",
|
| 15 |
+
"<|video_pad|>"
|
| 16 |
+
],
|
| 17 |
+
"eos_token": {
|
| 18 |
+
"content": "<|im_end|>",
|
| 19 |
+
"lstrip": false,
|
| 20 |
+
"normalized": false,
|
| 21 |
+
"rstrip": false,
|
| 22 |
+
"single_word": false
|
| 23 |
+
},
|
| 24 |
+
"pad_token": {
|
| 25 |
+
"content": "<|vision_pad|>",
|
| 26 |
+
"lstrip": false,
|
| 27 |
+
"normalized": false,
|
| 28 |
+
"rstrip": false,
|
| 29 |
+
"single_word": false
|
| 30 |
+
}
|
| 31 |
+
}
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/9c5ae00e602b8860cbd784ba82a8aa14e8feecec692e7076590d014d7b7fdafa
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9c5ae00e602b8860cbd784ba82a8aa14e8feecec692e7076590d014d7b7fdafa
|
| 3 |
+
size 11421896
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/blobs/a5de2eb6cac65b89846ac1bcfb837a3d83459f40
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_bos_token": false,
|
| 3 |
+
"add_prefix_space": false,
|
| 4 |
+
"added_tokens_decoder": {
|
| 5 |
+
"151643": {
|
| 6 |
+
"content": "<|endoftext|>",
|
| 7 |
+
"lstrip": false,
|
| 8 |
+
"normalized": false,
|
| 9 |
+
"rstrip": false,
|
| 10 |
+
"single_word": false,
|
| 11 |
+
"special": true
|
| 12 |
+
},
|
| 13 |
+
"151644": {
|
| 14 |
+
"content": "<|im_start|>",
|
| 15 |
+
"lstrip": false,
|
| 16 |
+
"normalized": false,
|
| 17 |
+
"rstrip": false,
|
| 18 |
+
"single_word": false,
|
| 19 |
+
"special": true
|
| 20 |
+
},
|
| 21 |
+
"151645": {
|
| 22 |
+
"content": "<|im_end|>",
|
| 23 |
+
"lstrip": false,
|
| 24 |
+
"normalized": false,
|
| 25 |
+
"rstrip": false,
|
| 26 |
+
"single_word": false,
|
| 27 |
+
"special": true
|
| 28 |
+
},
|
| 29 |
+
"151646": {
|
| 30 |
+
"content": "<|object_ref_start|>",
|
| 31 |
+
"lstrip": false,
|
| 32 |
+
"normalized": false,
|
| 33 |
+
"rstrip": false,
|
| 34 |
+
"single_word": false,
|
| 35 |
+
"special": true
|
| 36 |
+
},
|
| 37 |
+
"151647": {
|
| 38 |
+
"content": "<|object_ref_end|>",
|
| 39 |
+
"lstrip": false,
|
| 40 |
+
"normalized": false,
|
| 41 |
+
"rstrip": false,
|
| 42 |
+
"single_word": false,
|
| 43 |
+
"special": true
|
| 44 |
+
},
|
| 45 |
+
"151648": {
|
| 46 |
+
"content": "<|box_start|>",
|
| 47 |
+
"lstrip": false,
|
| 48 |
+
"normalized": false,
|
| 49 |
+
"rstrip": false,
|
| 50 |
+
"single_word": false,
|
| 51 |
+
"special": true
|
| 52 |
+
},
|
| 53 |
+
"151649": {
|
| 54 |
+
"content": "<|box_end|>",
|
| 55 |
+
"lstrip": false,
|
| 56 |
+
"normalized": false,
|
| 57 |
+
"rstrip": false,
|
| 58 |
+
"single_word": false,
|
| 59 |
+
"special": true
|
| 60 |
+
},
|
| 61 |
+
"151650": {
|
| 62 |
+
"content": "<|quad_start|>",
|
| 63 |
+
"lstrip": false,
|
| 64 |
+
"normalized": false,
|
| 65 |
+
"rstrip": false,
|
| 66 |
+
"single_word": false,
|
| 67 |
+
"special": true
|
| 68 |
+
},
|
| 69 |
+
"151651": {
|
| 70 |
+
"content": "<|quad_end|>",
|
| 71 |
+
"lstrip": false,
|
| 72 |
+
"normalized": false,
|
| 73 |
+
"rstrip": false,
|
| 74 |
+
"single_word": false,
|
| 75 |
+
"special": true
|
| 76 |
+
},
|
| 77 |
+
"151652": {
|
| 78 |
+
"content": "<|vision_start|>",
|
| 79 |
+
"lstrip": false,
|
| 80 |
+
"normalized": false,
|
| 81 |
+
"rstrip": false,
|
| 82 |
+
"single_word": false,
|
| 83 |
+
"special": true
|
| 84 |
+
},
|
| 85 |
+
"151653": {
|
| 86 |
+
"content": "<|vision_end|>",
|
| 87 |
+
"lstrip": false,
|
| 88 |
+
"normalized": false,
|
| 89 |
+
"rstrip": false,
|
| 90 |
+
"single_word": false,
|
| 91 |
+
"special": true
|
| 92 |
+
},
|
| 93 |
+
"151654": {
|
| 94 |
+
"content": "<|vision_pad|>",
|
| 95 |
+
"lstrip": false,
|
| 96 |
+
"normalized": false,
|
| 97 |
+
"rstrip": false,
|
| 98 |
+
"single_word": false,
|
| 99 |
+
"special": true
|
| 100 |
+
},
|
| 101 |
+
"151655": {
|
| 102 |
+
"content": "<|image_pad|>",
|
| 103 |
+
"lstrip": false,
|
| 104 |
+
"normalized": false,
|
| 105 |
+
"rstrip": false,
|
| 106 |
+
"single_word": false,
|
| 107 |
+
"special": true
|
| 108 |
+
},
|
| 109 |
+
"151656": {
|
| 110 |
+
"content": "<|video_pad|>",
|
| 111 |
+
"lstrip": false,
|
| 112 |
+
"normalized": false,
|
| 113 |
+
"rstrip": false,
|
| 114 |
+
"single_word": false,
|
| 115 |
+
"special": true
|
| 116 |
+
},
|
| 117 |
+
"151657": {
|
| 118 |
+
"content": "<tool_call>",
|
| 119 |
+
"lstrip": false,
|
| 120 |
+
"normalized": false,
|
| 121 |
+
"rstrip": false,
|
| 122 |
+
"single_word": false,
|
| 123 |
+
"special": false
|
| 124 |
+
},
|
| 125 |
+
"151658": {
|
| 126 |
+
"content": "</tool_call>",
|
| 127 |
+
"lstrip": false,
|
| 128 |
+
"normalized": false,
|
| 129 |
+
"rstrip": false,
|
| 130 |
+
"single_word": false,
|
| 131 |
+
"special": false
|
| 132 |
+
},
|
| 133 |
+
"151659": {
|
| 134 |
+
"content": "<|fim_prefix|>",
|
| 135 |
+
"lstrip": false,
|
| 136 |
+
"normalized": false,
|
| 137 |
+
"rstrip": false,
|
| 138 |
+
"single_word": false,
|
| 139 |
+
"special": false
|
| 140 |
+
},
|
| 141 |
+
"151660": {
|
| 142 |
+
"content": "<|fim_middle|>",
|
| 143 |
+
"lstrip": false,
|
| 144 |
+
"normalized": false,
|
| 145 |
+
"rstrip": false,
|
| 146 |
+
"single_word": false,
|
| 147 |
+
"special": false
|
| 148 |
+
},
|
| 149 |
+
"151661": {
|
| 150 |
+
"content": "<|fim_suffix|>",
|
| 151 |
+
"lstrip": false,
|
| 152 |
+
"normalized": false,
|
| 153 |
+
"rstrip": false,
|
| 154 |
+
"single_word": false,
|
| 155 |
+
"special": false
|
| 156 |
+
},
|
| 157 |
+
"151662": {
|
| 158 |
+
"content": "<|fim_pad|>",
|
| 159 |
+
"lstrip": false,
|
| 160 |
+
"normalized": false,
|
| 161 |
+
"rstrip": false,
|
| 162 |
+
"single_word": false,
|
| 163 |
+
"special": false
|
| 164 |
+
},
|
| 165 |
+
"151663": {
|
| 166 |
+
"content": "<|repo_name|>",
|
| 167 |
+
"lstrip": false,
|
| 168 |
+
"normalized": false,
|
| 169 |
+
"rstrip": false,
|
| 170 |
+
"single_word": false,
|
| 171 |
+
"special": false
|
| 172 |
+
},
|
| 173 |
+
"151664": {
|
| 174 |
+
"content": "<|file_sep|>",
|
| 175 |
+
"lstrip": false,
|
| 176 |
+
"normalized": false,
|
| 177 |
+
"rstrip": false,
|
| 178 |
+
"single_word": false,
|
| 179 |
+
"special": false
|
| 180 |
+
}
|
| 181 |
+
},
|
| 182 |
+
"additional_special_tokens": [
|
| 183 |
+
"<|im_start|>",
|
| 184 |
+
"<|im_end|>",
|
| 185 |
+
"<|object_ref_start|>",
|
| 186 |
+
"<|object_ref_end|>",
|
| 187 |
+
"<|box_start|>",
|
| 188 |
+
"<|box_end|>",
|
| 189 |
+
"<|quad_start|>",
|
| 190 |
+
"<|quad_end|>",
|
| 191 |
+
"<|vision_start|>",
|
| 192 |
+
"<|vision_end|>",
|
| 193 |
+
"<|vision_pad|>",
|
| 194 |
+
"<|image_pad|>",
|
| 195 |
+
"<|video_pad|>"
|
| 196 |
+
],
|
| 197 |
+
"bos_token": null,
|
| 198 |
+
"chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n",
|
| 199 |
+
"clean_up_tokenization_spaces": false,
|
| 200 |
+
"eos_token": "<|im_end|>",
|
| 201 |
+
"errors": "replace",
|
| 202 |
+
"extra_special_tokens": {},
|
| 203 |
+
"model_max_length": 32768,
|
| 204 |
+
"pad_token": "<|vision_pad|>",
|
| 205 |
+
"padding_side": "left",
|
| 206 |
+
"split_special_tokens": false,
|
| 207 |
+
"tokenizer_class": "Qwen2Tokenizer",
|
| 208 |
+
"unk_token": null
|
| 209 |
+
}
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/refs/main
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3d254dbee5e3beae81bb8a717ad3a03427a09d26
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/added_tokens.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"</tool_call>": 151658,
|
| 3 |
+
"<tool_call>": 151657,
|
| 4 |
+
"<|box_end|>": 151649,
|
| 5 |
+
"<|box_start|>": 151648,
|
| 6 |
+
"<|endoftext|>": 151643,
|
| 7 |
+
"<|file_sep|>": 151664,
|
| 8 |
+
"<|fim_middle|>": 151660,
|
| 9 |
+
"<|fim_pad|>": 151662,
|
| 10 |
+
"<|fim_prefix|>": 151659,
|
| 11 |
+
"<|fim_suffix|>": 151661,
|
| 12 |
+
"<|im_end|>": 151645,
|
| 13 |
+
"<|im_start|>": 151644,
|
| 14 |
+
"<|image_pad|>": 151655,
|
| 15 |
+
"<|object_ref_end|>": 151647,
|
| 16 |
+
"<|object_ref_start|>": 151646,
|
| 17 |
+
"<|quad_end|>": 151651,
|
| 18 |
+
"<|quad_start|>": 151650,
|
| 19 |
+
"<|repo_name|>": 151663,
|
| 20 |
+
"<|video_pad|>": 151656,
|
| 21 |
+
"<|vision_end|>": 151653,
|
| 22 |
+
"<|vision_pad|>": 151654,
|
| 23 |
+
"<|vision_start|>": 151652
|
| 24 |
+
}
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/config.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_name_or_path": "Qwen/Qwen2.5-1.5B-Instruct",
|
| 3 |
+
"architectures": [
|
| 4 |
+
"Qwen2ForCausalLM"
|
| 5 |
+
],
|
| 6 |
+
"attention_dropout": 0.0,
|
| 7 |
+
"eos_token_id": 151645,
|
| 8 |
+
"hidden_act": "silu",
|
| 9 |
+
"hidden_size": 1536,
|
| 10 |
+
"initializer_range": 0.02,
|
| 11 |
+
"intermediate_size": 8960,
|
| 12 |
+
"max_position_embeddings": 32768,
|
| 13 |
+
"max_window_layers": 21,
|
| 14 |
+
"model_type": "qwen2",
|
| 15 |
+
"num_attention_heads": 12,
|
| 16 |
+
"num_hidden_layers": 28,
|
| 17 |
+
"num_key_value_heads": 2,
|
| 18 |
+
"pad_token_id": 151654,
|
| 19 |
+
"quantization_config": {
|
| 20 |
+
"_load_in_4bit": true,
|
| 21 |
+
"_load_in_8bit": false,
|
| 22 |
+
"bnb_4bit_compute_dtype": "bfloat16",
|
| 23 |
+
"bnb_4bit_quant_storage": "uint8",
|
| 24 |
+
"bnb_4bit_quant_type": "nf4",
|
| 25 |
+
"bnb_4bit_use_double_quant": true,
|
| 26 |
+
"llm_int8_enable_fp32_cpu_offload": false,
|
| 27 |
+
"llm_int8_has_fp16_weight": false,
|
| 28 |
+
"llm_int8_skip_modules": [
|
| 29 |
+
"lm_head",
|
| 30 |
+
"multi_modal_projector",
|
| 31 |
+
"merger",
|
| 32 |
+
"modality_projection",
|
| 33 |
+
"model.layers.0.self_attn",
|
| 34 |
+
"model.layers.1.mlp",
|
| 35 |
+
"model.layers.2.mlp",
|
| 36 |
+
"model.layers.3.mlp",
|
| 37 |
+
"model.layers.7.mlp",
|
| 38 |
+
"model.layers.24.mlp",
|
| 39 |
+
"model.layers.26.mlp",
|
| 40 |
+
"model.layers.15.self_attn"
|
| 41 |
+
],
|
| 42 |
+
"llm_int8_threshold": 6.0,
|
| 43 |
+
"load_in_4bit": true,
|
| 44 |
+
"load_in_8bit": false,
|
| 45 |
+
"quant_method": "bitsandbytes"
|
| 46 |
+
},
|
| 47 |
+
"rms_norm_eps": 1e-06,
|
| 48 |
+
"rope_scaling": null,
|
| 49 |
+
"rope_theta": 1000000.0,
|
| 50 |
+
"sliding_window": null,
|
| 51 |
+
"tie_word_embeddings": true,
|
| 52 |
+
"torch_dtype": "bfloat16",
|
| 53 |
+
"transformers_version": "4.49.0.dev0",
|
| 54 |
+
"unsloth_fixed": true,
|
| 55 |
+
"use_cache": true,
|
| 56 |
+
"use_sliding_window": false,
|
| 57 |
+
"vocab_size": 151936
|
| 58 |
+
}
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/merges.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/special_tokens_map.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"additional_special_tokens": [
|
| 3 |
+
"<|im_start|>",
|
| 4 |
+
"<|im_end|>",
|
| 5 |
+
"<|object_ref_start|>",
|
| 6 |
+
"<|object_ref_end|>",
|
| 7 |
+
"<|box_start|>",
|
| 8 |
+
"<|box_end|>",
|
| 9 |
+
"<|quad_start|>",
|
| 10 |
+
"<|quad_end|>",
|
| 11 |
+
"<|vision_start|>",
|
| 12 |
+
"<|vision_end|>",
|
| 13 |
+
"<|vision_pad|>",
|
| 14 |
+
"<|image_pad|>",
|
| 15 |
+
"<|video_pad|>"
|
| 16 |
+
],
|
| 17 |
+
"eos_token": {
|
| 18 |
+
"content": "<|im_end|>",
|
| 19 |
+
"lstrip": false,
|
| 20 |
+
"normalized": false,
|
| 21 |
+
"rstrip": false,
|
| 22 |
+
"single_word": false
|
| 23 |
+
},
|
| 24 |
+
"pad_token": {
|
| 25 |
+
"content": "<|vision_pad|>",
|
| 26 |
+
"lstrip": false,
|
| 27 |
+
"normalized": false,
|
| 28 |
+
"rstrip": false,
|
| 29 |
+
"single_word": false
|
| 30 |
+
}
|
| 31 |
+
}
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9c5ae00e602b8860cbd784ba82a8aa14e8feecec692e7076590d014d7b7fdafa
|
| 3 |
+
size 11421896
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/tokenizer_config.json
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_bos_token": false,
|
| 3 |
+
"add_prefix_space": false,
|
| 4 |
+
"added_tokens_decoder": {
|
| 5 |
+
"151643": {
|
| 6 |
+
"content": "<|endoftext|>",
|
| 7 |
+
"lstrip": false,
|
| 8 |
+
"normalized": false,
|
| 9 |
+
"rstrip": false,
|
| 10 |
+
"single_word": false,
|
| 11 |
+
"special": true
|
| 12 |
+
},
|
| 13 |
+
"151644": {
|
| 14 |
+
"content": "<|im_start|>",
|
| 15 |
+
"lstrip": false,
|
| 16 |
+
"normalized": false,
|
| 17 |
+
"rstrip": false,
|
| 18 |
+
"single_word": false,
|
| 19 |
+
"special": true
|
| 20 |
+
},
|
| 21 |
+
"151645": {
|
| 22 |
+
"content": "<|im_end|>",
|
| 23 |
+
"lstrip": false,
|
| 24 |
+
"normalized": false,
|
| 25 |
+
"rstrip": false,
|
| 26 |
+
"single_word": false,
|
| 27 |
+
"special": true
|
| 28 |
+
},
|
| 29 |
+
"151646": {
|
| 30 |
+
"content": "<|object_ref_start|>",
|
| 31 |
+
"lstrip": false,
|
| 32 |
+
"normalized": false,
|
| 33 |
+
"rstrip": false,
|
| 34 |
+
"single_word": false,
|
| 35 |
+
"special": true
|
| 36 |
+
},
|
| 37 |
+
"151647": {
|
| 38 |
+
"content": "<|object_ref_end|>",
|
| 39 |
+
"lstrip": false,
|
| 40 |
+
"normalized": false,
|
| 41 |
+
"rstrip": false,
|
| 42 |
+
"single_word": false,
|
| 43 |
+
"special": true
|
| 44 |
+
},
|
| 45 |
+
"151648": {
|
| 46 |
+
"content": "<|box_start|>",
|
| 47 |
+
"lstrip": false,
|
| 48 |
+
"normalized": false,
|
| 49 |
+
"rstrip": false,
|
| 50 |
+
"single_word": false,
|
| 51 |
+
"special": true
|
| 52 |
+
},
|
| 53 |
+
"151649": {
|
| 54 |
+
"content": "<|box_end|>",
|
| 55 |
+
"lstrip": false,
|
| 56 |
+
"normalized": false,
|
| 57 |
+
"rstrip": false,
|
| 58 |
+
"single_word": false,
|
| 59 |
+
"special": true
|
| 60 |
+
},
|
| 61 |
+
"151650": {
|
| 62 |
+
"content": "<|quad_start|>",
|
| 63 |
+
"lstrip": false,
|
| 64 |
+
"normalized": false,
|
| 65 |
+
"rstrip": false,
|
| 66 |
+
"single_word": false,
|
| 67 |
+
"special": true
|
| 68 |
+
},
|
| 69 |
+
"151651": {
|
| 70 |
+
"content": "<|quad_end|>",
|
| 71 |
+
"lstrip": false,
|
| 72 |
+
"normalized": false,
|
| 73 |
+
"rstrip": false,
|
| 74 |
+
"single_word": false,
|
| 75 |
+
"special": true
|
| 76 |
+
},
|
| 77 |
+
"151652": {
|
| 78 |
+
"content": "<|vision_start|>",
|
| 79 |
+
"lstrip": false,
|
| 80 |
+
"normalized": false,
|
| 81 |
+
"rstrip": false,
|
| 82 |
+
"single_word": false,
|
| 83 |
+
"special": true
|
| 84 |
+
},
|
| 85 |
+
"151653": {
|
| 86 |
+
"content": "<|vision_end|>",
|
| 87 |
+
"lstrip": false,
|
| 88 |
+
"normalized": false,
|
| 89 |
+
"rstrip": false,
|
| 90 |
+
"single_word": false,
|
| 91 |
+
"special": true
|
| 92 |
+
},
|
| 93 |
+
"151654": {
|
| 94 |
+
"content": "<|vision_pad|>",
|
| 95 |
+
"lstrip": false,
|
| 96 |
+
"normalized": false,
|
| 97 |
+
"rstrip": false,
|
| 98 |
+
"single_word": false,
|
| 99 |
+
"special": true
|
| 100 |
+
},
|
| 101 |
+
"151655": {
|
| 102 |
+
"content": "<|image_pad|>",
|
| 103 |
+
"lstrip": false,
|
| 104 |
+
"normalized": false,
|
| 105 |
+
"rstrip": false,
|
| 106 |
+
"single_word": false,
|
| 107 |
+
"special": true
|
| 108 |
+
},
|
| 109 |
+
"151656": {
|
| 110 |
+
"content": "<|video_pad|>",
|
| 111 |
+
"lstrip": false,
|
| 112 |
+
"normalized": false,
|
| 113 |
+
"rstrip": false,
|
| 114 |
+
"single_word": false,
|
| 115 |
+
"special": true
|
| 116 |
+
},
|
| 117 |
+
"151657": {
|
| 118 |
+
"content": "<tool_call>",
|
| 119 |
+
"lstrip": false,
|
| 120 |
+
"normalized": false,
|
| 121 |
+
"rstrip": false,
|
| 122 |
+
"single_word": false,
|
| 123 |
+
"special": false
|
| 124 |
+
},
|
| 125 |
+
"151658": {
|
| 126 |
+
"content": "</tool_call>",
|
| 127 |
+
"lstrip": false,
|
| 128 |
+
"normalized": false,
|
| 129 |
+
"rstrip": false,
|
| 130 |
+
"single_word": false,
|
| 131 |
+
"special": false
|
| 132 |
+
},
|
| 133 |
+
"151659": {
|
| 134 |
+
"content": "<|fim_prefix|>",
|
| 135 |
+
"lstrip": false,
|
| 136 |
+
"normalized": false,
|
| 137 |
+
"rstrip": false,
|
| 138 |
+
"single_word": false,
|
| 139 |
+
"special": false
|
| 140 |
+
},
|
| 141 |
+
"151660": {
|
| 142 |
+
"content": "<|fim_middle|>",
|
| 143 |
+
"lstrip": false,
|
| 144 |
+
"normalized": false,
|
| 145 |
+
"rstrip": false,
|
| 146 |
+
"single_word": false,
|
| 147 |
+
"special": false
|
| 148 |
+
},
|
| 149 |
+
"151661": {
|
| 150 |
+
"content": "<|fim_suffix|>",
|
| 151 |
+
"lstrip": false,
|
| 152 |
+
"normalized": false,
|
| 153 |
+
"rstrip": false,
|
| 154 |
+
"single_word": false,
|
| 155 |
+
"special": false
|
| 156 |
+
},
|
| 157 |
+
"151662": {
|
| 158 |
+
"content": "<|fim_pad|>",
|
| 159 |
+
"lstrip": false,
|
| 160 |
+
"normalized": false,
|
| 161 |
+
"rstrip": false,
|
| 162 |
+
"single_word": false,
|
| 163 |
+
"special": false
|
| 164 |
+
},
|
| 165 |
+
"151663": {
|
| 166 |
+
"content": "<|repo_name|>",
|
| 167 |
+
"lstrip": false,
|
| 168 |
+
"normalized": false,
|
| 169 |
+
"rstrip": false,
|
| 170 |
+
"single_word": false,
|
| 171 |
+
"special": false
|
| 172 |
+
},
|
| 173 |
+
"151664": {
|
| 174 |
+
"content": "<|file_sep|>",
|
| 175 |
+
"lstrip": false,
|
| 176 |
+
"normalized": false,
|
| 177 |
+
"rstrip": false,
|
| 178 |
+
"single_word": false,
|
| 179 |
+
"special": false
|
| 180 |
+
}
|
| 181 |
+
},
|
| 182 |
+
"additional_special_tokens": [
|
| 183 |
+
"<|im_start|>",
|
| 184 |
+
"<|im_end|>",
|
| 185 |
+
"<|object_ref_start|>",
|
| 186 |
+
"<|object_ref_end|>",
|
| 187 |
+
"<|box_start|>",
|
| 188 |
+
"<|box_end|>",
|
| 189 |
+
"<|quad_start|>",
|
| 190 |
+
"<|quad_end|>",
|
| 191 |
+
"<|vision_start|>",
|
| 192 |
+
"<|vision_end|>",
|
| 193 |
+
"<|vision_pad|>",
|
| 194 |
+
"<|image_pad|>",
|
| 195 |
+
"<|video_pad|>"
|
| 196 |
+
],
|
| 197 |
+
"bos_token": null,
|
| 198 |
+
"chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n",
|
| 199 |
+
"clean_up_tokenization_spaces": false,
|
| 200 |
+
"eos_token": "<|im_end|>",
|
| 201 |
+
"errors": "replace",
|
| 202 |
+
"extra_special_tokens": {},
|
| 203 |
+
"model_max_length": 32768,
|
| 204 |
+
"pad_token": "<|vision_pad|>",
|
| 205 |
+
"padding_side": "left",
|
| 206 |
+
"split_special_tokens": false,
|
| 207 |
+
"tokenizer_class": "Qwen2Tokenizer",
|
| 208 |
+
"unk_token": null
|
| 209 |
+
}
|
huggingface_tokenizers_cache/models--unsloth--qwen2.5-1.5b-instruct-unsloth-bnb-4bit/snapshots/3d254dbee5e3beae81bb8a717ad3a03427a09d26/vocab.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
inference.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Inference Script — AI Sprint Manager OpenEnv
|
| 3 |
+
============================================================
|
| 4 |
+
MANDATORY:
|
| 5 |
+
API_BASE_URL : LLM endpoint
|
| 6 |
+
MODEL_NAME : Model identifier
|
| 7 |
+
HF_TOKEN : Hugging Face / API key
|
| 8 |
+
"""
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
import os
|
| 11 |
+
import json
|
| 12 |
+
import time
|
| 13 |
+
import sys
|
| 14 |
+
import requests
|
| 15 |
+
from dotenv import load_dotenv
|
| 16 |
+
from openai import OpenAI
|
| 17 |
+
|
| 18 |
+
load_dotenv()
|
| 19 |
+
|
| 20 |
+
# ── Config ────────────────────────────────────────────────────────────────────
|
| 21 |
+
API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
|
| 22 |
+
API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY", "dummy")
|
| 23 |
+
MODEL_NAME = os.getenv("MODEL_NAME", "meta-llama/Llama-3.1-8B-Instruct")
|
| 24 |
+
ENV_BASE_URL = os.getenv("ENV_BASE_URL", "https://sejal-k-ai-sprint-manager.hf.space")
|
| 25 |
+
|
| 26 |
+
MAX_STEPS = 12
|
| 27 |
+
TEMPERATURE = 0.2
|
| 28 |
+
MAX_TOKENS = 300
|
| 29 |
+
TASKS = ["easy_sprint", "medium_sprint", "hard_sprint"]
|
| 30 |
+
|
| 31 |
+
client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
|
| 32 |
+
|
| 33 |
+
SYSTEM_PROMPT = """You are an expert Tech Lead managing an agile sprint.
|
| 34 |
+
Your goal: maximize task completion, balance developer workload, and meet deadlines.
|
| 35 |
+
|
| 36 |
+
Each step output a JSON action with this exact schema:
|
| 37 |
+
{
|
| 38 |
+
"action_type": "<assign|reassign|reprioritize|unblock|skip>",
|
| 39 |
+
"task_id": "<task id or null>",
|
| 40 |
+
"dev_id": "<developer id or null>",
|
| 41 |
+
"new_priority": <1-5 or null>
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
Rules:
|
| 45 |
+
- assign: put a backlog task onto an available developer
|
| 46 |
+
- reassign: move an in-progress task to a different developer
|
| 47 |
+
- reprioritize: change a task priority (1=highest)
|
| 48 |
+
- unblock: unblock a blocked task
|
| 49 |
+
- skip: do nothing
|
| 50 |
+
|
| 51 |
+
Output ONLY the JSON object. No explanation."""
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def build_user_prompt(obs: dict) -> str:
|
| 55 |
+
tasks_summary = "\n".join(
|
| 56 |
+
f" [{t['id']}] {t['name']} | {t['task_type']} | P{t['priority']} | "
|
| 57 |
+
f"effort={t['effort']} | due=Day{t['deadline']} | status={t['status']} | "
|
| 58 |
+
f"dev={t['assigned_to']} | progress={t['progress']:.0%}"
|
| 59 |
+
for t in obs["tasks"]
|
| 60 |
+
)
|
| 61 |
+
devs_summary = "\n".join(
|
| 62 |
+
f" [{d['id']}] {d['name']} | skill={d['skill']} | "
|
| 63 |
+
f"load={d['current_load']}/{d['capacity']} | available={d['is_available']}"
|
| 64 |
+
for d in obs["developers"]
|
| 65 |
+
)
|
| 66 |
+
events_str = "\n ".join(obs.get("events", [])) or "None"
|
| 67 |
+
return f"""Day: {obs['current_day']}/{obs['sprint_length']}
|
| 68 |
+
Done:{obs['tasks_completed']} Missed:{obs['tasks_missed']} InProgress:{obs['tasks_in_progress']} Backlog:{obs['tasks_backlog']}
|
| 69 |
+
Cumulative Reward: {obs['cumulative_reward']:.2f}
|
| 70 |
+
|
| 71 |
+
Events: {events_str}
|
| 72 |
+
|
| 73 |
+
TASKS:
|
| 74 |
+
{tasks_summary}
|
| 75 |
+
|
| 76 |
+
DEVELOPERS:
|
| 77 |
+
{devs_summary}
|
| 78 |
+
|
| 79 |
+
Output your JSON action:"""
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def call_env(endpoint: str, payload: dict = None, method: str = "POST") -> dict:
|
| 83 |
+
url = f"{ENV_BASE_URL}/{endpoint}"
|
| 84 |
+
if method == "GET":
|
| 85 |
+
resp = requests.get(url, timeout=30)
|
| 86 |
+
else:
|
| 87 |
+
resp = requests.post(url, json=payload or {}, timeout=30)
|
| 88 |
+
resp.raise_for_status()
|
| 89 |
+
return resp.json()
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def get_rule_based_action(obs: dict) -> str:
|
| 93 |
+
"""Fallback rule-based action when LLM unavailable."""
|
| 94 |
+
tasks = obs.get("tasks", [])
|
| 95 |
+
devs = obs.get("developers", [])
|
| 96 |
+
backlog = sorted(
|
| 97 |
+
[t for t in tasks if t["status"] == "backlog"],
|
| 98 |
+
key=lambda t: (t["priority"], t["deadline"])
|
| 99 |
+
)
|
| 100 |
+
if not backlog:
|
| 101 |
+
return '{"action_type": "skip"}'
|
| 102 |
+
task = backlog[0]
|
| 103 |
+
available = [d for d in devs if d["is_available"] and d["current_load"] < d["capacity"]]
|
| 104 |
+
skill_match = [d for d in available if d["skill"] == task["required_skill"] or d["skill"] == "fullstack"]
|
| 105 |
+
dev = skill_match[0] if skill_match else (available[0] if available else None)
|
| 106 |
+
if not dev:
|
| 107 |
+
return '{"action_type": "skip"}'
|
| 108 |
+
return json.dumps({"action_type": "assign", "task_id": task["id"], "dev_id": dev["id"], "new_priority": None})
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def parse_action(text: str) -> dict:
|
| 112 |
+
text = text.strip()
|
| 113 |
+
if "```" in text:
|
| 114 |
+
lines = [l for l in text.split("\n") if not l.strip().startswith("```")]
|
| 115 |
+
text = "\n".join(lines)
|
| 116 |
+
try:
|
| 117 |
+
return json.loads(text)
|
| 118 |
+
except json.JSONDecodeError:
|
| 119 |
+
start, end = text.find("{"), text.rfind("}") + 1
|
| 120 |
+
if start >= 0 and end > start:
|
| 121 |
+
try:
|
| 122 |
+
return json.loads(text[start:end])
|
| 123 |
+
except Exception:
|
| 124 |
+
pass
|
| 125 |
+
return {"action_type": "skip", "task_id": None, "dev_id": None, "new_priority": None}
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def run_episode(task_name: str) -> float:
|
| 129 |
+
"""Run one complete episode and return final score."""
|
| 130 |
+
|
| 131 |
+
# ── [START] block ─────────────────────────────────────────────────────────
|
| 132 |
+
print(f"[START] task={task_name}", flush=True)
|
| 133 |
+
|
| 134 |
+
obs = call_env("reset", {"task_name": task_name, "seed": 42})
|
| 135 |
+
final_score = 0.0
|
| 136 |
+
step_num = 0
|
| 137 |
+
|
| 138 |
+
for step_num in range(1, MAX_STEPS + 1):
|
| 139 |
+
if obs.get("done", False):
|
| 140 |
+
break
|
| 141 |
+
|
| 142 |
+
try:
|
| 143 |
+
completion = client.chat.completions.create(
|
| 144 |
+
model=MODEL_NAME,
|
| 145 |
+
messages=[
|
| 146 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 147 |
+
{"role": "user", "content": build_user_prompt(obs)},
|
| 148 |
+
],
|
| 149 |
+
temperature=TEMPERATURE,
|
| 150 |
+
max_tokens=MAX_TOKENS,
|
| 151 |
+
)
|
| 152 |
+
response_text = completion.choices[0].message.content or ""
|
| 153 |
+
except Exception as e:
|
| 154 |
+
response_text = get_rule_based_action(obs)
|
| 155 |
+
|
| 156 |
+
action = parse_action(response_text)
|
| 157 |
+
result = call_env("step", {"action": action})
|
| 158 |
+
obs = result["observation"]
|
| 159 |
+
reward = result["reward"]
|
| 160 |
+
done = result["done"]
|
| 161 |
+
info = result.get("info", {})
|
| 162 |
+
|
| 163 |
+
# ── [STEP] block ──────────────────────────────────────────────────────
|
| 164 |
+
print(
|
| 165 |
+
f"[STEP] task={task_name} step={step_num} "
|
| 166 |
+
f"action={action.get('action_type')} reward={reward:.4f} "
|
| 167 |
+
f"cumulative={obs.get('cumulative_reward', 0):.4f} done={done}",
|
| 168 |
+
flush=True
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
if done:
|
| 172 |
+
final_score = max(0.01, min(0.99, info.get("final_score", 0.01)))
|
| 173 |
+
break
|
| 174 |
+
|
| 175 |
+
# ── [END] block ───────────────────────────────────────────────────────────
|
| 176 |
+
print(
|
| 177 |
+
f"[END] task={task_name} score={final_score:.4f} steps={step_num}",
|
| 178 |
+
flush=True
|
| 179 |
+
)
|
| 180 |
+
return final_score
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def main():
|
| 184 |
+
print(f"[INFO] model={MODEL_NAME} server={ENV_BASE_URL}", flush=True)
|
| 185 |
+
|
| 186 |
+
try:
|
| 187 |
+
health = call_env("health", method="GET")
|
| 188 |
+
print(f"[INFO] health={health}", flush=True)
|
| 189 |
+
except Exception as e:
|
| 190 |
+
print(f"[ERROR] Cannot reach env server: {e}", flush=True)
|
| 191 |
+
sys.exit(1)
|
| 192 |
+
|
| 193 |
+
scores = {}
|
| 194 |
+
start_time = time.time()
|
| 195 |
+
|
| 196 |
+
for task in TASKS:
|
| 197 |
+
try:
|
| 198 |
+
score = run_episode(task)
|
| 199 |
+
scores[task] = score
|
| 200 |
+
except Exception as e:
|
| 201 |
+
print(f"[ERROR] task={task} error={e}", flush=True)
|
| 202 |
+
scores[task] = 0.0
|
| 203 |
+
|
| 204 |
+
elapsed = time.time() - start_time
|
| 205 |
+
|
| 206 |
+
# Human-readable summary
|
| 207 |
+
print("\n" + "="*60, flush=True)
|
| 208 |
+
print(" BASELINE SCORES", flush=True)
|
| 209 |
+
print("="*60, flush=True)
|
| 210 |
+
for task, score in scores.items():
|
| 211 |
+
bar = "█" * int(score * 20)
|
| 212 |
+
print(f" {task:<20} {score:.4f} {bar}", flush=True)
|
| 213 |
+
avg = sum(scores.values()) / len(scores) if scores else 0.0
|
| 214 |
+
print(f" {'AVERAGE':<20} {avg:.4f}", flush=True)
|
| 215 |
+
print(f"\n Runtime: {elapsed:.1f}s", flush=True)
|
| 216 |
+
print("="*60, flush=True)
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
if __name__ == "__main__":
|
| 220 |
+
main()
|
inference_r2.py
ADDED
|
@@ -0,0 +1,855 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
inference_r2.py — Round 2 LLM agent for AI Sprint Manager
|
| 3 |
+
==========================================================
|
| 4 |
+
|
| 5 |
+
ROOT-CAUSE FIXES in this version:
|
| 6 |
+
[FIX-I1] PROMPT ALIGNMENT — system prompt and user prompt now exactly match
|
| 7 |
+
train_llm.py (R2_SYSTEM_PROMPT + _build_r2_prompt format).
|
| 8 |
+
The fine-tuned model conditions strongly on the EXACT wording it
|
| 9 |
+
saw during training. The previous version used a completely different
|
| 10 |
+
system prompt ("You are a tech lead managing a 6-sprint...") and a
|
| 11 |
+
different user prompt format (day=/sprint= vs D/60 S/6).
|
| 12 |
+
This single mismatch is the biggest cause of poor inference scores.
|
| 13 |
+
|
| 14 |
+
[FIX-I2] LOCAL MODEL LOADING — sejal-k/ai-sprint-manager-trained is a LoRA
|
| 15 |
+
adapter, not a full model. The HF Router cannot serve it. The previous
|
| 16 |
+
version was routing all calls to the base Qwen model via HF Router,
|
| 17 |
+
producing base-model scores instead of fine-tuned scores.
|
| 18 |
+
Fix: load the adapter locally via Unsloth (preferred) or PEFT.
|
| 19 |
+
Set LOCAL_MODEL_PATH env var to the adapter path/HF repo ID.
|
| 20 |
+
|
| 21 |
+
[FIX-I3] LLM_CALL_EVERY=1 — was 3, meaning 2 out of every 3 steps used the
|
| 22 |
+
rule-based fallback regardless of model quality. With a local model
|
| 23 |
+
there is no rate-limit, so every step uses the LLM.
|
| 24 |
+
|
| 25 |
+
[FIX-I4] TEMPERATURE=0.3 for inference — was 0.1 (too greedy) and 0.8 (too
|
| 26 |
+
random). 0.3 gives slight diversity while staying near peak-prob output.
|
| 27 |
+
|
| 28 |
+
[FIX-I5] METADATA BUG — sort_key in smart_fallback used getattr(t, "metadata")
|
| 29 |
+
on plain dicts. Fixed to t.get("metadata", {}).
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
from __future__ import annotations
|
| 33 |
+
|
| 34 |
+
import json
|
| 35 |
+
import os
|
| 36 |
+
import re
|
| 37 |
+
import time
|
| 38 |
+
import random
|
| 39 |
+
from typing import Optional, Tuple
|
| 40 |
+
|
| 41 |
+
import requests
|
| 42 |
+
|
| 43 |
+
# ─── Config ───────────────────────────────────────────────────────────────────
|
| 44 |
+
# Set LOCAL_MODEL_PATH to use the fine-tuned adapter. Examples:
|
| 45 |
+
# export LOCAL_MODEL_PATH=results/trained_model (local checkpoint)
|
| 46 |
+
# export LOCAL_MODEL_PATH=sejal-k/ai-sprint-manager-trained (HF Hub adapter)
|
| 47 |
+
LOCAL_MODEL_PATH = os.getenv("LOCAL_MODEL_PATH", "priyaaaaaasharmaaaaa/trial1")
|
| 48 |
+
MODEL_NAME = os.getenv("MODEL_NAME", "meta-llama/Llama-3.1-8B-Instruct")
|
| 49 |
+
ENV_BASE_URL = os.getenv("ENV_BASE_URL", "https://sejal-k-ai-sprint-manager.hf.space")
|
| 50 |
+
API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
|
| 51 |
+
HF_TOKEN = os.getenv("HF_TOKEN", "")
|
| 52 |
+
|
| 53 |
+
# Inference control: USE_LLM=0 → smart_fallback only (sanity vs LLM).
|
| 54 |
+
_use_llm_raw = os.getenv("USE_LLM", "1").strip().lower()
|
| 55 |
+
USE_LLM = _use_llm_raw not in ("0", "false", "no", "off", "")
|
| 56 |
+
LLM_COOLDOWN_STEPS = int(os.getenv("LLM_COOLDOWN_STEPS", "15"))
|
| 57 |
+
MAX_LLM_SOFT_FAIL_STREAK = int(os.getenv("MAX_LLM_SOFT_FAIL_STREAK", "3"))
|
| 58 |
+
MAX_LLM_SKIP_STREAK = int(os.getenv("MAX_LLM_SKIP_STREAK", "4"))
|
| 59 |
+
MAX_SAME_BAD_ASSIGN_STREAK = int(os.getenv("MAX_SAME_BAD_ASSIGN_STREAK", "2"))
|
| 60 |
+
|
| 61 |
+
MAX_TOKENS = 96 # matches train_llm.py max_completion_length
|
| 62 |
+
MAX_RETRIES = 2
|
| 63 |
+
LLM_CALL_EVERY = 1 # [FIX-I3] every step — no rate limit with local model
|
| 64 |
+
TEMPERATURE = 0.3 # [FIX-I4]
|
| 65 |
+
|
| 66 |
+
TASK_ID_RE = re.compile(r"^T\d+$")
|
| 67 |
+
RETRYABLE_CODES = {429, 500, 502, 503, 504}
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
# ─── [FIX-I1] Prompts — EXACTLY matching train_llm.py ────────────────────────
|
| 71 |
+
# These strings must stay in sync with R2_SYSTEM_PROMPT and _build_r2_prompt()
|
| 72 |
+
# in train_llm.py. Any wording difference causes the fine-tuned model to produce
|
| 73 |
+
# lower-quality output because it no longer recognises its training context.
|
| 74 |
+
|
| 75 |
+
R2_SYSTEM_PROMPT = """You are an Engineering Manager running a 60-day software project.
|
| 76 |
+
Each step you MUST output exactly ONE JSON object and nothing else.
|
| 77 |
+
|
| 78 |
+
Schema (use null for unused fields):
|
| 79 |
+
{"action_type":"<assign|reassign|reprioritize|unblock|skip>","task_id":"<id or null>","dev_id":"<id or null>","new_priority":<1-5 or null>}
|
| 80 |
+
|
| 81 |
+
Rules (follow in order):
|
| 82 |
+
1. If ACTIVE INSTRUCTIONS exist, assign THEIR tasks first.
|
| 83 |
+
2. Only assign tasks with status=backlog (never in_progress or done).
|
| 84 |
+
3. Only assign if all dependency markers show ✓.
|
| 85 |
+
4. Only assign to an AVAILABLE developer with matching or fullstack skill.
|
| 86 |
+
5. Use unblock ONLY for explicitly blocked tasks whose deps are ✓.
|
| 87 |
+
6. skip is last resort.
|
| 88 |
+
|
| 89 |
+
Output ONLY the JSON. No explanation."""
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def build_user_prompt(
|
| 93 |
+
obs: dict,
|
| 94 |
+
*,
|
| 95 |
+
assigned_this_episode: Optional[set] = None,
|
| 96 |
+
assign_attempted_episode: Optional[set] = None,
|
| 97 |
+
) -> str:
|
| 98 |
+
"""
|
| 99 |
+
Core layout matches _build_r2_prompt() in train_llm.py.
|
| 100 |
+
Optional MEMORY block is inference-only: steers the model away from repeat
|
| 101 |
+
assigns and lists live IN_PROGRESS ids (training export can omit kwargs).
|
| 102 |
+
"""
|
| 103 |
+
current_sprint = obs.get("current_sprint", 1)
|
| 104 |
+
current_day = obs.get("current_day", 1)
|
| 105 |
+
days_left = max(0, current_sprint * 10 - current_day + 1)
|
| 106 |
+
tasks = obs.get("tasks", [])
|
| 107 |
+
done_ids = {t["id"] for t in tasks if t.get("status") == "done"}
|
| 108 |
+
|
| 109 |
+
active_insts = [i for i in obs.get("instruction_queue", []) if not i.get("followed", False)]
|
| 110 |
+
inst_section = (
|
| 111 |
+
"⚡FOLLOW: " + " | ".join(f"[{i['id']}] {i['text'][:50]}" for i in active_insts[:2])
|
| 112 |
+
) if active_insts else "No instructions."
|
| 113 |
+
|
| 114 |
+
debt_raw = obs.get("tech_debt", [])
|
| 115 |
+
debt_count = len(debt_raw) if isinstance(debt_raw, list) else int(debt_raw or 0)
|
| 116 |
+
|
| 117 |
+
backlog = sorted(
|
| 118 |
+
[t for t in tasks if t.get("status") == "backlog"],
|
| 119 |
+
key=lambda t: (t.get("priority", 9), t.get("deadline", 99))
|
| 120 |
+
)
|
| 121 |
+
in_prog = [t for t in tasks if t.get("status") == "in_progress"]
|
| 122 |
+
|
| 123 |
+
def fmt(t: dict) -> str:
|
| 124 |
+
meta = t.get("metadata", {}) or {} # [FIX-I5]
|
| 125 |
+
deps = t.get("depends_on", []) or meta.get("depends_on", [])
|
| 126 |
+
dep_ok = "✓" if all(d in done_ids for d in deps) else "✗"
|
| 127 |
+
return (f"[{t['id']}]P{t.get('priority','?')} "
|
| 128 |
+
f"{str(t.get('required_skill','?'))[:4]} {dep_ok} "
|
| 129 |
+
f"D{t.get('deadline', t.get('deadline_day','?'))}")
|
| 130 |
+
|
| 131 |
+
backlog_str = " ".join(fmt(t) for t in backlog[:6])
|
| 132 |
+
if len(backlog) > 6:
|
| 133 |
+
backlog_str += f" +{len(backlog)-6}"
|
| 134 |
+
|
| 135 |
+
inprog_str = " ".join(f"[{t['id']}]→{t.get('assigned_to','?')}" for t in in_prog) or "none"
|
| 136 |
+
avail_devs = [d for d in obs.get("developers", []) if d.get("is_available", False)]
|
| 137 |
+
devs_str = " ".join(
|
| 138 |
+
f"[{d['id']}]{str(d.get('name','?'))[:4]}({str(d.get('skill','?'))[:3]}) "
|
| 139 |
+
f"{d.get('current_load',0)}/{d.get('capacity',5)}"
|
| 140 |
+
for d in avail_devs
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
memory_lines: list[str] = []
|
| 144 |
+
in_prog_ids = [t["id"] for t in in_prog]
|
| 145 |
+
if in_prog_ids:
|
| 146 |
+
memory_lines.append(
|
| 147 |
+
"NO_REASSIGN_UNTIL_BACKLOG: " + " ".join(in_prog_ids)
|
| 148 |
+
+ " — do NOT assign/reassign these while they stay in_progress."
|
| 149 |
+
)
|
| 150 |
+
if assigned_this_episode:
|
| 151 |
+
memory_lines.append(
|
| 152 |
+
"ASSIGNED_OK_THIS_EP: " + " ".join(sorted(assigned_this_episode))
|
| 153 |
+
+ " — already started; do not assign again."
|
| 154 |
+
)
|
| 155 |
+
if assign_attempted_episode:
|
| 156 |
+
extra = assign_attempted_episode - set(assigned_this_episode or ())
|
| 157 |
+
if extra:
|
| 158 |
+
memory_lines.append(
|
| 159 |
+
"ASSIGN_ALREADY_TRIED: " + " ".join(sorted(extra))
|
| 160 |
+
+ " — pick a different backlog task if still open."
|
| 161 |
+
)
|
| 162 |
+
memory_block = ("EPISODE_MEMORY:\n" + "\n".join(memory_lines) + "\n") if memory_lines else ""
|
| 163 |
+
|
| 164 |
+
return (
|
| 165 |
+
f"D{current_day}/60 S{current_sprint}/6 {days_left}d "
|
| 166 |
+
f"done={obs.get('tasks_completed',0)} miss={obs.get('tasks_missed',0)} "
|
| 167 |
+
f"inst={obs.get('instruction_following_score',0):.2f} debt={debt_count}\n"
|
| 168 |
+
f"{inst_section}\n"
|
| 169 |
+
f"BACKLOG(✓=deps_ok): {backlog_str}\n"
|
| 170 |
+
f"IN_PROG: {inprog_str}\n"
|
| 171 |
+
f"DEVS(avail): {devs_str}\n"
|
| 172 |
+
f"{memory_block}"
|
| 173 |
+
f"JSON:"
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def _completed_ids(obs: dict) -> set:
|
| 178 |
+
return {t["id"] for t in obs.get("tasks", []) if t.get("status") == "done"}
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _deps_met_task(obs: dict, task: dict) -> bool:
|
| 182 |
+
done_ids = _completed_ids(obs)
|
| 183 |
+
meta = task.get("metadata", {}) or {}
|
| 184 |
+
deps = task.get("depends_on", []) or meta.get("depends_on", [])
|
| 185 |
+
return all(d in done_ids for d in deps)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _dev_by_id(obs: dict, dev_id: object) -> Optional[dict]:
|
| 189 |
+
sid = str(dev_id)
|
| 190 |
+
for d in obs.get("developers", []):
|
| 191 |
+
if str(d.get("id")) == sid:
|
| 192 |
+
return d
|
| 193 |
+
return None
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def validate_llm_action(
|
| 197 |
+
obs: dict,
|
| 198 |
+
action: Optional[dict],
|
| 199 |
+
assigned_this_episode: set,
|
| 200 |
+
) -> Tuple[bool, str]:
|
| 201 |
+
"""
|
| 202 |
+
Hard gate: reject LLM output that violates env rules (backlog, deps, dev).
|
| 203 |
+
"""
|
| 204 |
+
if not action:
|
| 205 |
+
return False, "empty"
|
| 206 |
+
at = action.get("action_type")
|
| 207 |
+
if at not in {"assign", "reassign", "reprioritize", "unblock", "skip"}:
|
| 208 |
+
return False, "bad_type"
|
| 209 |
+
|
| 210 |
+
if at == "skip":
|
| 211 |
+
return True, "ok"
|
| 212 |
+
|
| 213 |
+
tid = action.get("task_id")
|
| 214 |
+
if not tid:
|
| 215 |
+
return False, "missing_task_id"
|
| 216 |
+
|
| 217 |
+
by_id = {t["id"]: t for t in obs.get("tasks", [])}
|
| 218 |
+
task = by_id.get(tid)
|
| 219 |
+
if task is None:
|
| 220 |
+
return False, "unknown_task"
|
| 221 |
+
|
| 222 |
+
if at == "unblock":
|
| 223 |
+
if task.get("status") != "blocked":
|
| 224 |
+
return False, "unblock_not_blocked"
|
| 225 |
+
if not _deps_met_task(obs, task):
|
| 226 |
+
return False, "unblock_deps"
|
| 227 |
+
return True, "ok"
|
| 228 |
+
|
| 229 |
+
if at == "reprioritize":
|
| 230 |
+
if task.get("status") != "backlog":
|
| 231 |
+
return False, "reprioritize_not_backlog"
|
| 232 |
+
if not _deps_met_task(obs, task):
|
| 233 |
+
return False, "reprioritize_deps"
|
| 234 |
+
np = action.get("new_priority")
|
| 235 |
+
if np is None:
|
| 236 |
+
return False, "reprioritize_no_priority"
|
| 237 |
+
try:
|
| 238 |
+
npi = int(np)
|
| 239 |
+
except (TypeError, ValueError):
|
| 240 |
+
return False, "reprioritize_bad_priority"
|
| 241 |
+
if not (1 <= npi <= 5):
|
| 242 |
+
return False, "reprioritize_range"
|
| 243 |
+
return True, "ok"
|
| 244 |
+
|
| 245 |
+
if at in ("assign", "reassign"):
|
| 246 |
+
st = task.get("status")
|
| 247 |
+
if st != "backlog":
|
| 248 |
+
return False, f"assign_bad_status:{st}"
|
| 249 |
+
if not _deps_met_task(obs, task):
|
| 250 |
+
return False, "assign_deps"
|
| 251 |
+
if tid in assigned_this_episode:
|
| 252 |
+
return False, "assign_already_started_episode"
|
| 253 |
+
did = action.get("dev_id")
|
| 254 |
+
if not did:
|
| 255 |
+
return False, "assign_no_dev"
|
| 256 |
+
dev = _dev_by_id(obs, did)
|
| 257 |
+
if dev is None:
|
| 258 |
+
return False, "assign_unknown_dev"
|
| 259 |
+
if not dev.get("is_available", False):
|
| 260 |
+
return False, "assign_dev_unavailable"
|
| 261 |
+
try:
|
| 262 |
+
rem = int(dev.get("remaining_capacity", dev.get("capacity", 1)))
|
| 263 |
+
if rem <= 0:
|
| 264 |
+
return False, "assign_dev_no_capacity"
|
| 265 |
+
except (TypeError, ValueError):
|
| 266 |
+
pass
|
| 267 |
+
skill = task.get("required_skill", "")
|
| 268 |
+
dskill = dev.get("skill", "")
|
| 269 |
+
if dskill not in (skill, "fullstack"):
|
| 270 |
+
return False, "assign_skill_mismatch"
|
| 271 |
+
return True, "ok"
|
| 272 |
+
|
| 273 |
+
return False, "unhandled"
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
# ─── [FIX-I2] Local fine-tuned model loader ───────────────────────────────────
|
| 277 |
+
|
| 278 |
+
_local_model = None
|
| 279 |
+
_local_tokenizer = None
|
| 280 |
+
_local_backend = None
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def _load_local_model(model_path: str) -> bool:
|
| 284 |
+
"""
|
| 285 |
+
[FIX-I2] Load LoRA adapter locally. HF Router cannot serve LoRA adapters —
|
| 286 |
+
it requires fully merged weights. Unsloth first, PEFT+bitsandbytes fallback.
|
| 287 |
+
"""
|
| 288 |
+
global _local_model, _local_tokenizer, _local_backend
|
| 289 |
+
if _local_model is not None:
|
| 290 |
+
return True
|
| 291 |
+
|
| 292 |
+
print(f"[INFO] Loading fine-tuned model: {model_path}", flush=True)
|
| 293 |
+
|
| 294 |
+
unsloth_err: Optional[BaseException] = None
|
| 295 |
+
# Attempt 1: Unsloth (fastest, native 4-bit, same library used for training)
|
| 296 |
+
try:
|
| 297 |
+
from unsloth import FastLanguageModel
|
| 298 |
+
model, tokenizer = FastLanguageModel.from_pretrained(
|
| 299 |
+
model_name=model_path, max_seq_length=2048, dtype=None,
|
| 300 |
+
load_in_4bit=True, token=HF_TOKEN or None,
|
| 301 |
+
)
|
| 302 |
+
FastLanguageModel.for_inference(model)
|
| 303 |
+
_local_model, _local_tokenizer, _local_backend = model, tokenizer, "unsloth"
|
| 304 |
+
print("[INFO] Loaded via Unsloth (fast 4-bit inference).", flush=True)
|
| 305 |
+
return True
|
| 306 |
+
except Exception as e:
|
| 307 |
+
unsloth_err = e
|
| 308 |
+
print(f"[WARN] Unsloth failed: {e}", flush=True)
|
| 309 |
+
|
| 310 |
+
# Attempt 2: PEFT + bitsandbytes
|
| 311 |
+
try:
|
| 312 |
+
import torch
|
| 313 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
|
| 314 |
+
from peft import PeftModel
|
| 315 |
+
import huggingface_hub
|
| 316 |
+
|
| 317 |
+
try:
|
| 318 |
+
cfg_path = huggingface_hub.hf_hub_download(
|
| 319 |
+
model_path, "adapter_config.json", token=HF_TOKEN or None
|
| 320 |
+
)
|
| 321 |
+
except Exception:
|
| 322 |
+
cfg_path = os.path.join(model_path, "adapter_config.json")
|
| 323 |
+
|
| 324 |
+
with open(cfg_path) as f:
|
| 325 |
+
adapter_cfg = json.load(f)
|
| 326 |
+
base_id = adapter_cfg.get("base_model_name_or_path", "Qwen/Qwen2.5-1.5B-Instruct")
|
| 327 |
+
print(f"[INFO] Base model: {base_id}", flush=True)
|
| 328 |
+
|
| 329 |
+
bnb = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_compute_dtype=torch.float16)
|
| 330 |
+
tok = AutoTokenizer.from_pretrained(base_id, token=HF_TOKEN or None)
|
| 331 |
+
base = AutoModelForCausalLM.from_pretrained(
|
| 332 |
+
base_id, quantization_config=bnb, device_map="auto", token=HF_TOKEN or None
|
| 333 |
+
)
|
| 334 |
+
model = PeftModel.from_pretrained(base, model_path, token=HF_TOKEN or None)
|
| 335 |
+
model.eval()
|
| 336 |
+
_local_model, _local_tokenizer, _local_backend = model, tok, "peft"
|
| 337 |
+
print("[INFO] Loaded via PEFT + bitsandbytes 4-bit.", flush=True)
|
| 338 |
+
return True
|
| 339 |
+
except Exception as e2:
|
| 340 |
+
print(
|
| 341 |
+
f"[ERROR] Cannot load local model.\n Unsloth: {unsloth_err}\n PEFT: {e2}",
|
| 342 |
+
flush=True,
|
| 343 |
+
)
|
| 344 |
+
return False
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def _call_local_model(user_prompt: str) -> Optional[dict]:
|
| 348 |
+
"""Run one inference step on the locally loaded fine-tuned model."""
|
| 349 |
+
if _local_model is None:
|
| 350 |
+
return None
|
| 351 |
+
import torch
|
| 352 |
+
|
| 353 |
+
messages = [
|
| 354 |
+
{"role": "system", "content": R2_SYSTEM_PROMPT},
|
| 355 |
+
{"role": "user", "content": user_prompt},
|
| 356 |
+
]
|
| 357 |
+
tok = _local_tokenizer
|
| 358 |
+
try:
|
| 359 |
+
if hasattr(tok, "apply_chat_template"):
|
| 360 |
+
prompt_text = tok.apply_chat_template(
|
| 361 |
+
messages, tokenize=False, add_generation_prompt=True
|
| 362 |
+
)
|
| 363 |
+
else:
|
| 364 |
+
prompt_text = "\n".join(
|
| 365 |
+
f"<|{m['role']}|>\n{m['content']}" for m in messages
|
| 366 |
+
) + "\n<|assistant|>\n"
|
| 367 |
+
|
| 368 |
+
inputs = tok(prompt_text, return_tensors="pt").to(_local_model.device)
|
| 369 |
+
inp_len = inputs["input_ids"].shape[1]
|
| 370 |
+
|
| 371 |
+
with torch.no_grad():
|
| 372 |
+
outputs = _local_model.generate(
|
| 373 |
+
**inputs, max_new_tokens=MAX_TOKENS,
|
| 374 |
+
temperature=TEMPERATURE, do_sample=True,
|
| 375 |
+
pad_token_id=tok.pad_token_id or tok.eos_token_id,
|
| 376 |
+
)
|
| 377 |
+
|
| 378 |
+
completion = tok.decode(outputs[0][inp_len:], skip_special_tokens=True).strip()
|
| 379 |
+
return parse_action(completion)
|
| 380 |
+
|
| 381 |
+
except Exception as e:
|
| 382 |
+
print(f" [WARN] Local model inference error: {e}", flush=True)
|
| 383 |
+
return None
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
# ─── HF Router client (fallback when no local model) ─────────────────────────
|
| 387 |
+
|
| 388 |
+
def _call_api_model(user_prompt: str) -> Optional[dict]:
|
| 389 |
+
"""Use HF Router — only for full (non-adapter) models."""
|
| 390 |
+
try:
|
| 391 |
+
from openai import OpenAI, APIStatusError
|
| 392 |
+
except ImportError:
|
| 393 |
+
return None
|
| 394 |
+
if not HF_TOKEN:
|
| 395 |
+
return None
|
| 396 |
+
|
| 397 |
+
client = OpenAI(base_url=API_BASE_URL, api_key=HF_TOKEN)
|
| 398 |
+
messages = [
|
| 399 |
+
{"role": "system", "content": R2_SYSTEM_PROMPT},
|
| 400 |
+
{"role": "user", "content": user_prompt},
|
| 401 |
+
]
|
| 402 |
+
for attempt in range(1, MAX_RETRIES + 2):
|
| 403 |
+
try:
|
| 404 |
+
resp = client.chat.completions.create(
|
| 405 |
+
model=MODEL_NAME, messages=messages,
|
| 406 |
+
max_tokens=MAX_TOKENS, temperature=TEMPERATURE,
|
| 407 |
+
)
|
| 408 |
+
return parse_action(resp.choices[0].message.content or "")
|
| 409 |
+
except APIStatusError as e:
|
| 410 |
+
if e.status_code not in RETRYABLE_CODES:
|
| 411 |
+
return None
|
| 412 |
+
if attempt <= MAX_RETRIES:
|
| 413 |
+
time.sleep(2 ** attempt + random.uniform(0, 0.5))
|
| 414 |
+
else:
|
| 415 |
+
return None
|
| 416 |
+
except Exception:
|
| 417 |
+
if attempt <= MAX_RETRIES:
|
| 418 |
+
time.sleep(2 ** attempt)
|
| 419 |
+
else:
|
| 420 |
+
return None
|
| 421 |
+
return None
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
def call_llm_user_prompt(user_prompt: str) -> Optional[dict]:
|
| 425 |
+
"""Prefer local fine-tuned model; fall back to HF Router."""
|
| 426 |
+
if _local_model is not None:
|
| 427 |
+
return _call_local_model(user_prompt)
|
| 428 |
+
return _call_api_model(user_prompt)
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
# ─── Action parser ────────────────────────────────────────────────────────────
|
| 432 |
+
|
| 433 |
+
def parse_action(raw: str) -> Optional[dict]:
|
| 434 |
+
if not raw:
|
| 435 |
+
return None
|
| 436 |
+
raw = raw.strip()
|
| 437 |
+
raw = re.sub(r"^```[a-z]*\s*", "", raw)
|
| 438 |
+
raw = re.sub(r"\s*```$", "", raw)
|
| 439 |
+
|
| 440 |
+
# Find last balanced JSON object (handles CoT prefix the model might emit)
|
| 441 |
+
depth = 0; obj_start = -1; last_start = -1; last_end = -1
|
| 442 |
+
for i, ch in enumerate(raw):
|
| 443 |
+
if ch == "{":
|
| 444 |
+
if depth == 0: obj_start = i
|
| 445 |
+
depth += 1
|
| 446 |
+
elif ch == "}":
|
| 447 |
+
depth -= 1
|
| 448 |
+
if depth == 0 and obj_start >= 0:
|
| 449 |
+
last_start, last_end = obj_start, i + 1
|
| 450 |
+
|
| 451 |
+
obj = None
|
| 452 |
+
if last_start >= 0:
|
| 453 |
+
try:
|
| 454 |
+
obj = json.loads(raw[last_start:last_end])
|
| 455 |
+
except json.JSONDecodeError:
|
| 456 |
+
pass
|
| 457 |
+
if obj is None:
|
| 458 |
+
try:
|
| 459 |
+
obj = json.loads(raw)
|
| 460 |
+
except Exception:
|
| 461 |
+
return None
|
| 462 |
+
|
| 463 |
+
action_type = obj.get("action_type", "")
|
| 464 |
+
if action_type not in {"assign", "reassign", "reprioritize", "unblock", "skip"}:
|
| 465 |
+
return None
|
| 466 |
+
|
| 467 |
+
null_vals = {"null", "none", "None", "Null", "", "undefined", "N/A", "nil"}
|
| 468 |
+
for key in ("task_id", "dev_id", "new_priority"):
|
| 469 |
+
v = obj.get(key)
|
| 470 |
+
if v is not None and str(v).strip() in null_vals:
|
| 471 |
+
obj[key] = None
|
| 472 |
+
|
| 473 |
+
task_id = obj.get("task_id")
|
| 474 |
+
if task_id is not None and not TASK_ID_RE.match(str(task_id)):
|
| 475 |
+
print(f" [INVALID] task_id={repr(task_id)} rejected", flush=True)
|
| 476 |
+
return None
|
| 477 |
+
|
| 478 |
+
if action_type in {"assign", "reassign"}:
|
| 479 |
+
if not task_id or not obj.get("dev_id"):
|
| 480 |
+
return None
|
| 481 |
+
|
| 482 |
+
return {
|
| 483 |
+
"action_type": action_type,
|
| 484 |
+
"task_id": task_id,
|
| 485 |
+
"dev_id": obj.get("dev_id"),
|
| 486 |
+
"new_priority": obj.get("new_priority"),
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
|
| 490 |
+
# Aliases for evaluate_r2 / train naming
|
| 491 |
+
_build_r2_prompt = build_user_prompt
|
| 492 |
+
_parse_action = parse_action
|
| 493 |
+
|
| 494 |
+
|
| 495 |
+
# ─── Smart fallback ───────────────────────────────────────────────────────────
|
| 496 |
+
|
| 497 |
+
def smart_fallback(obs: dict, assigned_this_episode: set, last_dev_idx: list) -> dict:
|
| 498 |
+
tasks = obs.get("tasks", [])
|
| 499 |
+
devs = obs.get("developers", [])
|
| 500 |
+
instructions = obs.get("instruction_queue", [])
|
| 501 |
+
current_sprint = obs.get("current_sprint", 1)
|
| 502 |
+
completed_ids = {t["id"] for t in tasks if t.get("status") == "done"}
|
| 503 |
+
|
| 504 |
+
def deps_met(task: dict) -> bool:
|
| 505 |
+
meta = task.get("metadata", {}) or {} # [FIX-I5]
|
| 506 |
+
deps = task.get("depends_on", []) or meta.get("depends_on", [])
|
| 507 |
+
return all(dep in completed_ids for dep in deps)
|
| 508 |
+
|
| 509 |
+
skip = {"action_type": "skip", "task_id": None, "dev_id": None, "new_priority": None}
|
| 510 |
+
|
| 511 |
+
# Tier 1: unblock blocked tasks with met deps
|
| 512 |
+
for task in tasks:
|
| 513 |
+
if task.get("status") == "blocked" and deps_met(task):
|
| 514 |
+
return {"action_type": "unblock", "task_id": task["id"],
|
| 515 |
+
"dev_id": None, "new_priority": None}
|
| 516 |
+
|
| 517 |
+
instruction_task_ids: set = set()
|
| 518 |
+
for inst in instructions:
|
| 519 |
+
if not inst.get("followed", False):
|
| 520 |
+
for tid in inst.get("affects_tasks", []):
|
| 521 |
+
instruction_task_ids.add(tid)
|
| 522 |
+
|
| 523 |
+
# Tier 2: reprioritize low-priority instruction tasks
|
| 524 |
+
for task in tasks:
|
| 525 |
+
if (task.get("status") == "backlog"
|
| 526 |
+
and task["id"] in instruction_task_ids
|
| 527 |
+
and task.get("priority", 9) > 2
|
| 528 |
+
and deps_met(task)):
|
| 529 |
+
return {"action_type": "reprioritize", "task_id": task["id"],
|
| 530 |
+
"dev_id": None, "new_priority": 1}
|
| 531 |
+
|
| 532 |
+
# Tier 3: assign
|
| 533 |
+
assignable = [t for t in tasks if t.get("status") == "backlog" and deps_met(t)]
|
| 534 |
+
if not assignable:
|
| 535 |
+
return skip
|
| 536 |
+
|
| 537 |
+
def sort_key(t: dict) -> tuple: # [FIX-I5]
|
| 538 |
+
meta = t.get("metadata", {}) or {}
|
| 539 |
+
sprint_target = meta.get("sprint", current_sprint + 99)
|
| 540 |
+
in_inst = 0 if t["id"] in instruction_task_ids else 1
|
| 541 |
+
return (in_inst, sprint_target, t.get("priority", 99))
|
| 542 |
+
|
| 543 |
+
assignable.sort(key=sort_key)
|
| 544 |
+
task = assignable[0]
|
| 545 |
+
|
| 546 |
+
available_devs = [
|
| 547 |
+
d for d in devs
|
| 548 |
+
if d.get("is_available", False)
|
| 549 |
+
and d.get("remaining_capacity", d.get("capacity", 1)) > 0
|
| 550 |
+
]
|
| 551 |
+
if not available_devs:
|
| 552 |
+
available_devs = [d for d in devs if d.get("is_available", False)]
|
| 553 |
+
if not available_devs:
|
| 554 |
+
available_devs = devs
|
| 555 |
+
|
| 556 |
+
skill = task.get("required_skill", "")
|
| 557 |
+
skilled_devs = [d for d in available_devs
|
| 558 |
+
if d.get("skill") == skill or d.get("skill") == "fullstack"]
|
| 559 |
+
pool = skilled_devs if skilled_devs else available_devs
|
| 560 |
+
idx = last_dev_idx[0] % len(pool)
|
| 561 |
+
dev = pool[idx]
|
| 562 |
+
last_dev_idx[0] = (idx + 1) % len(pool)
|
| 563 |
+
|
| 564 |
+
return {"action_type": "assign", "task_id": task["id"],
|
| 565 |
+
"dev_id": dev["id"], "new_priority": None}
|
| 566 |
+
|
| 567 |
+
|
| 568 |
+
# ─── Environment helpers ──────────────────────────────────────────────────────
|
| 569 |
+
|
| 570 |
+
def _post(url: str, payload: dict, timeout: int = 15) -> dict:
|
| 571 |
+
resp = requests.post(url, json=payload, timeout=timeout)
|
| 572 |
+
resp.raise_for_status()
|
| 573 |
+
return resp.json()
|
| 574 |
+
|
| 575 |
+
|
| 576 |
+
def step_env(action: dict) -> dict:
|
| 577 |
+
return _post(f"{ENV_BASE_URL}/project/step", {"action": action})
|
| 578 |
+
|
| 579 |
+
|
| 580 |
+
def reset_env(scenario: str, seed: int = 42) -> dict:
|
| 581 |
+
return _post(f"{ENV_BASE_URL}/project/reset", {"task_name": scenario, "seed": seed})
|
| 582 |
+
|
| 583 |
+
|
| 584 |
+
def health() -> dict:
|
| 585 |
+
resp = requests.get(f"{ENV_BASE_URL}/project/health", timeout=10)
|
| 586 |
+
resp.raise_for_status()
|
| 587 |
+
return resp.json()
|
| 588 |
+
|
| 589 |
+
|
| 590 |
+
# ─── Episode runner ───────────────────────────────────────────────────────────
|
| 591 |
+
|
| 592 |
+
def run_episode(scenario: str, seed: int = 42) -> dict:
|
| 593 |
+
obs_data = reset_env(scenario, seed)
|
| 594 |
+
obs = obs_data.get("observation", obs_data)
|
| 595 |
+
|
| 596 |
+
assigned_this_episode: set = set()
|
| 597 |
+
assign_attempted_episode: set = set()
|
| 598 |
+
last_dev_idx = [0]
|
| 599 |
+
cumulative = 0.0
|
| 600 |
+
step_num = 0
|
| 601 |
+
|
| 602 |
+
llm_skip_streak = 0
|
| 603 |
+
llm_soft_fail_streak = 0
|
| 604 |
+
bad_assign_tid: Optional[str] = None
|
| 605 |
+
bad_assign_streak = 0
|
| 606 |
+
llm_cooldown_until = 0
|
| 607 |
+
|
| 608 |
+
MAX_STEPS = 200 # safety cap so your code doesn’t spiral forever
|
| 609 |
+
MAX_STEP_RETRIES = 3
|
| 610 |
+
|
| 611 |
+
print(f"\n[START] task={scenario}", flush=True)
|
| 612 |
+
|
| 613 |
+
while True:
|
| 614 |
+
step_num += 1
|
| 615 |
+
|
| 616 |
+
if step_num > MAX_STEPS:
|
| 617 |
+
print("[WARN] Max steps reached — forcing termination", flush=True)
|
| 618 |
+
break
|
| 619 |
+
|
| 620 |
+
day = obs.get("current_day", step_num)
|
| 621 |
+
sprint = obs.get("current_sprint", 1)
|
| 622 |
+
|
| 623 |
+
router_ok = _local_model is not None or bool(HF_TOKEN)
|
| 624 |
+
allow_llm = (
|
| 625 |
+
USE_LLM
|
| 626 |
+
and (step_num % LLM_CALL_EVERY == 1)
|
| 627 |
+
and router_ok
|
| 628 |
+
and step_num > llm_cooldown_until
|
| 629 |
+
)
|
| 630 |
+
|
| 631 |
+
action = None
|
| 632 |
+
|
| 633 |
+
if allow_llm:
|
| 634 |
+
user_prompt = build_user_prompt(
|
| 635 |
+
obs,
|
| 636 |
+
assigned_this_episode=assigned_this_episode,
|
| 637 |
+
assign_attempted_episode=assign_attempted_episode,
|
| 638 |
+
)
|
| 639 |
+
proposed = call_llm_user_prompt(user_prompt)
|
| 640 |
+
|
| 641 |
+
if proposed is None:
|
| 642 |
+
llm_soft_fail_streak += 1
|
| 643 |
+
print(" [LLM] no parse / API fail → fallback", flush=True)
|
| 644 |
+
else:
|
| 645 |
+
ok, reason = validate_llm_action(obs, proposed, assigned_this_episode)
|
| 646 |
+
if ok:
|
| 647 |
+
if proposed.get("action_type") == "skip":
|
| 648 |
+
llm_skip_streak += 1
|
| 649 |
+
if llm_skip_streak >= MAX_LLM_SKIP_STREAK:
|
| 650 |
+
action = None
|
| 651 |
+
llm_cooldown_until = max(
|
| 652 |
+
llm_cooldown_until, step_num + LLM_COOLDOWN_STEPS
|
| 653 |
+
)
|
| 654 |
+
llm_skip_streak = 0
|
| 655 |
+
llm_soft_fail_streak += 1
|
| 656 |
+
print(
|
| 657 |
+
" [COOLDOWN] skip-spam — rule-based this step + "
|
| 658 |
+
f"{LLM_COOLDOWN_STEPS}-step LLM pause",
|
| 659 |
+
flush=True,
|
| 660 |
+
)
|
| 661 |
+
else:
|
| 662 |
+
action = proposed
|
| 663 |
+
llm_soft_fail_streak = 0
|
| 664 |
+
bad_assign_tid = None
|
| 665 |
+
bad_assign_streak = 0
|
| 666 |
+
else:
|
| 667 |
+
llm_skip_streak = 0
|
| 668 |
+
action = proposed
|
| 669 |
+
llm_soft_fail_streak = 0
|
| 670 |
+
bad_assign_tid = None
|
| 671 |
+
bad_assign_streak = 0
|
| 672 |
+
else:
|
| 673 |
+
llm_soft_fail_streak += 1
|
| 674 |
+
print(
|
| 675 |
+
f" [REJECT] LLM action invalid ({reason}) → fallback",
|
| 676 |
+
flush=True,
|
| 677 |
+
)
|
| 678 |
+
if proposed.get("action_type") in ("assign", "reassign"):
|
| 679 |
+
tidp = proposed.get("task_id")
|
| 680 |
+
tid_key = str(tidp) if tidp is not None else None
|
| 681 |
+
if tid_key == bad_assign_tid:
|
| 682 |
+
bad_assign_streak += 1
|
| 683 |
+
else:
|
| 684 |
+
bad_assign_tid = tid_key
|
| 685 |
+
bad_assign_streak = 1
|
| 686 |
+
else:
|
| 687 |
+
bad_assign_tid = None
|
| 688 |
+
bad_assign_streak = 0
|
| 689 |
+
|
| 690 |
+
if bad_assign_streak >= MAX_SAME_BAD_ASSIGN_STREAK:
|
| 691 |
+
llm_cooldown_until = max(
|
| 692 |
+
llm_cooldown_until, step_num + LLM_COOLDOWN_STEPS
|
| 693 |
+
)
|
| 694 |
+
bad_assign_streak = 0
|
| 695 |
+
bad_assign_tid = None
|
| 696 |
+
print(
|
| 697 |
+
f" [COOLDOWN] repeated bad assign → rule-based for {LLM_COOLDOWN_STEPS} steps",
|
| 698 |
+
flush=True,
|
| 699 |
+
)
|
| 700 |
+
|
| 701 |
+
if llm_soft_fail_streak >= MAX_LLM_SOFT_FAIL_STREAK:
|
| 702 |
+
llm_cooldown_until = max(
|
| 703 |
+
llm_cooldown_until, step_num + LLM_COOLDOWN_STEPS
|
| 704 |
+
)
|
| 705 |
+
llm_soft_fail_streak = 0
|
| 706 |
+
print(
|
| 707 |
+
f" [COOLDOWN] repeated LLM invalid → rule-based for {LLM_COOLDOWN_STEPS} steps",
|
| 708 |
+
flush=True,
|
| 709 |
+
)
|
| 710 |
+
|
| 711 |
+
if action is None:
|
| 712 |
+
action = smart_fallback(obs, assigned_this_episode, last_dev_idx)
|
| 713 |
+
|
| 714 |
+
if action.get("action_type") in ("assign", "reassign") and action.get("task_id"):
|
| 715 |
+
assign_attempted_episode.add(action["task_id"])
|
| 716 |
+
|
| 717 |
+
# ─── STEP WITH RETRY + STATE VALIDATION ───────────────────────
|
| 718 |
+
success = False
|
| 719 |
+
|
| 720 |
+
for attempt in range(MAX_STEP_RETRIES):
|
| 721 |
+
result = step_env(action)
|
| 722 |
+
new_obs = result.get("observation", result)
|
| 723 |
+
|
| 724 |
+
# 🚨 HARD GUARD: detect time regression
|
| 725 |
+
if (
|
| 726 |
+
new_obs.get("current_day", 0) < obs.get("current_day", 0) or
|
| 727 |
+
new_obs.get("current_sprint", 0) < obs.get("current_sprint", 0)
|
| 728 |
+
):
|
| 729 |
+
print(f"[ERROR] State regression detected (attempt {attempt+1}) — retrying", flush=True)
|
| 730 |
+
time.sleep(0.5)
|
| 731 |
+
continue
|
| 732 |
+
|
| 733 |
+
success = True
|
| 734 |
+
break
|
| 735 |
+
|
| 736 |
+
if not success:
|
| 737 |
+
print("[FATAL] Repeated environment corruption — aborting episode", flush=True)
|
| 738 |
+
break
|
| 739 |
+
|
| 740 |
+
reward = result.get("reward", 0.0)
|
| 741 |
+
obs = new_obs
|
| 742 |
+
done = result.get("done", obs.get("done", False))
|
| 743 |
+
cumulative += reward
|
| 744 |
+
|
| 745 |
+
inst_score = obs.get("instruction_following_score", 0.0)
|
| 746 |
+
debt_raw = obs.get("tech_debt", 0)
|
| 747 |
+
|
| 748 |
+
# Track tasks successfully moved to in_progress (assign / reassign)
|
| 749 |
+
if action.get("action_type") in ("assign", "reassign") and action.get("task_id"):
|
| 750 |
+
tid_sent = action["task_id"]
|
| 751 |
+
post_s = {t["id"]: t.get("status") for t in obs.get("tasks", [])}
|
| 752 |
+
if post_s.get(tid_sent) == "in_progress":
|
| 753 |
+
assigned_this_episode.add(tid_sent)
|
| 754 |
+
|
| 755 |
+
debt_d = len(debt_raw) if isinstance(debt_raw, list) else int(debt_raw or 0)
|
| 756 |
+
|
| 757 |
+
print(
|
| 758 |
+
f"[STEP] task={scenario} step={step_num} day={day} sprint={sprint} "
|
| 759 |
+
f"action={action.get('action_type','?')} "
|
| 760 |
+
f"task_id={action.get('task_id','None')} "
|
| 761 |
+
f"dev={action.get('dev_id','None')} "
|
| 762 |
+
f"reward={reward:.4f} cumulative={cumulative:.4f} "
|
| 763 |
+
f"inst_score={inst_score:.3f} debt={debt_d} done={done}",
|
| 764 |
+
flush=True,
|
| 765 |
+
)
|
| 766 |
+
|
| 767 |
+
if done:
|
| 768 |
+
break
|
| 769 |
+
|
| 770 |
+
# ─── FINAL METRICS ───────────────────────────────────────────────
|
| 771 |
+
tasks = obs.get("tasks", [])
|
| 772 |
+
completed = sum(1 for t in tasks if t.get("status") == "done")
|
| 773 |
+
missed = sum(1 for t in tasks if t.get("status") == "missed")
|
| 774 |
+
inst_score = obs.get("instruction_following_score", 0.0)
|
| 775 |
+
|
| 776 |
+
debt_raw = obs.get("tech_debt", 0)
|
| 777 |
+
debt_count = len(debt_raw) if isinstance(debt_raw, list) else int(debt_raw or 0)
|
| 778 |
+
|
| 779 |
+
total = len(tasks) or 1
|
| 780 |
+
|
| 781 |
+
final_score = max(0.01, min(0.99,
|
| 782 |
+
(completed / total) * 0.55 +
|
| 783 |
+
inst_score * 0.30 +
|
| 784 |
+
max(0.01, 1.0 - debt_count * 0.02) * 0.15
|
| 785 |
+
))
|
| 786 |
+
|
| 787 |
+
print(
|
| 788 |
+
f"[END] task={scenario} score={final_score:.4f} steps={step_num} "
|
| 789 |
+
f"completed={completed} missed={missed} "
|
| 790 |
+
f"inst_score={inst_score:.3f} debt={debt_count}",
|
| 791 |
+
flush=True,
|
| 792 |
+
)
|
| 793 |
+
|
| 794 |
+
return {
|
| 795 |
+
"scenario": scenario,
|
| 796 |
+
"score": final_score,
|
| 797 |
+
"completed": completed,
|
| 798 |
+
"missed": missed,
|
| 799 |
+
"inst_score": inst_score,
|
| 800 |
+
"debt": debt_count,
|
| 801 |
+
"steps": step_num
|
| 802 |
+
}
|
| 803 |
+
|
| 804 |
+
# ─── Main ─────────────────────────────────────────────────────────────────────
|
| 805 |
+
|
| 806 |
+
def main():
|
| 807 |
+
scenarios = ["project_easy", "project_medium", "project_hard"]
|
| 808 |
+
|
| 809 |
+
local_ok = False
|
| 810 |
+
if LOCAL_MODEL_PATH:
|
| 811 |
+
local_ok = _load_local_model(LOCAL_MODEL_PATH)
|
| 812 |
+
if not local_ok:
|
| 813 |
+
print("[WARN] Local model load failed — scores will reflect BASE model!", flush=True)
|
| 814 |
+
|
| 815 |
+
if not USE_LLM:
|
| 816 |
+
mode = "rule-based-only (USE_LLM=0)"
|
| 817 |
+
else:
|
| 818 |
+
mode = ("local-finetuned" if local_ok
|
| 819 |
+
else ("hf-router-base" if HF_TOKEN else "rule-based-only"))
|
| 820 |
+
|
| 821 |
+
print(f"[INFO] model={LOCAL_MODEL_PATH if local_ok else MODEL_NAME}", flush=True)
|
| 822 |
+
print(f"[INFO] inference mode={mode}", flush=True)
|
| 823 |
+
print(f"[INFO] USE_LLM={USE_LLM} cooldown_steps={LLM_COOLDOWN_STEPS}", flush=True)
|
| 824 |
+
print(f"[INFO] server={ENV_BASE_URL}", flush=True)
|
| 825 |
+
|
| 826 |
+
try:
|
| 827 |
+
print(f"[INFO] health={health()}", flush=True)
|
| 828 |
+
except Exception as e:
|
| 829 |
+
print(f"[WARN] health check failed: {e}", flush=True)
|
| 830 |
+
|
| 831 |
+
results = {}
|
| 832 |
+
t0 = time.time()
|
| 833 |
+
for scenario in scenarios:
|
| 834 |
+
try:
|
| 835 |
+
results[scenario] = run_episode(scenario)
|
| 836 |
+
except Exception as e:
|
| 837 |
+
print(f"[ERROR] {scenario}: {e}", flush=True)
|
| 838 |
+
results[scenario] = {"score": 0.01, "error": str(e)}
|
| 839 |
+
|
| 840 |
+
scores = [results[s].get("score", 0) for s in scenarios if s in results]
|
| 841 |
+
avg = sum(scores) / len(scores) if scores else 0
|
| 842 |
+
|
| 843 |
+
print("\n" + "=" * 62, flush=True)
|
| 844 |
+
print(f" ROUND 2 — SCORES [{mode}]", flush=True)
|
| 845 |
+
print("=" * 62, flush=True)
|
| 846 |
+
for s in scenarios:
|
| 847 |
+
sc = results.get(s, {}).get("score", 0)
|
| 848 |
+
print(f" {s:<22} {sc:.4f} {'█' * int(sc * 20)}", flush=True)
|
| 849 |
+
print(f"\n AVERAGE {avg:.4f}", flush=True)
|
| 850 |
+
print(f"\n Runtime: {time.time()-t0:.1f}s", flush=True)
|
| 851 |
+
print("=" * 62, flush=True)
|
| 852 |
+
|
| 853 |
+
|
| 854 |
+
if __name__ == "__main__":
|
| 855 |
+
main()
|
openenv.yaml
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
spec_version: 1
|
| 2 |
+
name: ai-sprint-manager
|
| 3 |
+
type: environment
|
| 4 |
+
runtime: python
|
| 5 |
+
app: server:app
|
| 6 |
+
port: 8000
|
| 7 |
+
description: >
|
| 8 |
+
An RL environment where an AI agent acts as a Tech Lead managing
|
| 9 |
+
agile sprints — assigning tasks to developers, balancing workload,
|
| 10 |
+
and handling unexpected events to maximize team productivity.
|
| 11 |
+
tasks:
|
| 12 |
+
- id: easy_sprint
|
| 13 |
+
name: "Easy Sprint: Small Team, Clear Tasks"
|
| 14 |
+
difficulty: easy
|
| 15 |
+
- id: medium_sprint
|
| 16 |
+
name: "Medium Sprint: Bugs + Delays"
|
| 17 |
+
difficulty: medium
|
| 18 |
+
- id: hard_sprint
|
| 19 |
+
name: "Hard Sprint: Cascading Failures & Scope Changes"
|
| 20 |
+
difficulty: hard
|
| 21 |
+
|
| 22 |
+
- id: project_easy
|
| 23 |
+
description: >
|
| 24 |
+
AI Engineering Manager — 6-sprint project (60 days), small stable team,
|
| 25 |
+
clear backlog, no cascade failures. Tests long-horizon planning baseline.
|
| 26 |
+
difficulty: easy
|
| 27 |
+
reset_endpoint: /project/reset
|
| 28 |
+
step_endpoint: /project/step
|
| 29 |
+
state_endpoint: /project/state
|
| 30 |
+
health_endpoint: /project/health
|
| 31 |
+
reset_payload:
|
| 32 |
+
task_name: project_easy
|
| 33 |
+
seed: 42
|
| 34 |
+
max_steps: 60
|
| 35 |
+
score_field: instruction_following_score
|
| 36 |
+
metadata:
|
| 37 |
+
round: 2
|
| 38 |
+
num_sprints: 6
|
| 39 |
+
total_days: 60
|
| 40 |
+
num_tasks: 24
|
| 41 |
+
num_instructions: 12
|
| 42 |
+
theme: long_horizon_planning
|
| 43 |
+
|
| 44 |
+
- id: project_medium
|
| 45 |
+
description: >
|
| 46 |
+
AI Engineering Manager — 6-sprint project (60 days), mid-size team with
|
| 47 |
+
developer absences, urgent bugs, and conflicting instructions mid-sprint.
|
| 48 |
+
difficulty: medium
|
| 49 |
+
reset_endpoint: /project/reset
|
| 50 |
+
step_endpoint: /project/step
|
| 51 |
+
state_endpoint: /project/state
|
| 52 |
+
health_endpoint: /project/health
|
| 53 |
+
reset_payload:
|
| 54 |
+
task_name: project_medium
|
| 55 |
+
seed: 42
|
| 56 |
+
max_steps: 60
|
| 57 |
+
score_field: instruction_following_score
|
| 58 |
+
metadata:
|
| 59 |
+
round: 2
|
| 60 |
+
num_sprints: 6
|
| 61 |
+
total_days: 60
|
| 62 |
+
num_tasks: 30
|
| 63 |
+
num_instructions: 18
|
| 64 |
+
theme: long_horizon_planning
|
| 65 |
+
|
| 66 |
+
- id: project_hard
|
| 67 |
+
description: >
|
| 68 |
+
AI Engineering Manager — 6-sprint project (60 days), large team with
|
| 69 |
+
cascading failures, scope pivots, conflicting instructions, and hard
|
| 70 |
+
delivery deadline. Scale AI bonus prize target scenario.
|
| 71 |
+
difficulty: hard
|
| 72 |
+
reset_endpoint: /project/reset
|
| 73 |
+
step_endpoint: /project/step
|
| 74 |
+
state_endpoint: /project/state
|
| 75 |
+
health_endpoint: /project/health
|
| 76 |
+
reset_payload:
|
| 77 |
+
task_name: project_hard
|
| 78 |
+
seed: 42
|
| 79 |
+
max_steps: 60
|
| 80 |
+
score_field: instruction_following_score
|
| 81 |
+
metadata:
|
| 82 |
+
round: 2
|
| 83 |
+
num_sprints: 6
|
| 84 |
+
total_days: 60
|
| 85 |
+
num_tasks: 37
|
| 86 |
+
num_instructions: 25
|
| 87 |
+
theme: long_horizon_planning
|
| 88 |
+
scale_ai_bonus: true
|
plot_results.py
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
plot_results.py — Results Charts for Hackathon Presentation
|
| 3 |
+
============================================================
|
| 4 |
+
Generates publication-ready charts from evaluation JSON data.
|
| 5 |
+
Saves all charts to results/charts/ as both PNG and SVG.
|
| 6 |
+
|
| 7 |
+
BASELINE CONSTANTS (FINAL — measured, do not change):
|
| 8 |
+
R1 Llama-3.1-8B zero-shot: easy=0.0100, medium=0.4583, hard=0.0100, avg=0.1594
|
| 9 |
+
R2 Llama-3.1-8B zero-shot: easy=0.3198, medium=0.2443, hard=0.2520, avg=0.2720
|
| 10 |
+
Training model: Qwen/Qwen2.5-1.5B-Instruct (GRPO, 4-bit QLoRA)
|
| 11 |
+
|
| 12 |
+
Charts produced:
|
| 13 |
+
1. r1_scores_comparison.png — R1 Llama baseline vs trained bar chart
|
| 14 |
+
2. r2_scores_comparison.png — R2 Llama zero-shot vs trained bar chart (no rule-based)
|
| 15 |
+
3. sprint_rewards.png — Sprint-by-sprint reward for each R2 scenario
|
| 16 |
+
4. improvement_summary.png — Combined before/after delta chart (main slide chart)
|
| 17 |
+
5. training_curve.png — GRPO training loss/reward curve (if trainer_state.json present)
|
| 18 |
+
|
| 19 |
+
Usage:
|
| 20 |
+
# After running evaluate_r2.py --baseline-only:
|
| 21 |
+
python plot_results.py --eval results/r2_evaluation.json
|
| 22 |
+
|
| 23 |
+
# With training curve (after train_llm.py):
|
| 24 |
+
python plot_results.py --eval results/r2_evaluation.json \\
|
| 25 |
+
--trainer results/trained_model/trainer_state.json
|
| 26 |
+
|
| 27 |
+
# Hardcode known scores for presentation (no eval file needed):
|
| 28 |
+
python plot_results.py --demo
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
from __future__ import annotations
|
| 32 |
+
|
| 33 |
+
import argparse
|
| 34 |
+
import json
|
| 35 |
+
import os
|
| 36 |
+
from pathlib import Path
|
| 37 |
+
|
| 38 |
+
CHARTS_DIR = Path("results/charts")
|
| 39 |
+
CHARTS_DIR.mkdir(parents=True, exist_ok=True)
|
| 40 |
+
|
| 41 |
+
# ── Measured baselines (FINAL) ────────────────────────────────────────────────
|
| 42 |
+
LLAMA_BASELINE_R1 = {
|
| 43 |
+
"easy_sprint": 0.0100,
|
| 44 |
+
"medium_sprint": 0.4583,
|
| 45 |
+
"hard_sprint": 0.0100,
|
| 46 |
+
"average": 0.1594,
|
| 47 |
+
}
|
| 48 |
+
LLAMA_BASELINE_R2 = {
|
| 49 |
+
"project_easy": 0.3198,
|
| 50 |
+
"project_medium": 0.2443,
|
| 51 |
+
"project_hard": 0.2520,
|
| 52 |
+
"average": 0.2720,
|
| 53 |
+
}
|
| 54 |
+
TRAINING_MODEL = "Qwen/Qwen2.5-1.5B-Instruct"
|
| 55 |
+
|
| 56 |
+
# ── Colour palette ────────────────────────────────────────────────────────────
|
| 57 |
+
C_LLAMA = "#6B7280" # grey — Llama zero-shot baseline
|
| 58 |
+
C_RULE = "#3B82F6" # blue — rule-based baseline
|
| 59 |
+
C_TRAINED = "#10B981" # green — trained Qwen (post-GRPO)
|
| 60 |
+
C_EASY = "#60A5FA"
|
| 61 |
+
C_MEDIUM = "#F59E0B"
|
| 62 |
+
C_HARD = "#EF4444"
|
| 63 |
+
C_BG = "#F9FAFB"
|
| 64 |
+
C_TEXT = "#111827"
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def _setup_matplotlib():
|
| 68 |
+
import matplotlib
|
| 69 |
+
matplotlib.use("Agg") # headless
|
| 70 |
+
import matplotlib.pyplot as plt
|
| 71 |
+
import matplotlib.patches as mpatches
|
| 72 |
+
plt.rcParams.update({
|
| 73 |
+
"figure.facecolor": C_BG,
|
| 74 |
+
"axes.facecolor": C_BG,
|
| 75 |
+
"axes.edgecolor": "#D1D5DB",
|
| 76 |
+
"axes.labelcolor": C_TEXT,
|
| 77 |
+
"text.color": C_TEXT,
|
| 78 |
+
"xtick.color": C_TEXT,
|
| 79 |
+
"ytick.color": C_TEXT,
|
| 80 |
+
"grid.color": "#E5E7EB",
|
| 81 |
+
"grid.linestyle": "--",
|
| 82 |
+
"grid.alpha": 0.7,
|
| 83 |
+
"font.family": "sans-serif",
|
| 84 |
+
"font.size": 11,
|
| 85 |
+
"axes.titlesize": 13,
|
| 86 |
+
"axes.titleweight": "bold",
|
| 87 |
+
"figure.dpi": 150,
|
| 88 |
+
})
|
| 89 |
+
return plt, mpatches
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def save(plt, name: str):
|
| 93 |
+
png = CHARTS_DIR / f"{name}.png"
|
| 94 |
+
svg = CHARTS_DIR / f"{name}.svg"
|
| 95 |
+
plt.tight_layout()
|
| 96 |
+
plt.savefig(png, bbox_inches="tight")
|
| 97 |
+
plt.savefig(svg, bbox_inches="tight")
|
| 98 |
+
plt.close()
|
| 99 |
+
print(f" [SAVED] {png}", flush=True)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
# ── Chart 1: R1 scores comparison ────────────────────────────────────────────
|
| 103 |
+
|
| 104 |
+
def chart_r1_comparison(eval_data: dict):
|
| 105 |
+
plt, mpatches = _setup_matplotlib()
|
| 106 |
+
|
| 107 |
+
tasks = ["easy_sprint", "medium_sprint", "hard_sprint"]
|
| 108 |
+
labels = ["Easy Sprint", "Medium Sprint", "Hard Sprint"]
|
| 109 |
+
llama_base = [eval_data.get("r1_llama_baseline", LLAMA_BASELINE_R1).get(t, 0) for t in tasks]
|
| 110 |
+
rule_based = [eval_data.get("r1_rule_based", {}).get(t, {}).get("avg_score", 0) for t in tasks]
|
| 111 |
+
llm_scores = [eval_data.get("r1_llm", {}).get(t, {}).get("avg_score", 0) for t in tasks]
|
| 112 |
+
|
| 113 |
+
has_llm = any(v > 0 for v in llm_scores)
|
| 114 |
+
x = range(len(tasks))
|
| 115 |
+
width = 0.28 if has_llm else 0.38
|
| 116 |
+
fig, ax = plt.subplots(figsize=(9, 5))
|
| 117 |
+
|
| 118 |
+
b1 = ax.bar([i - width for i in x], llama_base, width, label=f"Llama-3.1-8B (zero-shot baseline)", color=C_LLAMA, zorder=3)
|
| 119 |
+
b2 = ax.bar([i for i in x], rule_based, width, label="Rule-based", color=C_RULE, zorder=3)
|
| 120 |
+
if has_llm:
|
| 121 |
+
b3 = ax.bar([i + width for i in x], llm_scores, width, label=f"{TRAINING_MODEL} (GRPO trained)", color=C_TRAINED, zorder=3)
|
| 122 |
+
|
| 123 |
+
def label_bars(bars):
|
| 124 |
+
for bar in bars:
|
| 125 |
+
h = bar.get_height()
|
| 126 |
+
if h > 0.01:
|
| 127 |
+
ax.text(bar.get_x() + bar.get_width()/2, h + 0.01,
|
| 128 |
+
f"{h:.2f}", ha="center", va="bottom", fontsize=9)
|
| 129 |
+
|
| 130 |
+
label_bars(b1); label_bars(b2)
|
| 131 |
+
if has_llm: label_bars(b3)
|
| 132 |
+
|
| 133 |
+
ax.set_xticks(list(x))
|
| 134 |
+
ax.set_xticklabels(labels)
|
| 135 |
+
ax.set_ylim(0, 1.15)
|
| 136 |
+
ax.set_ylabel("Score (0.01 – 0.99)")
|
| 137 |
+
ax.set_title("Round 1 — Score Comparison")
|
| 138 |
+
ax.legend(loc="upper right")
|
| 139 |
+
ax.yaxis.grid(True, zorder=0)
|
| 140 |
+
ax.set_axisbelow(True)
|
| 141 |
+
|
| 142 |
+
save(plt, "r1_scores_comparison")
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
# ── Chart 2: R2 scores comparison ────────────────────────────────────────────
|
| 146 |
+
|
| 147 |
+
def chart_r2_comparison(eval_data: dict):
|
| 148 |
+
plt, mpatches = _setup_matplotlib()
|
| 149 |
+
|
| 150 |
+
tasks = ["project_easy", "project_medium", "project_hard"]
|
| 151 |
+
labels = ["Easy (6 sprints)", "Medium (6 sprints)", "Hard (6 sprints)"]
|
| 152 |
+
llama_base = [eval_data.get("r2_llama_baseline", LLAMA_BASELINE_R2).get(t, 0) for t in tasks]
|
| 153 |
+
llm_scores = [eval_data.get("r2_llm", {}).get(t, {}).get("avg_score", 0) for t in tasks]
|
| 154 |
+
|
| 155 |
+
has_llm = any(v > 0 for v in llm_scores)
|
| 156 |
+
x = range(len(tasks))
|
| 157 |
+
width = 0.32 if has_llm else 0.5
|
| 158 |
+
fig, ax = plt.subplots(figsize=(9, 5))
|
| 159 |
+
|
| 160 |
+
b1 = ax.bar([i - width/2 for i in x], llama_base, width, label="Llama-3.1-8B (zero-shot)", color=C_LLAMA, zorder=3)
|
| 161 |
+
if has_llm:
|
| 162 |
+
b2 = ax.bar([i + width/2 for i in x], llm_scores, width, label=f"{TRAINING_MODEL} (GRPO)", color=C_TRAINED, zorder=3)
|
| 163 |
+
|
| 164 |
+
for bars in ([b1] + ([b2] if has_llm else [])):
|
| 165 |
+
for bar in bars:
|
| 166 |
+
h = bar.get_height()
|
| 167 |
+
if h > 0.01:
|
| 168 |
+
ax.text(bar.get_x() + bar.get_width()/2, h + 0.01,
|
| 169 |
+
f"{h:.2f}", ha="center", va="bottom", fontsize=9)
|
| 170 |
+
|
| 171 |
+
ax.set_xticks(list(x))
|
| 172 |
+
ax.set_xticklabels(labels)
|
| 173 |
+
ax.set_ylim(0, 1.15)
|
| 174 |
+
ax.set_ylabel("Project Score (delivery × instruction × health)")
|
| 175 |
+
ax.set_title("Round 2 — Multi-Sprint Project Score")
|
| 176 |
+
ax.legend(loc="upper right")
|
| 177 |
+
ax.yaxis.grid(True, zorder=0)
|
| 178 |
+
ax.set_axisbelow(True)
|
| 179 |
+
|
| 180 |
+
save(plt, "r2_scores_comparison")
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
# ── Chart 3: Sprint reward curves ─────────────────────────────────────────────
|
| 184 |
+
|
| 185 |
+
def chart_sprint_rewards(eval_data: dict):
|
| 186 |
+
"""Per-sprint reward for each R2 scenario (Llama baseline vs trained)."""
|
| 187 |
+
plt, _ = _setup_matplotlib()
|
| 188 |
+
tasks = ["project_easy", "project_medium", "project_hard"]
|
| 189 |
+
colors = [C_EASY, C_MEDIUM, C_HARD]
|
| 190 |
+
labels = ["Easy", "Medium", "Hard"]
|
| 191 |
+
|
| 192 |
+
fig, axes = plt.subplots(1, 3, figsize=(13, 4), sharey=True)
|
| 193 |
+
|
| 194 |
+
for ax, task, color, label in zip(axes, tasks, colors, labels):
|
| 195 |
+
llm_eps = eval_data.get("r2_llm", {}).get(task, {}).get("episodes", [])
|
| 196 |
+
|
| 197 |
+
if llm_eps:
|
| 198 |
+
sr = llm_eps[0].get("sprint_rewards", [])
|
| 199 |
+
if sr:
|
| 200 |
+
ax.plot(range(1, len(sr)+1), sr, "o-",
|
| 201 |
+
color=color, label="Qwen GRPO", linewidth=2, markersize=6)
|
| 202 |
+
|
| 203 |
+
ax.set_xlabel("Sprint")
|
| 204 |
+
ax.set_title(f"{label} Project")
|
| 205 |
+
ax.set_xticks(range(1, 7))
|
| 206 |
+
ax.set_ylim(0, 2.2)
|
| 207 |
+
ax.yaxis.grid(True)
|
| 208 |
+
ax.set_axisbelow(True)
|
| 209 |
+
if ax == axes[0]:
|
| 210 |
+
ax.set_ylabel("Sprint Reward")
|
| 211 |
+
ax.legend(fontsize=8)
|
| 212 |
+
|
| 213 |
+
fig.suptitle("Sprint-by-Sprint Reward — Qwen GRPO Trained", y=1.02, fontsize=13, fontweight="bold")
|
| 214 |
+
save(plt, "sprint_rewards")
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
# ── Chart 4: Improvement summary (main presentation slide) ───────────────────
|
| 218 |
+
|
| 219 |
+
def chart_improvement_summary(eval_data: dict):
|
| 220 |
+
"""Main before/after chart. Uses Llama zero-shot as the 'before' bar."""
|
| 221 |
+
plt, _ = _setup_matplotlib()
|
| 222 |
+
|
| 223 |
+
all_tasks = (
|
| 224 |
+
[f"R1: {t.replace('_sprint','').title()}" for t in ["easy_sprint","medium_sprint","hard_sprint"]] +
|
| 225 |
+
[f"R2: {t.replace('project_','').title()}" for t in ["project_easy","project_medium","project_hard"]]
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
llama_base, trained_scores = [], []
|
| 229 |
+
for t in ["easy_sprint","medium_sprint","hard_sprint"]:
|
| 230 |
+
llama_base.append(eval_data.get("r1_llama_baseline", LLAMA_BASELINE_R1).get(t, 0))
|
| 231 |
+
trained_scores.append(eval_data.get("r1_llm", {}).get(t, {}).get("avg_score", 0))
|
| 232 |
+
for t in ["project_easy","project_medium","project_hard"]:
|
| 233 |
+
llama_base.append(eval_data.get("r2_llama_baseline", LLAMA_BASELINE_R2).get(t, 0))
|
| 234 |
+
trained_scores.append(eval_data.get("r2_llm", {}).get(t, {}).get("avg_score", 0))
|
| 235 |
+
|
| 236 |
+
x = range(len(all_tasks))
|
| 237 |
+
width = 0.36
|
| 238 |
+
fig, ax = plt.subplots(figsize=(11, 5))
|
| 239 |
+
|
| 240 |
+
b1 = ax.bar([i - width/2 for i in x], llama_base, width,
|
| 241 |
+
label="Before: Llama-3.1-8B (zero-shot)", color=C_LLAMA, zorder=3)
|
| 242 |
+
b2 = ax.bar([i + width/2 for i in x], trained_scores, width,
|
| 243 |
+
label=f"After: {TRAINING_MODEL} (GRPO)", color=C_TRAINED, zorder=3)
|
| 244 |
+
|
| 245 |
+
# Delta arrows and labels
|
| 246 |
+
for i, (base, trained) in enumerate(zip(llama_base, trained_scores)):
|
| 247 |
+
if trained > base + 0.01:
|
| 248 |
+
ax.annotate("", xy=(i + width/2, trained + 0.02), xytext=(i - width/2, base + 0.02),
|
| 249 |
+
arrowprops=dict(arrowstyle="->", color="#059669", lw=1.5))
|
| 250 |
+
ax.text(i, max(base, trained) + 0.06, f"+{trained-base:.2f}",
|
| 251 |
+
ha="center", fontsize=8, color="#059669", fontweight="bold")
|
| 252 |
+
|
| 253 |
+
ax.set_xticks(list(x))
|
| 254 |
+
ax.set_xticklabels(all_tasks, rotation=15, ha="right")
|
| 255 |
+
ax.set_ylim(0, 1.25)
|
| 256 |
+
ax.set_ylabel("Score")
|
| 257 |
+
ax.set_title(f"Reward Improvement: Llama Zero-Shot → Qwen2.5-1.5B GRPO Trained")
|
| 258 |
+
ax.legend(loc="upper left")
|
| 259 |
+
ax.yaxis.grid(True, zorder=0)
|
| 260 |
+
ax.set_axisbelow(True)
|
| 261 |
+
|
| 262 |
+
# Divider between R1 and R2
|
| 263 |
+
ax.axvline(x=2.5, color="#9CA3AF", linestyle=":", linewidth=1.5)
|
| 264 |
+
ax.text(1.0, 1.20, "Round 1", ha="center", fontsize=10, color="#6B7280")
|
| 265 |
+
ax.text(4.0, 1.20, "Round 2", ha="center", fontsize=10, color="#6B7280")
|
| 266 |
+
|
| 267 |
+
save(plt, "improvement_summary")
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
# ── Chart 5: Training loss/reward curve ───────────────────────────────────────
|
| 271 |
+
|
| 272 |
+
def chart_training_curve(trainer_state_path: str):
|
| 273 |
+
plt, _ = _setup_matplotlib()
|
| 274 |
+
|
| 275 |
+
with open(trainer_state_path) as f:
|
| 276 |
+
state = json.load(f)
|
| 277 |
+
|
| 278 |
+
log_history = state.get("log_history", [])
|
| 279 |
+
if not log_history:
|
| 280 |
+
print(" [SKIP] No log_history in trainer_state.json", flush=True)
|
| 281 |
+
return
|
| 282 |
+
|
| 283 |
+
steps, losses, rewards = [], [], []
|
| 284 |
+
for entry in log_history:
|
| 285 |
+
if "loss" in entry:
|
| 286 |
+
steps.append(entry.get("step", 0))
|
| 287 |
+
losses.append(entry["loss"])
|
| 288 |
+
if "reward" in entry:
|
| 289 |
+
rewards.append((entry.get("step", 0), entry["reward"]))
|
| 290 |
+
|
| 291 |
+
fig, axes = plt.subplots(1, 2, figsize=(11, 4))
|
| 292 |
+
|
| 293 |
+
if steps and losses:
|
| 294 |
+
axes[0].plot(steps, losses, color=C_RULE, linewidth=2)
|
| 295 |
+
axes[0].set_xlabel("Training Step")
|
| 296 |
+
axes[0].set_ylabel("Loss")
|
| 297 |
+
axes[0].set_title("GRPO Training Loss (Qwen2.5-1.5B)")
|
| 298 |
+
axes[0].yaxis.grid(True)
|
| 299 |
+
|
| 300 |
+
if rewards:
|
| 301 |
+
rsteps, rvals = zip(*rewards)
|
| 302 |
+
axes[1].plot(rsteps, rvals, color=C_TRAINED, linewidth=2)
|
| 303 |
+
axes[1].set_xlabel("Training Step")
|
| 304 |
+
axes[1].set_ylabel("Mean Reward")
|
| 305 |
+
axes[1].set_title("GRPO Mean Reward per Step")
|
| 306 |
+
axes[1].yaxis.grid(True)
|
| 307 |
+
|
| 308 |
+
fig.suptitle(f"GRPO Training Curves — {TRAINING_MODEL}", fontsize=13, fontweight="bold")
|
| 309 |
+
save(plt, "training_curve")
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
# ── Demo mode (hardcoded real baselines, placeholder trained scores) ──────────
|
| 313 |
+
|
| 314 |
+
def demo_mode():
|
| 315 |
+
"""
|
| 316 |
+
Generate charts using real measured baselines.
|
| 317 |
+
Trained scores are placeholders — replace with real evaluate_r2.py output after on-site training.
|
| 318 |
+
"""
|
| 319 |
+
print("[INFO] Demo mode — real Llama baselines, placeholder trained scores", flush=True)
|
| 320 |
+
print(f"[INFO] Training model: {TRAINING_MODEL}", flush=True)
|
| 321 |
+
|
| 322 |
+
# Placeholder trained scores — update after on-site GRPO training
|
| 323 |
+
PLACEHOLDER_R1_TRAINED = {
|
| 324 |
+
"easy_sprint": 0.0, # update after training
|
| 325 |
+
"medium_sprint": 0.0, # update after training
|
| 326 |
+
"hard_sprint": 0.0, # update after training
|
| 327 |
+
}
|
| 328 |
+
PLACEHOLDER_R2_TRAINED = {
|
| 329 |
+
"project_easy": 0.0, # update after training
|
| 330 |
+
"project_medium": 0.0, # update after training
|
| 331 |
+
"project_hard": 0.0, # update after training
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
demo_data = {
|
| 335 |
+
"r1_llama_baseline": LLAMA_BASELINE_R1,
|
| 336 |
+
"r2_llama_baseline": LLAMA_BASELINE_R2,
|
| 337 |
+
"r1_rule_based": {
|
| 338 |
+
"easy_sprint": {"avg_score": 0.92},
|
| 339 |
+
"medium_sprint": {"avg_score": 0.35},
|
| 340 |
+
"hard_sprint": {"avg_score": 0.01},
|
| 341 |
+
},
|
| 342 |
+
"r1_llm": {
|
| 343 |
+
t: {"avg_score": v} for t, v in PLACEHOLDER_R1_TRAINED.items()
|
| 344 |
+
},
|
| 345 |
+
"r2_rule_based": {},
|
| 346 |
+
"r2_llm": {
|
| 347 |
+
t: {"avg_score": v, "episodes": []}
|
| 348 |
+
for t, v in PLACEHOLDER_R2_TRAINED.items()
|
| 349 |
+
},
|
| 350 |
+
"improvement": {
|
| 351 |
+
t: {
|
| 352 |
+
"llama_baseline": LLAMA_BASELINE_R2[t],
|
| 353 |
+
"trained_llm": PLACEHOLDER_R2_TRAINED[t],
|
| 354 |
+
"delta_vs_llama": round(PLACEHOLDER_R2_TRAINED[t] - LLAMA_BASELINE_R2[t], 4),
|
| 355 |
+
}
|
| 356 |
+
for t in ["project_easy", "project_medium", "project_hard"]
|
| 357 |
+
},
|
| 358 |
+
}
|
| 359 |
+
return demo_data
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
# ── CLI ───────────────────────────────────────────────────────────────────────
|
| 363 |
+
|
| 364 |
+
def main():
|
| 365 |
+
parser = argparse.ArgumentParser(description="Generate result charts for presentation")
|
| 366 |
+
parser.add_argument("--eval", type=str, default=None,
|
| 367 |
+
help="Path to r2_evaluation.json from evaluate_r2.py")
|
| 368 |
+
parser.add_argument("--trainer", type=str, default=None,
|
| 369 |
+
help="Path to trainer_state.json from train_llm.py output")
|
| 370 |
+
parser.add_argument("--demo", action="store_true",
|
| 371 |
+
help="Generate charts with real baselines + placeholder trained scores")
|
| 372 |
+
args = parser.parse_args()
|
| 373 |
+
|
| 374 |
+
try:
|
| 375 |
+
import matplotlib
|
| 376 |
+
except ImportError:
|
| 377 |
+
print("[ERROR] matplotlib not installed. Run: pip install matplotlib", flush=True)
|
| 378 |
+
import sys; sys.exit(1)
|
| 379 |
+
|
| 380 |
+
if args.demo:
|
| 381 |
+
eval_data = demo_mode()
|
| 382 |
+
elif args.eval:
|
| 383 |
+
with open(args.eval) as f:
|
| 384 |
+
eval_data = json.load(f)
|
| 385 |
+
# Back-fill baseline keys if running against old JSON format
|
| 386 |
+
if "r1_llama_baseline" not in eval_data:
|
| 387 |
+
eval_data["r1_llama_baseline"] = LLAMA_BASELINE_R1
|
| 388 |
+
if "r2_llama_baseline" not in eval_data:
|
| 389 |
+
eval_data["r2_llama_baseline"] = LLAMA_BASELINE_R2
|
| 390 |
+
else:
|
| 391 |
+
print("[INFO] No --eval file specified. Using --demo mode.", flush=True)
|
| 392 |
+
eval_data = demo_mode()
|
| 393 |
+
|
| 394 |
+
print(f"\nGenerating charts → {CHARTS_DIR}/", flush=True)
|
| 395 |
+
print(f" Baselines: R1 avg={LLAMA_BASELINE_R1['average']:.4f} R2 avg={LLAMA_BASELINE_R2['average']:.4f}", flush=True)
|
| 396 |
+
print(f" Training model: {TRAINING_MODEL}", flush=True)
|
| 397 |
+
|
| 398 |
+
print(" Chart 1: R1 scores comparison...", flush=True)
|
| 399 |
+
chart_r1_comparison(eval_data)
|
| 400 |
+
|
| 401 |
+
print(" Chart 2: R2 scores comparison...", flush=True)
|
| 402 |
+
chart_r2_comparison(eval_data)
|
| 403 |
+
|
| 404 |
+
print(" Chart 3: Sprint reward curves...", flush=True)
|
| 405 |
+
chart_sprint_rewards(eval_data)
|
| 406 |
+
|
| 407 |
+
print(" Chart 4: Improvement summary...", flush=True)
|
| 408 |
+
chart_improvement_summary(eval_data)
|
| 409 |
+
|
| 410 |
+
if args.trainer and Path(args.trainer).exists():
|
| 411 |
+
print(" Chart 5: Training curve...", flush=True)
|
| 412 |
+
chart_training_curve(args.trainer)
|
| 413 |
+
else:
|
| 414 |
+
print(" Chart 5: Training curve — skipped (no --trainer file provided)", flush=True)
|
| 415 |
+
|
| 416 |
+
print(f"\n✅ All charts saved to {CHARTS_DIR}/", flush=True)
|
| 417 |
+
print(f" Use improvement_summary.png in your HF blog post and pitch slides.", flush=True)
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
if __name__ == "__main__":
|
| 421 |
+
main()
|
project_client.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Project Manager — OpenEnv Client (Round 2)
|
| 3 |
+
|
| 4 |
+
Typed HTTP client for the multi-sprint /project/* endpoints.
|
| 5 |
+
Mirrors the R1 SprintEnvClient interface exactly, extended for R2.
|
| 6 |
+
|
| 7 |
+
Usage:
|
| 8 |
+
from project_client import ProjectEnvClient
|
| 9 |
+
|
| 10 |
+
client = ProjectEnvClient(base_url="https://sejal-k-ai-sprint-manager.hf.space")
|
| 11 |
+
obs = client.reset(task_name="project_easy", seed=42)
|
| 12 |
+
|
| 13 |
+
while not obs["done"]:
|
| 14 |
+
result = client.step({"action_type": "assign", "task_id": "T01", "dev_id": "dev1"})
|
| 15 |
+
obs = result.observation
|
| 16 |
+
|
| 17 |
+
client.close()
|
| 18 |
+
|
| 19 |
+
# Or as context manager:
|
| 20 |
+
with ProjectEnvClient() as client:
|
| 21 |
+
obs = client.reset("project_hard")
|
| 22 |
+
while not obs["done"]:
|
| 23 |
+
result = client.step({"action_type": "skip"})
|
| 24 |
+
obs = result.observation
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
from __future__ import annotations
|
| 28 |
+
|
| 29 |
+
import requests
|
| 30 |
+
from typing import Any, Optional
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
# ── Typed step result ──────────────────────────────────────────────────────────
|
| 34 |
+
|
| 35 |
+
class ProjectStepResult:
|
| 36 |
+
"""
|
| 37 |
+
Typed result from a project step() call.
|
| 38 |
+
|
| 39 |
+
Extends R1 StepResult with R2-specific fields surfaced as properties.
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
def __init__(self, payload: dict) -> None:
|
| 43 |
+
self.observation: dict = payload["observation"]
|
| 44 |
+
self.reward: float = payload["reward"]
|
| 45 |
+
self.done: bool = payload["done"]
|
| 46 |
+
self.info: dict = payload.get("info", {})
|
| 47 |
+
|
| 48 |
+
# ── R2 convenience accessors ──────────────────────────────────────────────
|
| 49 |
+
|
| 50 |
+
@property
|
| 51 |
+
def current_sprint(self) -> int:
|
| 52 |
+
return self.observation.get("current_sprint", 1)
|
| 53 |
+
|
| 54 |
+
@property
|
| 55 |
+
def current_day(self) -> int:
|
| 56 |
+
return self.observation.get("current_day", 1)
|
| 57 |
+
|
| 58 |
+
@property
|
| 59 |
+
def instruction_queue(self) -> list[dict]:
|
| 60 |
+
"""All instructions released up to current_day."""
|
| 61 |
+
return self.observation.get("instruction_queue", [])
|
| 62 |
+
|
| 63 |
+
@property
|
| 64 |
+
def active_instructions(self) -> list[dict]:
|
| 65 |
+
"""Released instructions not yet followed."""
|
| 66 |
+
return [i for i in self.instruction_queue if not i.get("followed", False)]
|
| 67 |
+
|
| 68 |
+
@property
|
| 69 |
+
def tech_debt(self) -> list[str]:
|
| 70 |
+
"""Task IDs that became tech debt at sprint boundaries."""
|
| 71 |
+
return self.observation.get("tech_debt", [])
|
| 72 |
+
|
| 73 |
+
@property
|
| 74 |
+
def sprint_rewards(self) -> list[float]:
|
| 75 |
+
"""Per-sprint reward history."""
|
| 76 |
+
return self.observation.get("sprint_rewards", [])
|
| 77 |
+
|
| 78 |
+
@property
|
| 79 |
+
def instruction_following_score(self) -> float:
|
| 80 |
+
return self.observation.get("instruction_following_score", 1.0)
|
| 81 |
+
|
| 82 |
+
@property
|
| 83 |
+
def tasks_completed(self) -> int:
|
| 84 |
+
return self.observation.get("tasks_completed", 0)
|
| 85 |
+
|
| 86 |
+
@property
|
| 87 |
+
def tasks_missed(self) -> int:
|
| 88 |
+
return self.observation.get("tasks_missed", 0)
|
| 89 |
+
|
| 90 |
+
@property
|
| 91 |
+
def cumulative_reward(self) -> float:
|
| 92 |
+
return self.observation.get("cumulative_reward", 0.0)
|
| 93 |
+
|
| 94 |
+
def __repr__(self) -> str:
|
| 95 |
+
return (
|
| 96 |
+
f"ProjectStepResult("
|
| 97 |
+
f"reward={self.reward:+.3f}, done={self.done}, "
|
| 98 |
+
f"day={self.current_day}/60, sprint={self.current_sprint}/6, "
|
| 99 |
+
f"completed={self.tasks_completed}, "
|
| 100 |
+
f"inst_score={self.instruction_following_score:.2f}, "
|
| 101 |
+
f"debt={len(self.tech_debt)})"
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
# ── Client ─────────────────────────────────────────────────────────────────────
|
| 106 |
+
|
| 107 |
+
class ProjectEnvClient:
|
| 108 |
+
"""
|
| 109 |
+
HTTP client for the R2 multi-sprint Project Manager environment.
|
| 110 |
+
|
| 111 |
+
Wraps the /project/* REST API into a clean typed Python interface.
|
| 112 |
+
Use this in RL training loops (train_llm.py), evaluation scripts
|
| 113 |
+
(evaluate_r2.py), and notebooks.
|
| 114 |
+
|
| 115 |
+
All endpoints mirror the R1 SprintEnvClient interface so training
|
| 116 |
+
code can swap between R1 and R2 with minimal changes.
|
| 117 |
+
"""
|
| 118 |
+
|
| 119 |
+
def __init__(
|
| 120 |
+
self,
|
| 121 |
+
base_url: str = "http://localhost:7860",
|
| 122 |
+
timeout: int = 60, # 60s — R2 steps can be slower than R1
|
| 123 |
+
) -> None:
|
| 124 |
+
self.base_url = base_url.rstrip("/")
|
| 125 |
+
self.timeout = timeout
|
| 126 |
+
self._session = requests.Session()
|
| 127 |
+
self._prefix = "/project"
|
| 128 |
+
|
| 129 |
+
# ── Core API ──────────────────────────────────────────────────────────────
|
| 130 |
+
|
| 131 |
+
def reset(
|
| 132 |
+
self,
|
| 133 |
+
task_name: str = "project_easy",
|
| 134 |
+
seed: Optional[int] = None,
|
| 135 |
+
episode_id: Optional[str] = None,
|
| 136 |
+
) -> dict:
|
| 137 |
+
"""
|
| 138 |
+
Start a new 60-day multi-sprint episode.
|
| 139 |
+
|
| 140 |
+
Args:
|
| 141 |
+
task_name: "project_easy" | "project_medium" | "project_hard"
|
| 142 |
+
seed: Random seed for reproducibility
|
| 143 |
+
episode_id: Optional custom episode identifier
|
| 144 |
+
|
| 145 |
+
Returns:
|
| 146 |
+
Initial observation dict (includes current_sprint, instruction_queue,
|
| 147 |
+
tech_debt, sprint_rewards).
|
| 148 |
+
"""
|
| 149 |
+
payload: dict[str, Any] = {"task_name": task_name}
|
| 150 |
+
if seed is not None:
|
| 151 |
+
payload["seed"] = seed
|
| 152 |
+
if episode_id is not None:
|
| 153 |
+
payload["episode_id"] = episode_id
|
| 154 |
+
|
| 155 |
+
resp = self._session.post(
|
| 156 |
+
f"{self.base_url}{self._prefix}/reset",
|
| 157 |
+
json=payload,
|
| 158 |
+
timeout=self.timeout,
|
| 159 |
+
)
|
| 160 |
+
resp.raise_for_status()
|
| 161 |
+
return resp.json()
|
| 162 |
+
|
| 163 |
+
def step(self, action: dict[str, Any]) -> ProjectStepResult:
|
| 164 |
+
"""
|
| 165 |
+
Take one action and advance the project by one day.
|
| 166 |
+
|
| 167 |
+
Args:
|
| 168 |
+
action: dict with keys: action_type, task_id, dev_id,
|
| 169 |
+
new_priority, task_ids (for sprint_plan), notes.
|
| 170 |
+
Minimum required: {"action_type": "skip"}
|
| 171 |
+
|
| 172 |
+
Returns:
|
| 173 |
+
ProjectStepResult with observation, reward, done, info,
|
| 174 |
+
plus R2 convenience properties.
|
| 175 |
+
"""
|
| 176 |
+
payload = {"action": action}
|
| 177 |
+
resp = self._session.post(
|
| 178 |
+
f"{self.base_url}{self._prefix}/step",
|
| 179 |
+
json=payload,
|
| 180 |
+
timeout=self.timeout,
|
| 181 |
+
)
|
| 182 |
+
resp.raise_for_status()
|
| 183 |
+
return ProjectStepResult(resp.json())
|
| 184 |
+
|
| 185 |
+
def state(self) -> dict:
|
| 186 |
+
"""Return the full current internal state snapshot."""
|
| 187 |
+
resp = self._session.get(
|
| 188 |
+
f"{self.base_url}{self._prefix}/state",
|
| 189 |
+
timeout=self.timeout,
|
| 190 |
+
)
|
| 191 |
+
resp.raise_for_status()
|
| 192 |
+
return resp.json()
|
| 193 |
+
|
| 194 |
+
def health(self) -> dict:
|
| 195 |
+
"""
|
| 196 |
+
Check R2 server health.
|
| 197 |
+
Returns round=2 and current episode summary.
|
| 198 |
+
"""
|
| 199 |
+
resp = self._session.get(
|
| 200 |
+
f"{self.base_url}{self._prefix}/health",
|
| 201 |
+
timeout=self.timeout,
|
| 202 |
+
)
|
| 203 |
+
resp.raise_for_status()
|
| 204 |
+
return resp.json()
|
| 205 |
+
|
| 206 |
+
def list_tasks(self) -> list[dict]:
|
| 207 |
+
"""List all available R2 multi-sprint scenarios."""
|
| 208 |
+
resp = self._session.get(
|
| 209 |
+
f"{self.base_url}{self._prefix}/tasks",
|
| 210 |
+
timeout=self.timeout,
|
| 211 |
+
)
|
| 212 |
+
resp.raise_for_status()
|
| 213 |
+
return resp.json()["tasks"]
|
| 214 |
+
|
| 215 |
+
# ── Helpers ───────────────────────────────────────────────────────────────
|
| 216 |
+
|
| 217 |
+
def run_episode(
|
| 218 |
+
self,
|
| 219 |
+
task_name: str,
|
| 220 |
+
policy_fn, # Callable[[dict], dict]
|
| 221 |
+
seed: Optional[int] = None,
|
| 222 |
+
max_steps: int = 60,
|
| 223 |
+
verbose: bool = False,
|
| 224 |
+
) -> dict:
|
| 225 |
+
"""
|
| 226 |
+
Convenience: run a full episode with a policy function.
|
| 227 |
+
|
| 228 |
+
Args:
|
| 229 |
+
task_name: Scenario name
|
| 230 |
+
policy_fn: fn(observation: dict) -> action: dict
|
| 231 |
+
seed: Optional random seed
|
| 232 |
+
max_steps: Safety cap (default 60 = full project)
|
| 233 |
+
verbose: Print step summaries to stdout
|
| 234 |
+
|
| 235 |
+
Returns:
|
| 236 |
+
dict with keys: steps, cumulative_reward, final_score,
|
| 237 |
+
tasks_completed, tasks_missed, instruction_following_score,
|
| 238 |
+
tech_debt, sprint_rewards, done
|
| 239 |
+
"""
|
| 240 |
+
obs = self.reset(task_name=task_name, seed=seed)
|
| 241 |
+
total_reward = 0.0
|
| 242 |
+
steps = 0
|
| 243 |
+
|
| 244 |
+
for step_num in range(1, max_steps + 1):
|
| 245 |
+
if obs.get("done", False):
|
| 246 |
+
break
|
| 247 |
+
action = policy_fn(obs)
|
| 248 |
+
result = self.step(action)
|
| 249 |
+
obs = result.observation
|
| 250 |
+
total_reward += result.reward
|
| 251 |
+
steps += 1
|
| 252 |
+
|
| 253 |
+
if verbose:
|
| 254 |
+
print(
|
| 255 |
+
f" day={obs['current_day']-1:02d}/60 "
|
| 256 |
+
f"sprint={obs['current_sprint']}/6 "
|
| 257 |
+
f"done={obs['tasks_completed']} "
|
| 258 |
+
f"missed={obs['tasks_missed']} "
|
| 259 |
+
f"debt={len(obs['tech_debt'])} "
|
| 260 |
+
f"inst={obs['instruction_following_score']:.2f} "
|
| 261 |
+
f"reward={result.reward:+.3f}",
|
| 262 |
+
flush=True,
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
return {
|
| 266 |
+
"steps": steps,
|
| 267 |
+
"cumulative_reward": round(total_reward, 4),
|
| 268 |
+
"tasks_completed": obs.get("tasks_completed", 0),
|
| 269 |
+
"tasks_missed": obs.get("tasks_missed", 0),
|
| 270 |
+
"instruction_following_score": obs.get("instruction_following_score", 0.0),
|
| 271 |
+
"tech_debt": obs.get("tech_debt", []),
|
| 272 |
+
"sprint_rewards": obs.get("sprint_rewards", []),
|
| 273 |
+
"done": obs.get("done", False),
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
# ── Session lifecycle ─────────────────────────────────────────────────────
|
| 277 |
+
|
| 278 |
+
def close(self) -> None:
|
| 279 |
+
"""Close the underlying HTTP session."""
|
| 280 |
+
self._session.close()
|
| 281 |
+
|
| 282 |
+
def __enter__(self) -> "ProjectEnvClient":
|
| 283 |
+
return self
|
| 284 |
+
|
| 285 |
+
def __exit__(self, *args: Any) -> None:
|
| 286 |
+
self.close()
|
| 287 |
+
|
| 288 |
+
def __repr__(self) -> str:
|
| 289 |
+
return f"ProjectEnvClient(base_url='{self.base_url}', prefix='{self._prefix}')"
|
pyproject.toml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "ai-sprint-manager"
|
| 3 |
+
version = "1.0.0"
|
| 4 |
+
description = "RL environment for agile sprint management - OpenEnv"
|
| 5 |
+
requires-python = ">=3.11,<3.13"
|
| 6 |
+
dependencies = [
|
| 7 |
+
"fastapi==0.115.5",
|
| 8 |
+
"uvicorn==0.32.1",
|
| 9 |
+
"pydantic==2.10.3",
|
| 10 |
+
"openai",
|
| 11 |
+
"requests",
|
| 12 |
+
"openenv-core",
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
[project.scripts]
|
| 16 |
+
server = "server.app:main"
|
| 17 |
+
|
| 18 |
+
[build-system]
|
| 19 |
+
requires = ["setuptools>=68"]
|
| 20 |
+
build-backend = "setuptools.backends.legacy:build"
|
| 21 |
+
|
| 22 |
+
[tool.setuptools.packages.find]
|
| 23 |
+
where = ["."]
|
| 24 |
+
include = ["sprint_env*", "server*"]
|
requirements.env
ADDED
|
File without changes
|
requirements.txt
ADDED
|
Binary file (438 Bytes). View file
|
|
|
results/phase1/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
base_model: unsloth/qwen2.5-1.5b-instruct-unsloth-bnb-4bit
|
| 3 |
+
library_name: peft
|
| 4 |
+
model_name: phase1
|
| 5 |
+
tags:
|
| 6 |
+
- base_model:adapter:unsloth/qwen2.5-1.5b-instruct-unsloth-bnb-4bit
|
| 7 |
+
- grpo
|
| 8 |
+
- lora
|
| 9 |
+
- transformers
|
| 10 |
+
- trl
|
| 11 |
+
- unsloth
|
| 12 |
+
licence: license
|
| 13 |
+
pipeline_tag: text-generation
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# Model Card for phase1
|
| 17 |
+
|
| 18 |
+
This model is a fine-tuned version of [unsloth/qwen2.5-1.5b-instruct-unsloth-bnb-4bit](https://huggingface.co/unsloth/qwen2.5-1.5b-instruct-unsloth-bnb-4bit).
|
| 19 |
+
It has been trained using [TRL](https://github.com/huggingface/trl).
|
| 20 |
+
|
| 21 |
+
## Quick start
|
| 22 |
+
|
| 23 |
+
```python
|
| 24 |
+
from transformers import pipeline
|
| 25 |
+
|
| 26 |
+
question = "If you had a time machine, but could only go to the past or the future once and never return, which would you choose and why?"
|
| 27 |
+
generator = pipeline("text-generation", model="None", device="cuda")
|
| 28 |
+
output = generator([{"role": "user", "content": question}], max_new_tokens=128, return_full_text=False)[0]
|
| 29 |
+
print(output["generated_text"])
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
## Training procedure
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
This model was trained with GRPO, a method introduced in [DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models](https://huggingface.co/papers/2402.03300).
|
| 38 |
+
|
| 39 |
+
### Framework versions
|
| 40 |
+
|
| 41 |
+
- PEFT 0.18.1
|
| 42 |
+
- TRL: 0.24.0
|
| 43 |
+
- Transformers: 5.5.0
|
| 44 |
+
- Pytorch: 2.10.0+cu128
|
| 45 |
+
- Datasets: 4.3.0
|
| 46 |
+
- Tokenizers: 0.22.2
|
| 47 |
+
|
| 48 |
+
## Citations
|
| 49 |
+
|
| 50 |
+
Cite GRPO as:
|
| 51 |
+
|
| 52 |
+
```bibtex
|
| 53 |
+
@article{shao2024deepseekmath,
|
| 54 |
+
title = {{DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models}},
|
| 55 |
+
author = {Zhihong Shao and Peiyi Wang and Qihao Zhu and Runxin Xu and Junxiao Song and Mingchuan Zhang and Y. K. Li and Y. Wu and Daya Guo},
|
| 56 |
+
year = 2024,
|
| 57 |
+
eprint = {arXiv:2402.03300},
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
Cite TRL as:
|
| 63 |
+
|
| 64 |
+
```bibtex
|
| 65 |
+
@misc{vonwerra2022trl,
|
| 66 |
+
title = {{TRL: Transformer Reinforcement Learning}},
|
| 67 |
+
author = {Leandro von Werra and Younes Belkada and Lewis Tunstall and Edward Beeching and Tristan Thrush and Nathan Lambert and Shengyi Huang and Kashif Rasul and Quentin Gallou{\'e}dec},
|
| 68 |
+
year = 2020,
|
| 69 |
+
journal = {GitHub repository},
|
| 70 |
+
publisher = {GitHub},
|
| 71 |
+
howpublished = {\url{https://github.com/huggingface/trl}}
|
| 72 |
+
}
|
| 73 |
+
```
|
results/phase1/adapter_config.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"alora_invocation_tokens": null,
|
| 3 |
+
"alpha_pattern": {},
|
| 4 |
+
"arrow_config": null,
|
| 5 |
+
"auto_mapping": {
|
| 6 |
+
"base_model_class": "Qwen2ForCausalLM",
|
| 7 |
+
"parent_library": "transformers.models.qwen2.modeling_qwen2",
|
| 8 |
+
"unsloth_fixed": true
|
| 9 |
+
},
|
| 10 |
+
"base_model_name_or_path": "unsloth/qwen2.5-1.5b-instruct-unsloth-bnb-4bit",
|
| 11 |
+
"bias": "none",
|
| 12 |
+
"corda_config": null,
|
| 13 |
+
"ensure_weight_tying": false,
|
| 14 |
+
"eva_config": null,
|
| 15 |
+
"exclude_modules": null,
|
| 16 |
+
"fan_in_fan_out": false,
|
| 17 |
+
"inference_mode": true,
|
| 18 |
+
"init_lora_weights": true,
|
| 19 |
+
"layer_replication": null,
|
| 20 |
+
"layers_pattern": null,
|
| 21 |
+
"layers_to_transform": null,
|
| 22 |
+
"loftq_config": {},
|
| 23 |
+
"lora_alpha": 32,
|
| 24 |
+
"lora_bias": false,
|
| 25 |
+
"lora_dropout": 0.05,
|
| 26 |
+
"megatron_config": null,
|
| 27 |
+
"megatron_core": "megatron.core",
|
| 28 |
+
"modules_to_save": null,
|
| 29 |
+
"peft_type": "LORA",
|
| 30 |
+
"peft_version": "0.18.1",
|
| 31 |
+
"qalora_group_size": 16,
|
| 32 |
+
"r": 16,
|
| 33 |
+
"rank_pattern": {},
|
| 34 |
+
"revision": null,
|
| 35 |
+
"target_modules": [
|
| 36 |
+
"k_proj",
|
| 37 |
+
"up_proj",
|
| 38 |
+
"down_proj",
|
| 39 |
+
"o_proj",
|
| 40 |
+
"v_proj",
|
| 41 |
+
"gate_proj",
|
| 42 |
+
"q_proj"
|
| 43 |
+
],
|
| 44 |
+
"target_parameters": null,
|
| 45 |
+
"task_type": "CAUSAL_LM",
|
| 46 |
+
"trainable_token_indices": null,
|
| 47 |
+
"use_dora": false,
|
| 48 |
+
"use_qalora": false,
|
| 49 |
+
"use_rslora": false
|
| 50 |
+
}
|
results/phase1/adapter_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6d9ce380266a065d31b8d4962851f4d105daeda1ef56d4e9cd0835e5d5d0644a
|
| 3 |
+
size 73911112
|
results/phase1/chat_template.jinja
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{%- if tools %}
|
| 2 |
+
{{- '<|im_start|>system\n' }}
|
| 3 |
+
{%- if messages[0]['role'] == 'system' %}
|
| 4 |
+
{{- messages[0]['content'] }}
|
| 5 |
+
{%- else %}
|
| 6 |
+
{{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}
|
| 7 |
+
{%- endif %}
|
| 8 |
+
{{- "\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
|
| 9 |
+
{%- for tool in tools %}
|
| 10 |
+
{{- "\n" }}
|
| 11 |
+
{{- tool | tojson }}
|
| 12 |
+
{%- endfor %}
|
| 13 |
+
{{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
|
| 14 |
+
{%- else %}
|
| 15 |
+
{%- if messages[0]['role'] == 'system' %}
|
| 16 |
+
{{- '<|im_start|>system\n' + messages[0]['content'] + '<|im_end|>\n' }}
|
| 17 |
+
{%- else %}
|
| 18 |
+
{{- '<|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\n' }}
|
| 19 |
+
{%- endif %}
|
| 20 |
+
{%- endif %}
|
| 21 |
+
{%- for message in messages %}
|
| 22 |
+
{%- if (message.role == "user") or (message.role == "system" and not loop.first) or (message.role == "assistant" and not message.tool_calls) %}
|
| 23 |
+
{{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
|
| 24 |
+
{%- elif message.role == "assistant" %}
|
| 25 |
+
{{- '<|im_start|>' + message.role }}
|
| 26 |
+
{%- if message.content %}
|
| 27 |
+
{{- '\n' + message.content }}
|
| 28 |
+
{%- endif %}
|
| 29 |
+
{%- for tool_call in message.tool_calls %}
|
| 30 |
+
{%- if tool_call.function is defined %}
|
| 31 |
+
{%- set tool_call = tool_call.function %}
|
| 32 |
+
{%- endif %}
|
| 33 |
+
{{- '\n<tool_call>\n{"name": "' }}
|
| 34 |
+
{{- tool_call.name }}
|
| 35 |
+
{{- '", "arguments": ' }}
|
| 36 |
+
{{- tool_call.arguments | tojson }}
|
| 37 |
+
{{- '}\n</tool_call>' }}
|
| 38 |
+
{%- endfor %}
|
| 39 |
+
{{- '<|im_end|>\n' }}
|
| 40 |
+
{%- elif message.role == "tool" %}
|
| 41 |
+
{%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != "tool") %}
|
| 42 |
+
{{- '<|im_start|>user' }}
|
| 43 |
+
{%- endif %}
|
| 44 |
+
{{- '\n<tool_response>\n' }}
|
| 45 |
+
{{- message.content }}
|
| 46 |
+
{{- '\n</tool_response>' }}
|
| 47 |
+
{%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
|
| 48 |
+
{{- '<|im_end|>\n' }}
|
| 49 |
+
{%- endif %}
|
| 50 |
+
{%- endif %}
|
| 51 |
+
{%- endfor %}
|
| 52 |
+
{%- if add_generation_prompt %}
|
| 53 |
+
{{- '<|im_start|>assistant\n' }}
|
| 54 |
+
{%- endif %}
|
results/phase1/checkpoint-100/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
base_model: unsloth/qwen2.5-1.5b-instruct-unsloth-bnb-4bit
|
| 3 |
+
library_name: peft
|
| 4 |
+
pipeline_tag: text-generation
|
| 5 |
+
tags:
|
| 6 |
+
- base_model:adapter:unsloth/qwen2.5-1.5b-instruct-unsloth-bnb-4bit
|
| 7 |
+
- grpo
|
| 8 |
+
- lora
|
| 9 |
+
- transformers
|
| 10 |
+
- trl
|
| 11 |
+
- unsloth
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# Model Card for Model ID
|
| 15 |
+
|
| 16 |
+
<!-- Provide a quick summary of what the model is/does. -->
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
## Model Details
|
| 21 |
+
|
| 22 |
+
### Model Description
|
| 23 |
+
|
| 24 |
+
<!-- Provide a longer summary of what this model is. -->
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
- **Developed by:** [More Information Needed]
|
| 29 |
+
- **Funded by [optional]:** [More Information Needed]
|
| 30 |
+
- **Shared by [optional]:** [More Information Needed]
|
| 31 |
+
- **Model type:** [More Information Needed]
|
| 32 |
+
- **Language(s) (NLP):** [More Information Needed]
|
| 33 |
+
- **License:** [More Information Needed]
|
| 34 |
+
- **Finetuned from model [optional]:** [More Information Needed]
|
| 35 |
+
|
| 36 |
+
### Model Sources [optional]
|
| 37 |
+
|
| 38 |
+
<!-- Provide the basic links for the model. -->
|
| 39 |
+
|
| 40 |
+
- **Repository:** [More Information Needed]
|
| 41 |
+
- **Paper [optional]:** [More Information Needed]
|
| 42 |
+
- **Demo [optional]:** [More Information Needed]
|
| 43 |
+
|
| 44 |
+
## Uses
|
| 45 |
+
|
| 46 |
+
<!-- Address questions around how the model is intended to be used, including the foreseeable users of the model and those affected by the model. -->
|
| 47 |
+
|
| 48 |
+
### Direct Use
|
| 49 |
+
|
| 50 |
+
<!-- This section is for the model use without fine-tuning or plugging into a larger ecosystem/app. -->
|
| 51 |
+
|
| 52 |
+
[More Information Needed]
|
| 53 |
+
|
| 54 |
+
### Downstream Use [optional]
|
| 55 |
+
|
| 56 |
+
<!-- This section is for the model use when fine-tuned for a task, or when plugged into a larger ecosystem/app -->
|
| 57 |
+
|
| 58 |
+
[More Information Needed]
|
| 59 |
+
|
| 60 |
+
### Out-of-Scope Use
|
| 61 |
+
|
| 62 |
+
<!-- This section addresses misuse, malicious use, and uses that the model will not work well for. -->
|
| 63 |
+
|
| 64 |
+
[More Information Needed]
|
| 65 |
+
|
| 66 |
+
## Bias, Risks, and Limitations
|
| 67 |
+
|
| 68 |
+
<!-- This section is meant to convey both technical and sociotechnical limitations. -->
|
| 69 |
+
|
| 70 |
+
[More Information Needed]
|
| 71 |
+
|
| 72 |
+
### Recommendations
|
| 73 |
+
|
| 74 |
+
<!-- This section is meant to convey recommendations with respect to the bias, risk, and technical limitations. -->
|
| 75 |
+
|
| 76 |
+
Users (both direct and downstream) should be made aware of the risks, biases and limitations of the model. More information needed for further recommendations.
|
| 77 |
+
|
| 78 |
+
## How to Get Started with the Model
|
| 79 |
+
|
| 80 |
+
Use the code below to get started with the model.
|
| 81 |
+
|
| 82 |
+
[More Information Needed]
|
| 83 |
+
|
| 84 |
+
## Training Details
|
| 85 |
+
|
| 86 |
+
### Training Data
|
| 87 |
+
|
| 88 |
+
<!-- This should link to a Dataset Card, perhaps with a short stub of information on what the training data is all about as well as documentation related to data pre-processing or additional filtering. -->
|
| 89 |
+
|
| 90 |
+
[More Information Needed]
|
| 91 |
+
|
| 92 |
+
### Training Procedure
|
| 93 |
+
|
| 94 |
+
<!-- This relates heavily to the Technical Specifications. Content here should link to that section when it is relevant to the training procedure. -->
|
| 95 |
+
|
| 96 |
+
#### Preprocessing [optional]
|
| 97 |
+
|
| 98 |
+
[More Information Needed]
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
#### Training Hyperparameters
|
| 102 |
+
|
| 103 |
+
- **Training regime:** [More Information Needed] <!--fp32, fp16 mixed precision, bf16 mixed precision, bf16 non-mixed precision, fp16 non-mixed precision, fp8 mixed precision -->
|
| 104 |
+
|
| 105 |
+
#### Speeds, Sizes, Times [optional]
|
| 106 |
+
|
| 107 |
+
<!-- This section provides information about throughput, start/end time, checkpoint size if relevant, etc. -->
|
| 108 |
+
|
| 109 |
+
[More Information Needed]
|
| 110 |
+
|
| 111 |
+
## Evaluation
|
| 112 |
+
|
| 113 |
+
<!-- This section describes the evaluation protocols and provides the results. -->
|
| 114 |
+
|
| 115 |
+
### Testing Data, Factors & Metrics
|
| 116 |
+
|
| 117 |
+
#### Testing Data
|
| 118 |
+
|
| 119 |
+
<!-- This should link to a Dataset Card if possible. -->
|
| 120 |
+
|
| 121 |
+
[More Information Needed]
|
| 122 |
+
|
| 123 |
+
#### Factors
|
| 124 |
+
|
| 125 |
+
<!-- These are the things the evaluation is disaggregating by, e.g., subpopulations or domains. -->
|
| 126 |
+
|
| 127 |
+
[More Information Needed]
|
| 128 |
+
|
| 129 |
+
#### Metrics
|
| 130 |
+
|
| 131 |
+
<!-- These are the evaluation metrics being used, ideally with a description of why. -->
|
| 132 |
+
|
| 133 |
+
[More Information Needed]
|
| 134 |
+
|
| 135 |
+
### Results
|
| 136 |
+
|
| 137 |
+
[More Information Needed]
|
| 138 |
+
|
| 139 |
+
#### Summary
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
## Model Examination [optional]
|
| 144 |
+
|
| 145 |
+
<!-- Relevant interpretability work for the model goes here -->
|
| 146 |
+
|
| 147 |
+
[More Information Needed]
|
| 148 |
+
|
| 149 |
+
## Environmental Impact
|
| 150 |
+
|
| 151 |
+
<!-- Total emissions (in grams of CO2eq) and additional considerations, such as electricity usage, go here. Edit the suggested text below accordingly -->
|
| 152 |
+
|
| 153 |
+
Carbon emissions can be estimated using the [Machine Learning Impact calculator](https://mlco2.github.io/impact#compute) presented in [Lacoste et al. (2019)](https://arxiv.org/abs/1910.09700).
|
| 154 |
+
|
| 155 |
+
- **Hardware Type:** [More Information Needed]
|
| 156 |
+
- **Hours used:** [More Information Needed]
|
| 157 |
+
- **Cloud Provider:** [More Information Needed]
|
| 158 |
+
- **Compute Region:** [More Information Needed]
|
| 159 |
+
- **Carbon Emitted:** [More Information Needed]
|
| 160 |
+
|
| 161 |
+
## Technical Specifications [optional]
|
| 162 |
+
|
| 163 |
+
### Model Architecture and Objective
|
| 164 |
+
|
| 165 |
+
[More Information Needed]
|
| 166 |
+
|
| 167 |
+
### Compute Infrastructure
|
| 168 |
+
|
| 169 |
+
[More Information Needed]
|
| 170 |
+
|
| 171 |
+
#### Hardware
|
| 172 |
+
|
| 173 |
+
[More Information Needed]
|
| 174 |
+
|
| 175 |
+
#### Software
|
| 176 |
+
|
| 177 |
+
[More Information Needed]
|
| 178 |
+
|
| 179 |
+
## Citation [optional]
|
| 180 |
+
|
| 181 |
+
<!-- If there is a paper or blog post introducing the model, the APA and Bibtex information for that should go in this section. -->
|
| 182 |
+
|
| 183 |
+
**BibTeX:**
|
| 184 |
+
|
| 185 |
+
[More Information Needed]
|
| 186 |
+
|
| 187 |
+
**APA:**
|
| 188 |
+
|
| 189 |
+
[More Information Needed]
|
| 190 |
+
|
| 191 |
+
## Glossary [optional]
|
| 192 |
+
|
| 193 |
+
<!-- If relevant, include terms and calculations in this section that can help readers understand the model or model card. -->
|
| 194 |
+
|
| 195 |
+
[More Information Needed]
|
| 196 |
+
|
| 197 |
+
## More Information [optional]
|
| 198 |
+
|
| 199 |
+
[More Information Needed]
|
| 200 |
+
|
| 201 |
+
## Model Card Authors [optional]
|
| 202 |
+
|
| 203 |
+
[More Information Needed]
|
| 204 |
+
|
| 205 |
+
## Model Card Contact
|
| 206 |
+
|
| 207 |
+
[More Information Needed]
|
| 208 |
+
### Framework versions
|
| 209 |
+
|
| 210 |
+
- PEFT 0.18.1
|
results/phase1/checkpoint-100/adapter_config.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"alora_invocation_tokens": null,
|
| 3 |
+
"alpha_pattern": {},
|
| 4 |
+
"arrow_config": null,
|
| 5 |
+
"auto_mapping": {
|
| 6 |
+
"base_model_class": "Qwen2ForCausalLM",
|
| 7 |
+
"parent_library": "transformers.models.qwen2.modeling_qwen2",
|
| 8 |
+
"unsloth_fixed": true
|
| 9 |
+
},
|
| 10 |
+
"base_model_name_or_path": "unsloth/qwen2.5-1.5b-instruct-unsloth-bnb-4bit",
|
| 11 |
+
"bias": "none",
|
| 12 |
+
"corda_config": null,
|
| 13 |
+
"ensure_weight_tying": false,
|
| 14 |
+
"eva_config": null,
|
| 15 |
+
"exclude_modules": null,
|
| 16 |
+
"fan_in_fan_out": false,
|
| 17 |
+
"inference_mode": true,
|
| 18 |
+
"init_lora_weights": true,
|
| 19 |
+
"layer_replication": null,
|
| 20 |
+
"layers_pattern": null,
|
| 21 |
+
"layers_to_transform": null,
|
| 22 |
+
"loftq_config": {},
|
| 23 |
+
"lora_alpha": 32,
|
| 24 |
+
"lora_bias": false,
|
| 25 |
+
"lora_dropout": 0.05,
|
| 26 |
+
"megatron_config": null,
|
| 27 |
+
"megatron_core": "megatron.core",
|
| 28 |
+
"modules_to_save": null,
|
| 29 |
+
"peft_type": "LORA",
|
| 30 |
+
"peft_version": "0.18.1",
|
| 31 |
+
"qalora_group_size": 16,
|
| 32 |
+
"r": 16,
|
| 33 |
+
"rank_pattern": {},
|
| 34 |
+
"revision": null,
|
| 35 |
+
"target_modules": [
|
| 36 |
+
"k_proj",
|
| 37 |
+
"up_proj",
|
| 38 |
+
"down_proj",
|
| 39 |
+
"o_proj",
|
| 40 |
+
"v_proj",
|
| 41 |
+
"gate_proj",
|
| 42 |
+
"q_proj"
|
| 43 |
+
],
|
| 44 |
+
"target_parameters": null,
|
| 45 |
+
"task_type": "CAUSAL_LM",
|
| 46 |
+
"trainable_token_indices": null,
|
| 47 |
+
"use_dora": false,
|
| 48 |
+
"use_qalora": false,
|
| 49 |
+
"use_rslora": false
|
| 50 |
+
}
|
results/phase1/checkpoint-100/adapter_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d430aea910e6d8b7347568b5b75ef11de9ff5b23b6d2010fd456033e1f218b07
|
| 3 |
+
size 73911112
|
results/phase1/checkpoint-100/chat_template.jinja
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{%- if tools %}
|
| 2 |
+
{{- '<|im_start|>system\n' }}
|
| 3 |
+
{%- if messages[0]['role'] == 'system' %}
|
| 4 |
+
{{- messages[0]['content'] }}
|
| 5 |
+
{%- else %}
|
| 6 |
+
{{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}
|
| 7 |
+
{%- endif %}
|
| 8 |
+
{{- "\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
|
| 9 |
+
{%- for tool in tools %}
|
| 10 |
+
{{- "\n" }}
|
| 11 |
+
{{- tool | tojson }}
|
| 12 |
+
{%- endfor %}
|
| 13 |
+
{{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
|
| 14 |
+
{%- else %}
|
| 15 |
+
{%- if messages[0]['role'] == 'system' %}
|
| 16 |
+
{{- '<|im_start|>system\n' + messages[0]['content'] + '<|im_end|>\n' }}
|
| 17 |
+
{%- else %}
|
| 18 |
+
{{- '<|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\n' }}
|
| 19 |
+
{%- endif %}
|
| 20 |
+
{%- endif %}
|
| 21 |
+
{%- for message in messages %}
|
| 22 |
+
{%- if (message.role == "user") or (message.role == "system" and not loop.first) or (message.role == "assistant" and not message.tool_calls) %}
|
| 23 |
+
{{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
|
| 24 |
+
{%- elif message.role == "assistant" %}
|
| 25 |
+
{{- '<|im_start|>' + message.role }}
|
| 26 |
+
{%- if message.content %}
|
| 27 |
+
{{- '\n' + message.content }}
|
| 28 |
+
{%- endif %}
|
| 29 |
+
{%- for tool_call in message.tool_calls %}
|
| 30 |
+
{%- if tool_call.function is defined %}
|
| 31 |
+
{%- set tool_call = tool_call.function %}
|
| 32 |
+
{%- endif %}
|
| 33 |
+
{{- '\n<tool_call>\n{"name": "' }}
|
| 34 |
+
{{- tool_call.name }}
|
| 35 |
+
{{- '", "arguments": ' }}
|
| 36 |
+
{{- tool_call.arguments | tojson }}
|
| 37 |
+
{{- '}\n</tool_call>' }}
|
| 38 |
+
{%- endfor %}
|
| 39 |
+
{{- '<|im_end|>\n' }}
|
| 40 |
+
{%- elif message.role == "tool" %}
|
| 41 |
+
{%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != "tool") %}
|
| 42 |
+
{{- '<|im_start|>user' }}
|
| 43 |
+
{%- endif %}
|
| 44 |
+
{{- '\n<tool_response>\n' }}
|
| 45 |
+
{{- message.content }}
|
| 46 |
+
{{- '\n</tool_response>' }}
|
| 47 |
+
{%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
|
| 48 |
+
{{- '<|im_end|>\n' }}
|
| 49 |
+
{%- endif %}
|
| 50 |
+
{%- endif %}
|
| 51 |
+
{%- endfor %}
|
| 52 |
+
{%- if add_generation_prompt %}
|
| 53 |
+
{{- '<|im_start|>assistant\n' }}
|
| 54 |
+
{%- endif %}
|
results/phase1/checkpoint-100/optimizer.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:13de9f002823fbe63e71b9d1eb8ec6d2c4b8eca35a4ee367099322a459b3b1cb
|
| 3 |
+
size 37969669
|
results/phase1/checkpoint-100/rng_state.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c1449e60d52f06cceec8589785267b0457345be74a8ca37687099a6addff91e0
|
| 3 |
+
size 14645
|
results/phase1/checkpoint-100/scaler.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c4393a84a3109995aa1202073b039b12062e3189ed89aa0b94ef0510ba843009
|
| 3 |
+
size 1383
|
results/phase1/checkpoint-100/scheduler.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7254cd54e69ff9b68992c6e5838d1590c8e033e5a4605990d0b4034185923db6
|
| 3 |
+
size 1465
|
results/phase1/checkpoint-100/tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:af5891a15588546db1ac7f2baf8fa94835a51a85c032c39793a55bb048b47446
|
| 3 |
+
size 11422523
|