Spaces:
Running
Running
| """ | |
| FastAPI application for the API Testing Environment. | |
| Endpoints: | |
| - POST /reset: Reset the environment | |
| - POST /step: Execute an action | |
| - GET /state: Get current environment state | |
| - GET /schema: Get action/observation schemas | |
| - WS /ws: WebSocket endpoint for persistent sessions | |
| - GET / Info page | |
| Usage: | |
| uvicorn server.app:app --host 0.0.0.0 --port 8000 | |
| """ | |
| import os | |
| import logging | |
| try: | |
| from openenv.core.env_server.http_server import create_app | |
| from ..models import APITestAction, APITestObservation | |
| from .environment import APITestEnvironment | |
| except ImportError: | |
| from openenv.core.env_server.http_server import create_app | |
| from models import APITestAction, APITestObservation | |
| from server.environment import APITestEnvironment | |
| from fastapi.responses import RedirectResponse | |
| logger = logging.getLogger(__name__) | |
| app = create_app( | |
| APITestEnvironment, | |
| APITestAction, | |
| APITestObservation, | |
| env_name="api_testing_env", | |
| max_concurrent_envs=int(os.environ.get("MAX_ENVS", "1")), | |
| ) | |
| # Track whether the Gradio UI is available so root can redirect to it | |
| _GRADIO_MOUNTED = False | |
| async def info(): | |
| """JSON info about the environment (replaces the old `/` JSON endpoint).""" | |
| return { | |
| "name": "API Testing Environment", | |
| "description": "An OpenEnv RL environment where an AI agent learns to test REST APIs intelligently", | |
| "tasks": ["basic_validation", "edge_cases", "security_workflows"], | |
| "ui": "/ui", | |
| "docs": "/docs", | |
| "schema": "/schema", | |
| } | |
| async def list_tasks(): | |
| """List available tasks with descriptions.""" | |
| from .environment import TASKS | |
| return { | |
| task_id: { | |
| "description": task["description"], | |
| "difficulty": task["difficulty"], | |
| "max_steps": task["max_steps"], | |
| "total_bugs": task["total_bugs"], | |
| } | |
| for task_id, task in TASKS.items() | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Mount Gradio UI at /ui (only if gradio is installed and ENABLE_WEB_INTERFACE) | |
| # --------------------------------------------------------------------------- | |
| if os.environ.get("ENABLE_WEB_INTERFACE", "true").lower() in ("1", "true", "yes"): | |
| try: | |
| import gradio as gr # type: ignore | |
| # Make the repo root importable so gradio_app's `from models import ...` works | |
| import sys | |
| _REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) | |
| if _REPO_ROOT not in sys.path: | |
| sys.path.insert(0, _REPO_ROOT) | |
| from gradio_app import build_ui # type: ignore | |
| _gradio_ui = build_ui() | |
| app = gr.mount_gradio_app(app, _gradio_ui, path="/ui") | |
| _GRADIO_MOUNTED = True | |
| logger.info("Gradio UI mounted at /ui") | |
| except Exception as exc: # noqa: BLE001 | |
| logger.warning(f"Skipping Gradio mount ({type(exc).__name__}: {exc})") | |
| # --------------------------------------------------------------------------- | |
| # Root redirect: send visitors to the Gradio UI if mounted, else to JSON info | |
| # --------------------------------------------------------------------------- | |
| async def root_redirect(): | |
| """Redirect / to the Gradio UI when available, otherwise to /info JSON.""" | |
| if _GRADIO_MOUNTED: | |
| return RedirectResponse(url="/ui", status_code=307) | |
| return RedirectResponse(url="/info", status_code=307) | |
| def main(host: str = None, port: int = None): | |
| """Entry point for `uv run server` and `python -m server.app`. | |
| When invoked from the CLI without args, parses argv for --host / --port. | |
| """ | |
| import uvicorn | |
| if host is None or port is None: | |
| import argparse | |
| parser = argparse.ArgumentParser(description="API Testing Environment server") | |
| parser.add_argument("--host", default="0.0.0.0") | |
| parser.add_argument("--port", type=int, default=None) | |
| args, _ = parser.parse_known_args() | |
| host = host or args.host | |
| port = port or args.port | |
| if port is None: | |
| port = int(os.environ.get("PORT", "8000")) | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s %(levelname)s [%(name)s] %(message)s", | |
| ) | |
| logging.getLogger("httpx").setLevel(logging.WARNING) | |
| logging.getLogger("httpcore").setLevel(logging.WARNING) | |
| logging.getLogger("uvicorn.access").setLevel(logging.WARNING) | |
| uvicorn.run(app, host=host, port=port) | |
| if __name__ == "__main__": | |
| main() | |