基于 OpenSandbox 的沙箱后端

什么是沙箱(Sandbox)

智能体(Agent)需要生成代码、操作文件系统并运行 Shell 命令。由于我们无法预测智能体会执行什么操作,因此必须将其环境与主机系统隔离,防止其访问凭证、文件或网络。沙盒(Sandbox)通过创建智能体执行环境与主机系统之间的边界,提供了这种隔离能力。

在 Deep Agents 中,沙盒是一种特殊的后端(Backend)。与其他仅暴露文件操作的后端(State, Filesystem, Store)不同,沙盒后端还为智能体提供了一个 execute工具,用于在隔离环境中运行任意 Shell 命令。

沙盒就是给智能体准备的一个“封闭房间”:

  • 智能体在里面随便折腾;

  • 你的电脑、账号、文件、网络都被挡在外面。

    image-20260629155556149

沙箱的工具集

├── 文件系统操作工具

│ ├── ls - 列出目录内容

│ ├── read_file - 读取文件

│ ├── write_file - 写入文件

│ ├── edit_file - 编辑文件

│ ├── glob - 文件模式匹配

│ └── grep - 文本搜索

├── 执行工具

│ └── execute - 运行Shell命令

│ ├── 可以运行任意命令

│ ├── 返回stdout/stderr

│ └── 返回退出码

└── 安全边界

​ ├── 无法访问主机文件

​ ├── 无法读取主机环境变量

​ └── 无法干扰主机进程

为什么要用沙箱(Sandbox)

沙盒主要用于安全隔离。它们允许智能体执行任意代码、访问文件和联网,而不会泄露您的凭证、破坏本地文件或损害主机系统。当智能体自主运行时,这种隔离至关重要。

沙盒特别适用于以下场景:

编程智能体(Coding Agents):需要运行 Shell、Git 操作、克隆仓库(许多供应商提供原生 Git API,如 Daytona 的 Git 操作),甚至运行 Docker-in-Docker 来构建和测试流水线。

数据分析智能体:在安全隔离的环境中加载文件、安装数据分析库(如 pandas, numpy)、运行统计计算,并生成如 PPT 等输出文件。

优势一:文件系统隔离

  • ✅ 智能体只能访问沙盒内的文件
  • ❌ 无法读取你的/Documents、/Desktop等个人文件
  • ❌ 无法修改你的项目源代码

优势二:大量输出造成上下文“爆炸”

  • ✅ 智能体只需要知道临时文件的路径,以及临时文件内容的摘要信息
  • ❌ 某个步骤处理完成后,输出:file1.py, file2.py, … 总大小:2MB
  • ❌ 输出太大,直接返回会撑爆上下文窗口

优势三:环境隔离

  • ✅ 智能体可以自由安装Python库,或者Java的lib,或者NodeJs的库,或者Go语音的第三方依赖。
  • ❌ 不会污染你的全局项目代码的环境
  • ❌ 不会与其他项目产生依赖冲突

优势四:进程隔离

  • ✅ 智能体可以启动新进程
  • ❌ 无法杀死你的其他进程
  • ❌ 无法监控你的系统活动

优势五:网络控制

  • ✅ 可以配置网络访问权限
  • ❌ 防止数据外泄到未知服务器

主流沙盒提供商

提供商功能对比表

提供商 特点 适用场景 免费额度 网络控制
Daytona 简单易用,API直观 教学、快速原型 可配置
Modal 基于云函数,弹性强 生产环境、大规模 支持阻断
Runloop 开发箱模式,交互好 开发调试、团队协作 试用 可配置
LangSmith LangChain生态集成 LangChain项目 内测中 待确认
AgentCore AWS Bedrock集成 AWS用户、企业级 按使用量 AWS VPC
pip install langchain-daytona

# 2. 创建并启动沙箱(Daytona)
# 注意:此操作会向Daytona服务发起请求,创建一个远程隔离环境
print("步骤1: 创建远程沙箱环境...")
try:
    # (假设在.env文件中配置了OPENAI_API_KEY和DAYTONA_API_KEY)
    config = DaytonaConfig(
        api_key=DAYTONA_API_KEY,
        api_url=DAYTONA_BASE_URL,  # "https://app.daytona.io/api"
        target="us"
    )
    sandbox = Daytona(config).get('9058717f-98ce-442a-991a-69fece04c2e7')  # 使用之前的docker容器作为远程沙箱
    # sandbox = Daytona().create()  # 第一次创建沙箱
    print(f"沙箱创建成功!沙箱ID: {sandbox.id}")
except Exception as e:
    print(f"创建沙箱失败,请检查网络和API密钥: {e}")
    return

# 3. 将沙箱包装为Deep Agents可用的后端(Backend)
backend = DaytonaSandbox(sandbox=sandbox)

# 4. 创建Deep Agent,并授予其访问沙箱后端的能力
# 系统提示词定义了智能体的角色和能力
print("\n步骤2: 创建具有沙箱访问权限的智能体...")
agent = create_deep_agent(
    model=llm,  # 指定模型
    backend=backend,  # 关键:传入沙箱后端
    system_prompt="""你是一个专业的Python编码助手,拥有在一个完全隔离的沙箱环境中执行命令和操作文件的能力。
    你可以使用`execute`工具运行任何shell命令,使用`write_file`创建和编辑文件,使用`read_file`查看文件内容。
    请用清晰、安全的代码回应用户的请求。""",
)

# 5. 定义任务:创建一个简单的Python程序并运行
    user_request = """请完成以下任务:
    1. 在沙箱的 /home/daytona/ 目录下,创建一个名为 ‘test_sandbox.py’ 的Python文件。
    2. 文件内容应打印“Hello from the secure sandbox!”以及当前工作目录。
    3. 运行这个Python脚本,并告诉我输出结果。"""

自定义企业服务器的SandBox

image-20260629155918812

OpenSandbox相关介绍

OpenSandbox:是阿里巴巴开源的通用AI应用沙箱平台,提供多语言 SDK、统一的沙箱 API,以及 Docker/Kubernetes 运行时环境,专为解决AI时代代码执行的安全与效率问题而设计。它提供了一个安全、隔离、高效的执行环境,使AI模型能够安全地执行用户提交的代码,同时避免了对生产环境的潜在威胁。

它并非简单的Docker包装,而是一套完整的全栈平台,底层复用了阿里巴巴支撑大规模AI工作负载的内部基础设施,专门针对AIAgent的并行运行需求设计。其核心优势在于”全环境隔离”,这是它区别于Git Worktrees、普通容器化工具的核心亮点。

核心功能与技术特性

  • 多语言 SDK:提供 Python、Java/Kotlin、JavaScript/TypeScript、C#/.NET、Go 的沙箱 SDK。
  • 沙箱协议:定义了沙箱生命周期管理 API 和沙箱执行 API。你可以通过这些沙箱协议扩展自己的沙箱运行时。
  • 沙箱运行时:沙箱全生命周期管理,支持 Docker 和自研高性能 Kubernetes 运行时,实现本地运行、企业级大规模分布式沙箱调度。
  • 沙箱环境:内置 Command、Filesystem、Code Interpreter 实现。并提供 Coding Agent(Claude Code 等)、浏览器自动化(Chrome、Playwright)和桌面环境(VNC、VS Code)等示例。
  • 网络策略:提供统一的 Ingress Gateway 实现,并支持多种路由策略;提供单实例级别的沙箱出口网络限制
  • 强隔离安全:支持 gVisor、Kata Containers 和 Firecracker 微虚拟机等安全容器运行时,为沙箱工作负载与宿主机之间提供增强的安全隔离。

安装并配置 Sandbox Server

环境要求:

  • Docker(本地运行必需)
  • Python 3.10+(示例和本地运行所需)
  1. Linux中安装docker

    基于docker部署Docker ≥ 24.0.0,下面在Linux中安装docker。这里默认用户已经安装好了Linux系统,

    • 获取docker repo文件。

      wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
       
      #如果下载docker对应版本有问题,可以选择使用镜像源2
      wget -O /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo
      
    • 查看docker可以安装的版本:

      yum list docker-ce.x86_64 --showduplicates | sort -r
      
    • 安装docker:这里指定docker版本为29.6.1版本

      yum -y install docker-ce-29.6.1-1.el10
      
    • 设置docker 开机启动,并启动docker:

      systemctl enable docker
      systemctl start docker
      
    • 修改cgroup的配置,尤其是镜像加速源,并重启docker。

      # 如果文件不存在,需要创建。
      vim /etc/docker/daemon.json
      

      添加如下内容

      {
              "features": {
                "containerd-snapshotter": true
              },
              "exec-opts": ["native.cgroupdriver=systemd"],
              "registry-mirrors":[
                "https://docker.m.daocloud.io",
                "https://docker.rainbond.cc",
                "https://docker.lmirror.top",
                "https://docker-0.unsee.tech",
                "https://docker.hlmirror.com"
             ]
      }
      

      重启docker

      systemctl restart docker
      
  2. Linux 安装OpenSandbox

    • 安装python环境,可以先安装 Anaconda3

      #创建 python环境
      conda create --name sandbox python=3.11
       
      #切换python环境
      conda activate sandbox
      
    • 安装依赖-Code Interpreter SDK

      pip install opensandbox-code-interpreter
      
    • 安装 Opensandbox-Server

      pip install opensandbox-server
      
      opensandbox-server init-config ~/.sandbox.toml --example docker-zh
      
      编辑: vi.sandbox.xml
      1. 把ip改成linux节点ip
      2. 配置 api_key
      

      image-20260716103712433

    • 启动 Opensandbox-Server

      opensandbox-server
       
      # 新开窗口Show help,查看帮助命令
      opensandbox-server -h
      
    • window本地测试sandbox是否正常可以连接

      浏览器输入:http://192.168.11.2:8080/health ,可以看到{"status":"healthy"}就为正常。
      
    • 手动拉取镜像(在 opensandbox server 服务器端)

      docker pull sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/code-interpreter:v1.0.2
      
    • window本地创建代码测试创建沙箱

      image-20260716104737378

      新建一个测试:test_opensanbox.py

      import asyncio
      from datetime import timedelta
      
      from code_interpreter import CodeInterpreter, SupportedLanguage
      from opensandbox import Sandbox
      from opensandbox.config import ConnectionConfig
      from opensandbox.models import WriteEntry
      import os
      
      async def main() -> None:
          config = ConnectionConfig(
              domain="http://192.168.11.2:8080",
              use_server_proxy=True,
              api_key="1234567890abcdef",
              request_timeout=timedelta(seconds=60), # 请求超时时间,默认30秒
          )
      
          # 1. Create a sandbox
          sandbox = await Sandbox.create(
              "sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/code-interpreter:v1.0.2",
              entrypoint= ["/opt/opensandbox/code-interpreter.sh"],
              env={"PYTHON_VERSION": "3.13"},
              timeout=timedelta(minutes=10), # 自动终止时间箱运行超时时间,默认10分钟,超过时间会自动终止沙箱
              connection_config=config,
          )
      
          async with sandbox:
      
              # 2. Execute a shell command
              execution = await sandbox.commands.run("echo 'Hello OpenSandbox!'")
              print(execution.logs.stdout[0].text)
      
              # 3. Write a file
              await sandbox.files.write_files([
                  WriteEntry(path="/tmp/hello.txt", data="Hello World", mode=644)
              ])
      
              # 4. Read a file
              content = await sandbox.files.read_file("/tmp/hello.txt")
              print(f"Content: {content}") # Content: Hello World
      
              # 5. Create a code interpreter
              interpreter = await CodeInterpreter.create(sandbox)
      
              # 6. 执行 Python 代码(单次执行:直接传 language)
              result = await interpreter.codes.run(
                    """
                        import sys
                        print(sys.version)
                        result = 2 + 2
                        result
                    """,
                    language=SupportedLanguage.PYTHON,
              )
      
              print(result.result[0].text) # 4
              print(result.logs.stdout[0].text) # 3.13.11
      
          # 7. Cleanup the sandbox
          await sandbox.kill()
      
      if __name__ == "__main__":
          asyncio.run(main())
      
    • 配置沙箱的访问权限:

      from opensandbox.models.sandboxes import NetworkPolicy, NetworkRule
      
      sandbox = await Sandbox.create(
          "python:3.11",
          .....
          network_policy=NetworkPolicy(
              defaultAction="deny",
              egress=[
                  NetworkRule(action="allow", target="pypi.org"),
                  NetworkRule(action="allow", target="*.github.com"),
              ]
          )
      )
      

DeepAgents框架中自定义OpenSandboxBackend

在DeepAgents框架中有一个基类:BaseSandbox 所有沙盒功能都基于一个核心方法——execute()。这是一个巧妙的设计:

flowchart TD
    %% 定义卡片内部文本左对齐,并赋予不同的背景颜色
    %% 灰色代表抽象/基础方法,蓝色代表具体子类实现
    classDef abstract fill:#f5f5f5,stroke:#9e9e9e,stroke-width:1px,text-align:left;
    classDef concrete fill:#e3f2fd,stroke:#42a5f5,stroke-width:1px,text-align:left;

    %% -------------------
    %% 顶部:抽象基类沙盒
    %% -------------------
    subgraph Base [" BaseSandbox 基类 "]
        direction TB
        M1["<b>read_file()</b><br/>实现原理:<br/>execute(&quot;cat /path/to/file&quot;)"]:::abstract
        M2["<b>write_file()</b><br/>实现原理:<br/>execute(&quot;echo 'content' > file&quot;)"]:::abstract
        M3["<b>ls()</b><br/>实现原理:<br/>execute(&quot;ls -la&quot;)"]:::abstract
        M4["<b>grep()</b><br/>实现原理:<br/>execute(&quot;grep pattern file&quot;)"]:::abstract
        
        %% 使用隐形连接线强制内部这些节点垂直整齐排列
        M1 ~~~ M2 ~~~ M3 ~~~ M4
    end

    %% -------------------
    %% 底部:具体的厂商实现
    %% -------------------
    subgraph Provider [" 沙盒提供商的execute()实现 "]
        direction TB
        P1["<b>Daytona.execute(&quot;command&quot;)</b><br/>• 调用Daytona API<br/>• 在远程容器中执行命令"]:::concrete
        P2["<b>Modal.execute(&quot;command&quot;)</b><br/>• 调用Modal云服务<br/>• 在隔离环境中执行"]:::concrete
        P3["<b>Runloop.execute(&quot;command&quot;)</b><br/>• 调用Runloop API<br/>• 在开发箱中执行"]:::concrete
        
        %% 使用隐形连接线强制内部这些节点垂直整齐排列
        P1 ~~~ P2 ~~~ P3
    end

    %% -------------------
    %% 连接两个模块主节点
    %% -------------------
    Base -->|"所有操作都转换为Shell命令"| Provider
  1. 两层文件访问平面

    文件进出沙盒有两种截然不同的方式,理解何时使用哪种方式非常重要:

    1. 智能体文件系统工具 (Agent Filesystem Tools)

      read_filewrite_fileedit_filelsglobgrepexecute是 LLM 在执行过程中调用的工具。这些操作通过沙盒内部的 execute()进行。智能体使用它们来读取代码、写入文件并作为任务的一部分运行命令。

    2. 文件传输 API (File Transfer APIs)

      uploadFiles()downloadFiles()是您的应用程序代码调用的方法。它们使用提供商的本机文件传输 API(而非 Shell 命令),旨在在您的主机环境和沙盒之间移动文件。使用这些 API 来:

      • 初始化沙盒:在智能体运行前,向其填充源代码、配置或数据。
      • 检索产物:在智能体完成后,获取生成的代码、构建输出、报告等。
      • 预置依赖:预先准备好智能体需要的依赖项。

      使用 upload_files()在智能体运行前填充沙盒。路径必须是绝对路径,内容必须是字节流(bytes):

      使用 download_files()在智能体完成后从沙盒中检索文件或者下载文件到其他地方:

      image-20260629201529421

  2. 自定义OpenSandboxBackend类

    from __future__ import annotations
    
    import logging
    from collections.abc import Callable
    from typing import cast
    
    from opensandbox import SandboxSync
    from deepagents.backends.protocol import (
        ExecuteResponse,
        FileDownloadResponse,
        FileUploadResponse,
    )
    from deepagents.backends.sandbox import BaseSandbox
    
    SyncPollingInterval = float | Callable[[float], float]
    PollingStrategy = Callable[[float], float]
    
    # 配置日志
    logger = logging.getLogger(__name__)
    # logger.setLevel(logging.DEBUG)
    logger.setLevel(logging.ERROR)
    
    # 如果没有配置日志处理器,则添加一个
    if not logger.handlers:
        handler = logging.StreamHandler()
        handler.setLevel(logging.DEBUG)
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
    
    
    class OpenSandbox(BaseSandbox):
        """符合 SandboxBackendProtocol 协议的 OpenSandbox 实现。
    
        该实现继承了 BaseSandbox 的所有文件操作方法,
        并仅使用 OpenSandbox 的 API 实现了 execute()、download_files() 和 upload_files() 方法。
        """
    
        def __init__(
            self,
            *,
            sandbox: SandboxSync,
            timeout: int = 30 * 60,
            sync_polling_interval: SyncPollingInterval = 0.1,
        ) -> None:
            """创建一个包装已有 OpenSandbox 沙盒的后端实例。
    
            Args:
                sandbox:要包装的现有 OpenSandbox 沙盒实例。
                timeout:调用 `execute()` 且未显式指定 `timeout` 时使用的默认命令超时时间(秒)。
                sync_polling_interval:在同步执行路径上,轮询 OpenSandbox 命令完成状态的间隔时间(秒);
                    也可以是一个可调用对象,接收已执行的秒数并返回下一次轮询的延迟时间。
            """
            logger.info(f"正在初始化 OpenSandbox,沙盒 ID: {sandbox.id}")
            self._sandbox = sandbox
            # sandbox.kill()  # 手动关闭沙箱
            self._default_timeout = timeout
    
            # 处理轮询策略
            if callable(sync_polling_interval):
                polling_strategy = cast("PollingStrategy", sync_polling_interval)
            else:
                def polling_strategy(_elapsed: float) -> float:
                    return sync_polling_interval
    
            self._sync_polling_interval = polling_strategy
            logger.debug(f"OpenSandbox 初始化完成,默认超时时间={timeout}秒")
    
        @property
        def id(self) -> str:
            """返回 OpenSandbox 沙盒的 ID。"""
            sandbox_id = self._sandbox.id
            logger.debug(f"获取沙盒 ID: {sandbox_id}")
            return sandbox_id
    
        def execute(
            self,
            command: str,
            *,
            timeout: int | None = None,
        ) -> ExecuteResponse:
            """在沙盒内部执行一条 Shell 命令。
    
            Args:
                command:要执行的 Shell 命令字符串。
                timeout:等待命令完成的最大时间(秒)。
                    如果为 None,则使用后端默认的超时时间。
            """
            effective_timeout = timeout if timeout is not None else self._default_timeout
            logger.debug(f"准备执行命令:{command[:100]}...(超时时间={effective_timeout}秒)")
            return self._execute_command(command, timeout=effective_timeout)
    
        def _execute_command(
            self,
            command: str,
            *,
            timeout: int,
        ) -> ExecuteResponse:
            """使用 OpenSandbox 的 API 执行命令。"""
            try:
                logger.debug(f"通过 OpenSandbox API 执行命令:{command}")
                result = self._sandbox.commands.run(command)
                logger.debug(f"命令执行完成,退出码:{result.exit_code}")
    
                # 提取标准输出与标准错误
                stdout = ""
                stderr = ""
    
                if result.logs.stdout:
                    stdout = "\n".join([log.text for log in result.logs.stdout])
                    logger.debug(f"命令标准输出长度:{len(stdout)} 字符")
    
                if result.logs.stderr:
                    stderr = "\n".join([log.text for log in result.logs.stderr])
                    logger.debug(f"命令标准错误长度:{len(stderr)} 字符")
    
                # 合并输出
                output = stdout
                if stderr and stderr.strip():
                    output += f"\n<stderr>{stderr.strip()}</stderr>"
    
                logger.info(f"命令执行成功,退出码:{result.exit_code or 0}")
                return ExecuteResponse(
                    output=output,
                    exit_code=result.exit_code or 0,
                    truncated=False,
                )
    
            except Exception as e:
                error_msg = str(e)
                logger.error(f"执行命令时发生错误:{error_msg}", exc_info=True)
    
                if "timeout" in error_msg.lower():
                    logger.warning(f"命令在 {timeout} 秒后超时")
                    return ExecuteResponse(
                        output=f"命令在 {timeout} 秒后超时",
                        exit_code=124,
                        truncated=False,
                    )
    
                return ExecuteResponse(
                    output=f"执行命令时出错:{error_msg}",
                    exit_code=1,
                    truncated=False,
                )
    
        def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
            """从沙盒中下载文件。"""
            logger.info(f"开始下载 {len(paths)} 个文件:{paths}")
            responses: list[FileDownloadResponse] = []
    
            for i, path in enumerate(paths):
                logger.debug(f"正在处理第 {i+1}/{len(paths)} 个文件:{path}")
    
                if not path.startswith("/"):
                    logger.error(f"非法路径(必须是绝对路径):{path}")
                    responses.append(
                        FileDownloadResponse(path=path, content=None, error="invalid_path")
                    )
                    continue
    
                try:
                    logger.debug(f"正在从沙盒读取文件:{path}")
                    content = self._sandbox.files.read_file(path)
                    content_bytes = content.encode("utf-8") if isinstance(content, str) else content
    
                    logger.debug(f"文件读取成功,大小:{len(content_bytes)} 字节")
                    responses.append(
                        FileDownloadResponse(
                            path=path,
                            content=content_bytes,
                            error=None,
                        )
                    )
    
                except Exception as e:
                    logger.error(f"读取文件 {path} 时出错:{str(e)}", exc_info=True)
    
                    # 尝试检查文件是否存在
                    try:
                        logger.debug(f"检查文件是否存在:{path}")
                        result = self._sandbox.commands.run(f"test -f '{path}' && echo 'exists'")
    
                        if not result.logs.stdout or "exists" not in result.logs.stdout[0].text:
                            logger.error(f"文件不存在:{path}")
                            responses.append(
                                FileDownloadResponse(
                                    path=path,
                                    content=None,
                                    error="file_not_found",
                                )
                            )
                        else:
                            logger.error(f"文件存在但读取失败:{path},错误:{str(e)}")
                            responses.append(
                                FileDownloadResponse(
                                    path=path,
                                    content=None,
                                    error=f"read_error: {str(e)}",
                                )
                            )
                    except Exception as check_error:
                        logger.error(f"检查文件是否存在时出错:{check_error}", exc_info=True)
                        responses.append(
                            FileDownloadResponse(
                                path=path,
                                content=None,
                                error=f"check_error: {str(check_error)}",
                            )
                        )
    
            success_count = sum(1 for r in responses if r.error is None)
            logger.info(f"文件下载完成,成功 {success_count}/{len(paths)} 个")
            return responses
    
        def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
            """将文件上传到沙盒中。"""
            from opensandbox.models import WriteEntry
    
            logger.info(f"准备上传 {len(files)} 个文件")
            responses: list[FileUploadResponse] = []
            upload_entries = []
    
            for i, (path, content) in enumerate(files):
                logger.debug(f"正在处理第 {i+1}/{len(files)} 个待上传文件:{path},大小:{len(content)} 字节")
    
                if not path.startswith("/"):
                    logger.error(f"非法路径(必须是绝对路径):{path}")
                    responses.append(FileUploadResponse(path=path, error="invalid_path"))
                    continue
    
                try:
                    # 将字节内容转换为字符串
                    if isinstance(content, bytes):
                        try:
                            content_str = content.decode("utf-8")
                            logger.debug(f"已按 UTF-8 解码字节内容,长度:{len(content_str)} 字符")
                        except UnicodeDecodeError as decode_error:
                            logger.warning(f"UTF-8 解码失败,将以字符串形式存储:{decode_error}")
                            content_str = str(content)
                    else:
                        content_str = str(content)
    
                    upload_entries.append(WriteEntry(path=path, data=content_str, mode=0o644))
                    responses.append(FileUploadResponse(path=path, error=None))
                    logger.debug(f"文件已加入上传队列:{path}")
    
                except Exception as e:
                    logger.error(f"准备上传文件 {path} 时出错:{str(e)}", exc_info=True)
                    responses.append(FileUploadResponse(path=path, error=str(e)))
    
            # 如果有文件要上传
            if upload_entries:
                logger.info(f"正在向沙盒写入 {len(upload_entries)} 个文件")
                try:
                    self._sandbox.files.write_files(upload_entries)
                    logger.info(f"成功上传 {len(upload_entries)} 个文件")
    
                except Exception as e:
                    logger.error(f"上传文件时出错:{str(e)}", exc_info=True)
                    # 如果有任何错误,更新所有响应
                    for i, resp in enumerate(responses):
                        if resp.error is None:
                            responses[i] = FileUploadResponse(
                                path=resp.path,
                                error=f"upload_failed: {str(e)}"
                            )
            else:
                logger.warning("没有有效的文件需要上传")
    
            # 统计上传结果
            success_count = sum(1 for r in responses if r.error is None)
            error_count = len(responses) - success_count
            logger.info(f"文件上传完成,成功 {success_count} 个,失败 {error_count} 个")
    
            return responses
    
--- 本文结束 The End ---