Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cookbook/zh/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ agent = AgnoAgent(
print("✅ Agno agent created successfully")
```

(autogen-agent-zh)=

#### (可选)使用AutoGen Agent

````{note}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,12 @@ def _try_pull_from_acr(self, image):
acr_registry = "agentscope-registry.ap-southeast-1.cr.aliyuncs.com"
acr_image = f"{acr_registry}/{image}"

logger.debug(f"Attempting to pull from ACR: {acr_image}")
logger.info(
f"Attempting to pull from ACR: {acr_image}, it might take "
f"several minutes.",
)
self.client.images.pull(acr_image)
logger.debug(f"Successfully pulled image from ACR: {acr_image}")
logger.info(f"Successfully pulled image from ACR: {acr_image}")

# Retag the image
acr_img_obj = self.client.images.get(acr_image)
Expand Down Expand Up @@ -265,11 +268,15 @@ def create(
self.client.images.get(image)
logger.debug(f"Image '{image}' found locally.")
except docker.errors.ImageNotFound:
logger.debug(
logger.info(
f"Image '{image}' not found locally. "
f"Attempting to pull it...",
)
try:
logger.info(
f"Attempting to pull: {image}, "
f"it might take several minutes.",
)
self.client.images.pull(image)
logger.debug(
f"Image '{image}' successfully pulled from default "
Expand All @@ -280,7 +287,7 @@ def create(
logger.warning(
f"Failed to pull from default registry: {e}",
)
logger.debug("Trying to pull from ACR fallback...")
logger.warning("Trying to pull from ACR fallback...")

pull_success = self._try_pull_from_acr(image)

Expand All @@ -289,11 +296,11 @@ def create(
f"Failed to pull image '{image}' from both "
f"default and ACR",
)
return False
return None, None, None

except docker.errors.APIError as e:
logger.error(f"Error occurred while checking the image: {e}")
return False
return None, None, None

# Create and run the container
container = self.client.containers.run(
Expand All @@ -307,10 +314,10 @@ def create(
)
container.reload()
_id = container.id
return _id, list(port_mapping.values())
return _id, list(port_mapping.values()), "localhost"
except Exception as e:
logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
return None, None
return None, None, None

def start(self, container_id):
"""Start a Docker container."""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# pylint: disable=too-many-branches
import os
import time
import hashlib
import traceback
Expand Down Expand Up @@ -89,7 +90,9 @@ def _create_pod_spec(
runtime_config = {}

container_name = name or "main-container"

# Container specification
# TODO: use image from docker registry first
container = client.V1Container(
name=container_name,
image=f"agentscope-registry.ap-southeast-1.cr.aliyuncs.com"
Expand Down Expand Up @@ -244,6 +247,7 @@ def create(
)

exposed_ports = []
pod_node_ip = "localhost"
# Auto-create services for exposed ports (like Docker's port
# mapping)
if ports:
Expand All @@ -259,19 +263,22 @@ def create(
parsed_ports,
)
if service_created:
exposed_ports = self._get_service_node_ports(name)
(
exposed_ports,
pod_node_ip,
) = self._get_service_node_ports(name)
logger.debug(
f"Pod '{name}' created with exposed ports: {exposed_ports}",
)

if not self.wait_for_pod_ready(name, timeout=60):
logger.error(f"Pod '{name}' failed to become ready")
return None, None
return None, None, None
Comment thread
rayrayraykk marked this conversation as resolved.

return name, exposed_ports
return name, exposed_ports, pod_node_ip
except Exception as e:
logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
return None, None
return None, None, None

def start(self, container_id):
"""
Expand Down Expand Up @@ -510,7 +517,10 @@ def _create_multi_port_service(self, pod_name, port_list):
service = client.V1Service(
api_version="v1",
kind="Service",
metadata=client.V1ObjectMeta(name=service_name),
metadata=client.V1ObjectMeta(
name=service_name,
namespace=self.namespace,
),
spec=service_spec,
)

Expand Down Expand Up @@ -540,11 +550,56 @@ def _get_service_node_ports(self, pod_name):
)

node_ports = []
pod_node_ip = self._get_pod_node_ip(pod_name)

for port in service_info.spec.ports:
if port.node_port:
node_ports.append(port.node_port)

return node_ports
return node_ports, pod_node_ip
except Exception as e:
logger.error(f"Failed to get node port: {e}")
return None

def _get_pod_node_ip(self, pod_name):
"""Get the IP of the node where the pod is running"""

# Check if we are running in Colima, where pod runs in VM
docker_host = os.getenv("DOCKER_HOST", "")
if "colima" in docker_host.lower():
return "localhost"

try:
pod = self.v1.read_namespaced_pod(
name=pod_name,
namespace=self.namespace,
)

node_name = pod.spec.node_name
if not node_name:
logger.warning(
f"Pod {pod_name} is not scheduled to any node yet",
)
return None

node = self.v1.read_node(name=node_name)

external_ip = None
internal_ip = None

for address in node.status.addresses:
if address.type == "ExternalIP":
external_ip = address.address
elif address.type == "InternalIP":
internal_ip = address.address

result_ip = external_ip or internal_ip
logger.debug(
f"Using IP: {result_ip} (external: {external_ip}, internal:"
f" {internal_ip})",
)
return result_ip

except Exception as e:
logger.error(f"Failed to get pod node IP: {e}")
return None
93 changes: 61 additions & 32 deletions src/agentscope_runtime/sandbox/manager/sandbox_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@

from functools import wraps
from typing import Optional, Dict
from urllib.parse import urlparse, urlunparse

import shortuuid
import requests

from ..model import (
ContainerModel,
SandboxManagerEnvConfig,
DEFAULT_LOCAL_MANAGER_CONFIG,
)
from ..enums import SandboxType
from ..registry import SandboxRegistry
Expand Down Expand Up @@ -82,7 +82,7 @@ def wrapper(self, *args, **kwargs):
class SandboxManager:
def __init__(
self,
config: SandboxManagerEnvConfig = DEFAULT_LOCAL_MANAGER_CONFIG,
config: Optional[SandboxManagerEnvConfig] = None,
base_url=None,
bearer_token=None,
default_type: SandboxType | str = SandboxType.BASE,
Expand All @@ -97,19 +97,28 @@ def __init__(
self.http_session.headers.update(
{"Authorization": f"Bearer {bearer_token}"},
)
# Remote mode, return directly
return
else:
self.http_session = None
self.base_url = None

if not config:
config = SandboxManagerEnvConfig(
file_system="local",
redis_enabled=False,
container_deployment="docker",
pool_size=0,
default_mount_dir="sessions_mount_dir",
)

self.default_type = SandboxType(default_type)
self.workdir = "/workspace"

self.config = config
self.pool_size = self.config.pool_size
self.prefix = self.config.container_prefix_key
self.default_mount_dir = (
self.config.default_mount_dir or "sessions_mount_dir"
)
self.default_mount_dir = self.config.default_mount_dir
self.storage_folder = (
Comment thread
rayrayraykk marked this conversation as resolved.
self.config.storage_folder or self.default_mount_dir
)
Expand Down Expand Up @@ -408,20 +417,24 @@ def create(
short_uuid = shortuuid.ShortUUID(alphabet=alphabet).uuid()
session_id = str(short_uuid)

if mount_dir is None:
mount_dir = os.path.join(self.default_mount_dir, session_id)
os.makedirs(mount_dir, exist_ok=True)
if not mount_dir:
if self.default_mount_dir:
mount_dir = os.path.join(self.default_mount_dir, session_id)
os.makedirs(mount_dir, exist_ok=True)

if not os.path.isabs(mount_dir):
mount_dir = os.path.abspath(mount_dir)
if mount_dir:
if not os.path.isabs(mount_dir):
mount_dir = os.path.abspath(mount_dir)

if storage_path is None:
storage_path = self.storage.path_join(
self.storage_folder,
session_id,
)
if self.storage_folder:
storage_path = self.storage.path_join(
self.storage_folder,
session_id,
)

self.storage.download_folder(storage_path, mount_dir)
if mount_dir and storage_path:
self.storage.download_folder(storage_path, mount_dir)

try:
# Check for an existing container with the same name
Expand All @@ -435,14 +448,17 @@ def create(
runtime_token = secrets.token_hex(16)

# Prepare volume bindings if a mount directory is provided
volume_bindings = {
mount_dir: {
"bind": self.workdir,
"mode": "rw",
},
}
if mount_dir:
volume_bindings = {
mount_dir: {
"bind": self.workdir,
"mode": "rw",
},
}
else:
volume_bindings = {}

_id, ports = self.client.create(
_id, ports, ip = self.client.create(
Comment thread
rayrayraykk marked this conversation as resolved.
image,
name=container_name,
ports=["80/tcp"], # Nginx
Expand Down Expand Up @@ -471,16 +487,16 @@ def create(
session_id=session_id,
container_id=_id,
container_name=container_name,
base_url=f"http://localhost:{ports[0]}/fastapi",
browser_url=f"http://localhost:{ports[0]}/steel-api"
base_url=f"http://{ip}:{ports[0]}/fastapi",
browser_url=f"http://{ip}:{ports[0]}/steel-api"
f"/{runtime_token}",
front_browser_ws=f"ws://localhost:"
front_browser_ws=f"ws://{ip}:"
f"{ports[0]}/steel-api/"
f"{runtime_token}/v1/sessions/cast",
client_browser_ws=f"ws://localhost:"
client_browser_ws=f"ws://{ip}:"
f"{ports[0]}/steel-api/{runtime_token}/&sessionId"
f"={BROWSER_SESSION_ID}",
artifacts_sio=f"http://localhost:{ports[0]}/v1",
artifacts_sio=f"http://{ip}:{ports[0]}/v1",
ports=[ports[0]],
mount_dir=str(mount_dir),
Comment thread
rayrayraykk marked this conversation as resolved.
storage_path=storage_path,
Expand Down Expand Up @@ -526,10 +542,11 @@ def release(self, identity):
logger.debug(f"Container for {identity} destroyed.")

# Upload to storage
self.storage.upload_folder(
container_info.mount_dir,
container_info.storage_path,
)
if container_info.mount_dir and container_info.storage_path:
self.storage.upload_folder(
container_info.mount_dir,
container_info.storage_path,
)

return True
except Exception as e:
Expand Down Expand Up @@ -631,8 +648,20 @@ def _establish_connection(self, identity):
"sandbox-appworld" in container_model.version
or "sandbox-bfcl" in container_model.version
):
parsed = urlparse(container_model.base_url)
base_url = urlunparse(
(
parsed.scheme,
parsed.netloc,
"",
"",
"",
"",
),
)

return TrainingSandboxClient(
base_url=f"http://localhost:{container_model.ports[0]}",
base_url=base_url,
).__enter__()

return SandboxHttpClient(
Expand Down
Loading