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.
- agentscope_runtime/__init__.py +4 -0
- agentscope_runtime/engine/__init__.py +9 -0
- agentscope_runtime/engine/agents/__init__.py +2 -0
- agentscope_runtime/engine/agents/agentscope_agent/__init__.py +6 -0
- agentscope_runtime/engine/agents/agentscope_agent/agent.py +342 -0
- agentscope_runtime/engine/agents/agentscope_agent/hooks.py +156 -0
- agentscope_runtime/engine/agents/agno_agent.py +220 -0
- agentscope_runtime/engine/agents/base_agent.py +29 -0
- agentscope_runtime/engine/agents/langgraph_agent.py +59 -0
- agentscope_runtime/engine/agents/llm_agent.py +51 -0
- agentscope_runtime/engine/deployers/__init__.py +3 -0
- agentscope_runtime/engine/deployers/adapter/__init__.py +0 -0
- agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +2 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +425 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_agent_adapter.py +69 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +60 -0
- agentscope_runtime/engine/deployers/adapter/protocol_adapter.py +24 -0
- agentscope_runtime/engine/deployers/base.py +17 -0
- agentscope_runtime/engine/deployers/local_deployer.py +586 -0
- agentscope_runtime/engine/helpers/helper.py +127 -0
- agentscope_runtime/engine/llms/__init__.py +3 -0
- agentscope_runtime/engine/llms/base_llm.py +60 -0
- agentscope_runtime/engine/llms/qwen_llm.py +47 -0
- agentscope_runtime/engine/misc/__init__.py +0 -0
- agentscope_runtime/engine/runner.py +186 -0
- agentscope_runtime/engine/schemas/__init__.py +0 -0
- agentscope_runtime/engine/schemas/agent_schemas.py +551 -0
- agentscope_runtime/engine/schemas/context.py +54 -0
- agentscope_runtime/engine/services/__init__.py +9 -0
- agentscope_runtime/engine/services/base.py +77 -0
- agentscope_runtime/engine/services/context_manager.py +129 -0
- agentscope_runtime/engine/services/environment_manager.py +50 -0
- agentscope_runtime/engine/services/manager.py +174 -0
- agentscope_runtime/engine/services/memory_service.py +270 -0
- agentscope_runtime/engine/services/sandbox_service.py +198 -0
- agentscope_runtime/engine/services/session_history_service.py +256 -0
- agentscope_runtime/engine/tracing/__init__.py +40 -0
- agentscope_runtime/engine/tracing/base.py +309 -0
- agentscope_runtime/engine/tracing/local_logging_handler.py +356 -0
- agentscope_runtime/engine/tracing/tracing_metric.py +69 -0
- agentscope_runtime/engine/tracing/wrapper.py +321 -0
- agentscope_runtime/sandbox/__init__.py +14 -0
- agentscope_runtime/sandbox/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/base/__init__.py +0 -0
- agentscope_runtime/sandbox/box/base/base_sandbox.py +37 -0
- agentscope_runtime/sandbox/box/base/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +176 -0
- agentscope_runtime/sandbox/box/dummy/__init__.py +0 -0
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +26 -0
- agentscope_runtime/sandbox/box/filesystem/__init__.py +0 -0
- agentscope_runtime/sandbox/box/filesystem/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +87 -0
- agentscope_runtime/sandbox/box/sandbox.py +115 -0
- agentscope_runtime/sandbox/box/shared/__init__.py +0 -0
- agentscope_runtime/sandbox/box/shared/app.py +44 -0
- agentscope_runtime/sandbox/box/shared/dependencies/__init__.py +5 -0
- agentscope_runtime/sandbox/box/shared/dependencies/deps.py +22 -0
- agentscope_runtime/sandbox/box/shared/routers/__init__.py +12 -0
- agentscope_runtime/sandbox/box/shared/routers/generic.py +173 -0
- agentscope_runtime/sandbox/box/shared/routers/mcp.py +207 -0
- agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +153 -0
- agentscope_runtime/sandbox/box/shared/routers/runtime_watcher.py +187 -0
- agentscope_runtime/sandbox/box/shared/routers/workspace.py +325 -0
- agentscope_runtime/sandbox/box/training_box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/training_box/base.py +120 -0
- agentscope_runtime/sandbox/box/training_box/env_service.py +752 -0
- agentscope_runtime/sandbox/box/training_box/environments/__init__.py +0 -0
- agentscope_runtime/sandbox/box/training_box/environments/appworld/appworld_env.py +987 -0
- agentscope_runtime/sandbox/box/training_box/registry.py +54 -0
- agentscope_runtime/sandbox/box/training_box/src/trajectory.py +278 -0
- agentscope_runtime/sandbox/box/training_box/training_box.py +219 -0
- agentscope_runtime/sandbox/build.py +213 -0
- agentscope_runtime/sandbox/client/__init__.py +5 -0
- agentscope_runtime/sandbox/client/http_client.py +527 -0
- agentscope_runtime/sandbox/client/training_client.py +265 -0
- agentscope_runtime/sandbox/constant.py +5 -0
- agentscope_runtime/sandbox/custom/__init__.py +16 -0
- agentscope_runtime/sandbox/custom/custom_sandbox.py +40 -0
- agentscope_runtime/sandbox/custom/example.py +37 -0
- agentscope_runtime/sandbox/enums.py +68 -0
- agentscope_runtime/sandbox/manager/__init__.py +4 -0
- agentscope_runtime/sandbox/manager/collections/__init__.py +22 -0
- agentscope_runtime/sandbox/manager/collections/base_mapping.py +20 -0
- agentscope_runtime/sandbox/manager/collections/base_queue.py +25 -0
- agentscope_runtime/sandbox/manager/collections/base_set.py +25 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +22 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_queue.py +28 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_set.py +27 -0
- agentscope_runtime/sandbox/manager/collections/redis_mapping.py +26 -0
- agentscope_runtime/sandbox/manager/collections/redis_queue.py +27 -0
- agentscope_runtime/sandbox/manager/collections/redis_set.py +23 -0
- agentscope_runtime/sandbox/manager/container_clients/__init__.py +8 -0
- agentscope_runtime/sandbox/manager/container_clients/base_client.py +39 -0
- agentscope_runtime/sandbox/manager/container_clients/docker_client.py +170 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +694 -0
- agentscope_runtime/sandbox/manager/server/__init__.py +0 -0
- agentscope_runtime/sandbox/manager/server/app.py +194 -0
- agentscope_runtime/sandbox/manager/server/config.py +68 -0
- agentscope_runtime/sandbox/manager/server/models.py +17 -0
- agentscope_runtime/sandbox/manager/storage/__init__.py +10 -0
- agentscope_runtime/sandbox/manager/storage/data_storage.py +16 -0
- agentscope_runtime/sandbox/manager/storage/local_storage.py +44 -0
- agentscope_runtime/sandbox/manager/storage/oss_storage.py +89 -0
- agentscope_runtime/sandbox/manager/utils.py +78 -0
- agentscope_runtime/sandbox/mcp_server.py +192 -0
- agentscope_runtime/sandbox/model/__init__.py +12 -0
- agentscope_runtime/sandbox/model/api.py +16 -0
- agentscope_runtime/sandbox/model/container.py +72 -0
- agentscope_runtime/sandbox/model/manager_config.py +158 -0
- agentscope_runtime/sandbox/registry.py +129 -0
- agentscope_runtime/sandbox/tools/__init__.py +12 -0
- agentscope_runtime/sandbox/tools/base/__init__.py +8 -0
- agentscope_runtime/sandbox/tools/base/tool.py +52 -0
- agentscope_runtime/sandbox/tools/browser/__init__.py +57 -0
- agentscope_runtime/sandbox/tools/browser/tool.py +597 -0
- agentscope_runtime/sandbox/tools/filesystem/__init__.py +32 -0
- agentscope_runtime/sandbox/tools/filesystem/tool.py +319 -0
- agentscope_runtime/sandbox/tools/function_tool.py +321 -0
- agentscope_runtime/sandbox/tools/mcp_tool.py +191 -0
- agentscope_runtime/sandbox/tools/sandbox_tool.py +104 -0
- agentscope_runtime/sandbox/tools/tool.py +123 -0
- agentscope_runtime/sandbox/tools/utils.py +68 -0
- agentscope_runtime/version.py +2 -0
- agentscope_runtime-0.1.0.dist-info/METADATA +327 -0
- agentscope_runtime-0.1.0.dist-info/RECORD +131 -0
- agentscope_runtime-0.1.0.dist-info/WHEEL +5 -0
- agentscope_runtime-0.1.0.dist-info/entry_points.txt +4 -0
- agentscope_runtime-0.1.0.dist-info/licenses/LICENSE +202 -0
- 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)
|