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.
Files changed (36) hide show
  1. agentscope_runtime/engine/agents/agentscope_agent/agent.py +1 -0
  2. agentscope_runtime/engine/agents/agno_agent.py +1 -0
  3. agentscope_runtime/engine/agents/autogen_agent.py +245 -0
  4. agentscope_runtime/engine/schemas/agent_schemas.py +1 -1
  5. agentscope_runtime/engine/services/context_manager.py +28 -1
  6. agentscope_runtime/engine/services/memory_service.py +2 -2
  7. agentscope_runtime/engine/services/rag_service.py +101 -0
  8. agentscope_runtime/engine/services/redis_memory_service.py +187 -0
  9. agentscope_runtime/engine/services/redis_session_history_service.py +155 -0
  10. agentscope_runtime/sandbox/box/training_box/env_service.py +1 -1
  11. agentscope_runtime/sandbox/box/training_box/environments/bfcl/bfcl_dataprocess.py +216 -0
  12. agentscope_runtime/sandbox/box/training_box/environments/bfcl/bfcl_env.py +380 -0
  13. agentscope_runtime/sandbox/box/training_box/environments/bfcl/env_handler.py +934 -0
  14. agentscope_runtime/sandbox/box/training_box/training_box.py +139 -9
  15. agentscope_runtime/sandbox/build.py +1 -1
  16. agentscope_runtime/sandbox/custom/custom_sandbox.py +0 -1
  17. agentscope_runtime/sandbox/custom/example.py +0 -1
  18. agentscope_runtime/sandbox/enums.py +2 -0
  19. agentscope_runtime/sandbox/manager/container_clients/__init__.py +2 -0
  20. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +263 -11
  21. agentscope_runtime/sandbox/manager/container_clients/kubernetes_client.py +605 -0
  22. agentscope_runtime/sandbox/manager/sandbox_manager.py +112 -113
  23. agentscope_runtime/sandbox/manager/server/app.py +96 -28
  24. agentscope_runtime/sandbox/manager/server/config.py +28 -16
  25. agentscope_runtime/sandbox/model/__init__.py +1 -5
  26. agentscope_runtime/sandbox/model/container.py +3 -1
  27. agentscope_runtime/sandbox/model/manager_config.py +21 -15
  28. agentscope_runtime/sandbox/tools/tool.py +111 -0
  29. agentscope_runtime/version.py +1 -1
  30. {agentscope_runtime-0.1.0.dist-info → agentscope_runtime-0.1.2.dist-info}/METADATA +79 -13
  31. {agentscope_runtime-0.1.0.dist-info → agentscope_runtime-0.1.2.dist-info}/RECORD +35 -28
  32. agentscope_runtime/sandbox/manager/utils.py +0 -78
  33. {agentscope_runtime-0.1.0.dist-info → agentscope_runtime-0.1.2.dist-info}/WHEEL +0 -0
  34. {agentscope_runtime-0.1.0.dist-info → agentscope_runtime-0.1.2.dist-info}/entry_points.txt +0 -0
  35. {agentscope_runtime-0.1.0.dist-info → agentscope_runtime-0.1.2.dist-info}/licenses/LICENSE +0 -0
  36. {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 uuid import uuid4
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 = DEFAULT_LOCAL_MANAGER_CONFIG,
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
- logger.error(f"Error making request: {e}")
211
- return {"data": f"Error: {e}"}
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
- session_id = str(uuid4())
416
+ alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"
417
+ short_uuid = shortuuid.ShortUUID(alphabet=alphabet).uuid()
418
+ session_id = str(short_uuid)
418
419
 
419
- if mount_dir is None:
420
- mount_dir = os.path.join(self.default_mount_dir, session_id)
421
- os.makedirs(mount_dir, exist_ok=True)
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 not os.path.isabs(mount_dir):
424
- mount_dir = os.path.abspath(mount_dir)
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
- storage_path = self.storage.path_join(
428
- self.storage_folder,
429
- session_id,
430
- )
430
+ if self.storage_folder:
431
+ storage_path = self.storage.path_join(
432
+ self.storage_folder,
433
+ session_id,
434
+ )
431
435
 
432
- self.storage.download_folder(storage_path, mount_dir)
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
- volume_bindings = {
453
- mount_dir: {
454
- "bind": self.workdir,
455
- "mode": "rw",
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
- if not self.client.create(
461
+ _id, ports, ip = self.client.create(
460
462
  image,
461
463
  name=container_name,
462
- ports=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
- # Build the ContainerModel
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=container_attrs["Id"], # Docker id pattern
488
+ container_id=_id,
487
489
  container_name=container_name,
488
- base_url=f"http://localhost:{ports['80/tcp']}/fastapi",
489
- browser_url=f"http://localhost:{ports['80/tcp']}/steel-api"
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://localhost:"
492
- f"{ports['80/tcp']}/steel-api/"
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://localhost:"
495
- f"{ports['80/tcp']}/steel-api/{runtime_token}/&sessionId"
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://localhost" f":{ports['80/tcp']}/v1",
498
- ports=free_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
- self.storage.upload_folder(
548
- container_info.mount_dir,
549
- container_info.storage_path,
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 "appworld" in container_model.version:
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=f"http://localhost:{container_model.ports[0]}",
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
- import traceback
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 settings
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 = SandboxManagerEnvConfig(
47
- container_prefix_key=settings.CONTAINER_PREFIX_KEY,
48
- file_system=settings.FILE_SYSTEM,
49
- redis_enabled=settings.REDIS_ENABLED,
50
- container_deployment=settings.CONTAINER_DEPLOYMENT,
51
- default_mount_dir=settings.DEFAULT_MOUNT_DIR,
52
- storage_folder=settings.STORAGE_FOLDER,
53
- port_range=settings.PORT_RANGE,
54
- pool_size=settings.POOL_SIZE,
55
- oss_endpoint=settings.OSS_ENDPOINT,
56
- oss_access_key_id=settings.OSS_ACCESS_KEY_ID,
57
- oss_access_key_secret=settings.OSS_ACCESS_KEY_SECRET,
58
- oss_bucket_name=settings.OSS_BUCKET_NAME,
59
- redis_server=settings.REDIS_SERVER,
60
- redis_port=settings.REDIS_PORT,
61
- redis_db=settings.REDIS_DB,
62
- redis_user=settings.REDIS_USER,
63
- redis_password=settings.REDIS_PASSWORD,
64
- redis_port_key=settings.REDIS_PORT_KEY,
65
- redis_container_pool_key=settings.REDIS_CONTAINER_POOL_KEY,
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=_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
- logger.error(
139
+ error = (
124
140
  f"Error in {method.__name__}: {str(e)},"
125
- f" {traceback.format_exc()}",
141
+ # f" {traceback.format_exc()}"
126
142
  )
127
- raise HTTPException(status_code=500, detail=str(e)) from e
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
- class Config:
57
- env_file = env_file if os.path.exists(env_file) else env_example_file
58
- case_sensitive = True
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
- settings = Settings()
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"