Spaces:
Sleeping
Sleeping
| # 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 Constraint Env Environment. | |
| This module creates an HTTP server that exposes the ConstraintEnvironment | |
| over HTTP and WebSocket endpoints, compatible with EnvClient. | |
| 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 | |
| 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: | |
| python -m server.app | |
| """ | |
| import os | |
| try: | |
| from openenv.core.env_server.http_server import create_app | |
| except Exception as e: # pragma: no cover | |
| raise ImportError( | |
| "openenv is required for the web interface. Install dependencies with '\n uv sync\n'" | |
| ) from e | |
| try: | |
| from ..models import ConstraintAction, ConstraintObservation | |
| from .constraint_env_environment import ConstraintEnvironment | |
| except ImportError: | |
| from constraint_env.models import ConstraintAction, ConstraintObservation | |
| from constraint_env.server.constraint_env_environment import ConstraintEnvironment | |
| # Load the dataset so the environment can be initialised without crashing. | |
| try: | |
| from dataset_example import dataset as _DATASET | |
| except ImportError: | |
| from constraint_env.dataset_example import dataset as _DATASET # type: ignore | |
| def _make_env(): | |
| """Factory that passes the pre-loaded dataset into ConstraintEnvironment.""" | |
| return ConstraintEnvironment(dataset=_DATASET) | |
| try: | |
| from .gradio_ui import build_constraint_gradio_ui as _gradio_builder | |
| except ImportError: | |
| try: | |
| from constraint_env.server.gradio_ui import build_constraint_gradio_ui as _gradio_builder | |
| except ImportError: | |
| _gradio_builder = None | |
| # Create the app β pass the factory so create_app calls _make_env() per session. | |
| import gradio as gr | |
| _orig_tabbed = gr.TabbedInterface | |
| def _swapped_tabbed(interface_list, tab_names, **kwargs): | |
| if len(interface_list) == 2 and "Custom" in tab_names: | |
| idx_custom = tab_names.index("Custom") | |
| idx_play = tab_names.index("Playground") | |
| return _orig_tabbed( | |
| [interface_list[idx_custom], interface_list[idx_play]], | |
| ["Constraint Compiler", "Base Playground"], | |
| **kwargs | |
| ) | |
| return _orig_tabbed(interface_list, tab_names, **kwargs) | |
| gr.TabbedInterface = _swapped_tabbed | |
| import openenv.core.env_server.web_interface as wi | |
| wi._load_readme_from_filesystem = lambda *args: """ | |
| An interactive compiler environment where AI agents (or humans!) translate natural language scheduling rules into rigorous JSON Abstract Syntax Trees (ASTs). | |
| ### Objective | |
| Read the natural language prompt, draft the corresponding JSON AST using the environment's strict DSL schema, and compile it. If you make a mistake, the environment will give you deterministic compiler feedback so you can debug your logic! | |
| ### How to Interact | |
| 1. **Reset** β Click `Reset` to load a fresh task and read the Prompt. | |
| 2. **Draft** β Write your JSON AST in the input box. | |
| 3. **Step** β Click `Step` to submit your AST to the OpenEnv compiler. | |
| 4. **Debug** β Check the Observation output. Read the feedback, fix your JSON, and click `Step` again! | |
| ### Rules of the Compiler | |
| * **Valid JSON Only:** The compiler will immediately reject malformed JSON. | |
| * **Declare Variables:** Every variable used in `where` or `assert` must be declared in the `forall` array. | |
| * **Step Penalties:** Every time you submit an incorrect AST, you lose `-0.05` points from your maximum possible reward. Solve it in as few steps as possible! | |
| ### Difficulty Levels | |
| * **π’ Easy** β Direct equality matches and simple assertions (Target: 0.80+). | |
| * **π‘ Medium** β Aggregate objectives requiring `sum` operations and nested loops (Target: 0.65+). | |
| * **π΄ Hard** β Deep conditional traversals with compound boolean logic and `NOT` filters (Target: 0.45+). | |
| ### Example Step JSON Input | |
| ```json | |
| { | |
| "type": "hard", | |
| "name": "cs_department_meeting", | |
| "forall": [ | |
| {"b": "branches"}, | |
| {"sub": {"subjects": "b"}}, | |
| {"d": "days"}, | |
| {"s": "slots"} | |
| ], | |
| "where": { | |
| "operator": "AND", | |
| "left": {"operator": "==", "left": {"name": "b"}, "right": "CS"}, | |
| "right": {"operator": "==", "left": "d", "right": 2} | |
| }, | |
| "assert": { | |
| "operator": "==", | |
| "left": { | |
| "target": "schedule", | |
| "args": [{"name": "b"}, {"name": "sub"}, "d", "s"] | |
| }, | |
| "right": 0 | |
| } | |
| } | |
| ```""" | |
| app = create_app( | |
| _make_env, | |
| ConstraintAction, | |
| ConstraintObservation, | |
| env_name="Timetable Constraint Environment (NL-to-AST)", | |
| max_concurrent_envs=1, | |
| gradio_builder=_gradio_builder, | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # PWA manifest β browsers request this at root level for the web UI | |
| # --------------------------------------------------------------------------- | |
| import os | |
| from pathlib import Path | |
| from fastapi.staticfiles import StaticFiles | |
| from fastapi.responses import JSONResponse | |
| _ASSETS_DIR = Path(__file__).parent.parent / "assets" | |
| if _ASSETS_DIR.exists(): | |
| app.mount("/assets", StaticFiles(directory=str(_ASSETS_DIR)), name="assets") | |
| async def web_manifest(): | |
| return JSONResponse( | |
| content={ | |
| "name": "Timetable Constraint Environment (NL-to-AST)", | |
| "short_name": "NL-to-AST", | |
| "description": "RL training environment: natural-language β constraint AST", | |
| "start_url": "/web/", | |
| "display": "standalone", | |
| "background_color": "#1e1e2e", | |
| "theme_color": "#7c3aed", | |
| "icons": [ | |
| { | |
| "src": "https://huggingface.co/front/assets/huggingface_logo-noborder.svg", | |
| "sizes": "any", | |
| "type": "image/svg+xml", | |
| } | |
| ], | |
| }, | |
| headers={"Cache-Control": "public, max-age=3600"}, | |
| ) | |
| def main(): | |
| """ | |
| Entry point for direct execution via uv run or python -m. | |
| Callable with no arguments (required for openenv validate). | |
| Respects $PORT env var so it works on HF Spaces (7860) and locally. | |
| uv run server/app.py | |
| python -m constraint_env.server.app | |
| """ | |
| import argparse | |
| import uvicorn | |
| parser = argparse.ArgumentParser(description="Constraint Env FastAPI Server") | |
| parser.add_argument("--host", type=str, default="0.0.0.0") | |
| parser.add_argument("--port", type=int, default=int(os.environ.get("PORT", 7860))) | |
| args = parser.parse_args() | |
| uvicorn.run(app, host=args.host, port=args.port) | |
| if __name__ == "__main__": | |
| main() | |