DeepAgent:虚拟文件系统

一、使用虚拟文件系统的原因

如果在Agent开发过程中,将会话记录,全部放入会话历史中,会导致出现以下问题:

  1. 上下文窗口溢出与截断:大量信息堆积在上下文中,导致超出模型的最大上下文容量,产生报错或被截断。
  2. 成本与延迟飙升:Transformer注意力复杂度随长度近似平方增长,每轮输入随轮数增长,成本线性上升。
  3. 模型注意力稀释,性能下降:中间部分信息被忽略。指令遵循下降、幻觉增加、重复提问、前后矛盾。
  4. 旧信息、过时状态污染决策:用户已经改口、撤销指令、更新偏好,但旧记录仍在上下文里。
  5. 上下文污染与安全风险:敏感信息、API Key、用户隐私也会因全量回传扩大泄露面,多租户场景还可能串数据。
  6. 工程与可维护性变差:出问题时很难定位是哪条历史影响了输出。

在DeepAgent中使用虚拟文件系统可以实现,按需读取、结构化存储、搜索定位。

二、内置文件系统工具

工具 用途
**ls** 查看目录下内容
**read_file** 阅读文件
**write_file** 写文件(创建文件、覆盖)
**edit_file** 替换(改局部内容时)
**delete** 删除文件或目录
**glob** 模式匹配查找文件
**grep** 全文检索

搜索结果及其边界:

  1. read_file:会返回分页信息下一段偏移量
  2. grep、glob:可能返回有效但不完整的结果,并通过 truncated=True 明示截断
  3. grep:默认最多保留 1000 个匹配

调用成功只代表工具正常执行,不代表已经得到全集,后续应缩小目录或匹配条件继续搜索。

工具输出文本是给模型读的,不是给程序解析的。程序应该消费结构化 Backend 结果;文本格式变化只影响展示和快照,不应影响业务逻辑。

2.1 read_file特性

  1. 分片读取

  2. 对于大文件,支持按偏移量和行数读取,避免把整个文件塞进上下文

1
# 默认最多读取前 100 行 read_file("/dcz/doc.md") # 从第 1 行开始,读取 50 行 read_file("/dcz/doc.md", offset=1, limit=50)
  1. 原生多模态支持

  2. 图片:**.png** **.jpg** **.jpeg** **.gif** **.webp** **.heic** **.heif**

  3. 视频:**.mp4** **.mpeg** **.mov** **.avi** **.flv** **.mpg** **.webm** **.wmv** **.3gpp**

  4. 音频:**.wav** **.mp3** **.aiff** **.aac** **.ogg** **.flac**

  5. 文档:**.pdf** **.ppt** **.pptx**

2.2 grep的三种输出模式

  1. files_with_matches:只返回文件路径(快速定位)
  2. content:返回匹配行和上下文(深入查看)
  3. count:返回匹配数量(概览统计)
1
2
3
4
5
# 找到所有包含 "TODO" 的 Python 文件
grep("TODO", glob="**/*.py", output_mode="files_with_matches")

# 查看匹配内容
grep("def create_agent", output_mode="content")

三、上下文自动管理:不只是存文件

3.1 大结果自动卸载

当工具调用的输入或输出超过 20,000 tokens 时(可通过 tool_token_limit_before_evict 配置),Deep Agents 会自动:

  1. 将完整内容写入虚拟文件系统
  2. 在对话历史中替换为文件路径引用 + 前 10 行预览
  3. Agent 需要时可以按需读回
1
2
3
4
5
6
7
8
9
原始结果:[50000 tokens 的搜索结果]

自动卸载后:
"结果已保存到 /workspace/search_results_001.md,
前 10 行预览:
1 # Search Results for 'LangGraph'
2
3 ## Result 1: Official Documentation
4 ..."

这个机制是完全自动的——Agent 不需要手动管理,但可以随时通过 read_file 或 grep 重新访问完整内容。

3.2 对话历史总结

当上下文大小达到模型窗口的 85% 时,如果没有更多可卸载的内容,Deep Agents 会启动自动总结:

  1. 用 LLM 生成对话的结构化摘要(意图、产出物、下一步)
  2. 将完整的原始对话写入文件系统保存
  3. 用摘要替换对话历史中的旧消息

这种”双保险”设计意味着:

  1. Agent 既有精炼的工作记忆(摘要)
  2. 又能在需要时回溯细节(文件系统中的完整记录)。
  3. img

四、可插拔的存储后端

可以根据场景选择不同的存储策略

4.1 StateBackend(默认):临时存储

1
2
3
4
from deepagents import create_deep_agent

# 默认就是 StateBackend,不需要显式指定
agent = create_deep_agent(model=model)

文件存在 LangGraph 的 Agent State 中。

特点

  • 同一个对话线程内持久化(多轮对话不丢失)
  • 对话结束后丢失(换一个 thread 就没了)
  • 主 Agent 和子 Agent 共享文件

适合场景:大多数情况下的默认选择,Agent 的”草稿纸”。

4.2 FilesystemBackend:本地磁盘

1
2
3
4
5
6
from deepagents.backends import FilesystemBackend

agent = create_deep_agent(
model=model,
backend=FilesystemBackend(root_dir=".", virtual_mode=True)
)

文件直接读写本地文件系统。

特点

  • root_dir 指定 Agent 可访问的根目录;

  • 相对路径会被解析为绝对路径(Path(root_dir).resolve()),“.” 即当前工作目录

  • virtual_mode=True 启用路径沙箱(阻止 …、~ 及越界的绝对路径),强烈建议开启;

  • 若为默认的 virtual_mode=False,即使设了 root_dir 也不提供任何越界保护

  • 文件修改是永久的、不可逆的

  • FilesystemBackend 的 virtual_mode=True 只提供路径遍历防护,不是完整沙箱。

适合场景:本地开发 CLI(编程助手)、CI/CD 流水线。

⚠️ 安全提示:Agent 可以读取 root_dir 下所有文件,包括 .env、密钥等敏感文件。Web 服务或 API 场景中切勿使用此后端,应改用沙箱后端。建议配合 Human-in-the-Loop 使用。

4.3 LocalShellBackend:本地 Shell 执行

1
2
3
4
5
6
from deepagents.backends import LocalShellBackend

agent = create_deep_agent(
model=model,
backend=LocalShellBackend(root_dir=".", virtual_mode=True, env={"PATH": "/usr/bin:/bin"})
)

LocalShellBackend 是 FilesystemBackend 的扩展,在文件系统工具之外额外提供 execute 工具,可直接在宿主机运行 Shell 命令。

特点

  • 命令通过 subprocess.run(shell=True) 执行,无任何沙箱隔离
  • 支持 timeout(默认 120 秒)、max_output_bytes(默认 100,000)、env 等参数
  • root_dir 作为命令的工作目录,但命令可访问系统上任意路径
  • LocalShellBackend 无沙箱隔离,生产/多用户环境禁用

适合场景:本地开发环境的编程助手、你完全信任 Agent 行为的个人开发机。

⚠️ 极高风险警告:Agent 可执行任意 Shell 命令,包括删除文件、外传数据、消耗资源。绝对不要在生产环境或多用户系统中使用。 沙箱后端是生产环境的安全替代方案。

如果确实要在个人开发机中临时使用,至少做几层防护:

  1. 将 root_dir 指向一个专门的临时工作区,而不是用户主目录或整个仓库上级目录
  2. 显式设置 virtual_mode=True,并用最小化的 env / PATH 降低命令可见范围
  3. 不把 .env、私钥、云凭证、生产配置文件放进 Agent 可访问目录
  4. 对 rm、mv、安装依赖、修改配置、访问网络等高风险操作增加 Human-in-the-Loop 审批
  5. 需要运行不可信代码、处理用户上传文件或对外提供服务时,直接换用沙箱后端,不要用 LocalShellBackend

4.4 StoreBackend:跨会话持久化

1
2
3
4
5
6
7
8
9
10
from langgraph.store.memory import InMemoryStore
from deepagents.backends import StoreBackend

agent = create_deep_agent(
model=model,
backend=StoreBackend(
namespace=lambda rt: (rt.server_info.user.identity,), # 按用户隔离数据
),
store=InMemoryStore() # 开发用;部署到 LangSmith 时可省略,平台自动提供
)

文件存在 LangGraph 的 Store 中。

特点

  • 跨线程持久化——不同对话都能访问同一份文件
  • namespace 参数控制数据隔离:lambda rt: (rt.server_info.user.identity,) 按用户隔离,防止数据混用
  • 开发用 InMemoryStore,部署到 LangSmith 时省略 store 参数(平台自动配置)

适合场景:长期记忆、跨会话的用户偏好、累积的知识库。

4.5 CompositeBackend:混合路由

这是最灵活的方案——不同路径走不同后端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore

agent = create_deep_agent(
model=model,
backend=CompositeBackend(
default=StateBackend(), # 默认:临时存储
routes={
"/memories/": StoreBackend(
namespace=lambda rt: (rt.server_info.user.identity,),
),
}
),
store=InMemoryStore()
)

💡 namespace 本地需要加 if rt.server_info else (“local-user”,) 兜底。

效果:

  • Agent 写入 /workspace/plan.md → StateBackend(临时)
  • Agent 写入 /memories/preferences.txt → StoreBackend(持久化,按用户隔离)
  • ls、glob、grep 自动聚合所有后端的结果,路径前缀保留

这种设计让 Agent 既有快速的”草稿纸”(State),又有持久的”记忆库”(Store),通过路径前缀自然隔离。

4.6 沙箱后端:安全代码执行

当使用沙箱后端(Modal、Daytona、Runloop 等)时,除了文件系统工具外,Agent 还会获得一个额外的 execute 工具,可以在隔离环境中执行 Shell 命令:

1
2
3
4
5
6
# 沙箱后端自动提供 execute 工具
agent = create_deep_agent(
model=model,
backend=sandbox # 沙箱实例
)
# Agent 现在可以运行: execute("pip install pandas && python analyze.py")

img

4.7 后端对比表

后端 数据/执行位置 持久化范围 Shell /execute 能力 隔离与安全 关键配置 适合场景
StateBackend(默认) LangGraph 的 Agent State 同一对话线程内持久;换 thread 丢失 随 Agent State,无独立沙箱 默认即可,无需显式指定 大多数默认选择、Agent 的“草稿纸”;主 Agent 和子 Agent 共享文件
FilesystemBackend 本地磁盘 永久、不可逆 无,仅文件系统工具 依赖 virtual_mode=True开启路径沙箱;否则即使设 root_dir也无越界保护 root_dir="."virtual_mode=True 本地开发 CLI、编程助手、CI/CD
LocalShellBackend 本地磁盘 + 宿主机 Shell 文件永久 execute,通过 subprocess.run(shell=True)执行 无沙箱隔离;命令可访问系统任意路径 root_dirvirtual_mode=Trueenvtimeout(默认 120s)、max_output_bytes(默认 100,000) 个人开发机、完全信任 Agent 行为的本地编程助手
StoreBackend LangGraph Store 跨线程、跨会话持久 通过 namespace做数据隔离 namespace必填;开发用 InMemoryStore,部署 LangSmith 可省略 store 长期记忆、跨会话用户偏好、累积知识库
CompositeBackend 按路径路由到不同后端 取决于被路由的后端 取决于被路由的后端 取决于被路由的后端 defaultroutes;v0.7 必须直接传 CompositeBackend(...) 实例 State 草稿 + Store 记忆混合,例如 /workspace/临时、/memories/持久
沙箱后端(Modal、Daytona、Runloop 等) 隔离沙箱环境 取决于沙箱配置 自动提供 execute 隔离环境,生产环境的安全替代方案 backend=sandbox实例 生产、不可信代码、用户上传文件、对外服务
  • 默认 / 临时草稿StateBackend
  • 本地文件持久化FilesystemBackend + virtual_mode=True
  • 需要本地 Shell 且完全信任 AgentLocalShellBackend,但必须加严格防护
  • 跨会话长期记忆StoreBackend
  • 草稿 + 长期记忆混合CompositeBackend
  • 生产 / 不可信代码 / 多用户服务:沙箱后端,不要用 LocalShellBackend

4.8 关键配置与风险提醒

后端 重点提醒
StateBackend 对话结束或换 thread 后文件丢失;适合临时草稿,不适合长期记忆。
FilesystemBackend virtual_mode从 0.5.0 起不显式声明会弃用警告,0.6.0 起必填,建议直接 virtual_mode=True。Agent 可读取 root_dir 下所有文件,包括 .env、密钥等;Web 服务或 API 场景切勿使用。
LocalShellBackend 极高风险:可删除文件、外传数据、消耗资源。绝对不要用于生产环境或多用户系统。若个人临时使用,至少:指向专用临时工作区、virtual_mode=True、最小化 env/PATH、不放敏感文件、高风险操作加 Human-in-the-Loop;不可信代码直接换沙箱后端。
StoreBackend namespace从 v0.5.0 起必填。rt.server_info.user.identity 在 LangSmith 部署时可用,但本地 invoke()rt.server_infoNone,需要兜底,例如 ("local-user",)
CompositeBackend v0.7 兼容提醒:backend=必须直接传 CompositeBackend(...) 等实例;backend=lambda rt: ...工厂函数已移除。 StoreBackend(namespace=lambda rt: ...)仍支持。lsglobgrep会自动聚合所有后端结果,并保留路径前缀。本地调试 namespace也要兜底。
沙箱后端 除文件系统工具外,Agent 会获得隔离环境中的 execute工具,例如可运行 pip install pandas && python analyze.py。生产环境优先选它。

五、 自定义后端与安全策略

5.1 声明式权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from deepagents import create_deep_agent, FilesystemPermission

agent = create_deep_agent(
model=model,
backend=CompositeBackend(
default=StateBackend(),
routes={
"/memories/": StoreBackend(
namespace=lambda rt: (rt.server_info.user.identity,),
),
},
),
permissions=[
FilesystemPermission(
operations=["write"],
paths=["/policies/**"],
mode="deny", # 禁止写入 /policies/ 下的任何文件
),
],
)

权限规则在工具调用前按声明顺序求值,采用 first-match-wins:第一个同时匹配 operations 和 paths 的规则决定结果;如果没有规则匹配,则默认允许。因此配置权限时,应将更具体的规则放在更宽泛的规则之前

FilesystemPermission 的 mode 决定命中规则后的处理方式:

mode 行为 使用场景
allow 允许操作继续执行 为特定路径设置显式例外
deny 直接拒绝,不执行文件操作 无论谁发起都不应访问的路径
interrupt 暂停并等待人工决策 可以操作,但必须先经过审批的敏感路径 需要 Checkpointer,并使用与工具审批相同的 Command(resume=…)恢复协议

5.2 实现自定义后端

如果内置后端不满足需求(比如要接入 S3 或 Postgres),可以实现 BackendProtocol 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from deepagents.backends.protocol import (
BackendProtocol, WriteResult, EditResult, DeleteResult,
LsResult, ReadResult, GrepResult, GlobResult,
)

class S3Backend(BackendProtocol):
def __init__(self, bucket: str, prefix: str = ""):
self.bucket = bucket
self.prefix = prefix.rstrip("/")

def ls(self, path: str) -> LsResult:
# 列出 S3 对象,返回 FileInfo 列表
...

def read(self, file_path: str, offset: int = 0, limit: int = 2000) -> ReadResult:
# 读取 S3 对象,返回 ReadResult(file_data=...) 或 ReadResult(error=...)
...

def write(self, file_path: str, content: str) -> WriteResult:
# 写入 S3 对象,外部存储后端 files_update=None
...

def edit(self, file_path: str, old_string: str, new_string: str,
replace_all: bool = False) -> EditResult:
# 读取 → 替换 → 写回
...

def grep(self, pattern: str, path: str | None = None, glob: str | None = None) -> GrepResult:
# 搜索匹配内容
...

def glob(self, pattern: str, path: str = "/") -> GlobResult:
# 模式匹配,返回 FileInfo 列表
...

def delete(self, file_path: str) -> DeleteResult:
# 删除对象或目录前缀;需要暴露 delete 工具时实现
...

BackendProtocol 的核心读写与搜索接口包括 ls、read、write、edit、grep、glob。如果后端要向 Agent 暴露 v0.7 的删除能力,还要实现 delete() 并返回 DeleteResult。包装器也必须同步转发或拒绝删除,不能只保护 write() 和 edit()。

5.3 安全策略:PolicyWrapper

对于需要拦截策略(速率限制、审计日志、内容检查)的场景,可以通过继承或包装后端实现:

方式一:继承现有后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from deepagents.backends.filesystem import FilesystemBackend
from deepagents.backends.protocol import WriteResult, EditResult, DeleteResult

class GuardedBackend(FilesystemBackend):
def __init__(self, *, deny_prefixes: list[str], **kwargs):
super().__init__(**kwargs)
self.deny_prefixes = [p if p.endswith("/") else p + "/" for p in deny_prefixes]

def write(self, file_path: str, content: str) -> WriteResult:
if any(file_path.startswith(p) for p in self.deny_prefixes):
return WriteResult(error=f"写入被拒绝:{file_path}")
return super().write(file_path, content)

def edit(self, file_path: str, old_string: str, new_string: str,
replace_all: bool = False) -> EditResult:
if any(file_path.startswith(p) for p in self.deny_prefixes):
return EditResult(error=f"编辑被拒绝:{file_path}")
return super().edit(file_path, old_string, new_string, replace_all)

def delete(self, file_path: str) -> DeleteResult:
if any(file_path.startswith(p) for p in self.deny_prefixes):
return DeleteResult(error=f"删除被拒绝:{file_path}")
return super().delete(file_path)

方式二:通用包装器(适用于任何后端)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from deepagents.backends.protocol import BackendProtocol, WriteResult, EditResult, DeleteResult

class PolicyWrapper(BackendProtocol):
def __init__(self, inner: BackendProtocol, deny_prefixes: list[str]):
self.inner = inner
self.deny_prefixes = [p if p.endswith("/") else p + "/" for p in deny_prefixes]

def _deny(self, path: str) -> bool:
return any(path.startswith(p) for p in self.deny_prefixes)

def ls(self, path): return self.inner.ls(path)
def read(self, file_path, offset=0, limit=2000): return self.inner.read(file_path, offset=offset, limit=limit)
def grep(self, pattern, path=None, glob=None): return self.inner.grep(pattern, path, glob)
def glob(self, pattern, path="/"): return self.inner.glob(pattern, path)

def write(self, file_path: str, content: str) -> WriteResult:
if self._deny(file_path):
return WriteResult(error=f"写入被拒绝:{file_path}")
return self.inner.write(file_path, content)

def edit(self, file_path: str, old_string: str, new_string: str,
replace_all: bool = False) -> EditResult:
if self._deny(file_path):
return EditResult(error=f"编辑被拒绝:{file_path}")
return self.inner.edit(file_path, old_string, new_string, replace_all)

def delete(self, file_path: str) -> DeleteResult:
if self._deny(file_path):
return DeleteResult(error=f"删除被拒绝:{file_path}")
return self.inner.delete(file_path)

**本文参考文献如下,非原创、非原创、非原创:
**https://datawhalechina.github.io/deepagents-in-action/chapters/ch03-virtual-filesystem/