Skip to main content
编码代理需要的不仅仅是一个聊天窗口。它们需要文件浏览器、代码查看器和差异面板,即 IDE 体验。此模式将深度代理连接到沙箱,使其能够在隔离环境中读取、写入和执行代码,然后通过自定义 API 服务器暴露沙箱文件系统,以便前端可以在代理工作时实时显示文件。

架构

沙箱模式有三个层次:
  1. 具有沙箱后端的深度代理: 代理从沙箱自动获取文件系统工具(read_filewrite_fileedit_fileexecute
  2. 自定义 API 服务器 — 一个通过 langgraph.jsonhttp.app 字段暴露的 FastAPI 应用,提供前端可以调用的文件浏览端点
  3. IDE 前端: 三面板布局(文件树、代码/差异查看器、聊天),在代理进行更改时实时同步文件

沙箱生命周期

在深入代码之前,了解沙箱的作用域划分非常重要。作用域策略决定了谁共享一个沙箱、它存活多久以及如何在运行时解析它。

线程作用域沙箱(推荐)

每个 LangGraph 线程都有自己的沙箱。沙箱 ID 存储在线程的元数据中,并在运行时通过 getConfig() 解析。这是大多数应用程序的推荐方法:
  • 对话是隔离的 — 一个线程中的文件更改不会影响另一个线程
  • 沙箱状态在页面重新加载后持续存在(相同线程 = 相同沙箱)
  • 清理很简单:当线程被删除时,其沙箱也可以被删除

代理作用域沙箱

同一助手下的所有线程共享一个沙箱。适用于需要跨对话保留更改的持久项目环境:

用户作用域沙箱

每个用户在所有线程中拥有自己的沙箱。需要自定义身份验证和用户识别:

会话作用域沙箱(客户端)

对于没有 LangGraph 线程的简单应用,前端可以生成会话 ID 并直接传递。此方法不会跨浏览器会话持久化,最适合演示或原型设计:
本指南的其余部分将使用线程作用域沙箱作为主要示例。

设置代理

选择沙箱提供者

深度代理支持多种沙箱提供者。任何实现了 SandboxBackendProtocol 的提供者都可以工作:
代理自动获取文件系统工具(read_filewrite_fileedit_filelsglobgrep)和一个用于运行 shell 命令的 execute 工具。无需工具配置。

按线程解析沙箱

不要在模块级别创建沙箱(这将在线程间共享并可能过期),而是在运行时按线程解析沙箱。沙箱通过 getConfig() 从 LangGraph 配置中读取 thread_id

初始化沙箱

在代理运行之前,使用 uploadFiles 将项目文件填充到沙箱中:
对于 LangSmith 沙箱,容器镜像和资源限制来自沙箱快照。创建沙箱时传递 templateName(参见上面的 get_or_create_sandbox_for_thread)。upload_files 在运行时在该镜像基础上初始化或更新项目文件。
在上传 package.json 后运行 sandbox.execute("cd /app && npm install") 以在代理开始前安装依赖项。

添加文件浏览 API

代理可以读写文件,但前端也需要直接访问以浏览沙箱文件系统。添加一个自定义 FastAPI API 服务器,并通过 langgraph.json 中的 http.app 字段将其暴露。

创建 API 服务器

沙箱 API 端点使用线程 ID 作为 URL 路径参数。这确保了前端始终访问当前对话的正确沙箱,使用与代理后端相同的 get_or_create_sandbox_for_thread 函数:
代理的后端和 API 服务器都调用相同的 get_or_create_sandbox_for_thread 函数。这确保了它们始终为给定线程解析到相同的沙箱。线程元数据中的沙箱 ID 是唯一的事实来源 — 无需内存缓存。

配置 langgraph.json

注册代理图和 API 服务器。http.app 字段告诉 LangGraph 平台在默认路由旁边提供你的自定义路由:
你的自定义路由与 LangGraph API 位于同一主机。对于使用 langgraph dev 的本地开发,该主机是 http://localhost:2024
http.app 中定义的自定义路由优先于默认的 LangGraph 路由。这意味着你可以根据需要覆盖内置端点,但要小心不要意外覆盖像 /threads/runs 这样的路由。

构建前端

前端有三个面板:文件树侧边栏、代码/差异查看器和聊天面板。它使用 useStream 进行代理对话,并使用自定义 API 端点进行文件浏览。

线程创建

在页面加载时创建一个 LangGraph 线程,并将其 ID 持久化到 sessionStorage 中,以便页面重新加载时重新连接到相同的沙箱:
“新建线程”按钮会清除存储的 ID,以便下次挂载时创建一个新线程(和沙箱):

文件状态管理

跟踪沙箱文件系统的两个快照:原始状态(代理运行前)和当前状态(实时更新)。线程 ID 包含在 API URL 中,因此请求始终命中正确的沙箱:

实时文件同步

IDE 体验的关键是在代理工作时更新文件,而不是在它完成后。监视流中的消息以查找来自文件修改工具的 ToolMessage 实例。当 write_fileedit_file 工具调用完成时,刷新该特定文件。当 execute 完成时,刷新所有文件(因为 shell 命令可能修改任何文件):

检测更改的文件

在每次代理运行之前,对当前文件内容进行快照。文件刷新后,与快照进行比较以识别哪些文件发生了更改:
当用户选择一个已更改的文件时,默认显示差异视图,以便他们立即看到代理修改了什么。

显示差异

使用适合框架的差异库来渲染统一差异: 使用 @pierre/diffs (React) 的示例:

已更改文件摘要

显示所有已修改文件的摘要,包含行级添加/删除计数。这为用户提供了代理影响的快速概览 — 类似于 git status

三面板布局

IDE 布局将三个面板并排排列:
文件树显示 VS Code 风格的图标(使用 @iconify-json/vscode-icons)和修改文件上的琥珀色点。选择修改的文件会自动切换到差异标签页。

用例

沙箱是以下情况的正确选择:
  • 编码代理需要创建、修改和运行代码,需要超越聊天的可视化界面
  • 代码审查工作流,代理建议更改,用户在接受前审查差异
  • 教程或学习应用,AI 助手帮助用户逐步构建项目,在上下文中显示更改
  • 原型设计工具,用户用自然语言描述功能,并实时观看代理实现它们

最佳实践

  • 在生产应用中使用线程作用域沙箱。将沙箱 ID 存储在线程元数据中,并在运行时通过 getConfig() 解析。这避免了模块级状态,并保持沙箱按对话隔离。
  • 在代理后端和 API 服务器之间共享 getOrCreateSandboxForThread。两者都应以相同的方式解析沙箱 — 通过线程元数据 — 这样就有唯一的事实来源,无需内存缓存。
  • threadId 持久化到 sessionStorage,以便页面重新加载时重新连接到相同的线程和沙箱,而不是创建新的。
  • 在每个相关工具调用时同步文件,而不仅仅是在运行结束时。这使 IDE 感觉是实时的。监视 write_fileedit_fileexecute 工具消息并立即刷新。
  • 默认为已更改的文件显示差异视图。当用户单击被代理修改的文件时,首先显示差异 — 这是他们关心的。
  • 为只读操作显示紧凑的工具结果。不要在聊天中转储 read_file 的完整输出,而是显示一行,如 Read router.js L1-42。将完整输出显示保留给修改工具。
  • 用真实项目初始化沙箱。从空沙箱开始会让人迷失方向。上传一个可运行的入门项目,以便用户(和代理)立即拥有上下文。
  • 从文件树中过滤 node_modules。没有人想浏览数千个依赖文件。在获取树时将它们过滤掉。