|
|
@@ -7,8 +7,7 @@ import json
|
|
|
import httpx
|
|
|
from typing import Optional, List, Dict, Any
|
|
|
|
|
|
-from agent import tool
|
|
|
-from agent.tools.models import ToolResult
|
|
|
+from agent.tools import tool, ToolResult, ToolContext
|
|
|
|
|
|
|
|
|
# 服务地址,可根据实际部署情况修改
|
|
|
@@ -53,8 +52,10 @@ async def sandbox_create_environment(
|
|
|
use_gpu: bool = False,
|
|
|
gpu_count: int = -1,
|
|
|
server_url: str = None,
|
|
|
- timeout: float = DEFAULT_TIMEOUT
|
|
|
-) -> Dict[str, Any]:
|
|
|
+ timeout: float = DEFAULT_TIMEOUT,
|
|
|
+ context: Optional[ToolContext] = None,
|
|
|
+ uid: str = "",
|
|
|
+) -> ToolResult:
|
|
|
"""
|
|
|
创建一个隔离的 Docker 开发环境。
|
|
|
|
|
|
@@ -68,17 +69,10 @@ async def sandbox_create_environment(
|
|
|
gpu_count: 使用的 GPU 数量,-1 表示使用所有可用 GPU,默认为 -1。
|
|
|
server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
|
|
|
timeout: 请求超时时间(秒),默认 300 秒。
|
|
|
+ context: 工具上下文
|
|
|
|
|
|
Returns:
|
|
|
- dict: 包含以下字段:
|
|
|
- - sandbox_id (str): 沙盒唯一标识,后续操作需要用到
|
|
|
- - message (str): 提示信息
|
|
|
- - port_mapping (dict): 端口映射关系,如 {8080: 32001}
|
|
|
- - access_urls (list): 访问 URL 列表
|
|
|
-
|
|
|
- Raises:
|
|
|
- httpx.HTTPStatusError: HTTP 请求返回错误状态码时抛出
|
|
|
- httpx.RequestError: 网络请求失败时抛出
|
|
|
+ ToolResult: 包含沙盒创建结果
|
|
|
"""
|
|
|
url = f"{server_url or SANDBOX_SERVER_URL}/api/create_environment"
|
|
|
payload = {
|
|
|
@@ -91,10 +85,39 @@ async def sandbox_create_environment(
|
|
|
if ports:
|
|
|
payload["ports"] = ports
|
|
|
|
|
|
- async with httpx.AsyncClient(timeout=timeout) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- return response.json()
|
|
|
+ try:
|
|
|
+ async with httpx.AsyncClient(timeout=timeout) as client:
|
|
|
+ response = await client.post(url, json=payload)
|
|
|
+ response.raise_for_status()
|
|
|
+ data = response.json()
|
|
|
+
|
|
|
+ sandbox_id = data.get("sandbox_id", "")
|
|
|
+ port_mapping = data.get("port_mapping", {})
|
|
|
+ access_urls = data.get("access_urls", [])
|
|
|
+
|
|
|
+ output_parts = [f"沙盒 ID: {sandbox_id}"]
|
|
|
+ if port_mapping:
|
|
|
+ output_parts.append(f"端口映射: {json.dumps(port_mapping)}")
|
|
|
+ if access_urls:
|
|
|
+ output_parts.append(f"访问地址: {', '.join(access_urls)}")
|
|
|
+
|
|
|
+ return ToolResult(
|
|
|
+ title="沙盒环境创建成功",
|
|
|
+ output="\n".join(output_parts),
|
|
|
+ metadata=data
|
|
|
+ )
|
|
|
+ except httpx.HTTPStatusError as e:
|
|
|
+ return ToolResult(
|
|
|
+ title="沙盒创建失败",
|
|
|
+ output=f"HTTP 错误: {e.response.status_code}",
|
|
|
+ error=str(e)
|
|
|
+ )
|
|
|
+ except httpx.RequestError as e:
|
|
|
+ return ToolResult(
|
|
|
+ title="沙盒创建失败",
|
|
|
+ output=f"网络请求失败: {str(e)}",
|
|
|
+ error=str(e)
|
|
|
+ )
|
|
|
|
|
|
|
|
|
@tool(
|
|
|
@@ -125,8 +148,10 @@ async def sandbox_run_shell(
|
|
|
is_background: bool = False,
|
|
|
timeout: int = 120,
|
|
|
server_url: str = None,
|
|
|
- request_timeout: float = DEFAULT_TIMEOUT
|
|
|
-) -> Dict[str, Any]:
|
|
|
+ request_timeout: float = DEFAULT_TIMEOUT,
|
|
|
+ context: Optional[ToolContext] = None,
|
|
|
+ uid: str = "",
|
|
|
+) -> ToolResult:
|
|
|
"""
|
|
|
在指定的沙盒中执行 Shell 命令。
|
|
|
|
|
|
@@ -139,21 +164,10 @@ async def sandbox_run_shell(
|
|
|
timeout: 前台命令的超时时间(秒),默认 120 秒。后台命令不受此限制。
|
|
|
server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
|
|
|
request_timeout: HTTP 请求超时时间(秒),默认 300 秒。
|
|
|
+ context: 工具上下文
|
|
|
|
|
|
Returns:
|
|
|
- dict: 根据执行方式返回不同内容:
|
|
|
- 前台执行:
|
|
|
- - exit_code (int): 命令退出码
|
|
|
- - stdout (str): 标准输出
|
|
|
- - stderr (str): 标准错误
|
|
|
- 后台执行:
|
|
|
- - status (str): 状态
|
|
|
- - message (str): 提示信息
|
|
|
- - log_file (str): 日志文件路径
|
|
|
-
|
|
|
- Raises:
|
|
|
- httpx.HTTPStatusError: HTTP 请求返回错误状态码时抛出
|
|
|
- httpx.RequestError: 网络请求失败时抛出
|
|
|
+ ToolResult: 命令执行结果
|
|
|
"""
|
|
|
url = f"{server_url or SANDBOX_SERVER_URL}/api/run_shell"
|
|
|
payload = {
|
|
|
@@ -163,10 +177,62 @@ async def sandbox_run_shell(
|
|
|
"timeout": timeout
|
|
|
}
|
|
|
|
|
|
- async with httpx.AsyncClient(timeout=request_timeout) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- return response.json()
|
|
|
+ try:
|
|
|
+ async with httpx.AsyncClient(timeout=request_timeout) as client:
|
|
|
+ response = await client.post(url, json=payload)
|
|
|
+ response.raise_for_status()
|
|
|
+ data = response.json()
|
|
|
+
|
|
|
+ if is_background:
|
|
|
+ status = data.get("status", "")
|
|
|
+ message = data.get("message", "")
|
|
|
+ log_file = data.get("log_file", "")
|
|
|
+ output = f"状态: {status}\n消息: {message}"
|
|
|
+ if log_file:
|
|
|
+ output += f"\n日志文件: {log_file}"
|
|
|
+ return ToolResult(
|
|
|
+ title=f"后台命令已启动: {command[:50]}",
|
|
|
+ output=output,
|
|
|
+ metadata=data
|
|
|
+ )
|
|
|
+ else:
|
|
|
+ exit_code = data.get("exit_code", -1)
|
|
|
+ stdout = data.get("stdout", "")
|
|
|
+ stderr = data.get("stderr", "")
|
|
|
+
|
|
|
+ output_parts = []
|
|
|
+ if stdout:
|
|
|
+ output_parts.append(stdout)
|
|
|
+ if stderr:
|
|
|
+ if output_parts:
|
|
|
+ output_parts.append("\n--- stderr ---")
|
|
|
+ output_parts.append(stderr)
|
|
|
+ if not output_parts:
|
|
|
+ output_parts.append("(命令无输出)")
|
|
|
+
|
|
|
+ success = exit_code == 0
|
|
|
+ title = f"命令: {command[:50]}"
|
|
|
+ if not success:
|
|
|
+ title += f" (exit code: {exit_code})"
|
|
|
+
|
|
|
+ return ToolResult(
|
|
|
+ title=title,
|
|
|
+ output="\n".join(output_parts),
|
|
|
+ metadata=data,
|
|
|
+ error=None if success else f"Command failed with exit code {exit_code}"
|
|
|
+ )
|
|
|
+ except httpx.HTTPStatusError as e:
|
|
|
+ return ToolResult(
|
|
|
+ title="命令执行失败",
|
|
|
+ output=f"HTTP 错误: {e.response.status_code}",
|
|
|
+ error=str(e)
|
|
|
+ )
|
|
|
+ except httpx.RequestError as e:
|
|
|
+ return ToolResult(
|
|
|
+ title="命令执行失败",
|
|
|
+ output=f"网络请求失败: {str(e)}",
|
|
|
+ error=str(e)
|
|
|
+ )
|
|
|
|
|
|
|
|
|
@tool(
|
|
|
@@ -203,8 +269,10 @@ async def sandbox_rebuild_with_ports(
|
|
|
use_gpu: bool = False,
|
|
|
gpu_count: int = -1,
|
|
|
server_url: str = None,
|
|
|
- timeout: float = DEFAULT_TIMEOUT
|
|
|
-) -> Dict[str, Any]:
|
|
|
+ timeout: float = DEFAULT_TIMEOUT,
|
|
|
+ context: Optional[ToolContext] = None,
|
|
|
+ uid: str = "",
|
|
|
+) -> ToolResult:
|
|
|
"""
|
|
|
重建沙盒并应用新的端口映射。
|
|
|
|
|
|
@@ -223,17 +291,10 @@ async def sandbox_rebuild_with_ports(
|
|
|
gpu_count: 使用的 GPU 数量,-1 表示使用所有可用 GPU,默认为 -1。
|
|
|
server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
|
|
|
timeout: 请求超时时间(秒),默认 300 秒。
|
|
|
+ context: 工具上下文
|
|
|
|
|
|
Returns:
|
|
|
- dict: 包含以下字段:
|
|
|
- - old_sandbox_id (str): 旧沙盒 ID(已销毁)
|
|
|
- - new_sandbox_id (str): 新沙盒 ID(后续使用这个)
|
|
|
- - port_mapping (dict): 端口映射关系
|
|
|
- - access_urls (list): 访问 URL 列表
|
|
|
-
|
|
|
- Raises:
|
|
|
- httpx.HTTPStatusError: HTTP 请求返回错误状态码时抛出
|
|
|
- httpx.RequestError: 网络请求失败时抛出
|
|
|
+ ToolResult: 重建结果
|
|
|
"""
|
|
|
url = f"{server_url or SANDBOX_SERVER_URL}/api/rebuild_with_ports"
|
|
|
payload = {
|
|
|
@@ -245,10 +306,43 @@ async def sandbox_rebuild_with_ports(
|
|
|
"gpu_count": gpu_count
|
|
|
}
|
|
|
|
|
|
- async with httpx.AsyncClient(timeout=timeout) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- return response.json()
|
|
|
+ try:
|
|
|
+ async with httpx.AsyncClient(timeout=timeout) as client:
|
|
|
+ response = await client.post(url, json=payload)
|
|
|
+ response.raise_for_status()
|
|
|
+ data = response.json()
|
|
|
+
|
|
|
+ old_id = data.get("old_sandbox_id", "")
|
|
|
+ new_id = data.get("new_sandbox_id", "")
|
|
|
+ port_mapping = data.get("port_mapping", {})
|
|
|
+ access_urls = data.get("access_urls", [])
|
|
|
+
|
|
|
+ output_parts = [
|
|
|
+ f"旧沙盒 ID: {old_id} (已销毁)",
|
|
|
+ f"新沙盒 ID: {new_id}"
|
|
|
+ ]
|
|
|
+ if port_mapping:
|
|
|
+ output_parts.append(f"端口映射: {json.dumps(port_mapping)}")
|
|
|
+ if access_urls:
|
|
|
+ output_parts.append(f"访问地址: {', '.join(access_urls)}")
|
|
|
+
|
|
|
+ return ToolResult(
|
|
|
+ title="沙盒重建成功",
|
|
|
+ output="\n".join(output_parts),
|
|
|
+ metadata=data
|
|
|
+ )
|
|
|
+ except httpx.HTTPStatusError as e:
|
|
|
+ return ToolResult(
|
|
|
+ title="沙盒重建失败",
|
|
|
+ output=f"HTTP 错误: {e.response.status_code}",
|
|
|
+ error=str(e)
|
|
|
+ )
|
|
|
+ except httpx.RequestError as e:
|
|
|
+ return ToolResult(
|
|
|
+ title="沙盒重建失败",
|
|
|
+ output=f"网络请求失败: {str(e)}",
|
|
|
+ error=str(e)
|
|
|
+ )
|
|
|
|
|
|
|
|
|
@tool(
|
|
|
@@ -271,8 +365,10 @@ async def sandbox_rebuild_with_ports(
|
|
|
async def sandbox_destroy_environment(
|
|
|
sandbox_id: str,
|
|
|
server_url: str = None,
|
|
|
- timeout: float = DEFAULT_TIMEOUT
|
|
|
-) -> Dict[str, Any]:
|
|
|
+ timeout: float = DEFAULT_TIMEOUT,
|
|
|
+ context: Optional[ToolContext] = None,
|
|
|
+ uid: str = "",
|
|
|
+) -> ToolResult:
|
|
|
"""
|
|
|
销毁沙盒环境,释放资源。
|
|
|
|
|
|
@@ -280,23 +376,44 @@ async def sandbox_destroy_environment(
|
|
|
sandbox_id: 沙盒 ID。
|
|
|
server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
|
|
|
timeout: 请求超时时间(秒),默认 300 秒。
|
|
|
+ context: 工具上下文
|
|
|
|
|
|
Returns:
|
|
|
- dict: 包含以下字段:
|
|
|
- - status (str): 操作状态,如 "success"
|
|
|
- - message (str): 提示信息
|
|
|
- - removed_tools (list): 被移除的工具列表(如有关联的已注册工具)
|
|
|
-
|
|
|
- Raises:
|
|
|
- httpx.HTTPStatusError: HTTP 请求返回错误状态码时抛出
|
|
|
- httpx.RequestError: 网络请求失败时抛出
|
|
|
+ ToolResult: 销毁结果
|
|
|
"""
|
|
|
url = f"{server_url or SANDBOX_SERVER_URL}/api/destroy_environment"
|
|
|
payload = {
|
|
|
"sandbox_id": sandbox_id
|
|
|
}
|
|
|
|
|
|
- async with httpx.AsyncClient(timeout=timeout) as client:
|
|
|
- response = await client.post(url, json=payload)
|
|
|
- response.raise_for_status()
|
|
|
- return response.json()
|
|
|
+ try:
|
|
|
+ async with httpx.AsyncClient(timeout=timeout) as client:
|
|
|
+ response = await client.post(url, json=payload)
|
|
|
+ response.raise_for_status()
|
|
|
+ data = response.json()
|
|
|
+
|
|
|
+ status = data.get("status", "")
|
|
|
+ message = data.get("message", "")
|
|
|
+ removed_tools = data.get("removed_tools", [])
|
|
|
+
|
|
|
+ output_parts = [f"状态: {status}", f"消息: {message}"]
|
|
|
+ if removed_tools:
|
|
|
+ output_parts.append(f"已移除的工具: {', '.join(removed_tools)}")
|
|
|
+
|
|
|
+ return ToolResult(
|
|
|
+ title="沙盒环境已销毁",
|
|
|
+ output="\n".join(output_parts),
|
|
|
+ metadata=data
|
|
|
+ )
|
|
|
+ except httpx.HTTPStatusError as e:
|
|
|
+ return ToolResult(
|
|
|
+ title="沙盒销毁失败",
|
|
|
+ output=f"HTTP 错误: {e.response.status_code}",
|
|
|
+ error=str(e)
|
|
|
+ )
|
|
|
+ except httpx.RequestError as e:
|
|
|
+ return ToolResult(
|
|
|
+ title="沙盒销毁失败",
|
|
|
+ output=f"网络请求失败: {str(e)}",
|
|
|
+ error=str(e)
|
|
|
+ )
|