padmapriyagosakan commited on
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)

Files changed (6) hide show
  1. TESTING.md +32 -21
  2. environment.py +6 -1
  3. grader.py +23 -4
  4. inference.py +0 -4
  5. requirements.txt +6 -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": "1.0.0"}
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 12 tasks are returned with the correct difficulty distribution.
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']:6}] correct={t['correct_action']}\")
79
  "
80
  ```
81
 
82
  **Expected output**
83
  ```
84
- Total tasks: 12
85
- By difficulty: {'easy': 4, 'medium': 4, 'hard': 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
- HARD-001 [hard ] correct=escalate
96
- HARD-002 [hard ] correct=reject
97
- HARD-003 [hard ] correct=reject
98
- HARD-004 [hard ] correct=approve
99
- ```
100
-
101
- **Failure indicator:** count != 12, missing difficulty tier, wrong correct_action.
 
 
 
 
 
 
 
 
 
 
 
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
- reward = grade(action_type, task, inspected_already=inspected_already)
 
 
 
 
 
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
- Investigation bonuses are now INVESTIGATION_BONUS (0.20).
 
 
 
 
130
  """
131
  if _is_investigation(action_type):
132
  return 0.0 if inspected_already else INVESTIGATION_BONUS
133
 
134
- return _base_terminal_reward(action_type, task)
 
 
 
 
 
135
 
136
 
137
  # ---------------------------------------------------------------------------
@@ -356,7 +366,16 @@ def grade_episode(
356
  # Convenience wrapper used by the environment
357
  # ---------------------------------------------------------------------------
358
 
359
- def grade(action_type: str, task: PayOpsTask, inspected_already: bool = False) -> float:
 
 
 
 
 
360
  """Single-step reward used inside environment.step_async."""
361
- return step_reward(action_type, task, inspected_already=inspected_already)
 
 
 
 
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>=0.2.0
2
- fastapi>=0.115.0
3
- uvicorn>=0.24.0
4
- pydantic>=2.0.0
5
- openai>=1.0.0
6
- httpx>=0.27.0
 
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:${PYTHONPATH}"
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