Skip to content

Commit 4078161

Browse files
committed
chore: API docs and metadata extraction for action fields
1 parent 04eb97b commit 4078161

File tree

2 files changed

+269
-174
lines changed

2 files changed

+269
-174
lines changed

src/core/env_server/http_server.py

Lines changed: 188 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,31 @@ def register_routes(self, app: Any) -> None:
9090
if not isinstance(app, FastAPI):
9191
raise TypeError("app must be a FastAPI instance")
9292

93-
@app.post("/reset", response_model=ResetResponse)
93+
@app.post(
94+
"/reset",
95+
response_model=ResetResponse,
96+
tags=["Environment Control"],
97+
summary="Reset the environment",
98+
description="""
99+
Reset the environment to its initial state and return the first observation.
100+
101+
You can optionally provide a seed for reproducibility and an episode_id for tracking.
102+
""",
103+
responses={
104+
200: {
105+
"description": "Environment reset successfully",
106+
"content": {
107+
"application/json": {
108+
"example": {
109+
"observation": {"status": "ready", "data": {}},
110+
"reward": None,
111+
"done": False,
112+
}
113+
}
114+
},
115+
}
116+
},
117+
)
94118
async def reset(
95119
request: ResetRequest = Body(default_factory=ResetRequest),
96120
) -> ResetResponse:
@@ -114,7 +138,56 @@ async def reset(
114138
observation = self.env.reset(**valid_kwargs)
115139
return ResetResponse(**self._serialize_observation(observation))
116140

117-
@app.post("/step", response_model=StepResponse)
141+
@app.post(
142+
"/step",
143+
response_model=StepResponse,
144+
tags=["Environment Control"],
145+
summary="Execute an action in the environment",
146+
description="""
147+
Execute an action in the environment and receive the resulting observation.
148+
149+
The action must conform to the environment's action schema, which can be
150+
retrieved from the `/schema/action` endpoint. If the action is invalid,
151+
the endpoint will return HTTP 422 with detailed validation errors.
152+
153+
The response includes:
154+
- **observation**: The environment's response to the action
155+
- **reward**: Optional reward signal (float or None)
156+
- **done**: Boolean indicating if the episode has terminated
157+
""",
158+
responses={
159+
200: {
160+
"description": "Action executed successfully",
161+
"content": {
162+
"application/json": {
163+
"example": {
164+
"observation": {"status": "success", "data": {}},
165+
"reward": 1.0,
166+
"done": False,
167+
}
168+
}
169+
},
170+
},
171+
422: {
172+
"description": "Validation error - invalid action format or values",
173+
"content": {
174+
"application/json": {
175+
"example": {
176+
"detail": [
177+
{
178+
"type": "string_too_short",
179+
"loc": ["body", "action", "message"],
180+
"msg": "String should have at least 1 character",
181+
"input": "",
182+
}
183+
]
184+
}
185+
}
186+
},
187+
},
188+
500: {"description": "Internal server error during action execution"},
189+
},
190+
)
118191
async def step(request: StepRequest) -> StepResponse:
119192
"""Step endpoint - executes action and returns observation."""
120193
action_data = request.action
@@ -130,7 +203,7 @@ async def step(request: StepRequest) -> StepResponse:
130203

131204
# Handle optional parameters
132205
# Start with all fields from the request, including extra ones, but exclude 'action'
133-
kwargs = request.model_dump(exclude_unset=True, exclude={'action'})
206+
kwargs = request.model_dump(exclude_unset=True, exclude={"action"})
134207

135208
# Pass arguments only if environment accepts them
136209
sig = inspect.signature(self.env.step)
@@ -150,17 +223,45 @@ async def step(request: StepRequest) -> StepResponse:
150223
# Return serialized observation
151224
return StepResponse(**self._serialize_observation(observation))
152225

153-
@app.get("/state", response_model=State)
226+
@app.get(
227+
"/state",
228+
response_model=State,
229+
tags=["State Management"],
230+
summary="Get current environment state",
231+
description="""
232+
Retrieve the current internal state of the environment.
233+
234+
This endpoint allows inspection of the environment state without modifying it.
235+
The structure of the state object is defined by the environment's State model.
236+
""",
237+
)
154238
async def get_state() -> State:
155239
"""State endpoint - returns current environment state."""
156240
return self.env.state
157241

158-
@app.get("/health")
242+
@app.get(
243+
"/health",
244+
tags=["Health"],
245+
summary="Health check",
246+
description="Check if the environment server is running and healthy.",
247+
)
159248
async def health() -> Dict[str, str]:
160249
"""Health check endpoint."""
161250
return {"status": "healthy"}
162251

163-
@app.get("/schema/action", tags=["Schema"])
252+
@app.get(
253+
"/schema/action",
254+
tags=["Schema"],
255+
summary="Get action JSON schema",
256+
description="""
257+
Get JSON schema for actions accepted by this environment.
258+
259+
Returns the complete JSON schema definition for the Action model,
260+
including all field types, constraints, and validation rules.
261+
This schema can be used to validate actions before sending them
262+
to the environment, or to generate forms in web interfaces.
263+
""",
264+
)
164265
async def get_action_schema() -> Dict[str, Any]:
165266
"""
166267
Get JSON schema for actions accepted by this environment.
@@ -175,7 +276,18 @@ async def get_action_schema() -> Dict[str, Any]:
175276
"""
176277
return self.action_cls.model_json_schema()
177278

178-
@app.get("/schema/observation", tags=["Schema"])
279+
@app.get(
280+
"/schema/observation",
281+
tags=["Schema"],
282+
summary="Get observation JSON schema",
283+
description="""
284+
Get JSON schema for observations returned by this environment.
285+
286+
Returns the complete JSON schema definition for the Observation model,
287+
including all field types and nested structures. This schema describes
288+
what observations the environment will return after actions are executed.
289+
""",
290+
)
179291
async def get_observation_schema() -> Dict[str, Any]:
180292
"""
181293
Get JSON schema for observations returned by this environment.
@@ -189,7 +301,18 @@ async def get_observation_schema() -> Dict[str, Any]:
189301
"""
190302
return self.observation_cls.model_json_schema()
191303

192-
@app.get("/schema/state", tags=["Schema"])
304+
@app.get(
305+
"/schema/state",
306+
tags=["Schema"],
307+
summary="Get state JSON schema",
308+
description="""
309+
Get JSON schema for environment state objects.
310+
311+
Returns the complete JSON schema definition for the State model.
312+
This schema describes the internal state representation of the
313+
environment, which can be queried via the /state endpoint.
314+
""",
315+
)
193316
async def get_state_schema() -> Dict[str, Any]:
194317
"""
195318
Get JSON schema for environment state objects.
@@ -305,34 +428,70 @@ def create_fastapi_app(
305428
action_cls: Type[Action],
306429
observation_cls: Type[Observation],
307430
) -> Any:
308-
"""
309-
Create a FastAPI application with routes for the given environment.
310-
311-
Args:
312-
env: The Environment instance to serve
313-
action_cls: The Action subclass this environment expects
314-
observation_cls: The Observation subclass this environment returns
315-
316-
Returns:
317-
FastAPI application instance with routes registered
318-
319-
Example:
320-
>>> from envs.coding_env.server import CodeExecutionEnvironment
321-
>>> from envs.coding_env.models import CodeAction, CodeObservation
322-
>>>
323-
>>> env = CodeExecutionEnvironment()
324-
>>> app = create_fastapi_app(env, CodeAction, CodeObservation)
325-
>>>
326-
>>> # Run with: uvicorn module:app --host 0.0.0.0 --port 8000
327-
"""
431+
"""Create a FastAPI application with comprehensive documentation."""
328432
try:
329433
from fastapi import FastAPI
330434
except ImportError:
331435
raise ImportError(
332436
"FastAPI is required. Install with: pip install fastapi uvicorn"
333437
)
334438

335-
app = FastAPI(title="Environment HTTP Server")
439+
app = FastAPI(
440+
title="OpenEnv Environment HTTP API",
441+
version="1.0.0",
442+
description="""
443+
# OpenEnv Environment HTTP API
444+
445+
HTTP API for interacting with OpenEnv environments through a standardized interface.
446+
447+
## Features
448+
449+
* **Environment Reset**: Initialize or restart episodes
450+
* **Action Execution**: Send actions and receive observations
451+
* **State Inspection**: Query current environment state
452+
* **Schema Access**: Retrieve JSON schemas for actions and observations
453+
454+
## Workflow
455+
456+
1. Call `/reset` to start a new episode and get initial observation
457+
2. Call `/step` repeatedly with actions to interact with environment
458+
3. Episode ends when observation returns `done: true`
459+
4. Call `/state` anytime to inspect current environment state
460+
461+
## Documentation
462+
463+
* **Swagger UI**: Available at `/docs`
464+
* **ReDoc**: Available at `/redoc`
465+
* **OpenAPI Schema**: Available at `/openapi.json`
466+
""",
467+
openapi_tags=[
468+
{
469+
"name": "Environment Control",
470+
"description": "Core operations for environment interaction (reset, step)",
471+
},
472+
{
473+
"name": "State Management",
474+
"description": "Operations for inspecting environment state",
475+
},
476+
{
477+
"name": "Schema",
478+
"description": "JSON Schema endpoints for actions, observations, and state",
479+
},
480+
{"name": "Health", "description": "Service health and status checks"},
481+
],
482+
docs_url="/docs",
483+
redoc_url="/redoc",
484+
openapi_url="/openapi.json",
485+
contact={
486+
"name": "OpenEnv Team",
487+
"url": "https://github.com/meta-pytorch/OpenEnv",
488+
},
489+
license_info={
490+
"name": "BSD-3-Clause",
491+
"url": "https://github.com/meta-pytorch/OpenEnv/blob/main/LICENSE",
492+
},
493+
)
494+
336495
server = HTTPEnvServer(env, action_cls, observation_cls)
337496
server.register_routes(app)
338497
return app

0 commit comments

Comments
 (0)