> ## Documentation Index
> Fetch the complete documentation index at: https://cndoc-langchain.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 沙盒 SDK 使用指南

> 使用 Python 或 TypeScript SDK 以编程方式创建和管理沙盒。

<Warning>
  沙盒功能目前处于私密预览阶段。随着我们的迭代，API 和功能可能会发生变化。[注册等待列表](https://www.langchain.com/langsmith-sandboxes-waitlist?ref=docs.langchain.com)以获取访问权限。
</Warning>

[LangSmith SDK](/langsmith/reference) 提供了一个用于创建和交互沙盒的编程接口。

## 安装

<CodeGroup>
  ```bash Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  # uv
  uv add "langsmith[sandbox] @ git+https://github.com/langchain-ai/langsmith-sdk#subdirectory=python"

  # pip
  pip install "langsmith[sandbox] @ git+https://github.com/langchain-ai/langsmith-sdk#subdirectory=python"
  ```

  ```bash TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  npm install langsmith
  # 或
  yarn add langsmith
  ```
</CodeGroup>

Python 的 `[sandbox]` 额外依赖会安装 `websockets`，这支持实时流式传输和 `timeout=0`。如果没有它，`run()` 会自动回退到 HTTP。对于 TypeScript，请安装可选的 `ws` 包以支持 WebSocket 流式传输：

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
npm install ws
```

## 创建并运行沙盒

每个沙盒都从一个**快照**启动——一个由 Docker 镜像支持的文件系统镜像。选择一个现有快照或创建一个新快照（完整的构建/捕获/CRUD 流程请参见[快照](/langsmith/sandbox-snapshots)）；以下每个示例都使用你已有的 `snapshot_id` / `snapshotId`。

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from langsmith.sandbox import SandboxClient

  # 客户端使用环境变量中的 LANGSMITH_ENDPOINT 和 LANGSMITH_API_KEY
  client = SandboxClient()

  # 选择一个现有快照，或使用 client.create_snapshot(...) 构建一个
  snapshot_id = "550e8400-e29b-41d4-a716-446655440000"

  # 从快照创建一个沙盒并运行代码
  with client.sandbox(snapshot_id=snapshot_id) as sb:
      result = sb.run("python -c 'print(2 + 2)'")
      print(result.stdout)  # "4\n"
      print(result.success)  # True
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  import { SandboxClient } from "langsmith/experimental/sandbox";

  // 客户端使用环境变量中的 LANGSMITH_ENDPOINT 和 LANGSMITH_API_KEY
  const client = new SandboxClient();

  // 选择一个现有快照，或使用 client.createSnapshot(...) 构建一个
  const snapshotId = "550e8400-e29b-41d4-a716-446655440000";

  // 从快照创建一个沙盒并运行代码
  const sandbox = await client.createSandbox(snapshotId);
  const result = await sandbox.run("node -e 'console.log(2 + 2)'");
  console.log(result.stdout); // "4\n"

  // 不要忘记清理
  await sandbox.delete();
  ```
</CodeGroup>

## 运行命令

每次 `run()` 调用都会返回一个包含 `stdout`、`stderr`、`exit_code` 和 `success` 的 `ExecutionResult`。

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) as sb:
      result = sb.run("echo 'Hello, World!'")

      print(result.stdout)     # "Hello, World!\n"
      print(result.stderr)     # ""
      print(result.exit_code)  # 0
      print(result.success)    # True

      # 失败的命令会返回非零退出码
      result = sb.run("exit 1")
      print(result.success)    # False
      print(result.exit_code)  # 1
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const sandbox = await client.createSandbox(snapshotId);
  try {
    const result = await sandbox.run("echo 'Hello, World!'");

    console.log(result.stdout);     // "Hello, World!\n"
    console.log(result.stderr);     // ""
    console.log(result.exit_code);  // 0

    // 传递环境变量和工作目录
    const envResult = await sandbox.run("echo $MY_VAR", {
      env: { MY_VAR: "test-value" },
      cwd: "/tmp",
    });
  } finally {
    await sandbox.delete();
  }
  ```
</CodeGroup>

## 流式输出

对于长时间运行的命令，可以使用回调或 `CommandHandle` 实时流式输出。

### 使用回调进行流式传输

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  import sys

  with client.sandbox(snapshot_id=snapshot_id) as sb:
      result = sb.run(
          "make build",
          timeout=600,
          on_stdout=lambda s: print(s, end=""),
          on_stderr=lambda s: print(s, end="", file=sys.stderr),
      )
      print(f"\n构建{'成功' if result.success else '失败'}")
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const result = await sandbox.run("make build", {
    timeout: 600,
    onStdout: (data) => process.stdout.write(data),
    onStderr: (data) => process.stderr.write(data),
  });
  console.log(`退出码: ${result.exit_code}`);
  ```
</CodeGroup>

### 使用 CommandHandle 进行流式传输

设置 `wait=False` 以获取 `CommandHandle`，从而完全控制输出流。

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) as sb:
      handle = sb.run("make build", timeout=600, wait=False)

      print(f"命令 ID: {handle.command_id}")

      for chunk in handle:
          prefix = "OUT" if chunk.stream == "stdout" else "ERR"
          print(f"[{prefix}] {chunk.data}", end="")

      result = handle.result
      print(f"\n退出码: {result.exit_code}")
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const handle = await sandbox.run("python train.py", {
    wait: false,
    timeout: 600,
  });

  console.log(`命令 ID: ${handle.commandId}`);
  console.log(`PID: ${handle.pid}`);

  for await (const chunk of handle) {
    if (chunk.stream === "stdout") {
      process.stdout.write(chunk.data);
    } else {
      process.stderr.write(chunk.data);
    }
  }

  const result = await handle.result;
  console.log(`退出码: ${result.exit_code}`);
  ```
</CodeGroup>

### 发送标准输入和终止命令

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) as sb:
      handle = sb.run(
          "python -c 'name = input(\"Name: \"); print(f\"Hello {name}\")'",
          timeout=30,
          wait=False,
      )

      for chunk in handle:
          if "Name:" in chunk.data:
              handle.send_input("World\n")
          print(chunk.data, end="")

      result = handle.result
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const handle = await sandbox.run("python -i", { wait: false });

  // 向标准输入发送输入
  handle.sendInput("print(2 + 2)\n");
  handle.sendInput("exit()\n");

  for await (const chunk of handle) {
    process.stdout.write(chunk.data);
  }
  ```
</CodeGroup>

终止正在运行的命令：

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) as sb:
      handle = sb.run("python server.py", timeout=0, wait=False)

      for chunk in handle:
          print(chunk.data, end="")
          if "Ready" in chunk.data:
              break

      handle.kill()
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const handle = await sandbox.run("sleep 300", { wait: false });
  handle.kill();

  const result = await handle.result;
  console.log(result.exit_code); // 非零
  ```
</CodeGroup>

### 重新连接到正在运行的命令

如果客户端断开连接，可以使用命令 ID 重新连接：

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) as sb:
      handle = sb.run("make build", timeout=600, wait=False)
      command_id = handle.command_id

      # 稍后，可能在不同的进程中
      handle = sb.reconnect(command_id)
      for chunk in handle:
          print(chunk.data, end="")
      result = handle.result
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const handle = await sandbox.run("long-task", { wait: false });
  const commandId = handle.commandId;

  // 稍后，或从不同的客户端
  const newHandle = await sandbox.reconnect(commandId);
  for await (const chunk of newHandle) {
    process.stdout.write(chunk.data);
  }
  ```
</CodeGroup>

## 文件操作

在沙盒中读写文件：

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) as sb:
      # 写入文件
      sb.write("/app/script.py", "print('Hello from file!')")

      # 运行脚本
      result = sb.run("python /app/script.py")
      print(result.stdout)  # "Hello from file!\n"

      # 读取文件（返回字节）
      content = sb.read("/app/script.py")
      print(content.decode())  # "print('Hello from file!')"

      # 写入二进制文件
      sb.write("/app/data.bin", b"\x00\x01\x02\x03")
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const sandbox = await client.createSandbox(snapshotId);
  try {
    // 写入文件（字符串内容）
    await sandbox.write("/app/script.py", "print('Hello from file!')");

    // 运行脚本
    const result = await sandbox.run("python /app/script.py");
    console.log(result.stdout);  // "Hello from file!\n"

    // 读取文件（返回 Uint8Array）
    const content = await sandbox.read("/app/script.py");
    console.log(new TextDecoder().decode(content));

    // 写入二进制文件
    await sandbox.write("/app/data.bin", new Uint8Array([0x00, 0x01, 0x02, 0x03]));
  } finally {
    await sandbox.delete();
  }
  ```
</CodeGroup>

## 命令生命周期和 TTL

沙盒守护进程通过两种超时机制管理命令会话的生命周期：

* **会话 TTL（已完成的命令）**：命令完成后，其会话会在内存中保留一段 TTL 时间。在此窗口期内，你可以重新连接以检索输出。TTL 过期后，会话将被清理。
* **空闲超时（正在运行的命令）**：没有连接客户端的正在运行的命令将在空闲超时后被终止（默认：5 分钟）。每次客户端连接时，空闲计时器都会重置。设置为 `-1` 表示没有空闲超时。

### 组合生命周期选项

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) as sb:
      # 长时间运行的任务：30 分钟空闲超时，1 小时会话 TTL
      handle = sb.run(
          "python train.py",
          timeout=0,              # 无命令超时
          idle_timeout=1800,      # 30 分钟无客户端后终止
          ttl_seconds=3600,       # 退出后会话保留 1 小时
          wait=False,
      )

      # 即发即忘：无空闲超时，无限 TTL
      handle = sb.run(
          "python background_job.py",
          timeout=0,
          idle_timeout=-1,        # 永不因空闲而终止
          ttl_seconds=-1,         # 永久保留会话
          wait=False,
      )
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const sandbox = await client.createSandbox(snapshotId);
  try {
    // 长时间运行的任务：30 分钟空闲超时，1 小时会话 TTL
    const handle = await sandbox.run("python train.py", {
      timeout: 0,              // 无命令超时
      idleTimeout: 1800,       // 30 分钟无客户端后终止
      ttlSeconds: 3600,        // 退出后会话保留 1 小时
      wait: false,
    });

    // 即发即忘：无空闲超时，无限 TTL
    const bg = await sandbox.run("python background_job.py", {
      timeout: 0,
      idleTimeout: -1,         // 永不因空闲而终止
      ttlSeconds: -1,          // 永久保留会话
      wait: false,
    });
  } finally {
    await sandbox.delete();
  }
  ```
</CodeGroup>

设置 `kill_on_disconnect=True`（Python）或 `killOnDisconnect: true`（TypeScript）可以在最后一个客户端断开连接时立即终止命令，而不是等待空闲超时。

## 服务 URL（Python）

通过经过身份验证的 URL 访问在沙盒内运行的 HTTP 服务。你可以在浏览器中打开它，从代码中调用它，或与队友共享它。

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
with client.sandbox(snapshot_id=snapshot_id) as sb:
    sb.run("python -m http.server 8000", timeout=0, wait=False)

    svc = sb.service(port=8000)

    # 在浏览器中打开
    print(svc.browser_url)

    # 或使用内置辅助工具发出请求（身份验证会自动注入）
    resp = svc.get("/api/data")
    resp = svc.post("/api/data", json={"key": "value"})
```

更多详情，包括用例、REST API 访问和完整的 FastAPI 示例，请参见[服务 URL](/langsmith/sandbox-service-urls)。

## TCP 隧道（Python）

像访问本地服务一样访问沙盒内运行的任何 TCP 服务。隧道会打开一个本地 TCP 端口，并通过 WebSocket 将连接转发到沙盒内的目标端口。

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import psycopg2

# 从官方 postgres:16 镜像构建的快照
sb = client.create_sandbox(snapshot_id=postgres_snapshot_id)
pg_handle = sb.run(
    "POSTGRES_HOST_AUTH_METHOD=trust docker-entrypoint.sh postgres",
    timeout=0,
    wait=False,
)
import time; time.sleep(6)  # 等待 Postgres 启动

try:
    with sb.tunnel(remote_port=5432, local_port=25432) as t:
        conn = psycopg2.connect(
            host="127.0.0.1",
            port=t.local_port,
            user="postgres",
        )
        cursor = conn.cursor()
        cursor.execute("SELECT version()")
        print(cursor.fetchone())
        conn.close()
finally:
    pg_handle.kill()
    client.delete_sandbox(sb.name)
```

隧道适用于任何 TCP 服务（Redis、HTTP 服务器等），并且你可以同时打开多个隧道：

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
with sb.tunnel(remote_port=5432, local_port=25432) as t1, \
     sb.tunnel(remote_port=6379, local_port=26379) as t2:
    # 同时使用 Postgres 和 Redis
    pass
```

## 异步支持（Python）

Python SDK 提供了完整的异步客户端：

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
from langsmith.sandbox import AsyncSandboxClient

async def main():
    async with AsyncSandboxClient() as client:
        async with await client.sandbox(snapshot_id=snapshot_id) as sb:
            result = await sb.run("python -c 'print(1 + 1)'")
            print(result.stdout)  # "2\n"

            await sb.write("/app/test.txt", "async content")
            content = await sb.read("/app/test.txt")
            print(content.decode())

            # 异步流式传输
            handle = await sb.run("make build", timeout=600, wait=False)
            async for chunk in handle:
                print(chunk.data, end="")
            result = await handle.result

            # 异步服务 URL
            svc = await sb.service(port=8000)
            resp = await svc.get("/api/data")
            url = await svc.get_service_url()
            token = await svc.get_token()
```

## 跟踪沙盒活动

通过 `run()` 的 `env` 参数传递 LangSmith 跟踪环境变量，以发送从沙盒内运行的代码中产生的跟踪信息。在进程退出前调用 `flush()` 以确保所有跟踪信息都被发送。

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from langsmith.sandbox import SandboxClient

  client = SandboxClient()

  tracing_env = {
      "LANGSMITH_API_KEY": "lsv2_pt_...",
      "LANGSMITH_ENDPOINT": "https://api.smith.langchain.com",
      "LANGSMITH_TRACING": "true",
      "LANGSMITH_PROJECT": "my-sandbox-traces",
  }

  with client.sandbox(snapshot_id=snapshot_id) as sandbox:
      sandbox.run("pip install langsmith", timeout=120, env=tracing_env)
      result = sandbox.run("python3 my_agent.py", env=tracing_env)
      print(result.stdout)
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  import { SandboxClient } from "langsmith/experimental/sandbox";

  const client = new SandboxClient();

  const tracingEnv = {
    LANGSMITH_API_KEY: "lsv2_pt_...",
    LANGSMITH_ENDPOINT: "https://api.smith.langchain.com",
    LANGSMITH_TRACING: "true",
    LANGSMITH_PROJECT: "my-sandbox-traces",
  };

  const sandbox = await client.createSandbox(snapshotId);
  try {
    await sandbox.run("pip install langsmith", { timeout: 120, env: tracingEnv });
    const result = await sandbox.run("python3 my_agent.py", { env: tracingEnv });
    console.log(result.stdout);
  } finally {
    await sandbox.delete();
  }
  ```
</CodeGroup>

在沙盒内部，任何经过 LangSmith 插桩的代码（`@traceable`、LangChain、LangGraph）都会自动从注入的环境变量中获取跟踪配置。

<Warning>
  务必在沙盒进程退出前调用 `flush()` —— Python 中使用 `langsmith.Client().flush()`，TypeScript 中使用 `await new Client().flush()`。否则，跟踪信息可能会丢失，因为容器在命令完成时会被销毁。
</Warning>

## 错误处理

两个 SDK 都提供了类型化的异常用于特定错误处理：

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from langsmith.sandbox import (
      SandboxClientError,       # 基础异常
      ResourceCreationError,    # 资源创建失败
      ResourceNotFoundError,    # 资源不存在
      ResourceTimeoutError,     # 操作超时
      SandboxNotReadyError,     # 沙盒尚未就绪
      SandboxConnectionError,   # 网络/WebSocket 错误
      CommandTimeoutError,      # 命令超时
      QuotaExceededError,       # 达到配额限制
  )

  try:
      with client.sandbox(snapshot_id=snapshot_id) as sb:
          result = sb.run("sleep 999", timeout=10)
  except CommandTimeoutError as e:
      print(f"命令超时: {e}")
  except ResourceNotFoundError as e:
      print(f"{e.resource_type} 未找到: {e}")
  except SandboxClientError as e:
      print(f"错误: {e}")
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  import {
    LangSmithSandboxError,
    LangSmithResourceNotFoundError,
    LangSmithResourceTimeoutError,
    LangSmithSandboxConnectionError,
    LangSmithCommandTimeoutError,
    LangSmithQuotaExceededError,
  } from "langsmith/experimental/sandbox";

  try {
    const sandbox = await client.createSandbox("not-a-real-snapshot");
    await sandbox.delete();
  } catch (e) {
    if (e instanceof LangSmithResourceNotFoundError) {
      console.log(`${e.resourceType} 未找到: ${e.message}`);
    } else if (e instanceof LangSmithResourceTimeoutError) {
      console.log(`等待 ${e.resourceType} 超时: ${e.message}`);
    } else if (e instanceof LangSmithSandboxError) {
      console.log(`错误: ${e.message}`);
    }
  }
  ```
</CodeGroup>

<Note>
  更多详情，请参阅 GitHub 上的沙盒 SDK 参考文档：[Python](https://github.com/langchain-ai/langsmith-sdk/tree/main/python/langsmith/sandbox) 或 [TypeScript](https://github.com/langchain-ai/langsmith-sdk/tree/main/js/src/experimental/sandbox)。
</Note>

***

<div className="source-links">
  <Callout icon="terminal-2">
    [将这些文档连接](/use-these-docs)到 Claude、VSCode 等，通过 MCP 获取实时答案。
  </Callout>

  <Callout icon="edit">
    [在 GitHub 上编辑此页面](https://github.com/langchain-ai/docs/edit/main/src/langsmith/sandbox-sdk.mdx) 或 [提交问题](https://github.com/langchain-ai/docs/issues/new/choose)。
  </Callout>
</div>
