Spaces:
Sleeping
Sleeping
PROTHAM commited on
Commit ·
5834385
1
Parent(s): babab16
improve: hard tasks max_steps=10, fix reward gap, clearer descriptions
Browse files- graders/grader.py +12 -3
- server/api_debug_environment.py +3 -9
- tasks/expert.py +2 -0
- tasks/hard.py +21 -3
graders/grader.py
CHANGED
|
@@ -9,13 +9,22 @@ class APIGrader:
|
|
| 9 |
try:
|
| 10 |
body = json.loads(response_body)
|
| 11 |
except Exception:
|
| 12 |
-
return 0.
|
| 13 |
schema_score = self._schema_match(body, expected_schema)
|
|
|
|
|
|
|
| 14 |
attempt_bonus = max(0.0, 1.0 - (attempt / max_steps) * 0.15)
|
| 15 |
-
return round(min(1.0, max(0.0,
|
| 16 |
|
| 17 |
def _partial(self, status):
|
| 18 |
-
mapping = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
return mapping.get(status, 0.05)
|
| 20 |
|
| 21 |
def _schema_match(self, body, schema):
|
|
|
|
| 9 |
try:
|
| 10 |
body = json.loads(response_body)
|
| 11 |
except Exception:
|
| 12 |
+
return 0.25
|
| 13 |
schema_score = self._schema_match(body, expected_schema)
|
| 14 |
+
# Ensure correct status always scores higher than any error status
|
| 15 |
+
base_score = max(0.25, schema_score)
|
| 16 |
attempt_bonus = max(0.0, 1.0 - (attempt / max_steps) * 0.15)
|
| 17 |
+
return round(min(1.0, max(0.0, base_score * attempt_bonus)), 4)
|
| 18 |
|
| 19 |
def _partial(self, status):
|
| 20 |
+
mapping = {
|
| 21 |
+
0: 0.0,
|
| 22 |
+
401: 0.05, 403: 0.05, 404: 0.05,
|
| 23 |
+
405: 0.10, 415: 0.10,
|
| 24 |
+
422: 0.15,
|
| 25 |
+
429: 0.20,
|
| 26 |
+
200: 0.70,
|
| 27 |
+
}
|
| 28 |
return mapping.get(status, 0.05)
|
| 29 |
|
| 30 |
def _schema_match(self, body, schema):
|
server/api_debug_environment.py
CHANGED
|
@@ -21,21 +21,15 @@ class APIDebugEnvironment(Environment):
|
|
| 21 |
self._current_task = None
|
| 22 |
|
| 23 |
def reset(self, task_id: str = "easy", **kwargs) -> APIObservation:
|
| 24 |
-
# Reset the mock API state to ensure strict episode isolation
|
| 25 |
-
try:
|
| 26 |
-
with httpx.Client(base_url=MOCK_BASE, timeout=2.0) as http:
|
| 27 |
-
http.post("/mock_api/_admin/reset")
|
| 28 |
-
except Exception:
|
| 29 |
-
pass
|
| 30 |
-
|
| 31 |
if task_id not in TASK_REGISTRY:
|
| 32 |
task_id = "easy"
|
| 33 |
self._current_task = random.choice(TASK_REGISTRY[task_id])
|
|
|
|
| 34 |
self._state = APIState(
|
| 35 |
episode_id=f"ep_{random.randint(10000, 99999)}",
|
| 36 |
step_count=0,
|
| 37 |
task_id=task_id,
|
| 38 |
-
max_steps=
|
| 39 |
solved=False,
|
| 40 |
)
|
| 41 |
return APIObservation(
|
|
@@ -106,4 +100,4 @@ class APIDebugEnvironment(Environment):
|
|
| 106 |
|
| 107 |
@property
|
| 108 |
def state(self) -> APIState:
|
| 109 |
-
return self._state
|
|
|
|
| 21 |
self._current_task = None
|
| 22 |
|
| 23 |
def reset(self, task_id: str = "easy", **kwargs) -> APIObservation:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
if task_id not in TASK_REGISTRY:
|
| 25 |
task_id = "easy"
|
| 26 |
self._current_task = random.choice(TASK_REGISTRY[task_id])
|
| 27 |
+
max_steps = self._current_task.get("max_steps", 5)
|
| 28 |
self._state = APIState(
|
| 29 |
episode_id=f"ep_{random.randint(10000, 99999)}",
|
| 30 |
step_count=0,
|
| 31 |
task_id=task_id,
|
| 32 |
+
max_steps=max_steps,
|
| 33 |
solved=False,
|
| 34 |
)
|
| 35 |
return APIObservation(
|
|
|
|
| 100 |
|
| 101 |
@property
|
| 102 |
def state(self) -> APIState:
|
| 103 |
+
return self._state
|
tasks/expert.py
CHANGED
|
@@ -11,6 +11,7 @@ EXPERT_TASKS = [
|
|
| 11 |
},
|
| 12 |
"expected_status": 200,
|
| 13 |
"expected_schema": {"status": "", "cpu": 0.0, "mem": 0.0},
|
|
|
|
| 14 |
},
|
| 15 |
{
|
| 16 |
"id": "expert_stateful_chain",
|
|
@@ -24,5 +25,6 @@ EXPERT_TASKS = [
|
|
| 24 |
},
|
| 25 |
"expected_status": 200,
|
| 26 |
"expected_schema": {"status": "", "item_id": ""},
|
|
|
|
| 27 |
},
|
| 28 |
]
|
|
|
|
| 11 |
},
|
| 12 |
"expected_status": 200,
|
| 13 |
"expected_schema": {"status": "", "cpu": 0.0, "mem": 0.0},
|
| 14 |
+
"max_steps": 10,
|
| 15 |
},
|
| 16 |
{
|
| 17 |
"id": "expert_stateful_chain",
|
|
|
|
| 25 |
},
|
| 26 |
"expected_status": 200,
|
| 27 |
"expected_schema": {"status": "", "item_id": ""},
|
| 28 |
+
"max_steps": 10,
|
| 29 |
},
|
| 30 |
]
|
tasks/hard.py
CHANGED
|
@@ -1,7 +1,13 @@
|
|
| 1 |
HARD_TASKS = [
|
| 2 |
{
|
| 3 |
"id": "hard_token_exchange",
|
| 4 |
-
"description":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
"broken_request": {
|
| 6 |
"method": "GET",
|
| 7 |
"url": "/mock_api/protected",
|
|
@@ -11,10 +17,15 @@ HARD_TASKS = [
|
|
| 11 |
},
|
| 12 |
"expected_status": 200,
|
| 13 |
"expected_schema": {"data": ""},
|
|
|
|
| 14 |
},
|
| 15 |
{
|
| 16 |
"id": "hard_rate_limit",
|
| 17 |
-
"description":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
"broken_request": {
|
| 19 |
"method": "GET",
|
| 20 |
"url": "/mock_api/rate_limited",
|
|
@@ -24,10 +35,16 @@ HARD_TASKS = [
|
|
| 24 |
},
|
| 25 |
"expected_status": 200,
|
| 26 |
"expected_schema": {"data": ""},
|
|
|
|
| 27 |
},
|
| 28 |
{
|
| 29 |
"id": "hard_pagination",
|
| 30 |
-
"description":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
"broken_request": {
|
| 32 |
"method": "GET",
|
| 33 |
"url": "/mock_api/logs",
|
|
@@ -37,5 +54,6 @@ HARD_TASKS = [
|
|
| 37 |
},
|
| 38 |
"expected_status": 200,
|
| 39 |
"expected_schema": {"items": [], "has_more": False},
|
|
|
|
| 40 |
},
|
| 41 |
]
|
|
|
|
| 1 |
HARD_TASKS = [
|
| 2 |
{
|
| 3 |
"id": "hard_token_exchange",
|
| 4 |
+
"description": (
|
| 5 |
+
"The protected endpoint needs a fresh token. "
|
| 6 |
+
"Step 1: POST to /mock_api/auth/token with JSON body {\"client_id\": \"abc\", \"client_secret\": \"xyz\"} "
|
| 7 |
+
"to get an access_token from the response. "
|
| 8 |
+
"Step 2: Use that token as Authorization: Bearer <token> to GET /mock_api/protected. "
|
| 9 |
+
"The token in the broken request is expired and will return 401."
|
| 10 |
+
),
|
| 11 |
"broken_request": {
|
| 12 |
"method": "GET",
|
| 13 |
"url": "/mock_api/protected",
|
|
|
|
| 17 |
},
|
| 18 |
"expected_status": 200,
|
| 19 |
"expected_schema": {"data": ""},
|
| 20 |
+
"max_steps": 10,
|
| 21 |
},
|
| 22 |
{
|
| 23 |
"id": "hard_rate_limit",
|
| 24 |
+
"description": (
|
| 25 |
+
"The rate_limited endpoint returns 429 after 3 requests without the X-Retry-After header. "
|
| 26 |
+
"When you receive a 429, add the header X-Retry-After: 2 to your next request. "
|
| 27 |
+
"The response body on 429 contains Retry-After hint in headers."
|
| 28 |
+
),
|
| 29 |
"broken_request": {
|
| 30 |
"method": "GET",
|
| 31 |
"url": "/mock_api/rate_limited",
|
|
|
|
| 35 |
},
|
| 36 |
"expected_status": 200,
|
| 37 |
"expected_schema": {"data": ""},
|
| 38 |
+
"max_steps": 10,
|
| 39 |
},
|
| 40 |
{
|
| 41 |
"id": "hard_pagination",
|
| 42 |
+
"description": (
|
| 43 |
+
"GET /mock_api/logs returns the first page with next_cursor and has_more fields. "
|
| 44 |
+
"Pass cursor=<next_cursor> as a query param to get the next page. "
|
| 45 |
+
"Keep following the cursor chain until has_more is false. "
|
| 46 |
+
"The final page has has_more=false and no next_cursor."
|
| 47 |
+
),
|
| 48 |
"broken_request": {
|
| 49 |
"method": "GET",
|
| 50 |
"url": "/mock_api/logs",
|
|
|
|
| 54 |
},
|
| 55 |
"expected_status": 200,
|
| 56 |
"expected_schema": {"items": [], "has_more": False},
|
| 57 |
+
"max_steps": 10,
|
| 58 |
},
|
| 59 |
]
|