sandbox.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. """
  2. Sandbox Tools (Async)
  3. 通过 HTTP 异步调用沙盒管理服务的客户端库。
  4. """
  5. import json
  6. import httpx
  7. from typing import Optional, List, Dict, Any
  8. from agent import tool
  9. from agent.tools.models import ToolResult
  10. # 服务地址,可根据实际部署情况修改
  11. # SANDBOX_SERVER_URL = "http://192.168.100.20:9998"
  12. SANDBOX_SERVER_URL = "http://61.48.133.26:9998"
  13. # 默认超时时间(秒)
  14. DEFAULT_TIMEOUT = 300.0
  15. @tool(
  16. display={
  17. "zh": {
  18. "name": "创建沙盒环境",
  19. "params": {
  20. "image": "Docker 镜像",
  21. "mem_limit": "内存限制",
  22. "nano_cpus": "CPU 限制",
  23. "ports": "端口列表",
  24. "use_gpu": "启用 GPU",
  25. "gpu_count": "GPU 数量"
  26. }
  27. },
  28. "en": {
  29. "name": "Create Sandbox",
  30. "params": {
  31. "image": "Docker image",
  32. "mem_limit": "Memory limit",
  33. "nano_cpus": "CPU limit",
  34. "ports": "Port list",
  35. "use_gpu": "Enable GPU",
  36. "gpu_count": "GPU count"
  37. }
  38. }
  39. }
  40. )
  41. async def sandbox_create_environment(
  42. image: str = "agent-sandbox:latest",
  43. mem_limit: str = "512m",
  44. nano_cpus: int = 500000000,
  45. ports: Optional[List[int]] = None,
  46. use_gpu: bool = False,
  47. gpu_count: int = -1,
  48. server_url: str = None,
  49. timeout: float = DEFAULT_TIMEOUT
  50. ) -> Dict[str, Any]:
  51. """
  52. 创建一个隔离的 Docker 开发环境。
  53. Args:
  54. image: Docker 镜像名称,默认为 "agent-sandbox:latest"。
  55. 可以使用其他镜像如 "python:3.12-slim", "node:18-slim" 等。
  56. mem_limit: 容器最大内存限制,默认为 "512m"。
  57. nano_cpus: 容器最大 CPU 限制(纳秒),默认为 500000000(0.5 CPU)。
  58. ports: 需要映射的端口列表,如 [8080, 3000]。
  59. use_gpu: 是否启用 GPU 支持,默认为 False。需要宿主机安装 nvidia-container-toolkit。
  60. gpu_count: 使用的 GPU 数量,-1 表示使用所有可用 GPU,默认为 -1。
  61. server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
  62. timeout: 请求超时时间(秒),默认 300 秒。
  63. Returns:
  64. dict: 包含以下字段:
  65. - sandbox_id (str): 沙盒唯一标识,后续操作需要用到
  66. - message (str): 提示信息
  67. - port_mapping (dict): 端口映射关系,如 {8080: 32001}
  68. - access_urls (list): 访问 URL 列表
  69. Raises:
  70. httpx.HTTPStatusError: HTTP 请求返回错误状态码时抛出
  71. httpx.RequestError: 网络请求失败时抛出
  72. """
  73. url = f"{server_url or SANDBOX_SERVER_URL}/api/create_environment"
  74. payload = {
  75. "image": image,
  76. "mem_limit": mem_limit,
  77. "nano_cpus": nano_cpus,
  78. "use_gpu": use_gpu,
  79. "gpu_count": gpu_count
  80. }
  81. if ports:
  82. payload["ports"] = ports
  83. async with httpx.AsyncClient(timeout=timeout) as client:
  84. response = await client.post(url, json=payload)
  85. response.raise_for_status()
  86. return response.json()
  87. @tool(
  88. display={
  89. "zh": {
  90. "name": "执行沙盒命令",
  91. "params": {
  92. "sandbox_id": "沙盒 ID",
  93. "command": "Shell 命令",
  94. "is_background": "后台执行",
  95. "timeout": "超时时间"
  96. }
  97. },
  98. "en": {
  99. "name": "Run Shell in Sandbox",
  100. "params": {
  101. "sandbox_id": "Sandbox ID",
  102. "command": "Shell command",
  103. "is_background": "Run in background",
  104. "timeout": "Timeout"
  105. }
  106. }
  107. }
  108. )
  109. async def sandbox_run_shell(
  110. sandbox_id: str,
  111. command: str,
  112. is_background: bool = False,
  113. timeout: int = 120,
  114. server_url: str = None,
  115. request_timeout: float = DEFAULT_TIMEOUT
  116. ) -> Dict[str, Any]:
  117. """
  118. 在指定的沙盒中执行 Shell 命令。
  119. Args:
  120. sandbox_id: 沙盒 ID,由 create_environment 返回。
  121. command: 要执行的 Shell 命令,如 "pip install flask" 或 "python app.py"。
  122. is_background: 是否后台执行,默认为 False。
  123. - False:前台执行,等待命令完成并返回输出
  124. - True:后台执行,适合启动长期运行的服务
  125. timeout: 前台命令的超时时间(秒),默认 120 秒。后台命令不受此限制。
  126. server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
  127. request_timeout: HTTP 请求超时时间(秒),默认 300 秒。
  128. Returns:
  129. dict: 根据执行方式返回不同内容:
  130. 前台执行:
  131. - exit_code (int): 命令退出码
  132. - stdout (str): 标准输出
  133. - stderr (str): 标准错误
  134. 后台执行:
  135. - status (str): 状态
  136. - message (str): 提示信息
  137. - log_file (str): 日志文件路径
  138. Raises:
  139. httpx.HTTPStatusError: HTTP 请求返回错误状态码时抛出
  140. httpx.RequestError: 网络请求失败时抛出
  141. """
  142. url = f"{server_url or SANDBOX_SERVER_URL}/api/run_shell"
  143. payload = {
  144. "sandbox_id": sandbox_id,
  145. "command": command,
  146. "is_background": is_background,
  147. "timeout": timeout
  148. }
  149. async with httpx.AsyncClient(timeout=request_timeout) as client:
  150. response = await client.post(url, json=payload)
  151. response.raise_for_status()
  152. return response.json()
  153. @tool(
  154. display={
  155. "zh": {
  156. "name": "重建沙盒端口",
  157. "params": {
  158. "sandbox_id": "沙盒 ID",
  159. "ports": "端口列表",
  160. "mem_limit": "内存限制",
  161. "nano_cpus": "CPU 限制",
  162. "use_gpu": "启用 GPU",
  163. "gpu_count": "GPU 数量"
  164. }
  165. },
  166. "en": {
  167. "name": "Rebuild Sandbox Ports",
  168. "params": {
  169. "sandbox_id": "Sandbox ID",
  170. "ports": "Port list",
  171. "mem_limit": "Memory limit",
  172. "nano_cpus": "CPU limit",
  173. "use_gpu": "Enable GPU",
  174. "gpu_count": "GPU count"
  175. }
  176. }
  177. }
  178. )
  179. async def sandbox_rebuild_with_ports(
  180. sandbox_id: str,
  181. ports: List[int],
  182. mem_limit: str = "1g",
  183. nano_cpus: int = 1000000000,
  184. use_gpu: bool = False,
  185. gpu_count: int = -1,
  186. server_url: str = None,
  187. timeout: float = DEFAULT_TIMEOUT
  188. ) -> Dict[str, Any]:
  189. """
  190. 重建沙盒并应用新的端口映射。
  191. 使用场景:先创建沙盒克隆项目,阅读 README 后才知道需要暴露哪些端口,
  192. 此时调用此函数重建沙盒,应用正确的端口映射。
  193. 注意:重建后会返回新的 sandbox_id,后续操作需要使用新 ID。
  194. 容器内的所有文件(克隆的代码、安装的依赖等)都会保留。
  195. Args:
  196. sandbox_id: 当前沙盒 ID。
  197. ports: 需要映射的端口列表,如 [8080, 3306, 6379]。
  198. mem_limit: 容器最大内存限制,默认为 "1g"。
  199. nano_cpus: 容器最大 CPU 限制(纳秒),默认为 1000000000(1 CPU)。
  200. use_gpu: 是否启用 GPU 支持,默认为 False。需要宿主机安装 nvidia-container-toolkit。
  201. gpu_count: 使用的 GPU 数量,-1 表示使用所有可用 GPU,默认为 -1。
  202. server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
  203. timeout: 请求超时时间(秒),默认 300 秒。
  204. Returns:
  205. dict: 包含以下字段:
  206. - old_sandbox_id (str): 旧沙盒 ID(已销毁)
  207. - new_sandbox_id (str): 新沙盒 ID(后续使用这个)
  208. - port_mapping (dict): 端口映射关系
  209. - access_urls (list): 访问 URL 列表
  210. Raises:
  211. httpx.HTTPStatusError: HTTP 请求返回错误状态码时抛出
  212. httpx.RequestError: 网络请求失败时抛出
  213. """
  214. url = f"{server_url or SANDBOX_SERVER_URL}/api/rebuild_with_ports"
  215. payload = {
  216. "sandbox_id": sandbox_id,
  217. "ports": ports,
  218. "mem_limit": mem_limit,
  219. "nano_cpus": nano_cpus,
  220. "use_gpu": use_gpu,
  221. "gpu_count": gpu_count
  222. }
  223. async with httpx.AsyncClient(timeout=timeout) as client:
  224. response = await client.post(url, json=payload)
  225. response.raise_for_status()
  226. return response.json()
  227. @tool(
  228. requires_confirmation=True,
  229. display={
  230. "zh": {
  231. "name": "销毁沙盒环境",
  232. "params": {
  233. "sandbox_id": "沙盒 ID"
  234. }
  235. },
  236. "en": {
  237. "name": "Destroy Sandbox",
  238. "params": {
  239. "sandbox_id": "Sandbox ID"
  240. }
  241. }
  242. }
  243. )
  244. async def sandbox_destroy_environment(
  245. sandbox_id: str,
  246. server_url: str = None,
  247. timeout: float = DEFAULT_TIMEOUT
  248. ) -> Dict[str, Any]:
  249. """
  250. 销毁沙盒环境,释放资源。
  251. Args:
  252. sandbox_id: 沙盒 ID。
  253. server_url: 服务地址,默认使用全局配置 SANDBOX_SERVER_URL。
  254. timeout: 请求超时时间(秒),默认 300 秒。
  255. Returns:
  256. dict: 包含以下字段:
  257. - status (str): 操作状态,如 "success"
  258. - message (str): 提示信息
  259. - removed_tools (list): 被移除的工具列表(如有关联的已注册工具)
  260. Raises:
  261. httpx.HTTPStatusError: HTTP 请求返回错误状态码时抛出
  262. httpx.RequestError: 网络请求失败时抛出
  263. """
  264. url = f"{server_url or SANDBOX_SERVER_URL}/api/destroy_environment"
  265. payload = {
  266. "sandbox_id": sandbox_id
  267. }
  268. async with httpx.AsyncClient(timeout=timeout) as client:
  269. response = await client.post(url, json=payload)
  270. response.raise_for_status()
  271. return response.json()