Ver Fonte

fix(sandbox.py、__init__.py): 沙箱工具结果改为标准ToolResult、沙箱工具注册失败问题

tanjingyu há 1 mês atrás
pai
commit
c8d2569988
2 ficheiros alterados com 188 adições e 65 exclusões
  1. 6 0
      agent/tools/builtin/__init__.py
  2. 182 65
      agent/tools/builtin/sandbox.py

+ 6 - 0
agent/tools/builtin/__init__.py

@@ -15,6 +15,8 @@ from agent.tools.builtin.grep import grep_content
 from agent.tools.builtin.bash import bash_command
 from agent.tools.builtin.bash import bash_command
 from agent.tools.builtin.skill import skill, list_skills
 from agent.tools.builtin.skill import skill, list_skills
 from agent.tools.builtin.search import search_posts, get_search_suggestions
 from agent.tools.builtin.search import search_posts, get_search_suggestions
+from agent.tools.builtin.sandbox import (sandbox_create_environment, sandbox_run_shell,
+                                         sandbox_rebuild_with_ports,sandbox_destroy_environment)
 
 
 __all__ = [
 __all__ = [
     "read_file",
     "read_file",
@@ -27,4 +29,8 @@ __all__ = [
     "list_skills",
     "list_skills",
     "search_posts",
     "search_posts",
     "get_search_suggestions",
     "get_search_suggestions",
+    "sandbox_create_environment",
+    "sandbox_run_shell",
+    "sandbox_rebuild_with_ports",
+    "sandbox_destroy_environment",
 ]
 ]

+ 182 - 65
agent/tools/builtin/sandbox.py

@@ -7,8 +7,7 @@ import json
 import httpx
 import httpx
 from typing import Optional, List, Dict, Any
 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,
     use_gpu: bool = False,
     gpu_count: int = -1,
     gpu_count: int = -1,
     server_url: str = None,
     server_url: str = None,
-    timeout: float = DEFAULT_TIMEOUT
-) -> Dict[str, Any]:
+    timeout: float = DEFAULT_TIMEOUT,
+    context: Optional[ToolContext] = None,
+    uid: str = "",
+) -> ToolResult:
     """
     """
     创建一个隔离的 Docker 开发环境。
     创建一个隔离的 Docker 开发环境。
 
 
@@ -68,17 +69,10 @@ async def sandbox_create_environment(
         gpu_count: 使用的 GPU 数量,-1 表示使用所有可用 GPU,默认为 -1。
         gpu_count: 使用的 GPU 数量,-1 表示使用所有可用 GPU,默认为 -1。
         server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
         server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
         timeout: 请求超时时间(秒),默认 300 秒。
         timeout: 请求超时时间(秒),默认 300 秒。
+        context: 工具上下文
 
 
     Returns:
     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"
     url = f"{server_url or SANDBOX_SERVER_URL}/api/create_environment"
     payload = {
     payload = {
@@ -91,10 +85,39 @@ async def sandbox_create_environment(
     if ports:
     if ports:
         payload["ports"] = 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(
 @tool(
@@ -125,8 +148,10 @@ async def sandbox_run_shell(
     is_background: bool = False,
     is_background: bool = False,
     timeout: int = 120,
     timeout: int = 120,
     server_url: str = None,
     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 命令。
     在指定的沙盒中执行 Shell 命令。
 
 
@@ -139,21 +164,10 @@ async def sandbox_run_shell(
         timeout: 前台命令的超时时间(秒),默认 120 秒。后台命令不受此限制。
         timeout: 前台命令的超时时间(秒),默认 120 秒。后台命令不受此限制。
         server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
         server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
         request_timeout: HTTP 请求超时时间(秒),默认 300 秒。
         request_timeout: HTTP 请求超时时间(秒),默认 300 秒。
+        context: 工具上下文
 
 
     Returns:
     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"
     url = f"{server_url or SANDBOX_SERVER_URL}/api/run_shell"
     payload = {
     payload = {
@@ -163,10 +177,62 @@ async def sandbox_run_shell(
         "timeout": timeout
         "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(
 @tool(
@@ -203,8 +269,10 @@ async def sandbox_rebuild_with_ports(
     use_gpu: bool = False,
     use_gpu: bool = False,
     gpu_count: int = -1,
     gpu_count: int = -1,
     server_url: str = None,
     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。
         gpu_count: 使用的 GPU 数量,-1 表示使用所有可用 GPU,默认为 -1。
         server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
         server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
         timeout: 请求超时时间(秒),默认 300 秒。
         timeout: 请求超时时间(秒),默认 300 秒。
+        context: 工具上下文
 
 
     Returns:
     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"
     url = f"{server_url or SANDBOX_SERVER_URL}/api/rebuild_with_ports"
     payload = {
     payload = {
@@ -245,10 +306,43 @@ async def sandbox_rebuild_with_ports(
         "gpu_count": gpu_count
         "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(
 @tool(
@@ -271,8 +365,10 @@ async def sandbox_rebuild_with_ports(
 async def sandbox_destroy_environment(
 async def sandbox_destroy_environment(
     sandbox_id: str,
     sandbox_id: str,
     server_url: str = None,
     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。
         sandbox_id: 沙盒 ID。
         server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
         server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
         timeout: 请求超时时间(秒),默认 300 秒。
         timeout: 请求超时时间(秒),默认 300 秒。
+        context: 工具上下文
 
 
     Returns:
     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"
     url = f"{server_url or SANDBOX_SERVER_URL}/api/destroy_environment"
     payload = {
     payload = {
         "sandbox_id": sandbox_id
         "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)
+        )