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()