| """ |
| FastAPI application for the HR Onboarding/Offboarding Environment. |
| |
| Serves both the OpenEnv API endpoints and an interactive web playground UI. |
| """ |
|
|
| try: |
| from openenv.core.env_server.http_server import create_app |
| except Exception as e: |
| raise ImportError( |
| "openenv is required. Install with: uv sync" |
| ) from e |
|
|
| from models import HROnboardingAction, HROnboardingObservation |
| from .hr_onboarding_environment import HROnboardingEnvironment |
| from .tools import TOOL_DEFINITIONS |
| from .rubrics import RubricEvaluator |
|
|
| from fastapi import Request |
| from fastapi.responses import HTMLResponse, RedirectResponse |
| from fastapi.staticfiles import StaticFiles |
| from pathlib import Path |
| import os |
| import json |
|
|
|
|
| |
| os.environ.setdefault("ENABLE_WEB_INTERFACE", "true") |
|
|
|
|
| |
| app = create_app( |
| HROnboardingEnvironment, |
| HROnboardingAction, |
| HROnboardingObservation, |
| env_name="hr_onboarding_env", |
| max_concurrent_envs=4, |
| ) |
|
|
| |
| STATIC_DIR = Path(__file__).parent / "static" |
| app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") |
|
|
| |
| _playground_env = HROnboardingEnvironment(seed=42, max_steps=15) |
|
|
|
|
| |
|
|
| @app.get("/", include_in_schema=False) |
| def root_redirect(): |
| """Serve the interactive playground UI.""" |
| return RedirectResponse(url="/playground") |
|
|
|
|
| @app.get("/playground", include_in_schema=False) |
| def playground(): |
| """Serve the interactive playground HTML.""" |
| html_path = STATIC_DIR / "index.html" |
| return HTMLResponse(html_path.read_text()) |
|
|
|
|
| @app.get("/api/tasks") |
| def get_tasks(): |
| """Return all tasks with metadata for the task picker.""" |
| env = _playground_env |
| tasks = [] |
| |
| orig_idx = env._task_idx |
|
|
| for i in range(len(env._tasks)): |
| task = env._tasks[i] |
| tasks.append({ |
| "index": i, |
| "task_id": task.task_id, |
| "instruction": task.instruction, |
| "difficulty": task.difficulty, |
| "category": task.category, |
| "expected_tools": task.expected_tools, |
| "rubric_criteria": task.rubric_criteria, |
| "num_criteria": len(task.rubric_criteria), |
| }) |
|
|
| env._task_idx = orig_idx |
| return tasks |
|
|
|
|
| @app.get("/api/tool_definitions") |
| def get_tool_definitions(): |
| """Return all tool definitions with descriptions and parameters.""" |
| return TOOL_DEFINITIONS |
|
|
|
|
| @app.post("/api/reset") |
| async def playground_reset(request: Request): |
| """Reset the environment to a specific task.""" |
| body = await request.json() |
| task_idx = body.get("task_idx", 0) |
|
|
| env = _playground_env |
| |
| env._task_idx = task_idx |
| obs = env.reset() |
|
|
| return { |
| "task_id": obs.task_id, |
| "instruction": obs.instruction, |
| "step": obs.step, |
| "max_steps": obs.max_steps, |
| "available_tools": obs.available_tools, |
| "done": obs.done, |
| "reward": obs.reward, |
| "metadata": obs.metadata, |
| } |
|
|
|
|
| @app.post("/api/step") |
| async def playground_step(request: Request): |
| """Execute one tool call step.""" |
| body = await request.json() |
| tool_name = body.get("tool_name", "") |
| arguments = body.get("arguments", {}) |
|
|
| env = _playground_env |
| action = HROnboardingAction(tool_name=tool_name, arguments=arguments) |
| obs = env.step(action) |
|
|
| return { |
| "task_id": obs.task_id, |
| "instruction": obs.instruction, |
| "tool_name": obs.tool_name, |
| "tool_result": obs.tool_result, |
| "step": obs.step, |
| "max_steps": obs.max_steps, |
| "done": obs.done, |
| "reward": obs.reward, |
| "metadata": obs.metadata, |
| } |
|
|
|
|
| @app.post("/api/evaluate") |
| async def playground_evaluate(): |
| """Force evaluation of current episode.""" |
| env = _playground_env |
| evaluator = RubricEvaluator() |
|
|
| if env._current_task: |
| result = evaluator.evaluate(env._current_task, env.world.action_log) |
| return result |
|
|
| return {"score": 0, "passed": False, "criteria_results": [], "passed_count": 0, "total_criteria": 0} |
|
|
|
|
| def main(): |
| """Entry point for direct execution.""" |
| import argparse |
| import uvicorn |
|
|
| parser = argparse.ArgumentParser() |
| parser.add_argument("--host", type=str, default="0.0.0.0") |
| parser.add_argument("--port", type=int, default=7860) |
| args = parser.parse_args() |
|
|
| uvicorn.run(app, host=args.host, port=args.port) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|