manikandan-n-07 commited on
Commit
508fec0
·
1 Parent(s): 33523d4

fix: Phase 2 validation - /health=healthy, add /metadata, /schema, /mcp, httpx dep, safe client import

Browse files
Files changed (6) hide show
  1. Dockerfile +1 -1
  2. __init__.py +9 -1
  3. data/memory.json +153 -398
  4. pyproject.toml +1 -0
  5. requirements.txt +1 -0
  6. server/app.py +53 -1
Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
  # Use official lightweight Python image
2
- # REBUILD_TIMESTAMP: 2026-04-07 20:10 (Package Structure Fix)
3
  FROM python:3.10-slim
4
 
5
  # Set working directory
 
1
  # Use official lightweight Python image
2
+ # REBUILD_TIMESTAMP: 2026-04-07 23:30 (Phase2 Fix: /health=healthy, /metadata, /schema, /mcp, httpx)
3
  FROM python:3.10-slim
4
 
5
  # Set working directory
__init__.py CHANGED
@@ -1,6 +1,14 @@
1
- from .client import DroneEnvClient
2
  from .models import DroneAction, DroneObservation, DroneState
3
 
 
 
 
 
 
 
 
 
 
4
  __all__ = [
5
  "DroneAction",
6
  "DroneObservation",
 
 
1
  from .models import DroneAction, DroneObservation, DroneState
2
 
3
+ # Client import is optional — it requires httpx which may not be installed
4
+ # in all runtime environments. The graders must work independently.
5
+ try:
6
+ from .client import DroneEnvClient
7
+ _CLIENT_AVAILABLE = True
8
+ except ImportError:
9
+ _CLIENT_AVAILABLE = False
10
+ DroneEnvClient = None # type: ignore
11
+
12
  __all__ = [
13
  "DroneAction",
14
  "DroneObservation",
data/memory.json CHANGED
@@ -1,402 +1,4 @@
1
  [
2
- {
3
- "task": "drone_env.core.graders:grade_easy",
4
- "steps": [
5
- {
6
- "step": 1,
7
- "x": 4,
8
- "y": 9,
9
- "action": "DOWN",
10
- "reward": -0.05,
11
- "battery": 0.9833
12
- },
13
- {
14
- "step": 2,
15
- "x": 3,
16
- "y": 9,
17
- "action": "LEFT",
18
- "reward": 0.95,
19
- "battery": 0.9667
20
- }
21
- ],
22
- "grid_meta": {
23
- "width": 10,
24
- "height": 10
25
- },
26
- "delivery_positions": [
27
- [
28
- 3,
29
- 9
30
- ]
31
- ],
32
- "deliveries_done": 1,
33
- "total_reward": 0.9,
34
- "total_steps": 2
35
- },
36
- {
37
- "task": "drone_env.core.graders:grade_easy",
38
- "steps": [
39
- {
40
- "step": 1,
41
- "x": 4,
42
- "y": 5,
43
- "action": "DOWN",
44
- "reward": -0.05,
45
- "battery": 0.9833
46
- },
47
- {
48
- "step": 2,
49
- "x": 4,
50
- "y": 6,
51
- "action": "DOWN",
52
- "reward": -0.05,
53
- "battery": 0.9667
54
- },
55
- {
56
- "step": 3,
57
- "x": 4,
58
- "y": 7,
59
- "action": "DOWN",
60
- "reward": -0.05,
61
- "battery": 0.95
62
- },
63
- {
64
- "step": 4,
65
- "x": 5,
66
- "y": 7,
67
- "action": "RIGHT",
68
- "reward": 0.95,
69
- "battery": 0.9333
70
- }
71
- ],
72
- "grid_meta": {
73
- "width": 10,
74
- "height": 10
75
- },
76
- "delivery_positions": [
77
- [
78
- 5,
79
- 7
80
- ]
81
- ],
82
- "deliveries_done": 1,
83
- "total_reward": 0.8,
84
- "total_steps": 4
85
- },
86
- {
87
- "task": "drone_env.core.graders:grade_medium",
88
- "steps": [
89
- {
90
- "step": 1,
91
- "x": 8,
92
- "y": 12,
93
- "action": "UP",
94
- "reward": -0.05,
95
- "battery": 0.99
96
- },
97
- {
98
- "step": 2,
99
- "x": 8,
100
- "y": 11,
101
- "action": "UP",
102
- "reward": -0.05,
103
- "battery": 0.98
104
- },
105
- {
106
- "step": 3,
107
- "x": 8,
108
- "y": 10,
109
- "action": "UP",
110
- "reward": -0.05,
111
- "battery": 0.97
112
- },
113
- {
114
- "step": 4,
115
- "x": 8,
116
- "y": 9,
117
- "action": "UP",
118
- "reward": -0.05,
119
- "battery": 0.96
120
- },
121
- {
122
- "step": 5,
123
- "x": 8,
124
- "y": 8,
125
- "action": "UP",
126
- "reward": -0.05,
127
- "battery": 0.95
128
- },
129
- {
130
- "step": 6,
131
- "x": 8,
132
- "y": 7,
133
- "action": "UP",
134
- "reward": -0.05,
135
- "battery": 0.94
136
- },
137
- {
138
- "step": 7,
139
- "x": 8,
140
- "y": 6,
141
- "action": "UP",
142
- "reward": -0.05,
143
- "battery": 0.93
144
- },
145
- {
146
- "step": 8,
147
- "x": 8,
148
- "y": 5,
149
- "action": "UP",
150
- "reward": -0.05,
151
- "battery": 0.92
152
- },
153
- {
154
- "step": 9,
155
- "x": 8,
156
- "y": 4,
157
- "action": "UP",
158
- "reward": -0.05,
159
- "battery": 0.91
160
- },
161
- {
162
- "step": 10,
163
- "x": 8,
164
- "y": 3,
165
- "action": "UP",
166
- "reward": -0.05,
167
- "battery": 0.9
168
- },
169
- {
170
- "step": 11,
171
- "x": 9,
172
- "y": 3,
173
- "action": "RIGHT",
174
- "reward": -0.15,
175
- "battery": 0.89
176
- },
177
- {
178
- "step": 12,
179
- "x": 9,
180
- "y": 2,
181
- "action": "UP",
182
- "reward": -0.05,
183
- "battery": 0.88
184
- },
185
- {
186
- "step": 13,
187
- "x": 10,
188
- "y": 2,
189
- "action": "RIGHT",
190
- "reward": -0.05,
191
- "battery": 0.87
192
- },
193
- {
194
- "step": 14,
195
- "x": 10,
196
- "y": 1,
197
- "action": "UP",
198
- "reward": -0.05,
199
- "battery": 0.86
200
- },
201
- {
202
- "step": 15,
203
- "x": 11,
204
- "y": 1,
205
- "action": "RIGHT",
206
- "reward": -0.05,
207
- "battery": 0.85
208
- },
209
- {
210
- "step": 16,
211
- "x": 11,
212
- "y": 0,
213
- "action": "UP",
214
- "reward": -0.05,
215
- "battery": 0.84
216
- },
217
- {
218
- "step": 17,
219
- "x": 12,
220
- "y": 0,
221
- "action": "RIGHT",
222
- "reward": 0.75,
223
- "battery": 0.83
224
- },
225
- {
226
- "step": 18,
227
- "x": 11,
228
- "y": 0,
229
- "action": "LEFT",
230
- "reward": -0.05,
231
- "battery": 0.82
232
- },
233
- {
234
- "step": 19,
235
- "x": 10,
236
- "y": 0,
237
- "action": "LEFT",
238
- "reward": -0.05,
239
- "battery": 0.81
240
- },
241
- {
242
- "step": 20,
243
- "x": 9,
244
- "y": 0,
245
- "action": "LEFT",
246
- "reward": -0.05,
247
- "battery": 0.8
248
- },
249
- {
250
- "step": 21,
251
- "x": 9,
252
- "y": 1,
253
- "action": "DOWN",
254
- "reward": -0.05,
255
- "battery": 0.79
256
- },
257
- {
258
- "step": 22,
259
- "x": 8,
260
- "y": 1,
261
- "action": "LEFT",
262
- "reward": -0.05,
263
- "battery": 0.78
264
- },
265
- {
266
- "step": 23,
267
- "x": 8,
268
- "y": 2,
269
- "action": "DOWN",
270
- "reward": -0.05,
271
- "battery": 0.77
272
- },
273
- {
274
- "step": 24,
275
- "x": 7,
276
- "y": 2,
277
- "action": "LEFT",
278
- "reward": -0.05,
279
- "battery": 0.76
280
- },
281
- {
282
- "step": 25,
283
- "x": 7,
284
- "y": 3,
285
- "action": "DOWN",
286
- "reward": -0.1,
287
- "battery": 0.75
288
- },
289
- {
290
- "step": 26,
291
- "x": 6,
292
- "y": 3,
293
- "action": "LEFT",
294
- "reward": -0.05,
295
- "battery": 0.74
296
- },
297
- {
298
- "step": 27,
299
- "x": 6,
300
- "y": 4,
301
- "action": "DOWN",
302
- "reward": -0.05,
303
- "battery": 0.73
304
- },
305
- {
306
- "step": 28,
307
- "x": 5,
308
- "y": 4,
309
- "action": "LEFT",
310
- "reward": -0.05,
311
- "battery": 0.72
312
- },
313
- {
314
- "step": 29,
315
- "x": 5,
316
- "y": 5,
317
- "action": "DOWN",
318
- "reward": -0.05,
319
- "battery": 0.71
320
- },
321
- {
322
- "step": 30,
323
- "x": 4,
324
- "y": 5,
325
- "action": "LEFT",
326
- "reward": 0.75,
327
- "battery": 0.7
328
- },
329
- {
330
- "step": 31,
331
- "x": 4,
332
- "y": 6,
333
- "action": "DOWN",
334
- "reward": -0.05,
335
- "battery": 0.69
336
- },
337
- {
338
- "step": 32,
339
- "x": 4,
340
- "y": 7,
341
- "action": "DOWN",
342
- "reward": -0.05,
343
- "battery": 0.68
344
- },
345
- {
346
- "step": 33,
347
- "x": 4,
348
- "y": 8,
349
- "action": "DOWN",
350
- "reward": -0.05,
351
- "battery": 0.67
352
- },
353
- {
354
- "step": 34,
355
- "x": 3,
356
- "y": 8,
357
- "action": "LEFT",
358
- "reward": -0.05,
359
- "battery": 0.66
360
- },
361
- {
362
- "step": 35,
363
- "x": 3,
364
- "y": 9,
365
- "action": "DOWN",
366
- "reward": -0.05,
367
- "battery": 0.65
368
- },
369
- {
370
- "step": 36,
371
- "x": 2,
372
- "y": 9,
373
- "action": "LEFT",
374
- "reward": 0.75,
375
- "battery": 0.64
376
- }
377
- ],
378
- "grid_meta": {
379
- "width": 14,
380
- "height": 14
381
- },
382
- "delivery_positions": [
383
- [
384
- 12,
385
- 0
386
- ],
387
- [
388
- 4,
389
- 5
390
- ],
391
- [
392
- 2,
393
- 9
394
- ]
395
- ],
396
- "deliveries_done": 3,
397
- "total_reward": 0.45,
398
- "total_steps": 36
399
- },
400
  {
401
  "task": "drone_env.core.graders:grade_hard",
402
  "steps": [
@@ -17944,5 +17546,158 @@
17944
  "deliveries_done": 1,
17945
  "total_reward": 1.35,
17946
  "total_steps": 4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17947
  }
17948
  ]
 
1
  [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  {
3
  "task": "drone_env.core.graders:grade_hard",
4
  "steps": [
 
17546
  "deliveries_done": 1,
17547
  "total_reward": 1.35,
17548
  "total_steps": 4
17549
+ },
17550
+ {
17551
+ "task": "drone_env.core.graders:grade_easy",
17552
+ "steps": [
17553
+ {
17554
+ "step": 1,
17555
+ "x": 1,
17556
+ "y": 7,
17557
+ "action": "DOWN",
17558
+ "reward": 0.1,
17559
+ "battery": 0.9833,
17560
+ "message": "On road \ud83d\udee3\ufe0f"
17561
+ },
17562
+ {
17563
+ "step": 2,
17564
+ "x": 0,
17565
+ "y": 7,
17566
+ "action": "LEFT",
17567
+ "reward": 1.05,
17568
+ "battery": 0.9667,
17569
+ "message": "\u2705 Delivery 1/1 done!"
17570
+ }
17571
+ ],
17572
+ "grid_meta": {
17573
+ "width": 10,
17574
+ "height": 10
17575
+ },
17576
+ "delivery_positions": [
17577
+ [
17578
+ 0,
17579
+ 7
17580
+ ]
17581
+ ],
17582
+ "deliveries_done": 1,
17583
+ "total_reward": 1.15,
17584
+ "total_steps": 2
17585
+ },
17586
+ {
17587
+ "task": "drone_env.core.graders:grade_easy",
17588
+ "steps": [
17589
+ {
17590
+ "step": 1,
17591
+ "x": 4,
17592
+ "y": 5,
17593
+ "action": "UP",
17594
+ "reward": 0.1,
17595
+ "battery": 0.9833,
17596
+ "message": "On road \ud83d\udee3\ufe0f"
17597
+ },
17598
+ {
17599
+ "step": 2,
17600
+ "x": 4,
17601
+ "y": 4,
17602
+ "action": "UP",
17603
+ "reward": 0.1,
17604
+ "battery": 0.9667,
17605
+ "message": "On road \ud83d\udee3\ufe0f"
17606
+ },
17607
+ {
17608
+ "step": 3,
17609
+ "x": 4,
17610
+ "y": 3,
17611
+ "action": "UP",
17612
+ "reward": 0.1,
17613
+ "battery": 0.95,
17614
+ "message": "On road \ud83d\udee3\ufe0f"
17615
+ },
17616
+ {
17617
+ "step": 4,
17618
+ "x": 4,
17619
+ "y": 2,
17620
+ "action": "UP",
17621
+ "reward": 0.1,
17622
+ "battery": 0.9333,
17623
+ "message": "On road \ud83d\udee3\ufe0f"
17624
+ },
17625
+ {
17626
+ "step": 5,
17627
+ "x": 3,
17628
+ "y": 2,
17629
+ "action": "LEFT",
17630
+ "reward": 1.05,
17631
+ "battery": 0.9167,
17632
+ "message": "\u2705 Delivery 1/1 done!"
17633
+ }
17634
+ ],
17635
+ "grid_meta": {
17636
+ "width": 10,
17637
+ "height": 10
17638
+ },
17639
+ "delivery_positions": [
17640
+ [
17641
+ 3,
17642
+ 2
17643
+ ]
17644
+ ],
17645
+ "deliveries_done": 1,
17646
+ "total_reward": 1.45,
17647
+ "total_steps": 5
17648
+ },
17649
+ {
17650
+ "task": "drone_env.core.graders:grade_easy",
17651
+ "steps": [
17652
+ {
17653
+ "step": 1,
17654
+ "x": 3,
17655
+ "y": 3,
17656
+ "action": "UP",
17657
+ "reward": 0.1,
17658
+ "battery": 0.9833,
17659
+ "message": "On road \ud83d\udee3\ufe0f"
17660
+ },
17661
+ {
17662
+ "step": 2,
17663
+ "x": 3,
17664
+ "y": 2,
17665
+ "action": "UP",
17666
+ "reward": 0.1,
17667
+ "battery": 0.9667,
17668
+ "message": "Flying over a tree! \ud83c\udf33"
17669
+ },
17670
+ {
17671
+ "step": 3,
17672
+ "x": 3,
17673
+ "y": 1,
17674
+ "action": "UP",
17675
+ "reward": 0.1,
17676
+ "battery": 0.95,
17677
+ "message": "On road \ud83d\udee3\ufe0f"
17678
+ },
17679
+ {
17680
+ "step": 4,
17681
+ "x": 4,
17682
+ "y": 1,
17683
+ "action": "RIGHT",
17684
+ "reward": 1.05,
17685
+ "battery": 0.9333,
17686
+ "message": "\u2705 Delivery 1/1 done!"
17687
+ }
17688
+ ],
17689
+ "grid_meta": {
17690
+ "width": 10,
17691
+ "height": 10
17692
+ },
17693
+ "delivery_positions": [
17694
+ [
17695
+ 4,
17696
+ 1
17697
+ ]
17698
+ ],
17699
+ "deliveries_done": 1,
17700
+ "total_reward": 1.35,
17701
+ "total_steps": 4
17702
  }
17703
  ]
pyproject.toml CHANGED
@@ -17,6 +17,7 @@ dependencies = [
17
  "openenv-core[core]>=0.2.1",
18
  "torch>=2.0.0",
19
  "openai>=1.0.0",
 
20
  "python-multipart>=0.0.9",
21
  "fastapi>=0.115.0",
22
  "uvicorn>=0.24.0",
 
17
  "openenv-core[core]>=0.2.1",
18
  "torch>=2.0.0",
19
  "openai>=1.0.0",
20
+ "httpx>=0.27.0",
21
  "python-multipart>=0.0.9",
22
  "fastapi>=0.115.0",
23
  "uvicorn>=0.24.0",
requirements.txt CHANGED
@@ -2,6 +2,7 @@
2
  openenv-core[core]>=0.2.1
3
  torch>=2.0.0
4
  openai>=1.0.0
 
5
  fastapi>=0.115.0
6
  uvicorn>=0.24.0
7
  python-multipart>=0.0.9
 
2
  openenv-core[core]>=0.2.1
3
  torch>=2.0.0
4
  openai>=1.0.0
5
+ httpx>=0.27.0
6
  fastapi>=0.115.0
7
  uvicorn>=0.24.0
8
  python-multipart>=0.0.9
server/app.py CHANGED
@@ -76,7 +76,59 @@ _env = DroneDeliveryEnvironment()
76
 
77
  @app.get("/health")
78
  async def health():
79
- return {"status": "ok", "version": "0.2.1"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
  @app.post("/reset", response_model=DroneObservation)
82
  async def reset(action: Optional[DroneAction] = None):
 
76
 
77
  @app.get("/health")
78
  async def health():
79
+ # OpenEnv validator requires status == "healthy"
80
+ return {"status": "healthy", "version": "0.2.1"}
81
+
82
+
83
+ @app.get("/metadata")
84
+ async def metadata():
85
+ """OpenEnv spec: GET /metadata returns name and description."""
86
+ return {
87
+ "name": "drone_env",
88
+ "description": "SkyRelic Drone Delivery reinforcement-learning environment with easy/medium/hard delivery tasks on a grid world.",
89
+ "version": "0.2.1",
90
+ "tasks": [
91
+ {"id": "easy_delivery", "grader": "drone_env.core.graders:grade_easy"},
92
+ {"id": "medium_delivery", "grader": "drone_env.core.graders:grade_medium"},
93
+ {"id": "hard_delivery", "grader": "drone_env.core.graders:grade_hard"},
94
+ ],
95
+ }
96
+
97
+
98
+ @app.get("/schema")
99
+ async def schema():
100
+ """OpenEnv spec: GET /schema returns action, observation, and state schemas."""
101
+ return {
102
+ "action": DroneAction.model_json_schema(),
103
+ "observation": DroneObservation.model_json_schema(),
104
+ "state": DroneState.model_json_schema(),
105
+ }
106
+
107
+
108
+ @app.post("/mcp")
109
+ async def mcp(request: Request):
110
+ """OpenEnv spec: POST /mcp returns JSON-RPC 2.0 payload."""
111
+ body = {}
112
+ try:
113
+ body = await request.json()
114
+ except Exception:
115
+ pass
116
+ method = body.get("method", "tools/list")
117
+ req_id = body.get("id", 1)
118
+ if method == "tools/list":
119
+ result = {
120
+ "tools": [
121
+ {"name": "reset", "description": "Reset the drone environment"},
122
+ {"name": "step", "description": "Take one step in the environment"},
123
+ {"name": "state", "description": "Get current environment state"},
124
+ {"name": "grade", "description": "Get current task score"},
125
+ ]
126
+ }
127
+ elif method == "tools/call":
128
+ result = {"content": [{"type": "text", "text": "Use /reset, /step, /state, or /grade endpoints."}]}
129
+ else:
130
+ result = {"message": "OpenEnv drone_env MCP bridge"}
131
+ return {"jsonrpc": "2.0", "id": req_id, "result": result}
132
 
133
  @app.post("/reset", response_model=DroneObservation)
134
  async def reset(action: Optional[DroneAction] = None):