dbatcode28 commited on
Commit
47428ef
·
1 Parent(s): bd67155
.codex ADDED
File without changes
requirements.txt CHANGED
@@ -1,3 +1,4 @@
 
1
  pydantic>=2.7,<3
2
  openai>=1.30.0
3
  gradio>=4.44.0
 
1
+ openenv-core[core]>=0.2.2
2
  pydantic>=2.7,<3
3
  openai>=1.30.0
4
  gradio>=4.44.0
server/__pycache__/app.cpython-313.pyc CHANGED
Binary files a/server/__pycache__/app.cpython-313.pyc and b/server/__pycache__/app.cpython-313.pyc differ
 
server/app.py CHANGED
@@ -1,27 +1,236 @@
1
  from __future__ import annotations
2
 
3
- from fastapi import FastAPI
 
 
 
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  app = FastAPI(
7
  title="SupportOpsEnv Server",
8
- description="Minimal server entry point for OpenEnv validation and deployment hooks.",
9
  version="0.1.0",
10
  )
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  @app.get("/")
14
  def root() -> dict[str, str]:
15
  return {
16
  "name": "support-ops-env",
17
  "status": "ok",
18
- "message": "SupportOpsEnv server entry point is available.",
19
  }
20
 
21
 
22
- @app.get("/health")
23
- def health() -> dict[str, str]:
24
- return {"status": "healthy"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
 
27
  def main(host: str = "0.0.0.0", port: int = 8000) -> None:
@@ -29,8 +238,10 @@ def main(host: str = "0.0.0.0", port: int = 8000) -> None:
29
 
30
  uvicorn.run(app, host=host, port=port)
31
 
32
- def uv_main():
33
- return app
 
 
34
 
35
  if __name__ == "__main__":
36
  main()
 
1
  from __future__ import annotations
2
 
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any, Dict, Optional
6
+ from uuid import uuid4
7
 
8
+ from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
9
+ from pydantic import BaseModel, ConfigDict, Field, ValidationError
10
+
11
+ from support_ops_env.env import SupportOpsEnv
12
+ from support_ops_env.models import Action, Observation, StateModel
13
+
14
+
15
+ class ResetRequest(BaseModel):
16
+ model_config = ConfigDict(extra="allow")
17
+
18
+ seed: Optional[int] = Field(default=None, ge=0)
19
+ episode_id: Optional[str] = None
20
+ task_id: Optional[str] = None
21
+
22
+
23
+ class StepRequest(BaseModel):
24
+ model_config = ConfigDict(extra="allow")
25
+
26
+ action: Dict[str, Any]
27
+ timeout_s: Optional[float] = Field(default=None, gt=0)
28
+ request_id: Optional[str] = None
29
+
30
+
31
+ class StepResponse(BaseModel):
32
+ observation: Dict[str, Any]
33
+ reward: Optional[float] = None
34
+ done: bool = False
35
+
36
+
37
+ class HealthResponse(BaseModel):
38
+ status: str = "healthy"
39
+
40
+
41
+ class SchemaResponse(BaseModel):
42
+ action: Dict[str, Any]
43
+ observation: Dict[str, Any]
44
+ state: Dict[str, Any]
45
+
46
+
47
+ class EnvironmentMetadata(BaseModel):
48
+ name: str
49
+ description: str
50
+ readme_content: Optional[str] = None
51
+ version: str
52
+ documentation_url: Optional[str] = None
53
+
54
+
55
+ README_PATH = Path(__file__).resolve().parent.parent / "README.md"
56
 
57
  app = FastAPI(
58
  title="SupportOpsEnv Server",
59
+ description="OpenEnv-compatible server for the SupportOpsEnv benchmark.",
60
  version="0.1.0",
61
  )
62
 
63
+ _http_env = SupportOpsEnv()
64
+ _http_episode_id = str(uuid4())
65
+
66
+
67
+ def _serialize_observation(
68
+ observation: Observation,
69
+ reward: Optional[float] = None,
70
+ done: bool = False,
71
+ ) -> Dict[str, Any]:
72
+ return {
73
+ "observation": observation.model_dump(),
74
+ "reward": reward,
75
+ "done": done,
76
+ }
77
+
78
+
79
+ def _state_payload(env: SupportOpsEnv, episode_id: str) -> Dict[str, Any]:
80
+ state = env.state().model_dump()
81
+ state["episode_id"] = episode_id
82
+ return state
83
+
84
+
85
+ def _metadata() -> EnvironmentMetadata:
86
+ return EnvironmentMetadata(
87
+ name="support-ops-env",
88
+ description="Multi-step customer support triage and escalation benchmark for OpenEnv-style agents.",
89
+ readme_content=README_PATH.read_text(encoding="utf-8") if README_PATH.exists() else None,
90
+ version="0.1.0",
91
+ documentation_url="https://huggingface.co/spaces",
92
+ )
93
+
94
 
95
  @app.get("/")
96
  def root() -> dict[str, str]:
97
  return {
98
  "name": "support-ops-env",
99
  "status": "ok",
100
+ "message": "SupportOpsEnv OpenEnv server is available.",
101
  }
102
 
103
 
104
+ @app.get("/health", response_model=HealthResponse)
105
+ def health() -> HealthResponse:
106
+ return HealthResponse()
107
+
108
+
109
+ @app.get("/metadata", response_model=EnvironmentMetadata)
110
+ def metadata() -> EnvironmentMetadata:
111
+ return _metadata()
112
+
113
+
114
+ @app.get("/schema", response_model=SchemaResponse)
115
+ def schema() -> SchemaResponse:
116
+ return SchemaResponse(
117
+ action=Action.model_json_schema(),
118
+ observation=Observation.model_json_schema(),
119
+ state=StateModel.model_json_schema(),
120
+ )
121
+
122
+
123
+ @app.get("/state")
124
+ def state() -> Dict[str, Any]:
125
+ return _state_payload(_http_env, _http_episode_id)
126
+
127
+
128
+ @app.post("/reset", response_model=StepResponse)
129
+ def reset(request: ResetRequest = ResetRequest()) -> StepResponse:
130
+ global _http_episode_id
131
+
132
+ _http_episode_id = request.episode_id or str(uuid4())
133
+ observation = _http_env.reset(task_id=request.task_id)
134
+ return StepResponse(**_serialize_observation(observation))
135
+
136
+
137
+ @app.post("/step", response_model=StepResponse)
138
+ def step(request: StepRequest) -> StepResponse:
139
+ try:
140
+ action = Action.model_validate(request.action)
141
+ except ValidationError as exc:
142
+ raise HTTPException(status_code=422, detail=exc.errors()) from exc
143
+
144
+ observation, reward, done, _info = _http_env.step(action)
145
+ return StepResponse(
146
+ **_serialize_observation(observation, reward=reward.value, done=done)
147
+ )
148
+
149
+
150
+ @app.websocket("/ws")
151
+ async def websocket_endpoint(websocket: WebSocket) -> None:
152
+ await websocket.accept()
153
+
154
+ env = SupportOpsEnv()
155
+ episode_id = str(uuid4())
156
+
157
+ try:
158
+ while True:
159
+ raw_message = await websocket.receive_text()
160
+
161
+ try:
162
+ payload = json.loads(raw_message)
163
+ except json.JSONDecodeError as exc:
164
+ await websocket.send_json(
165
+ {
166
+ "type": "error",
167
+ "data": {"message": f"Invalid JSON: {exc}", "code": "invalid_json"},
168
+ }
169
+ )
170
+ continue
171
+
172
+ msg_type = payload.get("type")
173
+ data = payload.get("data", {})
174
+
175
+ try:
176
+ if msg_type == "reset":
177
+ reset_request = ResetRequest.model_validate(data)
178
+ episode_id = reset_request.episode_id or str(uuid4())
179
+ observation = env.reset(task_id=reset_request.task_id)
180
+ await websocket.send_json(
181
+ {"type": "observation", "data": _serialize_observation(observation)}
182
+ )
183
+ elif msg_type == "step":
184
+ action = Action.model_validate(data)
185
+ observation, reward, done, _info = env.step(action)
186
+ await websocket.send_json(
187
+ {
188
+ "type": "observation",
189
+ "data": _serialize_observation(
190
+ observation,
191
+ reward=reward.value,
192
+ done=done,
193
+ ),
194
+ }
195
+ )
196
+ elif msg_type == "state":
197
+ await websocket.send_json(
198
+ {"type": "state", "data": _state_payload(env, episode_id)}
199
+ )
200
+ elif msg_type == "close":
201
+ break
202
+ else:
203
+ await websocket.send_json(
204
+ {
205
+ "type": "error",
206
+ "data": {
207
+ "message": f"Unknown message type: {msg_type}",
208
+ "code": "unknown_type",
209
+ },
210
+ }
211
+ )
212
+ except ValidationError as exc:
213
+ await websocket.send_json(
214
+ {
215
+ "type": "error",
216
+ "data": {
217
+ "message": "Validation error",
218
+ "code": "validation_error",
219
+ "errors": exc.errors(),
220
+ },
221
+ }
222
+ )
223
+ except Exception as exc: # pragma: no cover
224
+ await websocket.send_json(
225
+ {
226
+ "type": "error",
227
+ "data": {"message": str(exc), "code": "execution_error"},
228
+ }
229
+ )
230
+ except WebSocketDisconnect:
231
+ pass
232
+ finally:
233
+ await websocket.close()
234
 
235
 
236
  def main(host: str = "0.0.0.0", port: int = 8000) -> None:
 
238
 
239
  uvicorn.run(app, host=host, port=port)
240
 
241
+
242
+ def uv_main() -> FastAPI:
243
+ return app
244
+
245
 
246
  if __name__ == "__main__":
247
  main()
tests/__pycache__/test_server_api.cpython-313.pyc ADDED
Binary file (2.97 kB). View file
 
tests/test_server_api.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import unittest
4
+
5
+ from server.app import (
6
+ StepRequest,
7
+ app,
8
+ health,
9
+ metadata,
10
+ reset,
11
+ schema,
12
+ state,
13
+ step,
14
+ )
15
+
16
+
17
+ class ServerApiTest(unittest.TestCase):
18
+ def test_required_routes_are_registered(self) -> None:
19
+ route_paths = {route.path for route in app.routes}
20
+ self.assertIn("/health", route_paths)
21
+ self.assertIn("/metadata", route_paths)
22
+ self.assertIn("/schema", route_paths)
23
+ self.assertIn("/reset", route_paths)
24
+ self.assertIn("/step", route_paths)
25
+ self.assertIn("/state", route_paths)
26
+ self.assertIn("/ws", route_paths)
27
+
28
+ def test_handlers_return_openenv_shaped_payloads(self) -> None:
29
+ self.assertEqual(health().status, "healthy")
30
+ self.assertEqual(metadata().name, "support-ops-env")
31
+ self.assertIn("action_type", schema().action["properties"])
32
+
33
+ reset_response = reset()
34
+ self.assertEqual(reset_response.observation["task_id"], "easy_account_takeover")
35
+ self.assertFalse(reset_response.done)
36
+
37
+ state_response = state()
38
+ self.assertEqual(state_response["task_id"], "easy_account_takeover")
39
+ self.assertIn("episode_id", state_response)
40
+
41
+ step_response = step(StepRequest(action={"action_type": "inspect_ticket", "target": "T1"}))
42
+ self.assertIn("observation", step_response.model_dump())
43
+ self.assertIsInstance(step_response.reward, float)
44
+
45
+
46
+ if __name__ == "__main__":
47
+ unittest.main()