Spaces:
Sleeping
Sleeping
File size: 5,304 Bytes
3951517 6942c9a 3951517 2b1a54d 6942c9a 2b1a54d 3951517 2b1a54d 3951517 2b1a54d 3951517 6942c9a 3951517 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | # Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
"""
FastAPI application for the VeriRL Verilog hardware design environment.
This module creates an HTTP + WebSocket server that exposes VerirlEnvironment
over endpoints compatible with EnvClient.
Endpoints:
POST /reset — start a new episode (pass task_id in JSON body)
POST /step — execute one action
GET /state — current episode state
GET /schema — JSON schemas for action / observation / state
WS /ws — persistent WebSocket session (required for multi-step episodes)
GET /health — liveness check
GET /tasks — list available benchmark tasks (custom extension)
GET /blog — rendered HTML view of BLOG.md
GET /blog/raw — raw BLOG.md markdown source
Usage:
# Development (with auto-reload):
uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
# Production:
uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
# Or run directly:
uv run --project . server
"""
from pathlib import Path
from fastapi.responses import HTMLResponse, PlainTextResponse
from omegaconf import OmegaConf
from pydantic import BaseModel
# Support both in-repo and standalone imports
try:
# In-repo imports (when running from OpenEnv repository)
from openenv.core.env_server.http_server import create_app
from ..models import VerirlAction, VerirlObservation
from .verirl_env_environment import _TASK_CONFIGS, VerirlEnvironment
except ImportError:
# Standalone imports (when environment is standalone with openenv from pip)
from openenv.core.env_server.http_server import create_app
from models import VerirlAction, VerirlObservation
from server.verirl_env_environment import _TASK_CONFIGS, VerirlEnvironment
def _load_max_concurrent_envs() -> int:
for candidate in [
Path(__file__).parent.parent / "config.yaml",
Path("/root/verirl/config.yaml"),
]:
if candidate.exists():
return int(OmegaConf.load(candidate).server.max_concurrent_envs)
return 64 # safe default if config not found
class TaskInfo(BaseModel):
"""Metadata for a single benchmark task."""
id: str
name: str
difficulty: str
max_turns: int
description: str
# Create the app — pass the class (factory) for concurrent WebSocket session support
app = create_app(
VerirlEnvironment,
VerirlAction,
VerirlObservation,
env_name="verirl_env",
max_concurrent_envs=_load_max_concurrent_envs(),
)
@app.get("/tasks", response_model=list[TaskInfo], tags=["Tasks"])
def list_tasks():
"""List all available benchmark tasks with their metadata."""
return [
TaskInfo(
id=config["id"],
name=config["name"],
difficulty=config["difficulty"],
max_turns=config["max_turns"],
description=config["description"],
)
for config in _TASK_CONFIGS
]
_BLOG_PATH = Path(__file__).parent.parent / "BLOG.md"
_BLOG_HTML_TEMPLATE = """\
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>VeriRL — Training Blog</title>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown-light.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.0/marked.min.js"></script>
<style>
body {{ box-sizing: border-box; min-width: 200px; max-width: 860px;
margin: 40px auto; padding: 0 24px; font-family: sans-serif; }}
.markdown-body img {{ max-width: 100%; }}
</style>
</head>
<body class="markdown-body">
<script>
const raw = {raw_json};
document.currentScript.insertAdjacentHTML('afterend', marked.parse(raw));
</script>
</body>
</html>
"""
@app.get("/blog/raw", response_class=PlainTextResponse, tags=["Blog"])
def blog_raw():
"""Return the raw BLOG.md markdown source."""
if not _BLOG_PATH.exists():
return PlainTextResponse("Blog not found.", status_code=404)
return _BLOG_PATH.read_text(encoding="utf-8")
@app.get("/blog", response_class=HTMLResponse, tags=["Blog"])
def blog():
"""Render BLOG.md as a styled HTML page."""
if not _BLOG_PATH.exists():
return HTMLResponse("<p>Blog not found.</p>", status_code=404)
import json
raw = _BLOG_PATH.read_text(encoding="utf-8")
html = _BLOG_HTML_TEMPLATE.format(raw_json=json.dumps(raw))
return HTMLResponse(html)
def main(host: str = "0.0.0.0", port: int = 8000):
"""
Entry point for direct execution via uv run or python -m.
Enables running the server without Docker:
uv run --project . server
uv run --project . server --port 8001
python -m verirl_env.server.app
"""
import argparse
import uvicorn
# Parse port from command line if provided
parser = argparse.ArgumentParser()
parser.add_argument("--port", type=int, default=port)
args, _ = parser.parse_known_args()
uvicorn.run(app, host=host, port=args.port)
if __name__ == "__main__":
main()
|