agentscope-runtime 0.1.0__py3-none-any.whl → 0.1.2__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/engine/agents/agentscope_agent/agent.py +1 -0
- agentscope_runtime/engine/agents/agno_agent.py +1 -0
- agentscope_runtime/engine/agents/autogen_agent.py +245 -0
- agentscope_runtime/engine/schemas/agent_schemas.py +1 -1
- agentscope_runtime/engine/services/context_manager.py +28 -1
- agentscope_runtime/engine/services/memory_service.py +2 -2
- agentscope_runtime/engine/services/rag_service.py +101 -0
- agentscope_runtime/engine/services/redis_memory_service.py +187 -0
- agentscope_runtime/engine/services/redis_session_history_service.py +155 -0
- agentscope_runtime/sandbox/box/training_box/env_service.py +1 -1
- agentscope_runtime/sandbox/box/training_box/environments/bfcl/bfcl_dataprocess.py +216 -0
- agentscope_runtime/sandbox/box/training_box/environments/bfcl/bfcl_env.py +380 -0
- agentscope_runtime/sandbox/box/training_box/environments/bfcl/env_handler.py +934 -0
- agentscope_runtime/sandbox/box/training_box/training_box.py +139 -9
- agentscope_runtime/sandbox/build.py +1 -1
- agentscope_runtime/sandbox/custom/custom_sandbox.py +0 -1
- agentscope_runtime/sandbox/custom/example.py +0 -1
- agentscope_runtime/sandbox/enums.py +2 -0
- agentscope_runtime/sandbox/manager/container_clients/__init__.py +2 -0
- agentscope_runtime/sandbox/manager/container_clients/docker_client.py +263 -11
- agentscope_runtime/sandbox/manager/container_clients/kubernetes_client.py +605 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +112 -113
- agentscope_runtime/sandbox/manager/server/app.py +96 -28
- agentscope_runtime/sandbox/manager/server/config.py +28 -16
- agentscope_runtime/sandbox/model/__init__.py +1 -5
- agentscope_runtime/sandbox/model/container.py +3 -1
- agentscope_runtime/sandbox/model/manager_config.py +21 -15
- agentscope_runtime/sandbox/tools/tool.py +111 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.1.0.dist-info → agentscope_runtime-0.1.2.dist-info}/METADATA +79 -13
- {agentscope_runtime-0.1.0.dist-info → agentscope_runtime-0.1.2.dist-info}/RECORD +35 -28
- agentscope_runtime/sandbox/manager/utils.py +0 -78
- {agentscope_runtime-0.1.0.dist-info → agentscope_runtime-0.1.2.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.1.0.dist-info → agentscope_runtime-0.1.2.dist-info}/entry_points.txt +0 -0
- {agentscope_runtime-0.1.0.dist-info → agentscope_runtime-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.1.0.dist-info → agentscope_runtime-0.1.2.dist-info}/top_level.txt +0 -0
|
@@ -2,40 +2,37 @@
|
|
|
2
2
|
# pylint: disable=redefined-outer-name, protected-access, too-many-branches
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
import json
|
|
5
6
|
import secrets
|
|
6
7
|
import inspect
|
|
7
|
-
import socket
|
|
8
8
|
import traceback
|
|
9
9
|
|
|
10
10
|
from functools import wraps
|
|
11
11
|
from typing import Optional, Dict
|
|
12
|
-
from
|
|
12
|
+
from urllib.parse import urlparse, urlunparse
|
|
13
13
|
|
|
14
|
+
import shortuuid
|
|
14
15
|
import requests
|
|
15
16
|
|
|
16
17
|
from ..model import (
|
|
17
18
|
ContainerModel,
|
|
18
19
|
SandboxManagerEnvConfig,
|
|
19
|
-
DEFAULT_LOCAL_MANAGER_CONFIG,
|
|
20
20
|
)
|
|
21
21
|
from ..enums import SandboxType
|
|
22
22
|
from ..registry import SandboxRegistry
|
|
23
23
|
from ..client import SandboxHttpClient, TrainingSandboxClient
|
|
24
24
|
|
|
25
|
-
from ..manager.utils import is_port_available, sweep_port
|
|
26
25
|
from ..manager.collections import (
|
|
27
26
|
RedisMapping,
|
|
28
|
-
RedisSetCollection,
|
|
29
27
|
RedisQueue,
|
|
30
28
|
InMemoryMapping,
|
|
31
29
|
InMemoryQueue,
|
|
32
|
-
InMemorySetCollection,
|
|
33
30
|
)
|
|
34
31
|
from ..manager.storage import (
|
|
35
32
|
LocalStorage,
|
|
36
33
|
OSSStorage,
|
|
37
34
|
)
|
|
38
|
-
from ..manager.container_clients import DockerClient
|
|
35
|
+
from ..manager.container_clients import DockerClient, KubernetesClient
|
|
39
36
|
from ..constant import BROWSER_SESSION_ID
|
|
40
37
|
|
|
41
38
|
logging.basicConfig(level=logging.INFO)
|
|
@@ -85,7 +82,7 @@ def remote_wrapper(
|
|
|
85
82
|
class SandboxManager:
|
|
86
83
|
def __init__(
|
|
87
84
|
self,
|
|
88
|
-
config: SandboxManagerEnvConfig =
|
|
85
|
+
config: Optional[SandboxManagerEnvConfig] = None,
|
|
89
86
|
base_url=None,
|
|
90
87
|
bearer_token=None,
|
|
91
88
|
default_type: SandboxType | str = SandboxType.BASE,
|
|
@@ -100,19 +97,28 @@ class SandboxManager:
|
|
|
100
97
|
self.http_session.headers.update(
|
|
101
98
|
{"Authorization": f"Bearer {bearer_token}"},
|
|
102
99
|
)
|
|
100
|
+
# Remote mode, return directly
|
|
101
|
+
return
|
|
103
102
|
else:
|
|
104
103
|
self.http_session = None
|
|
105
104
|
self.base_url = None
|
|
106
105
|
|
|
106
|
+
if not config:
|
|
107
|
+
config = SandboxManagerEnvConfig(
|
|
108
|
+
file_system="local",
|
|
109
|
+
redis_enabled=False,
|
|
110
|
+
container_deployment="docker",
|
|
111
|
+
pool_size=0,
|
|
112
|
+
default_mount_dir="sessions_mount_dir",
|
|
113
|
+
)
|
|
114
|
+
|
|
107
115
|
self.default_type = SandboxType(default_type)
|
|
108
116
|
self.workdir = "/workspace"
|
|
109
117
|
|
|
110
118
|
self.config = config
|
|
111
119
|
self.pool_size = self.config.pool_size
|
|
112
120
|
self.prefix = self.config.container_prefix_key
|
|
113
|
-
self.default_mount_dir =
|
|
114
|
-
self.config.default_mount_dir or "sessions_mount_dir"
|
|
115
|
-
)
|
|
121
|
+
self.default_mount_dir = self.config.default_mount_dir
|
|
116
122
|
self.storage_folder = (
|
|
117
123
|
self.config.storage_folder or self.default_mount_dir
|
|
118
124
|
)
|
|
@@ -136,32 +142,26 @@ class SandboxManager:
|
|
|
136
142
|
) from e
|
|
137
143
|
|
|
138
144
|
self.container_mapping = RedisMapping(redis_client)
|
|
139
|
-
self.port_set = RedisSetCollection(
|
|
140
|
-
redis_client,
|
|
141
|
-
set_name=self.config.redis_port_key,
|
|
142
|
-
)
|
|
143
145
|
self.pool_queue = RedisQueue(
|
|
144
146
|
redis_client,
|
|
145
147
|
self.config.redis_container_pool_key,
|
|
146
148
|
)
|
|
147
149
|
else:
|
|
148
150
|
self.container_mapping = InMemoryMapping()
|
|
149
|
-
self.port_set = InMemorySetCollection()
|
|
150
151
|
self.pool_queue = InMemoryQueue()
|
|
151
152
|
|
|
152
153
|
self.container_deployment = self.config.container_deployment
|
|
153
154
|
|
|
154
155
|
if base_url is None:
|
|
155
156
|
if self.container_deployment == "docker":
|
|
156
|
-
self.client = DockerClient()
|
|
157
|
+
self.client = DockerClient(config=self.config)
|
|
158
|
+
elif self.container_deployment == "k8s":
|
|
159
|
+
self.client = KubernetesClient(config=self.config)
|
|
157
160
|
else:
|
|
158
|
-
# TODO: support k8s deployment
|
|
159
161
|
raise NotImplementedError("Not implemented")
|
|
160
162
|
else:
|
|
161
163
|
self.client = None
|
|
162
164
|
|
|
163
|
-
self.port_range = range(*self.config.port_range)
|
|
164
|
-
|
|
165
165
|
self.file_system = self.config.file_system
|
|
166
166
|
if self.file_system == "oss":
|
|
167
167
|
self.storage = OSSStorage(
|
|
@@ -207,8 +207,35 @@ class SandboxManager:
|
|
|
207
207
|
try:
|
|
208
208
|
response.raise_for_status()
|
|
209
209
|
except requests.exceptions.HTTPError as e:
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
error_components = [
|
|
211
|
+
f"HTTP {response.status_code} Error: {str(e)}",
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
server_response = response.json()
|
|
216
|
+
if "detail" in server_response:
|
|
217
|
+
error_components.append(
|
|
218
|
+
f"Server Detail: {server_response['detail']}",
|
|
219
|
+
)
|
|
220
|
+
elif "error" in server_response:
|
|
221
|
+
error_components.append(
|
|
222
|
+
f"Server Error: {server_response['error']}",
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
error_components.append(
|
|
226
|
+
f"Server Response: {server_response}",
|
|
227
|
+
)
|
|
228
|
+
except (ValueError, json.JSONDecodeError):
|
|
229
|
+
if response.text:
|
|
230
|
+
error_components.append(
|
|
231
|
+
f"Server Response: {response.text}",
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
error = " | ".join(error_components)
|
|
235
|
+
|
|
236
|
+
logger.error(f"Error making request: {error}")
|
|
237
|
+
|
|
238
|
+
return {"data": f"Error: {error}"}
|
|
212
239
|
|
|
213
240
|
return response.json()
|
|
214
241
|
|
|
@@ -235,34 +262,6 @@ class SandboxManager:
|
|
|
235
262
|
logger.error(f"Error initializing runtime pool: {e}")
|
|
236
263
|
break
|
|
237
264
|
|
|
238
|
-
def _find_free_ports(self, n):
|
|
239
|
-
free_ports = []
|
|
240
|
-
|
|
241
|
-
for port in self.port_range:
|
|
242
|
-
if len(free_ports) >= n:
|
|
243
|
-
break # We have found enough ports
|
|
244
|
-
|
|
245
|
-
if not self.port_set.add(port):
|
|
246
|
-
continue
|
|
247
|
-
|
|
248
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
249
|
-
try:
|
|
250
|
-
s.bind(("", port))
|
|
251
|
-
free_ports.append(port) # Port is available
|
|
252
|
-
|
|
253
|
-
except OSError:
|
|
254
|
-
# Bind failed, port is in use
|
|
255
|
-
self.port_set.remove(port)
|
|
256
|
-
# Try the next one
|
|
257
|
-
continue
|
|
258
|
-
|
|
259
|
-
if len(free_ports) < n:
|
|
260
|
-
raise RuntimeError(
|
|
261
|
-
"Not enough free ports available in the specified range.",
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
return free_ports
|
|
265
|
-
|
|
266
265
|
@remote_wrapper()
|
|
267
266
|
def cleanup(self):
|
|
268
267
|
logger.debug(
|
|
@@ -414,22 +413,28 @@ class SandboxManager:
|
|
|
414
413
|
)
|
|
415
414
|
return None
|
|
416
415
|
|
|
417
|
-
|
|
416
|
+
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"
|
|
417
|
+
short_uuid = shortuuid.ShortUUID(alphabet=alphabet).uuid()
|
|
418
|
+
session_id = str(short_uuid)
|
|
418
419
|
|
|
419
|
-
if mount_dir
|
|
420
|
-
|
|
421
|
-
|
|
420
|
+
if not mount_dir:
|
|
421
|
+
if self.default_mount_dir:
|
|
422
|
+
mount_dir = os.path.join(self.default_mount_dir, session_id)
|
|
423
|
+
os.makedirs(mount_dir, exist_ok=True)
|
|
422
424
|
|
|
423
|
-
if
|
|
424
|
-
|
|
425
|
+
if mount_dir:
|
|
426
|
+
if not os.path.isabs(mount_dir):
|
|
427
|
+
mount_dir = os.path.abspath(mount_dir)
|
|
425
428
|
|
|
426
429
|
if storage_path is None:
|
|
427
|
-
|
|
428
|
-
self.
|
|
429
|
-
|
|
430
|
-
|
|
430
|
+
if self.storage_folder:
|
|
431
|
+
storage_path = self.storage.path_join(
|
|
432
|
+
self.storage_folder,
|
|
433
|
+
session_id,
|
|
434
|
+
)
|
|
431
435
|
|
|
432
|
-
|
|
436
|
+
if mount_dir and storage_path:
|
|
437
|
+
self.storage.download_folder(storage_path, mount_dir)
|
|
433
438
|
|
|
434
439
|
try:
|
|
435
440
|
# Check for an existing container with the same name
|
|
@@ -439,34 +444,33 @@ class SandboxManager:
|
|
|
439
444
|
f"Container with name {container_name} already exists.",
|
|
440
445
|
)
|
|
441
446
|
|
|
442
|
-
free_ports = self._find_free_ports(1)
|
|
443
|
-
|
|
444
|
-
ports = {
|
|
445
|
-
"80/tcp": free_ports[0], # nginx
|
|
446
|
-
}
|
|
447
|
-
|
|
448
447
|
# Generate a random secret token
|
|
449
448
|
runtime_token = secrets.token_hex(16)
|
|
450
449
|
|
|
451
450
|
# Prepare volume bindings if a mount directory is provided
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
451
|
+
if mount_dir:
|
|
452
|
+
volume_bindings = {
|
|
453
|
+
mount_dir: {
|
|
454
|
+
"bind": self.workdir,
|
|
455
|
+
"mode": "rw",
|
|
456
|
+
},
|
|
457
|
+
}
|
|
458
|
+
else:
|
|
459
|
+
volume_bindings = {}
|
|
458
460
|
|
|
459
|
-
|
|
461
|
+
_id, ports, ip = self.client.create(
|
|
460
462
|
image,
|
|
461
463
|
name=container_name,
|
|
462
|
-
ports=
|
|
464
|
+
ports=["80/tcp"], # Nginx
|
|
463
465
|
volumes=volume_bindings,
|
|
464
466
|
environment={
|
|
465
467
|
"SECRET_TOKEN": runtime_token,
|
|
466
468
|
**environment,
|
|
467
469
|
},
|
|
468
470
|
runtime_config=config.runtime_config,
|
|
469
|
-
)
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
if _id is None:
|
|
470
474
|
return None
|
|
471
475
|
|
|
472
476
|
# Check the container status
|
|
@@ -478,24 +482,22 @@ class SandboxManager:
|
|
|
478
482
|
)
|
|
479
483
|
return None
|
|
480
484
|
|
|
481
|
-
#
|
|
482
|
-
container_attrs = self.client.inspect(container_name)
|
|
483
|
-
|
|
485
|
+
# TODO: update ContainerModel according to images & backend
|
|
484
486
|
container_model = ContainerModel(
|
|
485
487
|
session_id=session_id,
|
|
486
|
-
container_id=
|
|
488
|
+
container_id=_id,
|
|
487
489
|
container_name=container_name,
|
|
488
|
-
base_url=f"http://
|
|
489
|
-
browser_url=f"http://
|
|
490
|
+
base_url=f"http://{ip}:{ports[0]}/fastapi",
|
|
491
|
+
browser_url=f"http://{ip}:{ports[0]}/steel-api"
|
|
490
492
|
f"/{runtime_token}",
|
|
491
|
-
front_browser_ws=f"ws://
|
|
492
|
-
f"{ports[
|
|
493
|
+
front_browser_ws=f"ws://{ip}:"
|
|
494
|
+
f"{ports[0]}/steel-api/"
|
|
493
495
|
f"{runtime_token}/v1/sessions/cast",
|
|
494
|
-
client_browser_ws=f"ws://
|
|
495
|
-
f"{ports[
|
|
496
|
+
client_browser_ws=f"ws://{ip}:"
|
|
497
|
+
f"{ports[0]}/steel-api/{runtime_token}/&sessionId"
|
|
496
498
|
f"={BROWSER_SESSION_ID}",
|
|
497
|
-
artifacts_sio=f"http://
|
|
498
|
-
ports=
|
|
499
|
+
artifacts_sio=f"http://{ip}:{ports[0]}/v1",
|
|
500
|
+
ports=[ports[0]],
|
|
499
501
|
mount_dir=str(mount_dir),
|
|
500
502
|
storage_path=storage_path,
|
|
501
503
|
runtime_token=runtime_token,
|
|
@@ -537,17 +539,14 @@ class SandboxManager:
|
|
|
537
539
|
self.client.stop(container_info.container_id, timeout=1)
|
|
538
540
|
self.client.remove(container_info.container_id, force=True)
|
|
539
541
|
|
|
540
|
-
# Release ports after the container is removed
|
|
541
|
-
for port in container_info.ports:
|
|
542
|
-
self.port_set.remove(port)
|
|
543
|
-
|
|
544
542
|
logger.debug(f"Container for {identity} destroyed.")
|
|
545
543
|
|
|
546
544
|
# Upload to storage
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
545
|
+
if container_info.mount_dir and container_info.storage_path:
|
|
546
|
+
self.storage.upload_folder(
|
|
547
|
+
container_info.mount_dir,
|
|
548
|
+
container_info.storage_path,
|
|
549
|
+
)
|
|
551
550
|
|
|
552
551
|
return True
|
|
553
552
|
except Exception as e:
|
|
@@ -570,14 +569,6 @@ class SandboxManager:
|
|
|
570
569
|
|
|
571
570
|
container_info = ContainerModel(**container_json)
|
|
572
571
|
|
|
573
|
-
# Check whether the ports are occupied by other processes
|
|
574
|
-
for port in container_info.ports:
|
|
575
|
-
if is_port_available(port):
|
|
576
|
-
continue
|
|
577
|
-
|
|
578
|
-
# If the port is occupied, sweep it
|
|
579
|
-
sweep_port(port)
|
|
580
|
-
|
|
581
572
|
self.client.start(container_info.container_id)
|
|
582
573
|
status = self.client.get_status(container_info.container_id)
|
|
583
574
|
if status != "running":
|
|
@@ -653,9 +644,24 @@ class SandboxManager:
|
|
|
653
644
|
enable_browser = "browser" in container_model.version
|
|
654
645
|
|
|
655
646
|
# TODO: remake docker name
|
|
656
|
-
if
|
|
647
|
+
if (
|
|
648
|
+
"sandbox-appworld" in container_model.version
|
|
649
|
+
or "sandbox-bfcl" in container_model.version
|
|
650
|
+
):
|
|
651
|
+
parsed = urlparse(container_model.base_url)
|
|
652
|
+
base_url = urlunparse(
|
|
653
|
+
(
|
|
654
|
+
parsed.scheme,
|
|
655
|
+
parsed.netloc,
|
|
656
|
+
"",
|
|
657
|
+
"",
|
|
658
|
+
"",
|
|
659
|
+
"",
|
|
660
|
+
),
|
|
661
|
+
)
|
|
662
|
+
|
|
657
663
|
return TrainingSandboxClient(
|
|
658
|
-
base_url=
|
|
664
|
+
base_url=base_url,
|
|
659
665
|
).__enter__()
|
|
660
666
|
|
|
661
667
|
return SandboxHttpClient(
|
|
@@ -685,10 +691,3 @@ class SandboxManager:
|
|
|
685
691
|
server_configs=server_configs,
|
|
686
692
|
overwrite=overwrite,
|
|
687
693
|
)
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
if __name__ == "__main__":
|
|
691
|
-
with SandboxManager() as manager:
|
|
692
|
-
name = manager.create("12345")
|
|
693
|
-
if name:
|
|
694
|
-
print(f"Created container: {name}")
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
# pylint: disable=protected-access, unused-argument
|
|
3
3
|
import inspect
|
|
4
4
|
import logging
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
from typing import Optional
|
|
6
7
|
|
|
7
8
|
from fastapi import FastAPI, HTTPException, Request, Depends
|
|
8
9
|
from fastapi.middleware.cors import CORSMiddleware
|
|
9
10
|
from fastapi.responses import JSONResponse
|
|
10
11
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
11
12
|
|
|
12
|
-
from ...manager.server.config import
|
|
13
|
+
from ...manager.server.config import get_settings
|
|
13
14
|
from ...manager.server.models import (
|
|
14
15
|
ErrorResponse,
|
|
15
16
|
HealthResponse,
|
|
@@ -42,34 +43,47 @@ app.add_middleware(
|
|
|
42
43
|
security = HTTPBearer(auto_error=False)
|
|
43
44
|
|
|
44
45
|
# Global SandboxManager instance
|
|
45
|
-
_runtime_manager = None
|
|
46
|
-
_config =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
46
|
+
_runtime_manager: Optional[SandboxManager] = None
|
|
47
|
+
_config: Optional[SandboxManagerEnvConfig] = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_config() -> SandboxManagerEnvConfig:
|
|
51
|
+
"""Return config"""
|
|
52
|
+
global _config
|
|
53
|
+
if _config is None:
|
|
54
|
+
settings = get_settings()
|
|
55
|
+
_config = SandboxManagerEnvConfig(
|
|
56
|
+
container_prefix_key=settings.CONTAINER_PREFIX_KEY,
|
|
57
|
+
file_system=settings.FILE_SYSTEM,
|
|
58
|
+
redis_enabled=settings.REDIS_ENABLED,
|
|
59
|
+
container_deployment=settings.CONTAINER_DEPLOYMENT,
|
|
60
|
+
default_mount_dir=settings.DEFAULT_MOUNT_DIR,
|
|
61
|
+
storage_folder=settings.STORAGE_FOLDER,
|
|
62
|
+
port_range=settings.PORT_RANGE,
|
|
63
|
+
pool_size=settings.POOL_SIZE,
|
|
64
|
+
oss_endpoint=settings.OSS_ENDPOINT,
|
|
65
|
+
oss_access_key_id=settings.OSS_ACCESS_KEY_ID,
|
|
66
|
+
oss_access_key_secret=settings.OSS_ACCESS_KEY_SECRET,
|
|
67
|
+
oss_bucket_name=settings.OSS_BUCKET_NAME,
|
|
68
|
+
redis_server=settings.REDIS_SERVER,
|
|
69
|
+
redis_port=settings.REDIS_PORT,
|
|
70
|
+
redis_db=settings.REDIS_DB,
|
|
71
|
+
redis_user=settings.REDIS_USER,
|
|
72
|
+
redis_password=settings.REDIS_PASSWORD,
|
|
73
|
+
redis_port_key=settings.REDIS_PORT_KEY,
|
|
74
|
+
redis_container_pool_key=settings.REDIS_CONTAINER_POOL_KEY,
|
|
75
|
+
k8s_namespace=settings.K8S_NAMESPACE,
|
|
76
|
+
kubeconfig_path=settings.KUBECONFIG_PATH,
|
|
77
|
+
)
|
|
78
|
+
return _config
|
|
67
79
|
|
|
68
80
|
|
|
69
81
|
def verify_token(
|
|
70
82
|
credentials: HTTPAuthorizationCredentials = Depends(security),
|
|
71
83
|
):
|
|
72
84
|
"""Verify Bearer token"""
|
|
85
|
+
settings = get_settings()
|
|
86
|
+
|
|
73
87
|
if not hasattr(settings, "BEARER_TOKEN") or not settings.BEARER_TOKEN:
|
|
74
88
|
logger.warning("BEARER_TOKEN not configured, skipping authentication")
|
|
75
89
|
return credentials
|
|
@@ -98,8 +112,10 @@ def get_runtime_manager():
|
|
|
98
112
|
"""Get or create the global SandboxManager instance"""
|
|
99
113
|
global _runtime_manager
|
|
100
114
|
if _runtime_manager is None:
|
|
115
|
+
settings = get_settings()
|
|
116
|
+
config = get_config()
|
|
101
117
|
_runtime_manager = SandboxManager(
|
|
102
|
-
config=
|
|
118
|
+
config=config,
|
|
103
119
|
default_type=settings.DEFAULT_SANDBOX_TYPE,
|
|
104
120
|
)
|
|
105
121
|
return _runtime_manager
|
|
@@ -120,11 +136,12 @@ def create_endpoint(method):
|
|
|
120
136
|
return JSONResponse(content={"data": result.model_dump_json()})
|
|
121
137
|
return JSONResponse(content={"data": result})
|
|
122
138
|
except Exception as e:
|
|
123
|
-
|
|
139
|
+
error = (
|
|
124
140
|
f"Error in {method.__name__}: {str(e)},"
|
|
125
|
-
f" {traceback.format_exc()}"
|
|
141
|
+
# f" {traceback.format_exc()}"
|
|
126
142
|
)
|
|
127
|
-
|
|
143
|
+
logger.error(error)
|
|
144
|
+
raise HTTPException(status_code=500, detail=error) from e
|
|
128
145
|
|
|
129
146
|
return endpoint
|
|
130
147
|
|
|
@@ -159,6 +176,7 @@ async def startup_event():
|
|
|
159
176
|
async def shutdown_event():
|
|
160
177
|
"""Cleanup resources on shutdown"""
|
|
161
178
|
global _runtime_manager
|
|
179
|
+
settings = get_settings()
|
|
162
180
|
if _runtime_manager and settings.AUTO_CLEANUP:
|
|
163
181
|
_runtime_manager.cleanup()
|
|
164
182
|
_runtime_manager = None
|
|
@@ -177,10 +195,60 @@ async def health_check():
|
|
|
177
195
|
)
|
|
178
196
|
|
|
179
197
|
|
|
198
|
+
def setup_logging(log_level: str):
|
|
199
|
+
"""Setup logging configuration based on log level"""
|
|
200
|
+
# Convert string to logging level
|
|
201
|
+
level_mapping = {
|
|
202
|
+
"DEBUG": logging.DEBUG,
|
|
203
|
+
"INFO": logging.INFO,
|
|
204
|
+
"WARNING": logging.WARNING,
|
|
205
|
+
"ERROR": logging.ERROR,
|
|
206
|
+
"CRITICAL": logging.CRITICAL,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
level = level_mapping.get(log_level.upper(), logging.INFO)
|
|
210
|
+
|
|
211
|
+
# Reconfigure logging
|
|
212
|
+
logging.basicConfig(
|
|
213
|
+
level=level,
|
|
214
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
215
|
+
force=True, # This will reconfigure existing loggers
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Update the logger for this module
|
|
219
|
+
global logger
|
|
220
|
+
logger.setLevel(level)
|
|
221
|
+
|
|
222
|
+
logger.info(f"Logging level set to {log_level.upper()}")
|
|
223
|
+
|
|
224
|
+
|
|
180
225
|
def main():
|
|
181
226
|
"""Main entry point for the Runtime Manager Service"""
|
|
227
|
+
import argparse
|
|
228
|
+
import os
|
|
182
229
|
import uvicorn
|
|
183
230
|
|
|
231
|
+
parser = argparse.ArgumentParser(description="Runtime Manager Service")
|
|
232
|
+
parser.add_argument("--config", type=str, help="Path to config file")
|
|
233
|
+
parser.add_argument(
|
|
234
|
+
"--log-level",
|
|
235
|
+
type=str,
|
|
236
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
237
|
+
default="INFO",
|
|
238
|
+
help="Set the logging level (default: INFO)",
|
|
239
|
+
)
|
|
240
|
+
args = parser.parse_args()
|
|
241
|
+
|
|
242
|
+
# Setup logging based on command line argument
|
|
243
|
+
setup_logging(args.log_level)
|
|
244
|
+
|
|
245
|
+
if args.config and not os.path.exists(args.config):
|
|
246
|
+
raise FileNotFoundError(
|
|
247
|
+
f"Error: Config file {args.config} does not exist",
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
settings = get_settings(args.config)
|
|
251
|
+
|
|
184
252
|
uvicorn.run(
|
|
185
253
|
"agentscope_runtime.sandbox.manager.server.app:app",
|
|
186
254
|
host=settings.HOST,
|
|
@@ -2,18 +2,9 @@
|
|
|
2
2
|
import os
|
|
3
3
|
from typing import Optional, Tuple, Literal
|
|
4
4
|
from pydantic_settings import BaseSettings
|
|
5
|
-
from pydantic import field_validator
|
|
5
|
+
from pydantic import field_validator, ConfigDict
|
|
6
6
|
from dotenv import load_dotenv
|
|
7
7
|
|
|
8
|
-
env_file = ".env"
|
|
9
|
-
env_example_file = ".env.example"
|
|
10
|
-
|
|
11
|
-
# Load the appropriate .env file
|
|
12
|
-
if os.path.exists(env_file):
|
|
13
|
-
load_dotenv(env_file)
|
|
14
|
-
elif os.path.exists(env_example_file):
|
|
15
|
-
load_dotenv(env_example_file)
|
|
16
|
-
|
|
17
8
|
|
|
18
9
|
class Settings(BaseSettings):
|
|
19
10
|
"""Runtime Manager Service Settings"""
|
|
@@ -27,11 +18,10 @@ class Settings(BaseSettings):
|
|
|
27
18
|
|
|
28
19
|
# Runtime Manager settings
|
|
29
20
|
DEFAULT_SANDBOX_TYPE: str = "base"
|
|
30
|
-
WORKDIR: str = "/workspace"
|
|
31
21
|
POOL_SIZE: int = 1
|
|
32
22
|
AUTO_CLEANUP: bool = True
|
|
33
23
|
CONTAINER_PREFIX_KEY: str = "runtime_sandbox_container_"
|
|
34
|
-
CONTAINER_DEPLOYMENT: Literal["docker", "cloud"] = "docker"
|
|
24
|
+
CONTAINER_DEPLOYMENT: Literal["docker", "cloud", "k8s"] = "docker"
|
|
35
25
|
DEFAULT_MOUNT_DIR: str = "sessions_mount_dir"
|
|
36
26
|
STORAGE_FOLDER: str = "runtime_sandbox_storage"
|
|
37
27
|
PORT_RANGE: Tuple[int, int] = (49152, 59152)
|
|
@@ -53,9 +43,14 @@ class Settings(BaseSettings):
|
|
|
53
43
|
OSS_ACCESS_KEY_SECRET: str = "your-access-key-secret"
|
|
54
44
|
OSS_BUCKET_NAME: str = "your-bucket-name"
|
|
55
45
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
46
|
+
# K8S settings
|
|
47
|
+
K8S_NAMESPACE: str = "default"
|
|
48
|
+
KUBECONFIG_PATH: Optional[str] = None
|
|
49
|
+
|
|
50
|
+
model_config = ConfigDict(
|
|
51
|
+
case_sensitive=True,
|
|
52
|
+
extra="allow",
|
|
53
|
+
)
|
|
59
54
|
|
|
60
55
|
@field_validator("WORKERS", mode="before")
|
|
61
56
|
@classmethod
|
|
@@ -65,4 +60,21 @@ class Settings(BaseSettings):
|
|
|
65
60
|
return value
|
|
66
61
|
|
|
67
62
|
|
|
68
|
-
|
|
63
|
+
_settings: Optional[Settings] = None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_settings(config_file: Optional[str] = None) -> Settings:
|
|
67
|
+
global _settings
|
|
68
|
+
|
|
69
|
+
env_file = ".env"
|
|
70
|
+
env_example_file = ".env.example"
|
|
71
|
+
|
|
72
|
+
if _settings is None:
|
|
73
|
+
if config_file and os.path.exists(config_file):
|
|
74
|
+
load_dotenv(config_file, override=True)
|
|
75
|
+
elif os.path.exists(env_file):
|
|
76
|
+
load_dotenv(env_file)
|
|
77
|
+
elif os.path.exists(env_example_file):
|
|
78
|
+
load_dotenv(env_example_file)
|
|
79
|
+
_settings = Settings()
|
|
80
|
+
return _settings
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
from .container import ContainerModel
|
|
3
|
-
from .manager_config import
|
|
4
|
-
SandboxManagerEnvConfig,
|
|
5
|
-
DEFAULT_LOCAL_MANAGER_CONFIG,
|
|
6
|
-
)
|
|
3
|
+
from .manager_config import SandboxManagerEnvConfig
|
|
7
4
|
|
|
8
5
|
__all__ = [
|
|
9
6
|
"ContainerModel",
|
|
10
7
|
"SandboxManagerEnvConfig",
|
|
11
|
-
"DEFAULT_LOCAL_MANAGER_CONFIG",
|
|
12
8
|
]
|
|
@@ -4,7 +4,6 @@ from typing import List
|
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
# TODO: support k8s version
|
|
8
7
|
class ContainerModel(BaseModel):
|
|
9
8
|
session_id: str = Field(
|
|
10
9
|
...,
|
|
@@ -70,3 +69,6 @@ class ContainerModel(BaseModel):
|
|
|
70
69
|
None,
|
|
71
70
|
description="Image version of the container",
|
|
72
71
|
)
|
|
72
|
+
|
|
73
|
+
class Config:
|
|
74
|
+
extra = "allow"
|