第 09 章

后端(Backends)

为 Deep Agents 选择和配置文件系统后端,实现虚拟文件系统与策略控制

从"默认存储"到"可插拔后端架构"的关键一步

为什么需要后端

你的 Agent 产生的文件应该存在哪里?不同的任务需要不同的答案

只用默认存储的困境

  • 所有文件只能存在线程状态里,跨线程不共享
  • 想访问本地文件?做不到——没有本地磁盘后端
  • 想区分"临时草稿"和"长期记忆"?只有一种存储
  • 需要连接 S3、数据库等外部存储?无法扩展
  • Agent 乱改敏感文件?没有策略拦截

可插拔后端的解法

  • StateBackend:线程作用域,临时草稿本
  • FilesystemBackend:访问本地磁盘文件
  • StoreBackend:跨线程持久化长期记忆
  • CompositeBackend:按路径路由到不同后端
  • 自定义后端:S3、Postgres、任何你想要的存储
核心理念:Deep Agents 通过 ls、read_file、write_file、edit_file、glob、grep 等工具暴露文件系统接口。这些工具通过可插拔的后端运行——你决定数据存在哪里、怎么存。

后端全景图

七种内置后端,覆盖从临时草稿到生产部署的全部场景

后端存储位置跨线程适用场景
StateBackend 线程状态 否 临时草稿、中间结果
FilesystemBackend 本地磁盘 是 本地项目、CI 沙箱
LocalShellBackend 本地磁盘 + Shell 是 编码助手、开发工具
StoreBackend LangGraph Store 是 长期记忆、跨会话数据
ContextHubBackend LangSmith Hub 是 Hub 仓库文件系统
Sandbox 隔离环境 否 安全执行代码
CompositeBackend 路由到多个后端 视配置 混合存储需求
架构关系:文件系统工具(ls、read_file 等)统一调用后端接口,后端决定数据实际存在哪里。read_file 在所有后端中原生支持图片、PDF、音频、视频等二进制文件。

架构关系图

graph TB
    Tools[文件系统工具] --> Backend[后端]
    Backend --> State[State]
    Backend --> Disk[文件系统]
    Backend --> Store[Store]
    Backend --> ContextHub[Context Hub]
    Backend --> Sandbox[沙箱]
    Backend --> LocalShell[本地 Shell]
    Backend --> Composite[组合]
    Backend --> Custom[自定义]
    Composite --> Router{路由}
    Router --> State
    Router --> Disk
    Router --> Store
    Router --> ContextHub
    Sandbox --> Execute["+ execute 工具"]
    LocalShell --> Execute["+ execute 工具"]

StateBackend

默认后端——线程作用域的临时草稿本

白话:StateBackend 就像你桌面上的"草稿纸"——写在上面的东西,这条对话里随时能翻到,但换一个对话窗口就看不到了。

这是 Deep Agent 的默认文件系统后端。文件存储在当前线程的 LangGraph Agent 状态中,通过检查点在同一线程的多次交互间持久化。文件不会跨线程共享。

import { createDeepAgent, StateBackend } from "deepagents";

// 默认就是 StateBackend,无需显式指定
const agent = createDeepAgent();

// 等价于显式指定
const agent2 = createDeepAgent({
  backend: new StateBackend(),
});

适用场景

  • Agent 写入中间结果的临时草稿本
  • 自动淘汰大型工具输出,Agent 可逐段重新读取
  • Supervisor 与子 Agent 之间共享文件

注意事项

  • 设计为在图中使用,图外调用方法不会生效
  • 子 Agent 写入的文件在状态中持续存在
  • 文件对同一线程的 Supervisor 和其他子 Agent 可用
注意:在图运行之外直接调用后端方法(如 state_backend.upload_files(...))不会生效,直到图执行时才会应用。

FilesystemBackend

让 Agent 访问本地磁盘——真实文件的读写

白话:StateBackend 是草稿纸,FilesystemBackend 就是真实的文件夹。Agent 可以读你电脑上的文件、修改代码、保存结果——就像你亲手在做。

FilesystemBackend 在可配置的 root_dir 下读取和写入真实文件。你可以设置 virtualMode: true 来沙箱化路径,阻止 ..、~ 以及根目录外的绝对路径。

import { createDeepAgent, FilesystemBackend } from "deepagents";

const agent = createDeepAgent({
  model: "google-genai:gemini-3.1-pro-preview",
  backend: new FilesystemBackend({
    rootDir: ".",         // 根目录,Agent 的文件操作限制在此目录下
    virtualMode: true,    // 启用路径沙箱化,阻止 .. 和绝对路径逃逸
  }),
});

适用场景

  • 本地项目开发(编码助手、开发工具)
  • CI/CD 流水线
  • 挂载的持久卷

安全特性

  • 安全的路径解析,防止符号链接遍历
  • 可用 ripgrep 进行快速 grep
  • virtualMode: true 限制在 root_dir 内
安全风险:
  • Agent 可以读取任何可访问的文件,包括密钥(API 密钥、凭据、.env 文件)
  • 结合网络工具,密钥可能通过 SSRF 攻击被泄露
  • 文件修改是永久性且不可逆的
推荐的安全措施:
  1. 启用 Human-in-the-Loop (HITL) 中间件来审查敏感操作
  2. 从可访问的文件系统路径中排除密钥(尤其在 CI/CD 中)
  3. 在需要文件系统交互的生产环境中使用沙箱后端
  4. 始终在 root_dir 下使用 virtualMode: true 以启用基于路径的访问限制(阻止 ..、~ 以及根目录外的绝对路径)。注意,默认设置(virtualMode: false)即使设置了 root_dir 也不提供任何安全保障。

不适用于 Web 服务器或 HTTP API——请改用 StateBackend、StoreBackend 或沙箱后端。

LocalShellBackend

在 FilesystemBackend 基础上增加 Shell 命令执行能力

LocalShellBackend 在 FilesystemBackend 之上扩展了 execute 工具,允许 Agent 在宿主机上运行 Shell 命令。命令通过 shell 执行(等价 Node.js child_process,无沙箱隔离)。

import { createDeepAgent, LocalShellBackend } from "deepagents";

const backend = new LocalShellBackend({
  workingDirectory: ".",   // Shell 命令的执行目录
  env: { PATH: "/usr/bin:/bin" },  // 限制可用的环境变量,避免泄露宿主环境
});
const agent = createDeepAgent({
  model: "google-genai:gemini-3.1-pro-preview",
  backend,  // Agent 获得文件读写 + Shell 命令执行能力
});

配置参数

  • timeout:命令超时(默认 120 秒)
  • maxOutputBytes:输出限制(默认 100KB)
  • env:自定义环境变量
  • inheritEnv:是否继承宿主环境

适用场景

  • 本地编码助手和开发工具
  • 信任 Agent 时的快速开发迭代
  • 具有适当密钥管理的 CI/CD 流水线
极度危险:

此后端授予 Agent 直接的文件系统读写权限以及宿主机上不受限制的 Shell 执行权限。

  • Agent 可以以你的用户权限执行任意 Shell 命令
  • Agent 可以读取任何可访问的文件,包括密钥(API 密钥、凭据、.env 文件)
  • 密钥可能被暴露
  • 文件修改和命令执行是永久性且不可逆的
  • 命令直接在你的宿主机系统上运行
  • 命令可以消耗无限的 CPU、内存、磁盘
推荐的安全措施:
  1. 启用 Human-in-the-Loop (HITL) 中间件在执行前审查和批准操作。强烈推荐。
  2. 仅在专用开发环境中运行。切勿在共享或生产系统上使用。
  3. 在需要 Shell 执行的生产环境中使用沙箱后端。

注意:启用 Shell 后,virtualMode 不提供安全保障(命令可访问系统任何路径)。

StoreBackend

跨线程持久化——Agent 的长期记忆存储

白话:如果说 StateBackend 是草稿纸,那 StoreBackend 就是文件柜——今天写进去的东西,明天打开另一个对话窗口还能读到。

StoreBackend 将文件存储在 LangGraph BaseStore 中,实现跨线程的持久化。非常适合存储长期记忆或在多次执行中适用的指令。

import { createDeepAgent, StoreBackend } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

// 本地开发用 InMemoryStore(内存存储);部署到 LangSmith 时自动提供
const store = new InMemoryStore();
const agent = createDeepAgent({
  model: "google-genai:gemini-3.1-pro-preview",
  backend: new StoreBackend({
    // namespace 工厂函数:按用户身份隔离,每个用户有独立存储空间
    namespace: (rt) => [rt.serverInfo.user.identity],
  }),
  store,  // 必须传入 store 实例,StoreBackend 依赖它读写数据
});

适用场景

  • 使用 LangGraph store 运行时(Redis、Postgres 等)
  • 通过 LangSmith Deployment 部署 Agent
  • 需要跨线程共享的长期数据

数据隔离

  • namespace 参数控制数据隔离
  • 多用户部署必须设置命名空间工厂
  • 按用户、助手或线程隔离数据
提示:部署到 LangSmith Deployment 时,省略 store 参数,平台会自动为 Agent 配置存储。

命名空间工厂

控制 StoreBackend 数据隔离的关键机制

命名空间工厂接收 LangGraph Runtime 对象,返回字符串元组作为 store 命名空间。使用它在用户、租户或助手之间隔离数据。

import { StoreBackend } from "deepagents";

// 按用户隔离:每个用户独立的存储空间,多用户部署必须用这种方式
const byUser = new StoreBackend({
  namespace: (rt) => [rt.serverInfo.user.identity],  // 用用户 ID 作为命名空间
});

// 按助手隔离:同一助手的所有用户共享数据
const byAssistant = new StoreBackend({
  namespace: (rt) => [rt.serverInfo.assistantId],  // 用助手 ID 作为命名空间
});

// 按线程隔离:存储限定在单个对话范围,对话结束数据就不共享了
const byThread = new StoreBackend({
  namespace: (rt) => [rt.executionInfo.threadId],  // 用线程 ID 作为命名空间
});
Runtime 属性提供的信息典型用途
rt.context 用户提供的上下文(如 user_id) 自定义隔离维度
rt.serverInfo 助手 ID、图 ID、认证用户 按用户或助手隔离
rt.executionInfo 线程 ID、运行 ID、检查点 ID 按会话隔离
版本要求:Runtime 参数在 deepagents>=1.9.1 中可用。早期的 1.9.x 版本传递的是 BackendContext 包装器。rt.serverInfo 和 rt.executionInfo 需要 deepagents>=1.9.0。
重要变更:namespace 参数在 v1.9.0 将变为必填。新代码请始终显式设置。命名空间组件只能包含字母数字、连字符、下划线、点号等,通配符会被拒绝以防注入。
组合命名空间:你可以组合多个组件来创建更具体的作用域——例如 (user_id, thread_id) 用于按用户按对话隔离,或附加后缀如 "filesystem" 来区分同一作用域使用多个 store 命名空间的情况。
Legacy 默认行为:当未提供命名空间工厂时,旧版默认使用 LangGraph 配置元数据中的 assistant_id。这意味着同一助手的所有用户共享相同的存储。对于多用户的生产部署,请始终提供命名空间工厂。

ContextHubBackend

LangSmith Hub 仓库作为文件系统——无需额外配置 Store

ContextHubBackend 将文件存储在 LangSmith Hub 仓库中。使用 owner/name 或 name 格式的仓库标识符来构造它。无需配置单独的 LangGraph Store。

import { createDeepAgent, ContextHubBackend } from "deepagents";

const agent = createDeepAgent({
  model: "google-genai:gemini-3.1-pro-preview",
  // 传入 Hub 仓库名(owner/name 或 name 格式),无需额外配置 Store
  backend: new ContextHubBackend("my-agent"),
});

工作原理

  • 首次使用时惰性拉取 Hub 仓库树
  • 从内存缓存中提供读取服务
  • 写入和编辑作为 Hub 提交持久化
  • 使用乐观的父提交写入机制

适用场景

  • 无需单独配置 Store 的 LangSmith 原生存储
  • 需要文件变更的提交历史
  • 受益于版本追踪的工作流
使用前:请确保已设置 LANGSMITH_API_KEY 环境变量。如果另一个写入者先更新了仓库,你的写入可能失败——冲突时需重新拉取并重试。仅支持 UTF-8 文本文件。
行为和限制:如果仓库不存在,首次拉取视为空;首次成功写入可以创建仓库。upload_files() 接受 UTF-8 文本,非 UTF-8 文件会被拒绝。

CompositeBackend

路由器——按路径前缀将文件操作分发到不同后端

CompositeBackend 是最灵活的后端。它根据路径前缀将文件操作路由到不同后端,让你同时使用多种存储策略。

import {
  createDeepAgent,
  CompositeBackend,
  StateBackend,
  StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore();
const agent = createDeepAgent({
  model: "google-genai:gemini-3.1-pro-preview",
  backend: new CompositeBackend(
    // 第一个参数:默认后端,未匹配路径前缀的文件操作走这里
    new StateBackend(),  // 线程作用域,临时草稿
    // 第二个参数:路由表,键是路径前缀,值是对应的后端
    {
      "/memories/": new StoreBackend({  // /memories/ 下的文件走跨线程持久化
        namespace: () => ["memories"],
      }),
    },
  ),
  store,  // StoreBackend 依赖的 LangGraph 存储
});
路由行为:/workspace/plan.md 走 StateBackend(线程作用域),/memories/agent.md 走 StoreBackend(跨线程持久化)。ls、glob、grep 会聚合所有后端的结果并显示原始路径前缀。更长的前缀优先匹配。
图:CompositeBackend 路由分发

后端对比总结

一表看清七种后端的核心差异,帮你做出正确选择

后端持久化范围execute 工具安全隔离推荐用途
StateBackend 线程内 否 天然隔离 默认选择、临时文件
FilesystemBackend 本地磁盘 否 需 virtualMode 本地开发、CI
LocalShellBackend 本地磁盘 是 无隔离 受控开发环境
StoreBackend 跨线程 否 命名空间隔离 长期记忆、多用户
ContextHubBackend Hub 仓库 否 Hub 权限 LangSmith 原生
Sandbox 隔离环境 是 沙箱隔离 安全执行代码
CompositeBackend 混合 视路由 视配置 复杂混合需求
选型建议:大多数场景从 StateBackend(默认)开始。需要跨线程数据时加 StoreBackend。本地开发用 FilesystemBackend。混合需求用 CompositeBackend 路由。生产环境优先考虑沙箱后端。

指定后端

一个参数搞定——将后端实例传给 createDeepAgent

将后端实例传递给 createDeepAgent({ backend: ... }) 即可。文件系统中间件将其用于所有工具操作。如果省略,默认为 new StateBackend()。

// 方式 1:省略 backend 参数,默认使用 StateBackend(线程内临时存储)
const agent1 = createDeepAgent();

// 方式 2:指定 FilesystemBackend,访问本地磁盘文件
const agent2 = createDeepAgent({
  backend: new FilesystemBackend({ rootDir: ".", virtualMode: true }),
});

// 方式 3:使用 CompositeBackend 按路径路由到不同后端
const agent3 = createDeepAgent({
  backend: new CompositeBackend(
    new StateBackend(),  // 默认后端
    {
      "/memories/": new StoreBackend({  // /memories/ 走持久化存储
        namespace: () => ["memories"],
      }),
    },
  ),
  store,  // StoreBackend 需要 store 实例
});

后端协议版本

  • 后端必须实现 BackendProtocolV1 或 BackendProtocolV2
  • V1 后端会自动适配为 V2,无需代码更改
  • 框架在运行时通过 adaptBackendProtocol() 自动处理

路由示例行为

  • /workspace/plan.md → StateBackend
  • /memories/agent.md → StoreBackend
  • ls、glob 聚合所有后端结果
  • 更长的前缀优先匹配

虚拟文件系统

构建自定义后端,将 S3 或 Postgres 映射为文件系统

你可以构建自定义后端,将远程或数据库文件系统映射到 Agent 的工具命名空间。所有路径都是绝对路径(/x/y.txt),你决定如何映射到存储键或数据库行。

import {
  type BackendProtocolV2,
  type LsResult, type ReadResult, type ReadRawResult, type GrepResult,
  type GlobResult, type WriteResult, type EditResult,
} from "deepagents";

class S3Backend implements BackendProtocolV2 {
  constructor(private bucket: string, private prefix = "") {}

  // 将虚拟路径转为 S3 对象键,加上前缀
  private key(path: string): string {
    return `${this.prefix}${path}`;
  }

  async ls(path: string): Promise<LsResult> {
    // 列出 key(path) 下的对象;返回 { files: [...] }
  }
  async read(filePath: string, offset?: number, limit?: number): Promise<ReadResult> {
    // 从 S3 获取对象内容;文本返回 string,二进制返回 Uint8Array
  }
  async readRaw(filePath: string): Promise<ReadRawResult> {
    // 返回 { data: FileData },含时间戳等元信息
  }

  async grep(pattern: string, path?: string | null, glob?: string | null): Promise<GrepResult> {
    // 搜索文本文件;跳过二进制文件;返回 { matches: [...] }
  }

  async glob(pattern: string, path = "/"): Promise<GlobResult> {
    // 相对于 path 应用 glob 模式匹配;返回 { files: [...] }
  }

  async write(filePath: string, content: string): Promise<WriteResult> {
    // 写入对象;外部后端返回 { path, filesUpdate: null }
  }

  async edit(filePath: string, oldString: string, newString: string, replaceAll?: boolean): Promise<EditResult> {
    // 先 read → 替换 oldString 为 newString → write 回去
  }
}
设计指南:高效实现 ls 和 glob(优先用服务端过滤)。外部持久化后端的 write/edit 应返回 filesUpdate: null。所有查询方法必须返回结构化 Result 对象,通过 mimeType 支持二进制文件。

Postgres 风格映射

表结构:files(path text PRIMARY KEY, content text, mime_type text, created_at timestamptz, modified_at timestamptz)

  • ls 使用 WHERE path LIKE $1 || '%' → 返回 LsResult
  • glob 在 SQL 中过滤或先获取再本地应用 glob → 返回 GlobResult
  • grep 按扩展名或最后修改时间获取候选行,扫描行(跳过 mime_type 为二进制的行)→ 返回 GrepResult

设计要点

  • 路径是绝对路径(/x/y.txt),你决定如何映射到存储键或数据库行
  • 使用 ls 和 glob 作为方法名
  • 外部持久化后端的 write/edit 返回 filesUpdate: null
  • 通过返回带有适当 mimeType 的 Uint8Array 内容来支持二进制文件

后端协议详解

BackendProtocolV2 规定的七个必需方法

V1 协议(BackendProtocol):在 1.9.0 版本之前,仅支持 BackendProtocol(现在的 BackendProtocolV1)。V1 的辅助类型使用不同的字段名:LsResult(error, entries)、ReadResult(error, file_data)、GrepResult(error, matches)、WriteResult(error, path, files_update)、EditResult(error, path, files_update, occurrences)。FileData V1 格式包含 content(str)、encoding("utf-8" 或 "base64")、created_at、modified_at。
方法返回类型说明
ls(path) LsResult 列出目录内容(非递归),按 path 排序
read(filePath, offset, limit) ReadResult 读取文件;二进制返回 Uint8Array
readRaw(filePath) ReadRawResult 读取原始 FileData(含时间戳)
grep(pattern, path, glob) GrepResult 搜索文本内容,跳过二进制文件
glob(pattern, path) GlobResult 按 glob 模式匹配文件
write(filePath, content) WriteResult 仅创建语义;冲突时返回 error
edit(filePath, old, new, replaceAll) EditResult 强制唯一性;支持 replaceAll

结果类型一览

类型成功字段
ReadResultcontent, mimeType
LsResultfiles (FileInfo[])
GrepResultmatches (GrepMatch[])
WriteResultpath
EditResultpath, occurrences

沙箱扩展

SandboxBackendProtocolV2 额外提供:

  • execute(command):在沙箱中运行 Shell 命令
  • readonly id:沙箱实例的唯一标识符

可选方法

  • uploadFiles(files: Array<[string, Uint8Array]>):上传多个文件(用于沙箱后端)
  • downloadFiles(paths: string[]):下载多个文件(用于沙箱后端)

V1 适配工具

  • adaptBackendProtocol(v1Backend):将 V1 后端适配为 V2
  • adaptSandboxProtocol(v1Sandbox):将 V1 沙箱适配为 V2
import { adaptBackendProtocol, adaptSandboxProtocol } from "deepagents";

// 将 V1 后端适配为 V2,使旧实现可以在新框架中使用
const v2Backend = adaptBackendProtocol(v1Backend);
const v2Sandbox = adaptSandboxProtocol(v1Sandbox);
提示:框架会自动适配传递给 createDeepAgent() 的 V1 后端。仅当直接调用协议方法时才需要手动适配。

权限控制

声明式控制 Agent 可以读写哪些文件和目录

权限应用于内置的文件系统工具,并在后端被调用之前进行评估。这意味着即使后端支持访问,权限规则也能阻止不安全的操作。

权限的工作位置

  • 在文件系统工具层执行,先于后端调用
  • 基于路径的允许/拒绝规则
  • 支持规则排序和优先级
  • 支持子 Agent 权限继承

与后端的配合

  • 权限在 CompositeBackend 路由之前评估
  • 被拒绝的操作不会到达后端
  • 适合控制敏感目录的访问
  • 详细配置请参阅权限指南
权限 vs 策略钩子:权限适合简单的"允许/拒绝"规则(如"禁止读写 /secrets/")。如果需要更复杂的逻辑(速率限制、审计日志、内容检查),请使用策略钩子。

策略钩子:子类

通过子类化后端来实现自定义验证逻辑

当权限的"允许/拒绝"不够用时,你可以通过子类化后端来拦截写操作、添加审计日志或执行内容检查。以下示例阻止选定前缀下的写入和编辑:

import { FilesystemBackend, type WriteResult, type EditResult } from "deepagents";

class GuardedBackend extends FilesystemBackend {
  private denyPrefixes: string[];

  constructor({ denyPrefixes, ...options }) {
    super(options);
    // 确保前缀以 "/" 结尾,避免匹配歧义
    this.denyPrefixes = denyPrefixes.map(
      p => p.endsWith("/") ? p : p + "/"
    );
  }

  // 拦截写入操作,禁止在指定前缀路径下创建文件
  async write(filePath: string, content: string): Promise<WriteResult> {
    if (this.denyPrefixes.some(p => filePath.startsWith(p))) {
      return { error: `Writes are not allowed under ${filePath}` };
    }
    return super.write(filePath, content);  // 通过检查,调用父类方法执行写入
  }

  // 拦截编辑操作,禁止在指定前缀路径下修改文件
  async edit(filePath: string, oldString: string, newString: string, replaceAll = false): Promise<EditResult> {
    if (this.denyPrefixes.some(p => filePath.startsWith(p))) {
      return { error: `Edits are not allowed under ${filePath}` };
    }
    return super.edit(filePath, oldString, newString, replaceAll);  // 通过检查,调用父类
  }
}
子类方式的优点:直接继承后端的所有方法,只需覆写需要拦截的方法。适合与特定后端紧密耦合的策略。

策略钩子:包装器

通用包装器——适用于任何后端的策略拦截

如果你需要在任意后端上应用策略,包装器模式比子类更灵活。它不依赖具体的后端实现,可以组合使用。

import { type BackendProtocolV2, type WriteResult, type EditResult } from "deepagents";

class PolicyWrapper implements BackendProtocolV2 {
  private denyPrefixes: string[];

  // inner 是被包装的后端实例,可以是任何实现了 BackendProtocolV2 的后端
  constructor(private inner: BackendProtocolV2, denyPrefixes = []) {
    this.denyPrefixes = denyPrefixes.map(
      p => p.endsWith("/") ? p : p + "/"
    );
  }

  private isDenied(path: string): boolean {
    return this.denyPrefixes.some(p => path.startsWith(p));
  }

  // 读取类方法直接透传给内部后端,不加任何拦截
  ls(path) { return this.inner.ls(path); }
  read(filePath, offset?, limit?) { return this.inner.read(filePath, offset, limit); }
  grep(pattern, path?, glob?) { return this.inner.grep(pattern, path, glob); }
  glob(pattern, path?) { return this.inner.glob(pattern, path); }

  // 写入类方法加拦截:检查路径是否在禁止列表中
  async write(filePath, content): Promise<WriteResult> {
    if (this.isDenied(filePath)) return { error: "Writes not allowed" };
    return this.inner.write(filePath, content);  // 未被禁止,委托给内部后端
  }
  async edit(filePath, old, newStr, replaceAll?): Promise<EditResult> {
    if (this.isDenied(filePath)) return { error: "Edits not allowed" };
    return this.inner.edit(filePath, old, newStr, replaceAll);
  }
}

子类方式

  • 与特定后端耦合
  • 覆写方法,调用 super
  • 适合 FilesystemBackend 等具体后端

包装器方式

  • 与后端解耦,适用于任意后端
  • 组合使用,可多层包装
  • 适合跨后端的统一策略
图:子类方式与包装器方式对比

多模态文件支持

V2 后端原生支持图片、音频、视频、PDF 等二进制文件

当 read() 遇到二进制文件(通过扩展名判断 MIME 类型)时,返回包含 Uint8Array 内容和相应 mimeType 的 ReadResult。文本文件返回 string 内容。

版本要求:多模态文件支持(PDF、音频、视频)需要 deepagents>=1.9.0。
const result = await backend.read("/workspace/screenshot.png");

if (result.error) {
  console.error(result.error);  // 读取失败,输出错误信息
} else if (result.content instanceof Uint8Array) {
  // 二进制文件 —— content 是 Uint8Array,mimeType 标识文件类型
  console.log(`Binary file: ${result.mimeType}`);  // 例如 "image/png"
} else {
  // 文本文件 —— content 是 string 类型
  console.log(`Text file: ${result.mimeType}`);     // 例如 "text/plain"
}
类别扩展名MIME 类型
图片.png, .jpg/.jpeg, .gif, .webp, .svg, .heic, .heifimage/png, image/jpeg, image/gif, image/webp, image/svg+xml, image/heic, image/heif
音频.mp3, .wav, .aiff, .aac, .ogg, .flacaudio/mpeg, audio/wav, audio/aiff, audio/aac, audio/ogg, audio/flac
视频.mp4, .webm, .mpeg/.mpg, .mov, .avi, .flv, .wmv, .3gppvideo/mp4, video/webm, video/mpeg, video/quicktime, video/x-msvideo, video/x-flv, video/x-ms-wmv, video/3gpp
文档.pdf, .ppt, .pptxapplication/pdf, application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation
文本.txt, .html, .json, .js, .ts, .py 等text/plain, text/html, application/json 等
FileData 格式:V2 使用 { content: string | Uint8Array, mimeType, created_at, modified_at }。框架透明处理 V1(行数组)和 V2 格式。
type FileData =
  // 当前格式 (v2)
  | {
      content: string | Uint8Array; // string 用于文本,Uint8Array 用于二进制
      mimeType: string;             // 例如 "text/plain", "image/png"
      created_at: string;           // ISO 8601 时间戳
      modified_at: string;          // ISO 8601 时间戳
    }
  // 旧版格式 (v1)
  | {
      content: string[];            // 行数组
      created_at: string;           // ISO 8601 时间戳
      modified_at: string;          // ISO 8601 时间戳
    };
向后兼容:后端在从状态或存储中读取时可能遇到任一格式。框架会透明地处理两种格式。新的写入默认使用 v2 格式。在旧版读取器需要旧版格式的滚动部署期间,可以向后端构造函数传递 fileFormat: "v1"(例如 new StoreBackend({ fileFormat: "v1" }))。

迁移指南

从后端工厂模式迁移到直接实例模式

从 deepagents 1.9.0 起,后端工厂模式已弃用。现在后端通过内部辅助函数解析运行时上下文,你可以直接传递实例。

之前(已弃用)

// 工厂函数包装
const agent = createDeepAgent({
  backend: (config) => new CompositeBackend(
    new StateBackend(config),
    {
      "/memories/": new StoreBackend(config, {
        namespace: (rt) => [rt.serverInfo.user.identity],
      }),
    },
  ),
});

之后(推荐)

// 直接传递实例
const agent = createDeepAgent({
  backend: new CompositeBackend(
    new StateBackend(),
    {
      "/memories/": new StoreBackend({
        namespace: (rt) => [rt.serverInfo.user.identity],
      }),
    },
  ),
});

BackendContext 迁移

  • 工厂参数从 BackendContext 变为 Runtime
  • ctx.runtime.context.userId → rt.serverInfo.user.identity
  • ctx.state 没有直接替代方案

V1 到 V2 方法重命名

  • lsInfo → ls
  • grepRaw → grep
  • globInfo → glob
  • 返回类型从裸数组变为 Result 对象

已弃用的 API

已弃用项替代方案
BackendFactory 类型直接传递后端实例
BackendRuntime 接口后端在内部解析上下文
StateBackend(runtime, options?) 构造函数重载new StateBackend(options?)
StoreBackend(stateAndStore, options?) 构造函数重载new StoreBackend(options?)
WriteResult 和 EditResult 上的 filesUpdate 字段状态写入现在由后端在内部处理
兼容性:工厂模式在运行时仍然可用(会发出弃用警告)。框架会自动适配传递给 createDeepAgent() 的 V1 后端。直接调用协议方法时需手动使用 adaptBackendProtocol()。

本章回顾

后端是 Deep Agents 文件系统的灵魂——你控制数据存在哪里

核心知识点

  • 七种内置后端覆盖从临时到生产的全部场景
  • StateBackend 是默认选择,线程作用域
  • StoreBackend 实现跨线程持久化
  • CompositeBackend 按路径路由到不同后端
  • 自定义后端只需实现 BackendProtocolV2 七个方法
  • 权限在工具层评估,策略钩子在方法层拦截
  • V2 后端原生支持图片、音频、视频等二进制文件

后续章节预告

  • 子 Agent:多 Agent 协作与任务分发
  • 异步子 Agent:并行执行多个子任务
  • 人机交互:HITL 中间件与审批流程
  • 权限系统:细粒度的文件访问控制
  • 记忆管理:短期 + 长期记忆的最佳实践
  • 沙箱:安全隔离的代码执行环境
核心结论:后端不是"选一个就用到底"的单选题。通过 CompositeBackend 路由 + 权限控制 + 策略钩子的组合,你可以为 Agent 构建一个安全、灵活、可扩展的虚拟文件系统。先用默认后端起步,随着需求增长逐步引入更复杂的配置。

延伸阅读

继续本课程

官方文档

相关概念

  • 记忆管理 — StoreBackend 的典型应用场景
  • 人机交互 — 配合 LocalShellBackend 使用的安全策略
  • Harness — 后端配置的运行时入口