Skip to main content
沙盒功能目前处于私密预览阶段。随着我们的迭代,API 和功能可能会发生变化。注册等待列表以获取访问权限。
LangSmith SDK 提供了一个用于创建和交互沙盒的编程接口。

安装

# 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"
Python 的 [sandbox] 额外依赖会安装 websockets,这支持实时流式传输和 timeout=0。如果没有它,run() 会自动回退到 HTTP。对于 TypeScript,请安装可选的 ws 包以支持 WebSocket 流式传输:
npm install ws

创建并运行沙盒

每个沙盒都从一个快照启动——一个由 Docker 镜像支持的文件系统镜像。选择一个现有快照或创建一个新快照(完整的构建/捕获/CRUD 流程请参见快照);以下每个示例都使用你已有的 snapshot_id / snapshotId
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

运行命令

每次 run() 调用都会返回一个包含 stdoutstderrexit_codesuccessExecutionResult
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

流式输出

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

使用回调进行流式传输

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 '失败'}")

使用 CommandHandle 进行流式传输

设置 wait=False 以获取 CommandHandle,从而完全控制输出流。
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}")

发送标准输入和终止命令

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
终止正在运行的命令:
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()

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

如果客户端断开连接,可以使用命令 ID 重新连接:
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

文件操作

在沙盒中读写文件:
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")

命令生命周期和 TTL

沙盒守护进程通过两种超时机制管理命令会话的生命周期:
  • 会话 TTL(已完成的命令):命令完成后,其会话会在内存中保留一段 TTL 时间。在此窗口期内,你可以重新连接以检索输出。TTL 过期后,会话将被清理。
  • 空闲超时(正在运行的命令):没有连接客户端的正在运行的命令将在空闲超时后被终止(默认:5 分钟)。每次客户端连接时,空闲计时器都会重置。设置为 -1 表示没有空闲超时。

组合生命周期选项

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,
    )
设置 kill_on_disconnect=True(Python)或 killOnDisconnect: true(TypeScript)可以在最后一个客户端断开连接时立即终止命令,而不是等待空闲超时。

服务 URL(Python)

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

TCP 隧道(Python)

像访问本地服务一样访问沙盒内运行的任何 TCP 服务。隧道会打开一个本地 TCP 端口,并通过 WebSocket 将连接转发到沙盒内的目标端口。
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 服务器等),并且你可以同时打开多个隧道:
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 提供了完整的异步客户端:
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() 以确保所有跟踪信息都被发送。
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)
在沙盒内部,任何经过 LangSmith 插桩的代码(@traceable、LangChain、LangGraph)都会自动从注入的环境变量中获取跟踪配置。
务必在沙盒进程退出前调用 flush() —— Python 中使用 langsmith.Client().flush(),TypeScript 中使用 await new Client().flush()。否则,跟踪信息可能会丢失,因为容器在命令完成时会被销毁。

错误处理

两个 SDK 都提供了类型化的异常用于特定错误处理:
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}")
更多详情,请参阅 GitHub 上的沙盒 SDK 参考文档:PythonTypeScript