agentscope-runtime 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. agentscope_runtime/__init__.py +4 -0
  2. agentscope_runtime/engine/__init__.py +9 -0
  3. agentscope_runtime/engine/agents/__init__.py +2 -0
  4. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +6 -0
  5. agentscope_runtime/engine/agents/agentscope_agent/agent.py +342 -0
  6. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +156 -0
  7. agentscope_runtime/engine/agents/agno_agent.py +220 -0
  8. agentscope_runtime/engine/agents/base_agent.py +29 -0
  9. agentscope_runtime/engine/agents/langgraph_agent.py +59 -0
  10. agentscope_runtime/engine/agents/llm_agent.py +51 -0
  11. agentscope_runtime/engine/deployers/__init__.py +3 -0
  12. agentscope_runtime/engine/deployers/adapter/__init__.py +0 -0
  13. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +2 -0
  14. agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +425 -0
  15. agentscope_runtime/engine/deployers/adapter/a2a/a2a_agent_adapter.py +69 -0
  16. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +60 -0
  17. agentscope_runtime/engine/deployers/adapter/protocol_adapter.py +24 -0
  18. agentscope_runtime/engine/deployers/base.py +17 -0
  19. agentscope_runtime/engine/deployers/local_deployer.py +586 -0
  20. agentscope_runtime/engine/helpers/helper.py +127 -0
  21. agentscope_runtime/engine/llms/__init__.py +3 -0
  22. agentscope_runtime/engine/llms/base_llm.py +60 -0
  23. agentscope_runtime/engine/llms/qwen_llm.py +47 -0
  24. agentscope_runtime/engine/misc/__init__.py +0 -0
  25. agentscope_runtime/engine/runner.py +186 -0
  26. agentscope_runtime/engine/schemas/__init__.py +0 -0
  27. agentscope_runtime/engine/schemas/agent_schemas.py +551 -0
  28. agentscope_runtime/engine/schemas/context.py +54 -0
  29. agentscope_runtime/engine/services/__init__.py +9 -0
  30. agentscope_runtime/engine/services/base.py +77 -0
  31. agentscope_runtime/engine/services/context_manager.py +129 -0
  32. agentscope_runtime/engine/services/environment_manager.py +50 -0
  33. agentscope_runtime/engine/services/manager.py +174 -0
  34. agentscope_runtime/engine/services/memory_service.py +270 -0
  35. agentscope_runtime/engine/services/sandbox_service.py +198 -0
  36. agentscope_runtime/engine/services/session_history_service.py +256 -0
  37. agentscope_runtime/engine/tracing/__init__.py +40 -0
  38. agentscope_runtime/engine/tracing/base.py +309 -0
  39. agentscope_runtime/engine/tracing/local_logging_handler.py +356 -0
  40. agentscope_runtime/engine/tracing/tracing_metric.py +69 -0
  41. agentscope_runtime/engine/tracing/wrapper.py +321 -0
  42. agentscope_runtime/sandbox/__init__.py +14 -0
  43. agentscope_runtime/sandbox/box/__init__.py +0 -0
  44. agentscope_runtime/sandbox/box/base/__init__.py +0 -0
  45. agentscope_runtime/sandbox/box/base/base_sandbox.py +37 -0
  46. agentscope_runtime/sandbox/box/base/box/__init__.py +0 -0
  47. agentscope_runtime/sandbox/box/browser/__init__.py +0 -0
  48. agentscope_runtime/sandbox/box/browser/box/__init__.py +0 -0
  49. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +176 -0
  50. agentscope_runtime/sandbox/box/dummy/__init__.py +0 -0
  51. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +26 -0
  52. agentscope_runtime/sandbox/box/filesystem/__init__.py +0 -0
  53. agentscope_runtime/sandbox/box/filesystem/box/__init__.py +0 -0
  54. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +87 -0
  55. agentscope_runtime/sandbox/box/sandbox.py +115 -0
  56. agentscope_runtime/sandbox/box/shared/__init__.py +0 -0
  57. agentscope_runtime/sandbox/box/shared/app.py +44 -0
  58. agentscope_runtime/sandbox/box/shared/dependencies/__init__.py +5 -0
  59. agentscope_runtime/sandbox/box/shared/dependencies/deps.py +22 -0
  60. agentscope_runtime/sandbox/box/shared/routers/__init__.py +12 -0
  61. agentscope_runtime/sandbox/box/shared/routers/generic.py +173 -0
  62. agentscope_runtime/sandbox/box/shared/routers/mcp.py +207 -0
  63. agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +153 -0
  64. agentscope_runtime/sandbox/box/shared/routers/runtime_watcher.py +187 -0
  65. agentscope_runtime/sandbox/box/shared/routers/workspace.py +325 -0
  66. agentscope_runtime/sandbox/box/training_box/__init__.py +0 -0
  67. agentscope_runtime/sandbox/box/training_box/base.py +120 -0
  68. agentscope_runtime/sandbox/box/training_box/env_service.py +752 -0
  69. agentscope_runtime/sandbox/box/training_box/environments/__init__.py +0 -0
  70. agentscope_runtime/sandbox/box/training_box/environments/appworld/appworld_env.py +987 -0
  71. agentscope_runtime/sandbox/box/training_box/registry.py +54 -0
  72. agentscope_runtime/sandbox/box/training_box/src/trajectory.py +278 -0
  73. agentscope_runtime/sandbox/box/training_box/training_box.py +219 -0
  74. agentscope_runtime/sandbox/build.py +213 -0
  75. agentscope_runtime/sandbox/client/__init__.py +5 -0
  76. agentscope_runtime/sandbox/client/http_client.py +527 -0
  77. agentscope_runtime/sandbox/client/training_client.py +265 -0
  78. agentscope_runtime/sandbox/constant.py +5 -0
  79. agentscope_runtime/sandbox/custom/__init__.py +16 -0
  80. agentscope_runtime/sandbox/custom/custom_sandbox.py +40 -0
  81. agentscope_runtime/sandbox/custom/example.py +37 -0
  82. agentscope_runtime/sandbox/enums.py +68 -0
  83. agentscope_runtime/sandbox/manager/__init__.py +4 -0
  84. agentscope_runtime/sandbox/manager/collections/__init__.py +22 -0
  85. agentscope_runtime/sandbox/manager/collections/base_mapping.py +20 -0
  86. agentscope_runtime/sandbox/manager/collections/base_queue.py +25 -0
  87. agentscope_runtime/sandbox/manager/collections/base_set.py +25 -0
  88. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +22 -0
  89. agentscope_runtime/sandbox/manager/collections/in_memory_queue.py +28 -0
  90. agentscope_runtime/sandbox/manager/collections/in_memory_set.py +27 -0
  91. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +26 -0
  92. agentscope_runtime/sandbox/manager/collections/redis_queue.py +27 -0
  93. agentscope_runtime/sandbox/manager/collections/redis_set.py +23 -0
  94. agentscope_runtime/sandbox/manager/container_clients/__init__.py +8 -0
  95. agentscope_runtime/sandbox/manager/container_clients/base_client.py +39 -0
  96. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +170 -0
  97. agentscope_runtime/sandbox/manager/sandbox_manager.py +694 -0
  98. agentscope_runtime/sandbox/manager/server/__init__.py +0 -0
  99. agentscope_runtime/sandbox/manager/server/app.py +194 -0
  100. agentscope_runtime/sandbox/manager/server/config.py +68 -0
  101. agentscope_runtime/sandbox/manager/server/models.py +17 -0
  102. agentscope_runtime/sandbox/manager/storage/__init__.py +10 -0
  103. agentscope_runtime/sandbox/manager/storage/data_storage.py +16 -0
  104. agentscope_runtime/sandbox/manager/storage/local_storage.py +44 -0
  105. agentscope_runtime/sandbox/manager/storage/oss_storage.py +89 -0
  106. agentscope_runtime/sandbox/manager/utils.py +78 -0
  107. agentscope_runtime/sandbox/mcp_server.py +192 -0
  108. agentscope_runtime/sandbox/model/__init__.py +12 -0
  109. agentscope_runtime/sandbox/model/api.py +16 -0
  110. agentscope_runtime/sandbox/model/container.py +72 -0
  111. agentscope_runtime/sandbox/model/manager_config.py +158 -0
  112. agentscope_runtime/sandbox/registry.py +129 -0
  113. agentscope_runtime/sandbox/tools/__init__.py +12 -0
  114. agentscope_runtime/sandbox/tools/base/__init__.py +8 -0
  115. agentscope_runtime/sandbox/tools/base/tool.py +52 -0
  116. agentscope_runtime/sandbox/tools/browser/__init__.py +57 -0
  117. agentscope_runtime/sandbox/tools/browser/tool.py +597 -0
  118. agentscope_runtime/sandbox/tools/filesystem/__init__.py +32 -0
  119. agentscope_runtime/sandbox/tools/filesystem/tool.py +319 -0
  120. agentscope_runtime/sandbox/tools/function_tool.py +321 -0
  121. agentscope_runtime/sandbox/tools/mcp_tool.py +191 -0
  122. agentscope_runtime/sandbox/tools/sandbox_tool.py +104 -0
  123. agentscope_runtime/sandbox/tools/tool.py +123 -0
  124. agentscope_runtime/sandbox/tools/utils.py +68 -0
  125. agentscope_runtime/version.py +2 -0
  126. agentscope_runtime-0.1.0.dist-info/METADATA +327 -0
  127. agentscope_runtime-0.1.0.dist-info/RECORD +131 -0
  128. agentscope_runtime-0.1.0.dist-info/WHEEL +5 -0
  129. agentscope_runtime-0.1.0.dist-info/entry_points.txt +4 -0
  130. agentscope_runtime-0.1.0.dist-info/licenses/LICENSE +202 -0
  131. agentscope_runtime-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,752 @@
1
+ # -*- coding: utf-8 -*-
2
+ # pylint: disable=unused-argument,redefined-outer-name
3
+ # pylint: disable=broad-except,import-outside-toplevel
4
+ """
5
+ Module for the EnvService class and related functionality.
6
+
7
+ This module provides an environment service that manages the creation,
8
+ execution, and release of training environment instances. It handles
9
+ the lifecycle of environment instances and exposes an API for
10
+ interacting with them.
11
+ """
12
+ import argparse
13
+ import asyncio
14
+ import importlib
15
+ import os
16
+ import sys
17
+ import time
18
+ import uuid
19
+ from contextlib import asynccontextmanager
20
+ from datetime import datetime, timedelta
21
+ from pathlib import Path
22
+
23
+ from typing import Any, Dict, List, Optional
24
+ import ray
25
+ import uvicorn
26
+ from fastapi import FastAPI, HTTPException, Response
27
+ from pydantic import BaseModel
28
+
29
+
30
+ from .registry import Registry
31
+
32
+
33
+ BASE_DIR = Path(__file__).resolve().parent.parent
34
+
35
+
36
+ def ensure_env(name: str, rel_path: str) -> None:
37
+ """
38
+ ensure env class name
39
+ """
40
+ os.environ[name] = str(BASE_DIR / rel_path)
41
+
42
+
43
+ SERVER_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
44
+
45
+ ROOT_DIR = os.path.dirname(SERVER_DIR)
46
+
47
+ if SERVER_DIR not in sys.path:
48
+ sys.path.insert(0, SERVER_DIR)
49
+
50
+ if ROOT_DIR not in sys.path:
51
+ sys.path.insert(0, ROOT_DIR)
52
+
53
+ PROJECT_ROOT = os.path.abspath(
54
+ os.path.join(os.path.dirname(__file__), "..", "..", ".."),
55
+ )
56
+
57
+ if PROJECT_ROOT not in sys.path:
58
+ sys.path.insert(0, PROJECT_ROOT)
59
+
60
+
61
+ def import_and_register_env(env_name, env_file=None):
62
+ """
63
+ Import and register an environment class.
64
+
65
+ Args:
66
+ env_name (str): Name of the environment.
67
+ env_file (str, optional): Detailed environment module name.
68
+ Defaults to f"{env_name}_env".
69
+
70
+ Returns:
71
+ The registered environment class or None on failure.
72
+ """
73
+ try:
74
+ if env_file is None:
75
+ env_file = f"{env_name}_env"
76
+
77
+ env_module_path = os.path.join(
78
+ SERVER_DIR,
79
+ "training_box/environments",
80
+ env_name,
81
+ f"{env_file}.py",
82
+ )
83
+
84
+ if not os.path.exists(env_module_path):
85
+ raise FileNotFoundError(
86
+ f"Environment file not found: {env_module_path}",
87
+ )
88
+
89
+ spec = importlib.util.spec_from_file_location(
90
+ f"{env_name}_env",
91
+ env_module_path,
92
+ )
93
+ module = importlib.util.module_from_spec(spec)
94
+ sys.modules[spec.name] = module
95
+ spec.loader.exec_module(module)
96
+
97
+ envir_class = getattr(module, f"{env_name.capitalize()}Env")
98
+
99
+ Registry.register(env_name)(envir_class)
100
+
101
+ print(f"Successfully imported and registered {envir_class.__name__}")
102
+ return envir_class
103
+ except Exception as e:
104
+ print(f"Error importing environment {env_name}: {e}")
105
+ import traceback
106
+
107
+ traceback.print_exc()
108
+ return None
109
+
110
+
111
+ class ServiceRequest(BaseModel):
112
+ """
113
+ Service request class .
114
+ """
115
+
116
+ env_type: Optional[str] = "appworld"
117
+ task_id: Optional[str] = None
118
+ instance_id: Optional[str] = None
119
+ messages: Dict[str, Any] = {}
120
+ params: Dict[str, Any] = {}
121
+
122
+
123
+ class EnvService:
124
+ """
125
+ Manages the lifecycle of training environment instances.
126
+
127
+ This class is responsible for creating, executing, and releasing
128
+ environment instances. It provides methods to interact with the
129
+ environments and handle their lifecycle.
130
+ """
131
+
132
+ def __init__(self):
133
+ """
134
+ class init
135
+ """
136
+ python_path = os.environ.get("PYTHONPATH", "")
137
+ python_path = (
138
+ f"{python_path}:{BASE_DIR}:{SERVER_DIR}:{ROOT_DIR}:{PROJECT_ROOT}"
139
+ )
140
+
141
+ if not ray.is_initialized():
142
+ ray.init()
143
+ self.env_actors = {}
144
+ self.remote_env = {}
145
+ self.last_access_time = {}
146
+ self.cleanup_interval = 300
147
+ self.max_idle_time = 3600
148
+
149
+ async def cleanup_inactive_instances(self):
150
+ """
151
+ Periodically clean up inactive environment instances.
152
+
153
+ Releases instances that have been idle for longer than the
154
+ specified maximum idle time.
155
+ """
156
+
157
+ current_time = datetime.now()
158
+ instances_to_release = []
159
+ for instance_id, last_access in self.last_access_time.items():
160
+ if (current_time - last_access) > timedelta(
161
+ seconds=self.max_idle_time,
162
+ ):
163
+ instances_to_release.append(instance_id)
164
+
165
+ for instance_id in instances_to_release:
166
+ await self.release_instance(instance_id)
167
+ print(f"Released inactive instance: {instance_id}")
168
+
169
+ def update_access_time(self, instance_id):
170
+ """Update the last access time for an environment instance."""
171
+ self.last_access_time[instance_id] = datetime.now()
172
+
173
+ def get_remote_env_cls(self, env_type: str):
174
+ """
175
+ Get the remote environment class for the specified environment type.
176
+
177
+ Args:
178
+ env_type (str): The type of environment.
179
+
180
+ Returns:
181
+ The remote environment class.
182
+ """
183
+ if env_type in self.remote_env:
184
+ return self.remote_env[env_type]
185
+
186
+ @ray.remote
187
+ class RemoteEnv:
188
+ """
189
+ Remote environment class.
190
+ """
191
+
192
+ def __init__(self, task_id, instance_id, params):
193
+ """Detailed init"""
194
+
195
+ server_dir = os.path.abspath(
196
+ os.path.join(os.path.dirname(__file__), "..", ".."),
197
+ )
198
+ if server_dir not in sys.path:
199
+ sys.path.insert(0, server_dir)
200
+
201
+ try:
202
+ module = importlib.import_module(
203
+ f"training_box.environments.{env_type}."
204
+ f"{env_type}_env",
205
+ )
206
+ envir_class = getattr(
207
+ module,
208
+ f"{env_type.capitalize()}Env",
209
+ )
210
+ self.env = envir_class(task_id, instance_id, params)
211
+ except ImportError as e:
212
+ print(f"Error importing {env_type}_env: {e}")
213
+ raise
214
+
215
+ def get_init_state(self, params):
216
+ """remote init state"""
217
+ return self.env.get_init_state(params)
218
+
219
+ def step(self, action, params):
220
+ """remote step"""
221
+ return self.env.step(action, params)
222
+
223
+ def evaluate(self, messages, params):
224
+ """remote eval"""
225
+ return self.env.evaluate(messages, params)
226
+
227
+ def get_info(self, messages, params):
228
+ """remote get info"""
229
+ return self.env.get_info(messages, params)
230
+
231
+ def close(self):
232
+ """remote close"""
233
+ return self.env.close()
234
+
235
+ self.remote_env[env_type] = RemoteEnv
236
+ return RemoteEnv
237
+
238
+ async def get_env_profile(
239
+ self,
240
+ env_type: str,
241
+ split: str = "train",
242
+ params: Dict = None,
243
+ ) -> List[str]:
244
+ """
245
+ Retrieve the environment profile for the specified environment type.
246
+
247
+ Args:
248
+ env_type (str): The type of environment.
249
+ split (str, optional): The data split to retrieve the profile for.
250
+ Defaults to "train".
251
+ params (Dict, optional): Additional parameters
252
+ for the profile retrieval.
253
+
254
+ Returns:
255
+ List[str]: The list of task IDs in the environment profile.
256
+ """
257
+
258
+ env_cls = Registry.get(env_type)
259
+ return env_cls.get_query_list(split)
260
+
261
+ async def get_info(
262
+ self,
263
+ instance_id: str,
264
+ messages: Dict = None,
265
+ params: Dict = None,
266
+ ) -> float:
267
+ """
268
+ Retrieve information about an environment instance.
269
+
270
+ Args:
271
+ instance_id (str): The ID of the environment instance.
272
+ messages (Dict, optional): Additional messages
273
+ for the information request.
274
+ params (Dict, optional): Additional parameters
275
+ for the information request.
276
+
277
+ Returns:
278
+ float: The requested environment information.
279
+ """
280
+ self.update_access_time(instance_id)
281
+ try:
282
+ if instance_id not in self.env_actors:
283
+ raise ValueError(f"Instance {instance_id} not found!")
284
+ return await self.env_actors[instance_id].get_info.remote(
285
+ messages,
286
+ params,
287
+ )
288
+ except Exception as e:
289
+ print(f"Error in get_info: {str(e)}")
290
+ raise
291
+
292
+ async def create_instance(
293
+ self,
294
+ env_type: str,
295
+ task_id: str,
296
+ instance_id: Optional[str] = None,
297
+ params: Dict = None,
298
+ ) -> str:
299
+ """
300
+ Create a new instance of the specified environment.
301
+
302
+ Args:
303
+ env_type (str): The type of environment to create.
304
+ task_id (str): The ID of the task associated
305
+ with the environment.
306
+ instance_id (Optional[str], optional):
307
+ The ID of the environment instance.
308
+ If not provided, a unique ID will be generated.
309
+ params (Dict, optional): Additional parameters
310
+ for creating the environment instance.
311
+
312
+ Returns:
313
+ str: The ID of the created environment instance.
314
+ """
315
+ try:
316
+ if instance_id is None:
317
+ instance_id = f"exp_{int(time.time())}_{uuid.uuid4().hex[:8]}"
318
+
319
+ env_remote_cls = self.get_remote_env_cls(env_type)
320
+
321
+ print(
322
+ f"Creating instance with env_type: {env_type}, "
323
+ f"task_id: {task_id}, "
324
+ f"instance_id: {instance_id}",
325
+ )
326
+
327
+ if env_type == "webshop":
328
+ params["server"] = SIM_SERVER
329
+ env_actor = env_remote_cls.remote(task_id, instance_id, params)
330
+ else:
331
+ env_actor = env_remote_cls.remote(task_id, instance_id, params)
332
+
333
+ self.env_actors[instance_id] = env_actor
334
+ init_state = await env_actor.get_init_state.remote(params)
335
+
336
+ self.update_access_time(instance_id)
337
+
338
+ return init_state
339
+
340
+ except Exception as e:
341
+ print(f"Error in create_instance: {str(e)}")
342
+ print(f"Current working directory: {os.getcwd()}")
343
+ print(f"PYTHONPATH: {os.environ.get('PYTHONPATH', '')}")
344
+ print(f"sys.path: {sys.path}")
345
+ import traceback
346
+
347
+ traceback.print_exc()
348
+ raise
349
+
350
+ async def step(
351
+ self,
352
+ instance_id: str,
353
+ action: Dict,
354
+ params: Dict = None,
355
+ ) -> str:
356
+ """
357
+ Execute a step in the specified environment instance.
358
+
359
+ Args:
360
+ instance_id (str):
361
+ The ID of the environment instance.
362
+ action (Dict):
363
+ The action to be executed in the environment.
364
+ params (Dict, optional):
365
+ Additional parameters for the step.
366
+
367
+ Returns:
368
+ str: The result of the step execution.
369
+ """
370
+ self.update_access_time(instance_id)
371
+ try:
372
+ if instance_id not in self.env_actors:
373
+ raise ValueError(f"Instance {instance_id} not found!")
374
+ return await self.env_actors[instance_id].step.remote(
375
+ action,
376
+ params,
377
+ )
378
+
379
+ except Exception as e:
380
+ print(f"Error in step: {str(e)}")
381
+ raise
382
+
383
+ async def evaluate(
384
+ self,
385
+ instance_id: str,
386
+ messages: Dict = None,
387
+ params: Dict = None,
388
+ ) -> float:
389
+ """
390
+ Evaluate the performance of the specified environment instance.
391
+
392
+ Args:
393
+ instance_id (str):
394
+ The ID of the environment instance.
395
+ messages (Dict, optional):
396
+ Additional messages for the evaluation.
397
+ params (Dict, optional):
398
+ Additional parameters for the evaluation.
399
+
400
+ Returns:
401
+ float: The evaluation score.
402
+ """
403
+ self.update_access_time(instance_id)
404
+
405
+ try:
406
+ if instance_id not in self.env_actors:
407
+ raise ValueError(f"Instance {instance_id} not found!")
408
+ return await self.env_actors[instance_id].evaluate.remote(
409
+ messages,
410
+ params,
411
+ )
412
+ except Exception as e:
413
+ print(f"Error in evaluate: {str(e)}")
414
+ raise
415
+
416
+ async def release_instance(self, instance_id: str) -> bool:
417
+ """
418
+ Release the specified environment instance.
419
+
420
+ Args:
421
+ instance_id (str):
422
+ The ID of the environment instance to be released.
423
+
424
+ Returns:
425
+ bool:
426
+ True if the instance was successfully released,
427
+ False otherwise.
428
+ """
429
+ if instance_id not in self.env_actors:
430
+ return False
431
+ env_actor = self.env_actors[instance_id]
432
+ await env_actor.close.remote()
433
+ ray.kill(self.env_actors[instance_id])
434
+ del self.env_actors[instance_id]
435
+ self.last_access_time.pop(instance_id, None)
436
+ return True
437
+
438
+
439
+ @asynccontextmanager
440
+ async def lifespan(app: FastAPI):
441
+ """
442
+ Manage the lifespan of the FastAPI application.
443
+
444
+ This context manager creates a background task
445
+ for cleaning up inactive environment instances
446
+ and cancels it during the shutdown process.
447
+ """
448
+ cleanup_task = asyncio.create_task(cleanup_loop())
449
+
450
+ yield
451
+
452
+ cleanup_task.cancel()
453
+ try:
454
+ await cleanup_task
455
+ except asyncio.CancelledError:
456
+ pass
457
+
458
+
459
+ async def cleanup_loop():
460
+ """
461
+ Periodically clean up inactive environment instances.
462
+
463
+ This coroutine is run as a background task
464
+ in the FastAPI application
465
+ lifespan context manager.
466
+ """
467
+ while True:
468
+ await asyncio.sleep(env_service.cleanup_interval)
469
+ await env_service.cleanup_inactive_instances()
470
+
471
+
472
+ app = FastAPI(lifespan=lifespan)
473
+ env_service = EnvService()
474
+ SIM_SERVER = None
475
+
476
+
477
+ @app.get(
478
+ "/healthz",
479
+ summary="Check the health of the API",
480
+ )
481
+ async def healthz():
482
+ """
483
+ Check the health of the API.
484
+
485
+ Returns:
486
+ Response: A successful response with status code 200.
487
+ """
488
+ return Response(content="OK", status_code=200)
489
+
490
+
491
+ @app.post("/get_env_profile")
492
+ async def handle_env_profile(request: ServiceRequest):
493
+ """
494
+ Retrieve the environment profile for the specified environment type.
495
+
496
+ Args:
497
+ request (ServiceRequest):
498
+ The service request containing the environment type
499
+ and optional parameters.
500
+
501
+ Returns:
502
+ dict: A dictionary containing the success status
503
+ and the list of task IDs.
504
+ """
505
+ try:
506
+ if request.env_type is None:
507
+ raise ValueError("env_type is required")
508
+
509
+ split = request.params.get("split", "train")
510
+
511
+ task_ids = await env_service.get_env_profile(
512
+ env_type=request.env_type,
513
+ split=split,
514
+ params=request.params,
515
+ )
516
+ return {"success": True, "data": task_ids}
517
+ except Exception as e:
518
+ import traceback
519
+
520
+ tb = "".join(traceback.format_exception(type(e), e, e.__traceback__))
521
+ raise HTTPException(status_code=500, detail=tb) from e
522
+
523
+
524
+ @app.post("/create")
525
+ async def handle_create(request: ServiceRequest):
526
+ """
527
+ Create a new environment instance based on the provided service request.
528
+
529
+ This endpoint is responsible for creating a new environment instance
530
+ with the specified environment type and task ID.
531
+
532
+ Args:
533
+ request (ServiceRequest):
534
+ The service request containing environment details.
535
+
536
+ Returns:
537
+ dict: A dictionary with the creation status
538
+ and initial state of the environment.
539
+
540
+ Raises:
541
+ HTTPException: If there's an error
542
+ in creating the instance (400 or 500 status codes).
543
+ """
544
+ try:
545
+ if not request.env_type:
546
+ raise ValueError("env_type is required")
547
+ if not request.task_id:
548
+ raise ValueError("task_id is required")
549
+
550
+ instance_id = request.instance_id
551
+
552
+ init_state = await env_service.create_instance(
553
+ env_type=request.env_type,
554
+ task_id=request.task_id,
555
+ instance_id=instance_id,
556
+ params=request.params,
557
+ )
558
+ return {"success": True, "data": init_state}
559
+ except ValueError as e:
560
+ raise HTTPException(status_code=400, detail=str(e)) from e
561
+ except Exception as e:
562
+ import traceback
563
+
564
+ tb = "".join(traceback.format_exception(type(e), e, e.__traceback__))
565
+ raise HTTPException(status_code=500, detail=tb) from e
566
+
567
+
568
+ @app.post("/step")
569
+ async def handle_step(request: ServiceRequest):
570
+ """
571
+ Execute a step in an existing environment instance.
572
+
573
+ This endpoint allows executing an action in a specific environment instance
574
+ identified by the instance ID.
575
+
576
+ Args:
577
+ request (ServiceRequest): The service request containing
578
+ the instance ID and action details.
579
+
580
+ Returns:
581
+ dict: A dictionary with the step execution status and result.
582
+
583
+ Raises:
584
+ HTTPException: If there's an error in
585
+ executing the step (400 or 500 status codes).
586
+ """
587
+ try:
588
+ if not request.instance_id:
589
+ raise ValueError("instance_id is required")
590
+
591
+ result = await env_service.step(
592
+ instance_id=request.instance_id,
593
+ action=request.messages,
594
+ params=request.params,
595
+ )
596
+ return {"success": True, "data": result}
597
+ except ValueError as e:
598
+ raise HTTPException(status_code=400, detail=str(e)) from e
599
+ except Exception as e:
600
+ import traceback
601
+
602
+ tb = "".join(traceback.format_exception(type(e), e, e.__traceback__))
603
+ raise HTTPException(status_code=500, detail=tb) from e
604
+
605
+
606
+ @app.post("/evaluate")
607
+ async def handle_evaluate(request: ServiceRequest):
608
+ """
609
+ Evaluate the performance of an environment instance.
610
+
611
+ This endpoint allows evaluating the performance of a specific environment
612
+ instance identified by the instance ID.
613
+
614
+ Args:
615
+ request (ServiceRequest): The service request containing
616
+ the instance ID and evaluation parameters.
617
+
618
+ Returns:
619
+ dict: A dictionary with the evaluation status and score.
620
+
621
+ Raises:
622
+ HTTPException: If there's an error
623
+ in evaluating the instance (400 or 500 status codes).
624
+ """
625
+ try:
626
+ if not request.instance_id:
627
+ raise ValueError("instance_id is required")
628
+
629
+ score = await env_service.evaluate(
630
+ instance_id=request.instance_id,
631
+ messages=request.messages,
632
+ params=request.params,
633
+ )
634
+ return {"success": True, "data": score}
635
+ except ValueError as e:
636
+ raise HTTPException(status_code=400, detail=str(e)) from e
637
+ except Exception as e:
638
+ import traceback
639
+
640
+ tb = "".join(traceback.format_exception(type(e), e, e.__traceback__))
641
+ raise HTTPException(status_code=500, detail=tb) from e
642
+
643
+
644
+ @app.post("/get_info")
645
+ async def handle_get_info(request: ServiceRequest):
646
+ """
647
+ Retrieve information about an environment instance.
648
+
649
+ This endpoint allows fetching additional information about a specific
650
+ environment instance identified by the instance ID.
651
+
652
+ Args:
653
+ request (ServiceRequest): The service request containing the
654
+ instance ID and optional parameters for information retrieval.
655
+
656
+ Returns:
657
+ dict: A dictionary with the information retrieval
658
+ status and environment info.
659
+
660
+ Raises:
661
+ HTTPException: If there's an error
662
+ in retrieving the information (400 or 500 status codes).
663
+ """
664
+ try:
665
+ if not request.instance_id:
666
+ raise ValueError("instance_id is required")
667
+
668
+ env_info = await env_service.get_info(
669
+ instance_id=request.instance_id,
670
+ messages=request.messages,
671
+ params=request.params,
672
+ )
673
+ return {"success": True, "data": env_info}
674
+ except ValueError as e:
675
+ raise HTTPException(status_code=400, detail=str(e)) from e
676
+ except Exception as e:
677
+ import traceback
678
+
679
+ tb = "".join(traceback.format_exception(type(e), e, e.__traceback__))
680
+ raise HTTPException(status_code=500, detail=tb) from e
681
+
682
+
683
+ @app.post("/release")
684
+ async def handle_release(request: ServiceRequest):
685
+ """
686
+ Release an existing environment instance.
687
+
688
+ This endpoint allows releasing a specific environment instance
689
+ identified by the instance ID, freeing up resources.
690
+
691
+ Args:
692
+ request (ServiceRequest): The service request containing
693
+ the instance ID to be released.
694
+
695
+ Returns:
696
+ dict: A dictionary with the release status.
697
+
698
+ Raises:
699
+ HTTPException: If there's an error in
700
+ releasing the instance (400 or 500 status codes).
701
+ """
702
+ try:
703
+ if not request.instance_id:
704
+ raise ValueError("instance_id is required")
705
+
706
+ success = await env_service.release_instance(request.instance_id)
707
+ return {"success": success, "data": None}
708
+ except ValueError as e:
709
+ raise HTTPException(status_code=400, detail=str(e)) from e
710
+ except Exception as e:
711
+ import traceback
712
+
713
+ tb = "".join(traceback.format_exception(type(e), e, e.__traceback__))
714
+ raise HTTPException(status_code=500, detail=tb) from e
715
+
716
+
717
+ if __name__ == "__main__":
718
+ parser = argparse.ArgumentParser(description="Run the environment service")
719
+ parser.add_argument(
720
+ "--env",
721
+ type=str,
722
+ default="appworld",
723
+ help="Environment to use",
724
+ )
725
+ parser.add_argument(
726
+ "--env_file_name",
727
+ type=str,
728
+ default=None,
729
+ help="Detailed Environment name, default to the env name",
730
+ )
731
+
732
+ parser.add_argument(
733
+ "--portal",
734
+ type=str,
735
+ default="0.0.0.0",
736
+ help="IP address to bind the server to",
737
+ )
738
+ parser.add_argument(
739
+ "--port",
740
+ type=int,
741
+ default=8000,
742
+ help="Port to run the server on",
743
+ )
744
+ args = parser.parse_args()
745
+
746
+ env_class = import_and_register_env(args.env, args.env_file_name)
747
+ if env_class is None:
748
+ print(f"Failed to import and register environment {args.env}")
749
+ sys.exit(1)
750
+
751
+ print(f"Starting server on {args.portal}:{args.port}")
752
+ uvicorn.run(app, host=args.portal, port=args.port)