|
|
@@ -12,7 +12,8 @@ from agent.tools.models import ToolResult
|
|
|
|
|
|
|
|
|
# 服务地址,可根据实际部署情况修改
|
|
|
-SANDBOX_SERVER_URL = "http://61.48.133.26:9999"
|
|
|
+# SANDBOX_SERVER_URL = "http://192.168.100.20:9998"
|
|
|
+SANDBOX_SERVER_URL = "http://61.48.133.26:9998"
|
|
|
|
|
|
# 默认超时时间(秒)
|
|
|
DEFAULT_TIMEOUT = 300.0
|
|
|
@@ -26,7 +27,9 @@ DEFAULT_TIMEOUT = 300.0
|
|
|
"image": "Docker 镜像",
|
|
|
"mem_limit": "内存限制",
|
|
|
"nano_cpus": "CPU 限制",
|
|
|
- "ports": "端口列表"
|
|
|
+ "ports": "端口列表",
|
|
|
+ "use_gpu": "启用 GPU",
|
|
|
+ "gpu_count": "GPU 数量"
|
|
|
}
|
|
|
},
|
|
|
"en": {
|
|
|
@@ -35,7 +38,9 @@ DEFAULT_TIMEOUT = 300.0
|
|
|
"image": "Docker image",
|
|
|
"mem_limit": "Memory limit",
|
|
|
"nano_cpus": "CPU limit",
|
|
|
- "ports": "Port list"
|
|
|
+ "ports": "Port list",
|
|
|
+ "use_gpu": "Enable GPU",
|
|
|
+ "gpu_count": "GPU count"
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -45,8 +50,11 @@ async def sandbox_create_environment(
|
|
|
mem_limit: str = "512m",
|
|
|
nano_cpus: int = 500000000,
|
|
|
ports: Optional[List[int]] = None,
|
|
|
- uid: str = ""
|
|
|
-) -> ToolResult:
|
|
|
+ use_gpu: bool = False,
|
|
|
+ gpu_count: int = -1,
|
|
|
+ server_url: str = None,
|
|
|
+ timeout: float = DEFAULT_TIMEOUT
|
|
|
+) -> Dict[str, Any]:
|
|
|
"""
|
|
|
创建一个隔离的 Docker 开发环境。
|
|
|
|
|
|
@@ -56,47 +64,37 @@ async def sandbox_create_environment(
|
|
|
mem_limit: 容器最大内存限制,默认为 "512m"。
|
|
|
nano_cpus: 容器最大 CPU 限制(纳秒),默认为 500000000(0.5 CPU)。
|
|
|
ports: 需要映射的端口列表,如 [8080, 3000]。
|
|
|
- uid: 用户ID(自动注入)
|
|
|
+ use_gpu: 是否启用 GPU 支持,默认为 False。需要宿主机安装 nvidia-container-toolkit。
|
|
|
+ gpu_count: 使用的 GPU 数量,-1 表示使用所有可用 GPU,默认为 -1。
|
|
|
+ server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
|
|
|
+ timeout: 请求超时时间(秒),默认 300 秒。
|
|
|
|
|
|
Returns:
|
|
|
- ToolResult 包含:
|
|
|
+ dict: 包含以下字段:
|
|
|
- sandbox_id (str): 沙盒唯一标识,后续操作需要用到
|
|
|
- message (str): 提示信息
|
|
|
- port_mapping (dict): 端口映射关系,如 {8080: 32001}
|
|
|
- access_urls (list): 访问 URL 列表
|
|
|
- """
|
|
|
- try:
|
|
|
- url = f"{SANDBOX_SERVER_URL}/api/create_environment"
|
|
|
- payload = {
|
|
|
- "image": image,
|
|
|
- "mem_limit": mem_limit,
|
|
|
- "nano_cpus": nano_cpus
|
|
|
- }
|
|
|
- if ports:
|
|
|
- payload["ports"] = ports
|
|
|
|
|
|
- async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- data = response.json()
|
|
|
+ Raises:
|
|
|
+ httpx.HTTPStatusError: HTTP 请求返回错误状态码时抛出
|
|
|
+ httpx.RequestError: 网络请求失败时抛出
|
|
|
+ """
|
|
|
+ url = f"{server_url or SANDBOX_SERVER_URL}/api/create_environment"
|
|
|
+ payload = {
|
|
|
+ "image": image,
|
|
|
+ "mem_limit": mem_limit,
|
|
|
+ "nano_cpus": nano_cpus,
|
|
|
+ "use_gpu": use_gpu,
|
|
|
+ "gpu_count": gpu_count
|
|
|
+ }
|
|
|
+ if ports:
|
|
|
+ payload["ports"] = ports
|
|
|
|
|
|
- return ToolResult(
|
|
|
- title="Sandbox Created",
|
|
|
- output=json.dumps(data, ensure_ascii=False, indent=2),
|
|
|
- long_term_memory=f"Created sandbox: {data.get('sandbox_id', 'unknown')}"
|
|
|
- )
|
|
|
- except httpx.HTTPStatusError as e:
|
|
|
- return ToolResult(
|
|
|
- title="Create Sandbox Failed",
|
|
|
- output="",
|
|
|
- error=f"HTTP error {e.response.status_code}: {e.response.text}"
|
|
|
- )
|
|
|
- except Exception as e:
|
|
|
- return ToolResult(
|
|
|
- title="Create Sandbox Failed",
|
|
|
- output="",
|
|
|
- error=str(e)
|
|
|
- )
|
|
|
+ async with httpx.AsyncClient(timeout=timeout) as client:
|
|
|
+ response = await client.post(url, json=payload)
|
|
|
+ response.raise_for_status()
|
|
|
+ return response.json()
|
|
|
|
|
|
|
|
|
@tool(
|
|
|
@@ -126,8 +124,9 @@ async def sandbox_run_shell(
|
|
|
command: str,
|
|
|
is_background: bool = False,
|
|
|
timeout: int = 120,
|
|
|
- uid: str = ""
|
|
|
-) -> ToolResult:
|
|
|
+ server_url: str = None,
|
|
|
+ request_timeout: float = DEFAULT_TIMEOUT
|
|
|
+) -> Dict[str, Any]:
|
|
|
"""
|
|
|
在指定的沙盒中执行 Shell 命令。
|
|
|
|
|
|
@@ -138,10 +137,11 @@ async def sandbox_run_shell(
|
|
|
- False:前台执行,等待命令完成并返回输出
|
|
|
- True:后台执行,适合启动长期运行的服务
|
|
|
timeout: 前台命令的超时时间(秒),默认 120 秒。后台命令不受此限制。
|
|
|
- uid: 用户ID(自动注入)
|
|
|
+ server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
|
|
|
+ request_timeout: HTTP 请求超时时间(秒),默认 300 秒。
|
|
|
|
|
|
Returns:
|
|
|
- ToolResult 包含:
|
|
|
+ dict: 根据执行方式返回不同内容:
|
|
|
前台执行:
|
|
|
- exit_code (int): 命令退出码
|
|
|
- stdout (str): 标准输出
|
|
|
@@ -150,38 +150,23 @@ async def sandbox_run_shell(
|
|
|
- status (str): 状态
|
|
|
- message (str): 提示信息
|
|
|
- log_file (str): 日志文件路径
|
|
|
- """
|
|
|
- try:
|
|
|
- url = f"{SANDBOX_SERVER_URL}/api/run_shell"
|
|
|
- payload = {
|
|
|
- "sandbox_id": sandbox_id,
|
|
|
- "command": command,
|
|
|
- "is_background": is_background,
|
|
|
- "timeout": timeout
|
|
|
- }
|
|
|
|
|
|
- async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- data = response.json()
|
|
|
+ Raises:
|
|
|
+ httpx.HTTPStatusError: HTTP 请求返回错误状态码时抛出
|
|
|
+ httpx.RequestError: 网络请求失败时抛出
|
|
|
+ """
|
|
|
+ url = f"{server_url or SANDBOX_SERVER_URL}/api/run_shell"
|
|
|
+ payload = {
|
|
|
+ "sandbox_id": sandbox_id,
|
|
|
+ "command": command,
|
|
|
+ "is_background": is_background,
|
|
|
+ "timeout": timeout
|
|
|
+ }
|
|
|
|
|
|
- return ToolResult(
|
|
|
- title=f"Shell: {command[:50]}{'...' if len(command) > 50 else ''}",
|
|
|
- output=json.dumps(data, ensure_ascii=False, indent=2),
|
|
|
- long_term_memory=f"Executed in sandbox {sandbox_id}: {command[:100]}"
|
|
|
- )
|
|
|
- except httpx.HTTPStatusError as e:
|
|
|
- return ToolResult(
|
|
|
- title="Run Shell Failed",
|
|
|
- output="",
|
|
|
- error=f"HTTP error {e.response.status_code}: {e.response.text}"
|
|
|
- )
|
|
|
- except Exception as e:
|
|
|
- return ToolResult(
|
|
|
- title="Run Shell Failed",
|
|
|
- output="",
|
|
|
- error=str(e)
|
|
|
- )
|
|
|
+ async with httpx.AsyncClient(timeout=request_timeout) as client:
|
|
|
+ response = await client.post(url, json=payload)
|
|
|
+ response.raise_for_status()
|
|
|
+ return response.json()
|
|
|
|
|
|
|
|
|
@tool(
|
|
|
@@ -192,7 +177,9 @@ async def sandbox_run_shell(
|
|
|
"sandbox_id": "沙盒 ID",
|
|
|
"ports": "端口列表",
|
|
|
"mem_limit": "内存限制",
|
|
|
- "nano_cpus": "CPU 限制"
|
|
|
+ "nano_cpus": "CPU 限制",
|
|
|
+ "use_gpu": "启用 GPU",
|
|
|
+ "gpu_count": "GPU 数量"
|
|
|
}
|
|
|
},
|
|
|
"en": {
|
|
|
@@ -201,7 +188,9 @@ async def sandbox_run_shell(
|
|
|
"sandbox_id": "Sandbox ID",
|
|
|
"ports": "Port list",
|
|
|
"mem_limit": "Memory limit",
|
|
|
- "nano_cpus": "CPU limit"
|
|
|
+ "nano_cpus": "CPU limit",
|
|
|
+ "use_gpu": "Enable GPU",
|
|
|
+ "gpu_count": "GPU count"
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -211,8 +200,11 @@ async def sandbox_rebuild_with_ports(
|
|
|
ports: List[int],
|
|
|
mem_limit: str = "1g",
|
|
|
nano_cpus: int = 1000000000,
|
|
|
- uid: str = ""
|
|
|
-) -> ToolResult:
|
|
|
+ use_gpu: bool = False,
|
|
|
+ gpu_count: int = -1,
|
|
|
+ server_url: str = None,
|
|
|
+ timeout: float = DEFAULT_TIMEOUT
|
|
|
+) -> Dict[str, Any]:
|
|
|
"""
|
|
|
重建沙盒并应用新的端口映射。
|
|
|
|
|
|
@@ -227,46 +219,36 @@ async def sandbox_rebuild_with_ports(
|
|
|
ports: 需要映射的端口列表,如 [8080, 3306, 6379]。
|
|
|
mem_limit: 容器最大内存限制,默认为 "1g"。
|
|
|
nano_cpus: 容器最大 CPU 限制(纳秒),默认为 1000000000(1 CPU)。
|
|
|
- uid: 用户ID(自动注入)
|
|
|
+ use_gpu: 是否启用 GPU 支持,默认为 False。需要宿主机安装 nvidia-container-toolkit。
|
|
|
+ gpu_count: 使用的 GPU 数量,-1 表示使用所有可用 GPU,默认为 -1。
|
|
|
+ server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
|
|
|
+ timeout: 请求超时时间(秒),默认 300 秒。
|
|
|
|
|
|
Returns:
|
|
|
- ToolResult 包含:
|
|
|
+ dict: 包含以下字段:
|
|
|
- old_sandbox_id (str): 旧沙盒 ID(已销毁)
|
|
|
- new_sandbox_id (str): 新沙盒 ID(后续使用这个)
|
|
|
- port_mapping (dict): 端口映射关系
|
|
|
- access_urls (list): 访问 URL 列表
|
|
|
- """
|
|
|
- try:
|
|
|
- url = f"{SANDBOX_SERVER_URL}/api/rebuild_with_ports"
|
|
|
- payload = {
|
|
|
- "sandbox_id": sandbox_id,
|
|
|
- "ports": ports,
|
|
|
- "mem_limit": mem_limit,
|
|
|
- "nano_cpus": nano_cpus
|
|
|
- }
|
|
|
|
|
|
- async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- data = response.json()
|
|
|
+ Raises:
|
|
|
+ httpx.HTTPStatusError: HTTP 请求返回错误状态码时抛出
|
|
|
+ httpx.RequestError: 网络请求失败时抛出
|
|
|
+ """
|
|
|
+ url = f"{server_url or SANDBOX_SERVER_URL}/api/rebuild_with_ports"
|
|
|
+ payload = {
|
|
|
+ "sandbox_id": sandbox_id,
|
|
|
+ "ports": ports,
|
|
|
+ "mem_limit": mem_limit,
|
|
|
+ "nano_cpus": nano_cpus,
|
|
|
+ "use_gpu": use_gpu,
|
|
|
+ "gpu_count": gpu_count
|
|
|
+ }
|
|
|
|
|
|
- return ToolResult(
|
|
|
- title="Sandbox Rebuilt",
|
|
|
- output=json.dumps(data, ensure_ascii=False, indent=2),
|
|
|
- long_term_memory=f"Rebuilt sandbox {sandbox_id} -> {data.get('new_sandbox_id', 'unknown')} with ports {ports}"
|
|
|
- )
|
|
|
- except httpx.HTTPStatusError as e:
|
|
|
- return ToolResult(
|
|
|
- title="Rebuild Sandbox Failed",
|
|
|
- output="",
|
|
|
- error=f"HTTP error {e.response.status_code}: {e.response.text}"
|
|
|
- )
|
|
|
- except Exception as e:
|
|
|
- return ToolResult(
|
|
|
- title="Rebuild Sandbox Failed",
|
|
|
- output="",
|
|
|
- error=str(e)
|
|
|
- )
|
|
|
+ async with httpx.AsyncClient(timeout=timeout) as client:
|
|
|
+ response = await client.post(url, json=payload)
|
|
|
+ response.raise_for_status()
|
|
|
+ return response.json()
|
|
|
|
|
|
|
|
|
@tool(
|
|
|
@@ -288,146 +270,33 @@ async def sandbox_rebuild_with_ports(
|
|
|
)
|
|
|
async def sandbox_destroy_environment(
|
|
|
sandbox_id: str,
|
|
|
- uid: str = ""
|
|
|
-) -> ToolResult:
|
|
|
+ server_url: str = None,
|
|
|
+ timeout: float = DEFAULT_TIMEOUT
|
|
|
+) -> Dict[str, Any]:
|
|
|
"""
|
|
|
销毁沙盒环境,释放资源。
|
|
|
|
|
|
Args:
|
|
|
sandbox_id: 沙盒 ID。
|
|
|
- uid: 用户ID(自动注入)
|
|
|
+ server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
|
|
|
+ timeout: 请求超时时间(秒),默认 300 秒。
|
|
|
|
|
|
Returns:
|
|
|
- ToolResult 包含:
|
|
|
+ dict: 包含以下字段:
|
|
|
- status (str): 操作状态,如 "success"
|
|
|
- message (str): 提示信息
|
|
|
- removed_tools (list): 被移除的工具列表(如有关联的已注册工具)
|
|
|
- """
|
|
|
- try:
|
|
|
- url = f"{SANDBOX_SERVER_URL}/api/destroy_environment"
|
|
|
- payload = {
|
|
|
- "sandbox_id": sandbox_id
|
|
|
- }
|
|
|
-
|
|
|
- async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- data = response.json()
|
|
|
-
|
|
|
- return ToolResult(
|
|
|
- title="Sandbox Destroyed",
|
|
|
- output=json.dumps(data, ensure_ascii=False, indent=2),
|
|
|
- long_term_memory=f"Destroyed sandbox: {sandbox_id}"
|
|
|
- )
|
|
|
- except httpx.HTTPStatusError as e:
|
|
|
- return ToolResult(
|
|
|
- title="Destroy Sandbox Failed",
|
|
|
- output="",
|
|
|
- error=f"HTTP error {e.response.status_code}: {e.response.text}"
|
|
|
- )
|
|
|
- except Exception as e:
|
|
|
- return ToolResult(
|
|
|
- title="Destroy Sandbox Failed",
|
|
|
- output="",
|
|
|
- error=str(e)
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-@tool(
|
|
|
- display={
|
|
|
- "zh": {
|
|
|
- "name": "注册沙盒工具",
|
|
|
- "params": {
|
|
|
- "tool_name": "工具名称",
|
|
|
- "description": "工具描述",
|
|
|
- "input_schema": "参数定义",
|
|
|
- "sandbox_id": "沙盒 ID",
|
|
|
- "internal_port": "内部端口",
|
|
|
- "endpoint_path": "API 路径",
|
|
|
- "http_method": "HTTP 方法"
|
|
|
- }
|
|
|
- },
|
|
|
- "en": {
|
|
|
- "name": "Register Sandbox Tool",
|
|
|
- "params": {
|
|
|
- "tool_name": "Tool name",
|
|
|
- "description": "Tool description",
|
|
|
- "input_schema": "Input schema",
|
|
|
- "sandbox_id": "Sandbox ID",
|
|
|
- "internal_port": "Internal port",
|
|
|
- "endpoint_path": "API path",
|
|
|
- "http_method": "HTTP method"
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-)
|
|
|
-async def sandbox_register_tool(
|
|
|
- tool_name: str,
|
|
|
- description: str,
|
|
|
- input_schema: Dict[str, Any],
|
|
|
- sandbox_id: str,
|
|
|
- internal_port: int,
|
|
|
- endpoint_path: str = "/",
|
|
|
- http_method: str = "POST",
|
|
|
- metadata: Optional[Dict[str, Any]] = None,
|
|
|
- uid: str = ""
|
|
|
-) -> ToolResult:
|
|
|
- """
|
|
|
- 将部署好的服务注册为工具。
|
|
|
-
|
|
|
- 注册后,该工具会出现在统一 MCP Server 的工具列表中,可被上游服务调用。
|
|
|
-
|
|
|
- Args:
|
|
|
- tool_name: 工具唯一标识(字母开头,只能包含字母、数字、下划线),
|
|
|
- 如 "rendercv_api"。
|
|
|
- description: 工具描述,描述该工具的功能。
|
|
|
- input_schema: JSON Schema 格式的参数定义,定义工具接收的参数。
|
|
|
- sandbox_id: 服务所在的沙盒 ID。
|
|
|
- internal_port: 服务在容器内的端口。
|
|
|
- endpoint_path: API 路径,默认 "/"。
|
|
|
- http_method: HTTP 方法,默认 "POST"。
|
|
|
- metadata: 额外元数据(可选)。
|
|
|
- uid: 用户ID(自动注入)
|
|
|
|
|
|
- Returns:
|
|
|
- ToolResult 包含:
|
|
|
- - status (str): 操作状态,"success" 或 "error"
|
|
|
- - message (str): 提示信息
|
|
|
- - tool_info (dict): 工具信息(成功时)
|
|
|
+ Raises:
|
|
|
+ httpx.HTTPStatusError: HTTP 请求返回错误状态码时抛出
|
|
|
+ httpx.RequestError: 网络请求失败时抛出
|
|
|
"""
|
|
|
- try:
|
|
|
- url = f"{SANDBOX_SERVER_URL}/api/register_tool"
|
|
|
- payload = {
|
|
|
- "tool_name": tool_name,
|
|
|
- "description": description,
|
|
|
- "input_schema": input_schema,
|
|
|
- "sandbox_id": sandbox_id,
|
|
|
- "internal_port": internal_port,
|
|
|
- "endpoint_path": endpoint_path,
|
|
|
- "http_method": http_method
|
|
|
- }
|
|
|
- if metadata:
|
|
|
- payload["metadata"] = metadata
|
|
|
-
|
|
|
- async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- data = response.json()
|
|
|
+ url = f"{server_url or SANDBOX_SERVER_URL}/api/destroy_environment"
|
|
|
+ payload = {
|
|
|
+ "sandbox_id": sandbox_id
|
|
|
+ }
|
|
|
|
|
|
- return ToolResult(
|
|
|
- title=f"Tool Registered: {tool_name}",
|
|
|
- output=json.dumps(data, ensure_ascii=False, indent=2),
|
|
|
- long_term_memory=f"Registered tool '{tool_name}' on sandbox {sandbox_id}:{internal_port}"
|
|
|
- )
|
|
|
- except httpx.HTTPStatusError as e:
|
|
|
- return ToolResult(
|
|
|
- title="Register Tool Failed",
|
|
|
- output="",
|
|
|
- error=f"HTTP error {e.response.status_code}: {e.response.text}"
|
|
|
- )
|
|
|
- except Exception as e:
|
|
|
- return ToolResult(
|
|
|
- title="Register Tool Failed",
|
|
|
- output="",
|
|
|
- error=str(e)
|
|
|
- )
|
|
|
+ async with httpx.AsyncClient(timeout=timeout) as client:
|
|
|
+ response = await client.post(url, json=payload)
|
|
|
+ response.raise_for_status()
|
|
|
+ return response.json()
|