Spaces:
Paused
Paused
Commit ·
3f78483
1
Parent(s): fb34eca
fix: align step_reward with grade_episode, pin deps, update docs, clean inference
Browse files- grader.py: step_reward now applies skip-inv penalties matching grade_episode
- environment.py: passes investigation_done to grade() for honest step rewards
- requirements.txt: pinned to exact versions (fastapi, uvicorn, pydantic, etc.)
- TESTING.md: updated to v2.0.0, 20 tasks (4/6/6/4 across 4 difficulty tiers)
- inference.py: removed dead investigation_hints block (hints always empty)
- server/Dockerfile: synced with root Dockerfile (PYTHONPATH, ENV vars)
- TESTING.md +32 -21
- environment.py +6 -1
- grader.py +23 -4
- inference.py +0 -4
- requirements.txt +6 -6
- server/Dockerfile +2 -2
TESTING.md
CHANGED
|
@@ -30,7 +30,7 @@ curl -s http://localhost:8000/health
|
|
| 30 |
|
| 31 |
**Expected output**
|
| 32 |
```json
|
| 33 |
-
{"status": "ok", "environment": "payops_env", "version": "
|
| 34 |
```
|
| 35 |
|
| 36 |
**Failure indicator:** Connection refused, or any field missing / wrong value.
|
|
@@ -63,7 +63,7 @@ curl -s http://localhost:8000/schema | python3 -m json.tool
|
|
| 63 |
|
| 64 |
## T-03 Tasks Endpoint
|
| 65 |
|
| 66 |
-
**Goal:** Confirm all
|
| 67 |
|
| 68 |
```bash
|
| 69 |
curl -s http://localhost:8000/tasks | python3 -c "
|
|
@@ -75,30 +75,41 @@ c = Counter(t['difficulty'] for t in d['tasks'])
|
|
| 75 |
print('By difficulty:', dict(c))
|
| 76 |
print()
|
| 77 |
for t in d['tasks']:
|
| 78 |
-
print(f\" {t['task_id']:12} [{t['difficulty']:
|
| 79 |
"
|
| 80 |
```
|
| 81 |
|
| 82 |
**Expected output**
|
| 83 |
```
|
| 84 |
-
Total tasks:
|
| 85 |
-
By difficulty: {'easy': 4, 'medium':
|
| 86 |
-
|
| 87 |
-
EASY-001 [easy
|
| 88 |
-
EASY-002 [easy
|
| 89 |
-
EASY-003 [easy
|
| 90 |
-
EASY-004 [easy
|
| 91 |
-
MED-001 [medium] correct=escalate
|
| 92 |
-
MED-002 [medium] correct=hold
|
| 93 |
-
MED-003 [medium] correct=flag
|
| 94 |
-
MED-004 [medium] correct=flag
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
HARD-
|
| 98 |
-
HARD-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
---
|
| 104 |
|
|
|
|
| 30 |
|
| 31 |
**Expected output**
|
| 32 |
```json
|
| 33 |
+
{"status": "ok", "environment": "payops_env", "version": "2.0.0"}
|
| 34 |
```
|
| 35 |
|
| 36 |
**Failure indicator:** Connection refused, or any field missing / wrong value.
|
|
|
|
| 63 |
|
| 64 |
## T-03 Tasks Endpoint
|
| 65 |
|
| 66 |
+
**Goal:** Confirm all 20 tasks are returned with the correct difficulty distribution.
|
| 67 |
|
| 68 |
```bash
|
| 69 |
curl -s http://localhost:8000/tasks | python3 -c "
|
|
|
|
| 75 |
print('By difficulty:', dict(c))
|
| 76 |
print()
|
| 77 |
for t in d['tasks']:
|
| 78 |
+
print(f\" {t['task_id']:12} [{t['difficulty']:8}] correct={t['correct_action']}\")
|
| 79 |
"
|
| 80 |
```
|
| 81 |
|
| 82 |
**Expected output**
|
| 83 |
```
|
| 84 |
+
Total tasks: 20
|
| 85 |
+
By difficulty: {'easy': 4, 'medium': 6, 'hard': 6, 'critical': 4}
|
| 86 |
+
|
| 87 |
+
EASY-001 [easy ] correct=approve
|
| 88 |
+
EASY-002 [easy ] correct=reject
|
| 89 |
+
EASY-003 [easy ] correct=approve
|
| 90 |
+
EASY-004 [easy ] correct=flag
|
| 91 |
+
MED-001 [medium ] correct=escalate
|
| 92 |
+
MED-002 [medium ] correct=hold
|
| 93 |
+
MED-003 [medium ] correct=flag
|
| 94 |
+
MED-004 [medium ] correct=flag
|
| 95 |
+
MED-005 [medium ] correct=hold
|
| 96 |
+
MED-006 [medium ] correct=escalate
|
| 97 |
+
HARD-001 [hard ] correct=escalate
|
| 98 |
+
HARD-002 [hard ] correct=reject
|
| 99 |
+
HARD-003 [hard ] correct=reject
|
| 100 |
+
HARD-004 [hard ] correct=approve
|
| 101 |
+
HARD-005 [hard ] correct=escalate
|
| 102 |
+
HARD-006 [hard ] correct=flag
|
| 103 |
+
CRIT-001 [critical] correct=approve
|
| 104 |
+
CRIT-002 [critical] correct=reject
|
| 105 |
+
CRIT-003 [critical] correct=escalate
|
| 106 |
+
CRIT-004 [critical] correct=reject
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
> Note: correct_action values for jitter-variant tasks (EASY-004, MED-001/003/004/006,
|
| 110 |
+
> HARD-001/006, CRIT-001/003/004) may differ per episode seed — the above shows default values.
|
| 111 |
+
|
| 112 |
+
**Failure indicator:** count != 20, missing difficulty tier, wrong correct_action.
|
| 113 |
|
| 114 |
---
|
| 115 |
|
environment.py
CHANGED
|
@@ -257,7 +257,12 @@ class PayOpsEnvironment:
|
|
| 257 |
sar_used = task_id in self._sar_filed
|
| 258 |
inspected_already = "inspect" in self._used_inv.get(task_id, set())
|
| 259 |
|
| 260 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
self._state.cumulative_reward = round(
|
| 262 |
self._state.cumulative_reward + reward, 4
|
| 263 |
)
|
|
|
|
| 257 |
sar_used = task_id in self._sar_filed
|
| 258 |
inspected_already = "inspect" in self._used_inv.get(task_id, set())
|
| 259 |
|
| 260 |
+
investigation_done = bool(self._used_inv.get(task_id, set()))
|
| 261 |
+
reward = grade(
|
| 262 |
+
action_type, task,
|
| 263 |
+
inspected_already=inspected_already,
|
| 264 |
+
investigation_done=investigation_done,
|
| 265 |
+
)
|
| 266 |
self._state.cumulative_reward = round(
|
| 267 |
self._state.cumulative_reward + reward, 4
|
| 268 |
)
|
grader.py
CHANGED
|
@@ -123,15 +123,25 @@ def step_reward(
|
|
| 123 |
action_type: str,
|
| 124 |
task: PayOpsTask,
|
| 125 |
inspected_already: bool = False,
|
|
|
|
| 126 |
) -> float:
|
| 127 |
"""
|
| 128 |
Single-step reward used by the real-time environment (step_async).
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
"""
|
| 131 |
if _is_investigation(action_type):
|
| 132 |
return 0.0 if inspected_already else INVESTIGATION_BONUS
|
| 133 |
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
|
| 137 |
# ---------------------------------------------------------------------------
|
|
@@ -356,7 +366,16 @@ def grade_episode(
|
|
| 356 |
# Convenience wrapper used by the environment
|
| 357 |
# ---------------------------------------------------------------------------
|
| 358 |
|
| 359 |
-
def grade(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
"""Single-step reward used inside environment.step_async."""
|
| 361 |
-
return step_reward(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
|
|
|
|
| 123 |
action_type: str,
|
| 124 |
task: PayOpsTask,
|
| 125 |
inspected_already: bool = False,
|
| 126 |
+
investigation_done: bool = True,
|
| 127 |
) -> float:
|
| 128 |
"""
|
| 129 |
Single-step reward used by the real-time environment (step_async).
|
| 130 |
+
|
| 131 |
+
``investigation_done`` must be False when the agent issued zero
|
| 132 |
+
investigation sub-actions for this task — the same skip-investigation
|
| 133 |
+
penalty applied by grade_episode is then applied here so the per-step
|
| 134 |
+
reward the agent sees during training matches the final episode score.
|
| 135 |
"""
|
| 136 |
if _is_investigation(action_type):
|
| 137 |
return 0.0 if inspected_already else INVESTIGATION_BONUS
|
| 138 |
|
| 139 |
+
base = _base_terminal_reward(action_type, task)
|
| 140 |
+
requires_inv = getattr(task, "requires_investigation", set())
|
| 141 |
+
if requires_inv and not investigation_done and task.difficulty in ("hard", "critical"):
|
| 142 |
+
correct = action_type == task.correct_action
|
| 143 |
+
base = base * (SKIP_INV_CORRECT_MULTIPLIER if correct else SKIP_INVESTIGATION_MULTIPLIER)
|
| 144 |
+
return base
|
| 145 |
|
| 146 |
|
| 147 |
# ---------------------------------------------------------------------------
|
|
|
|
| 366 |
# Convenience wrapper used by the environment
|
| 367 |
# ---------------------------------------------------------------------------
|
| 368 |
|
| 369 |
+
def grade(
|
| 370 |
+
action_type: str,
|
| 371 |
+
task: PayOpsTask,
|
| 372 |
+
inspected_already: bool = False,
|
| 373 |
+
investigation_done: bool = True,
|
| 374 |
+
) -> float:
|
| 375 |
"""Single-step reward used inside environment.step_async."""
|
| 376 |
+
return step_reward(
|
| 377 |
+
action_type, task,
|
| 378 |
+
inspected_already=inspected_already,
|
| 379 |
+
investigation_done=investigation_done,
|
| 380 |
+
)
|
| 381 |
|
inference.py
CHANGED
|
@@ -157,10 +157,6 @@ def build_observation_text(obs: dict) -> str:
|
|
| 157 |
f"Budget Remaining: {obs.get('budget_remaining', 5.0):.2f}",
|
| 158 |
f"Step : {obs.get('chain_step', 1)}/{obs.get('chain_total', 1)}",
|
| 159 |
]
|
| 160 |
-
# Show investigation hints prominently when present
|
| 161 |
-
hints = obs.get('investigation_hints', [])
|
| 162 |
-
if hints:
|
| 163 |
-
lines.append(f"INVESTIGATION RECOMMENDED: {', '.join(hints)} (use these before deciding for bonus reward + better evidence)")
|
| 164 |
# Append revealed intel if present
|
| 165 |
for field, label in [
|
| 166 |
("inspection_notes", "Inspection Notes"),
|
|
|
|
| 157 |
f"Budget Remaining: {obs.get('budget_remaining', 5.0):.2f}",
|
| 158 |
f"Step : {obs.get('chain_step', 1)}/{obs.get('chain_total', 1)}",
|
| 159 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
# Append revealed intel if present
|
| 161 |
for field, label in [
|
| 162 |
("inspection_notes", "Inspection Notes"),
|
requirements.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
-
openenv-core
|
| 2 |
-
fastapi
|
| 3 |
-
uvicorn
|
| 4 |
-
pydantic
|
| 5 |
-
openai
|
| 6 |
-
httpx
|
|
|
|
| 1 |
+
openenv-core==0.2.1
|
| 2 |
+
fastapi==0.135.1
|
| 3 |
+
uvicorn==0.42.0
|
| 4 |
+
pydantic==2.12.5
|
| 5 |
+
openai==2.29.0
|
| 6 |
+
httpx==0.28.1
|
server/Dockerfile
CHANGED
|
@@ -25,15 +25,15 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|
| 25 |
COPY . /app/payops_env
|
| 26 |
|
| 27 |
# Put parent directory on PYTHONPATH so `payops_env` is importable
|
| 28 |
-
ENV PYTHONPATH="/app
|
| 29 |
|
| 30 |
# HuggingFace Spaces requires port 7860; default to 7860
|
| 31 |
ENV PORT=7860
|
| 32 |
|
| 33 |
# Runtime env vars (overridden at deploy time via HF Space secrets)
|
|
|
|
| 34 |
ENV API_BASE_URL="" \
|
| 35 |
MODEL_NAME="" \
|
| 36 |
-
HF_TOKEN="" \
|
| 37 |
PAYOPS_BASE_URL="http://localhost:7860"
|
| 38 |
|
| 39 |
# Switch to non-root user
|
|
|
|
| 25 |
COPY . /app/payops_env
|
| 26 |
|
| 27 |
# Put parent directory on PYTHONPATH so `payops_env` is importable
|
| 28 |
+
ENV PYTHONPATH="/app"
|
| 29 |
|
| 30 |
# HuggingFace Spaces requires port 7860; default to 7860
|
| 31 |
ENV PORT=7860
|
| 32 |
|
| 33 |
# Runtime env vars (overridden at deploy time via HF Space secrets)
|
| 34 |
+
# HF_TOKEN / OPENAI_API_KEY are injected at runtime — not baked into the image
|
| 35 |
ENV API_BASE_URL="" \
|
| 36 |
MODEL_NAME="" \
|
|
|
|
| 37 |
PAYOPS_BASE_URL="http://localhost:7860"
|
| 38 |
|
| 39 |
# Switch to non-root user
|