Files changed (1) hide show
  1. server/app.py +85 -34
server/app.py CHANGED
@@ -5,75 +5,126 @@
5
  # LICENSE file in the root directory of this source tree.
6
 
7
  """
8
- FastAPI application for the First Rl Proj Environment.
9
-
10
- This module creates an HTTP server that exposes the FirstRlProjEnvironment
11
- over HTTP and WebSocket endpoints, compatible with EnvClient.
12
 
13
  Endpoints:
14
  - POST /reset: Reset the environment
15
  - POST /step: Execute an action
16
  - GET /state: Get current environment state
17
  - GET /schema: Get action/observation schemas
 
 
18
  - WS /ws: WebSocket endpoint for persistent sessions
19
-
20
- Usage:
21
- # Development (with auto-reload):
22
- uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
23
-
24
- # Production:
25
- uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
26
-
27
- # Or run directly:
28
- python -m server.app
29
  """
30
 
31
  try:
32
  from openenv.core.env_server.http_server import create_app
33
  except Exception as e: # pragma: no cover
34
  raise ImportError(
35
- "openenv is required for the web interface. Install dependencies with '\n uv sync\n'"
36
  ) from e
37
 
38
  try:
39
  from ..models import CodeAssessmentAction, CodeAssessmentObservation
40
- from .code_assessment_environment import CodeAssessmentEnvironment
41
  except (ImportError, ModuleNotFoundError):
42
  from models import CodeAssessmentAction, CodeAssessmentObservation
43
- from server.code_assessment_environment import CodeAssessmentEnvironment
44
 
45
 
46
- # Create the app with web interface and README integration
47
  app = create_app(
48
  CodeAssessmentEnvironment,
49
  CodeAssessmentAction,
50
  CodeAssessmentObservation,
51
  env_name="code_assessment_env",
52
- max_concurrent_envs=1, # increase this number to allow more concurrent WebSocket sessions
53
  )
54
 
55
 
56
- def main(host: str = "0.0.0.0", port: int = 8000):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  """
58
- Entry point for direct execution via uv run or python -m.
59
-
60
- This function enables running the server without Docker:
61
- uv run --project . server
62
- uv run --project . server --port 8001
63
- python -m first_rl_proj.server.app
64
 
65
- Args:
66
- host: Host address to bind to (default: "0.0.0.0")
67
- port: Port number to listen on (default: 8000)
 
 
 
68
 
69
- For production deployments, consider using uvicorn directly with
70
- multiple workers:
71
- uvicorn first_rl_proj.server.app:app --workers 4
72
  """
73
- import uvicorn
 
 
 
 
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  uvicorn.run(app, host=host, port=port)
76
 
77
 
78
  if __name__ == "__main__":
79
- main()
 
5
  # LICENSE file in the root directory of this source tree.
6
 
7
  """
8
+ FastAPI application for the AI Response Evaluation Environment.
 
 
 
9
 
10
  Endpoints:
11
  - POST /reset: Reset the environment
12
  - POST /step: Execute an action
13
  - GET /state: Get current environment state
14
  - GET /schema: Get action/observation schemas
15
+ - GET /tasks: Enumerate all tasks and their graders
16
+ - POST /grader: Score a single task submission
17
  - WS /ws: WebSocket endpoint for persistent sessions
 
 
 
 
 
 
 
 
 
 
18
  """
19
 
20
  try:
21
  from openenv.core.env_server.http_server import create_app
22
  except Exception as e: # pragma: no cover
23
  raise ImportError(
24
+ "openenv is required for the web interface. Install with 'uv sync'"
25
  ) from e
26
 
27
  try:
28
  from ..models import CodeAssessmentAction, CodeAssessmentObservation
29
+ from .code_assessment_environment import CodeAssessmentEnvironment, TASK_TYPES, TASK_INSTRUCTIONS, PROBLEMS
30
  except (ImportError, ModuleNotFoundError):
31
  from models import CodeAssessmentAction, CodeAssessmentObservation
32
+ from server.code_assessment_environment import CodeAssessmentEnvironment, TASK_TYPES, TASK_INSTRUCTIONS, PROBLEMS
33
 
34
 
35
+ # Create the base app with OpenEnv endpoints
36
  app = create_app(
37
  CodeAssessmentEnvironment,
38
  CodeAssessmentAction,
39
  CodeAssessmentObservation,
40
  env_name="code_assessment_env",
41
+ max_concurrent_envs=1,
42
  )
43
 
44
 
45
+ # ─── Task enumeration endpoint ──────────────────────────────────────────────
46
+ @app.get("/tasks")
47
+ async def list_tasks():
48
+ """Enumerate all tasks with their grader info and action schema."""
49
+ tasks = []
50
+ for difficulty, task_type in TASK_TYPES.items():
51
+ tasks.append({
52
+ "task_id": task_type,
53
+ "name": task_type,
54
+ "difficulty": difficulty,
55
+ "description": TASK_INSTRUCTIONS[task_type],
56
+ "num_problems": len(PROBLEMS[difficulty]),
57
+ "grader": {
58
+ "type": "programmatic",
59
+ "score_range": {"min": 0.01, "max": 0.99},
60
+ },
61
+ "action_schema": {
62
+ "type": "object",
63
+ "properties": {
64
+ "answer": {"type": "string"}
65
+ },
66
+ "required": ["answer"],
67
+ },
68
+ })
69
+ return {"tasks": tasks, "total": len(tasks)}
70
+
71
+
72
+ # ─── Per-task grader endpoint ───────────────────────────────────────────────
73
+ @app.post("/grader")
74
+ async def grade_task(payload: dict):
75
  """
76
+ Score a single answer for a specific task.
 
 
 
 
 
77
 
78
+ Request body:
79
+ {
80
+ "task_id": "correctness_check", # or tone_appropriateness, multi_dimensional
81
+ "answer": "incorrect, factual-error",
82
+ "problem_index": 0 # optional, random if omitted
83
+ }
84
 
85
+ Returns:
86
+ {"task_id": ..., "score": 0.xx, "is_correct": bool, "feedback": "..."}
 
87
  """
88
+ import random as _random
89
+
90
+ task_id = payload.get("task_id", "correctness_check")
91
+ answer = payload.get("answer", "")
92
+ problem_index = payload.get("problem_index")
93
 
94
+ # Map task_id to difficulty
95
+ difficulty = None
96
+ for diff, tt in TASK_TYPES.items():
97
+ if tt == task_id:
98
+ difficulty = diff
99
+ break
100
+
101
+ if difficulty is None:
102
+ return {"error": f"Unknown task_id: {task_id}", "score": 0.05}
103
+
104
+ problems = PROBLEMS[difficulty]
105
+ if problem_index is not None and 0 <= problem_index < len(problems):
106
+ problem = problems[problem_index]
107
+ else:
108
+ problem = _random.choice(problems)
109
+
110
+ env = CodeAssessmentEnvironment()
111
+ env._difficulty = difficulty
112
+ is_correct, score, feedback = env._grade(task_id, answer, problem)
113
+
114
+ return {
115
+ "task_id": task_id,
116
+ "difficulty": difficulty,
117
+ "score": score,
118
+ "is_correct": is_correct,
119
+ "feedback": feedback,
120
+ }
121
+
122
+
123
+ def main(host: str = "0.0.0.0", port: int = 8000):
124
+ """Entry point for direct execution."""
125
+ import uvicorn
126
  uvicorn.run(app, host=host, port=port)
127
 
128
 
129
  if __name__ == "__main__":
130
+ main()