通过“构建抽象的文件系统”我们知道Deep Agents的文件系统是建立在一个利用BackendProtocol协议抽象的文件系统之上的使得Agent能够以统一的方式进行文件操作无论底层存储是本地磁盘、云端S3、数据库还是内存。这种设计不仅提供了极大的灵活性还使得Agent能够适应不同的应用场景从而实现更复杂的数据管理和交互能力。这个文件系统是通过FilesystemMiddleware赋予Agent的。1. 提供文件操作工具集在Deep Agents框架中FilesystemMiddleware不仅仅是提供了简单的文件读写工具更是实现长程任务和上下文工程的核心基础设施。它通过将文件系统抽象为AI的外部工作记忆解决了大模型在处理复杂任务时因上下文窗口溢出而导致的健忘问题。它赋予Agent存储与计算分离能力为Agent自动注入了一套标准化的文件操作工具集ls列出目录read_file读取内容write_file写入文件edit_file精确编辑内容glob模式匹配查找grep文本搜索execute代码执行仅限于可作为沙箱的后端存储FilesystemMiddleware提供了自动上下文卸载Context Offloading的能力。为了防止Agent被海量的工具输出如超长的搜索结果或日志淹没FilesystemMiddleware实现了自动化调度阈值触发默认情况下当工具调用结果超过20,000个tokens可配置时会自动将结果保存到文件系统中引用替换Agent的对话历史中不会保留原始巨量数据对应的内容被替换为一个指向文件路径的指针动态加载Agent可以根据需要通过read_file的offset和limit参数实现分页读取仅获取当前推理所需的片段从而极大地节省了Context消耗与其说FilesystemMiddleware为Agent缔造了一个文件系统不如说它提供了一套工具集以文件系统的方式来操作各种后端存储。FilesystemMiddleware默认提供了七个工具ls、read、write、edit、glob、grep和execute它们会调用对应后端的接口来实现文件的列出、读取、写入、编辑、模式匹配和命令执行等功能。通过这些工具Agent可以以文件系统的方式来访问和操作不同类型的存储从而实现更复杂的数据管理和交互能力。如下面的代码片段所示当我们调用__init__方法创建FilesystemMiddleware实例时可以利用backend参数来指定一个后端实例或者直接传入一个后端工厂函数BackendFactory来动态创建后端。如果采用StateBackend作为默认后端文件内容和元数据的FileData对象最终会写入名为files的状态字段中。表示状态Schema类型的state_schema字段返回的类型是FilesystemState唯一的状态成员files返回的字典模拟存储的文件。BackendFactory:TypeAliasCallable[[ToolRuntime],BackendProtocol]BACKEND_TYPESBackendProtocol|BackendFactoryclassFilesystemMiddleware(AgentMiddleware[FilesystemState,ContextT,ResponseT]):state_schemaFilesystemStatedef__init__(self,*,backend:BACKEND_TYPES|NoneNone,system_prompt:str|NoneNone,custom_tool_descriptions:dict[str,str]|NoneNone,tool_token_limit_before_evict:int|None20000,max_execute_timeout:int3600,)-None:self.backendbackendifbackendisnotNoneelse(StateBackend)self._custom_system_promptsystem_prompt self._custom_tool_descriptionscustom_tool_descriptionsor{}self._tool_token_limit_before_evicttool_token_limit_before_evict self._max_execute_timeoutmax_execute_timeout self.tools[self._create_ls_tool(),self._create_read_file_tool(),self._create_write_file_tool(),self._create_edit_file_tool(),self._create_glob_tool(),self._create_grep_tool(),self._create_execute_tool(),]def_create_ls_tool(self)-BaseTooldef_create_read_file_tool(self)-BaseTooldef_create_write_file_tool(self)-BaseTooldef_create_edit_file_tool(self)-BaseTooldef_create_glob_tool(self)-BaseTooldef_create_grep_tool(self)-BaseTooldef_create_execute_tool(self)-BaseTool__init__方法中除了backend参数外还提供了以下几个可选参数system_prompt用于定制文件系统工具的系统提示语帮助Agent更好地理解工具的用途和使用方法custom_tool_descriptions一个字典用于定制每个工具的描述信息键是工具名称值是对应的描述文本tool_token_limit_before_evict一个整数指定在工具调用历史中当累计的工具调用参数的token数量超过这个限制时应该开始逐步淘汰旧的工具调用记录以节省上下文空间max_execute_timeout一个整数指定execute工具的最大执行时间以秒为单位防止Agent执行长时间运行的命令导致资源占用与其说FilesystemMiddleware为Agent缔造了一个文件系统不如说它提供了一套工具集以文件系统的方式来操作各种后端存储。它默认提供了七个工具ls、read、write、edit、glob、grep和execute它们会调用对应后端的接口来实现文件的列出、读取、写入、编辑、模式匹配和命令执行等功能。通过这些工具Agent可以以文件系统的方式来访问和操作不同类型的存储从而实现更复杂的数据管理和交互能力。2. 看看工具描述AI编程对程序员的自然语言的文字驾驭能力提出了更高的要求而这是程序最欠缺的能力也是很多人不屑的地方。对于LLM来说提示词是唯一的输入而工具描述是组成提示词的重要部分。即使你将定义工具的函数写得再完美没有一份清晰、准确、详细的工具描述LLM也无法正确地理解工具的功能和使用方法更无法在实际调用中正确地传递参数和处理返回值。工具描述就像是工具的使用说明书只有当LLM能够正确地理解和使用这些说明书中的信息才能发挥工具的最大效用。正如我一贯主张通过阅读知名框架的源码而不是阅读大量的书籍来学习架构设计一样我主张通过阅读知名框架中的提示词来学习如何撰写提示词而不是阅读一本又一本关于提示词工程的书籍。因为框架是高复用率的程序如果框架本身被验证是成功的那们证明其设计的提示词具有很高的质量。我们来看一下FilesystemMiddleware的工具描述是如何设计的也许我们就能从中学到一些撰写工具描述的技巧。lsLists all files in a directory. This is useful for exploring the filesystem and finding the right file to read or edit. You should almost ALWAYS use this tool before using the read_file or edit_file tools.read_fileReads a file from the filesystem. Assume this tool is able to read all files. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned. Usage: - By default, it reads up to 100 lines starting from the beginning of the file - **IMPORTANT for large files and codebase exploration**: Use pagination with offset and limit parameters to avoid context overflow - First scan: read_file(path, limit100) to see file structure - Read more sections: read_file(path, offset100, limit200) for next 200 lines - Only omit limit (read full file) when necessary for editing - Specify offset and limit: read_file(path, offset0, limit100) reads first 100 lines - Results are returned using cat -n format, with line numbers starting at 1 - Lines longer than 5,000 characters will be split into multiple lines with continuation markers (e.g., 5.1, 5.2, etc.). When you specify a limit, these continuation lines count towards the limit. - You have the capability to call multiple tools in a single response. It is always better to speculatively read multiple files as a batch that are potentially useful. - If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents. - Image files (.png, .jpg, .jpeg, .gif, .webp) are returned as multimodal image content blocks (see https://docs.langchain.com/oss/python/langchain/messages#multimodal). For image tasks: - Use read_file(file_path...) for .png/.jpg/.jpeg/.gif/.webp - Do NOT use offset/limit for images (pagination is text-only) - If image details were compacted from history, call read_file again on the same path - You should ALWAYS make sure a file has been read before editing it.write_fileWrites to a new file in the filesystem. Usage: - The write_file tool will create the a new file. - Prefer to edit existing files (with the edit_file tool) over creating new ones when possible.edit_filePerforms exact string replacements in files. Usage: - You must read the file before editing. This tool will error if you attempt an edit without reading the file first. - When editing, preserve the exact indentation (tabs/spaces) from the read output. Never include line number prefixes in old_string or new_string. - ALWAYS prefer editing existing files over creating new ones. - Only use emojis if the user explicitly requests it.globFind files matching a glob pattern. Supports standard glob patterns: * (any characters), ** (any directories), ? (single character). Returns a list of absolute file paths that match the pattern. Examples: - **/*.py - Find all Python files - *.txt - Find all text files in root - /subdir/**/*.md - Find all markdown files under /subdir - data_??.csv - Find files like data_01.csv, data_A1.csv, etc.grepSearch for a text pattern across files. Searches for literal text (not regex) and returns matching files or content based on output_mode. Special characters like parentheses, brackets, pipes, etc. are treated as literal characters, not regex operators. Examples: - Search all files: grep(patternTODO) - Search Python files only: grep(patternimport, glob*.py) - Show matching lines: grep(patternerror, output_modecontent) - Search for code with special chars: grep(patterndef __init__(self):) - Search for a pattern that includes glob special chars: grep(patterndata_*.csv, globdata_*.csv)executeExecutes a shell command in an isolated sandbox environment. Usage: Executes a given command in the sandbox environment with proper handling and security measures. Before executing the command, please follow these steps: 1. Directory Verification: - If the command will create new directories or files, first use the ls tool to verify the parent directory exists and is the correct location - For example, before running mkdir foo/bar, first use ls to check that foo exists and is the intended parent directory 2. Command Execution: - Always quote file paths that contain spaces with double quotes (e.g., cd path with spaces/file.txt) - Examples of proper quoting: - cd /Users/name/My Documents (correct) - cd /Users/name/My Documents (incorrect - will fail) - python /path/with spaces/script.py (correct) - python /path/with spaces/script.py (incorrect - will fail) - After ensuring proper quoting, execute the command - Capture the output of the command Usage notes: - Commands run in an isolated sandbox environment - Returns combined stdout/stderr output with exit code - If the output is very large, it may be truncated - For long-running commands, use the optional timeout parameter to override the default timeout (e.g., execute(commandmake build, timeout300)) - A timeout of 0 may disable timeouts on backends that support no-timeout execution - VERY IMPORTANT: You MUST avoid using search commands like find and grep. Instead use the grep, glob tools to search. You MUST avoid read tools like cat, head, tail, and use read_file to read files. - When issuing multiple commands, use the ; or operator to separate them. DO NOT use newlines (newlines are ok in quoted strings) - Use when commands depend on each other (e.g., mkdir dir cd dir) - Use ; only when you need to run commands sequentially but dont care if earlier commands fail - Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of cd Examples: Good examples: - execute(commandpytest /foo/bar/tests) - execute(commandpython /path/to/script.py) - execute(commandnpm install npm test) - execute(commandmake build, timeout300) Bad examples (avoid these): - execute(commandcd /foo/bar pytest tests) # Use absolute path instead - execute(commandcat file.txt) # Use read_file tool instead - execute(commandfind . -name *.py) # Use glob tool instead - execute(commandgrep -r pattern .) # Use grep tool instead Note: This tool is only available if the backend supports execution (SandboxBackendProtocol). If execution is not supported, the tool will return an error message.3. 指导LLM调用文件操作工具的系统提示词FilesystemMiddleware的__init__方法提供了系统提示词作为使用工具的说明书提示词的应用是通过重写的wrap_model_call/awrap_model_call方法来实现的。如果tool_token_limit_before_evict不为空在工具返回内容超过设置的这个阈值时为了避免上下文过载FilesystemMiddleware会将其内容以文件形式存储到后端并返回一个新的工具调用结果并返回文件的路径。由于tool_token_limit_before_evict参数设置的阈值以Token作为单位在进行阈值计算的时候会从返回的ToolMessage中提取文本内容并按照每个字符平均4个token的经验值来估算总的token数量。此项工作是通过重写的wrap_tool_call/awrap_tool_call方法来实现的。classFilesystemMiddleware(AgentMiddleware[FilesystemState,ContextT,ResponseT]):defwrap_model_call(self,request:ModelRequest[ContextT],handler:Callable[[ModelRequest[ContextT]],ModelResponse[ResponseT]],)-ModelResponse[ResponseT]asyncdefawrap_model_call(self,request:ModelRequest[ContextT],handler:Callable[[ModelRequest[ContextT]],Awaitable[ModelResponse[ResponseT]]],)-ModelResponse[ResponseT]defwrap_tool_call(self,request:ToolCallRequest,handler:Callable[[ToolCallRequest],ToolMessage|Command],)-ToolMessage|Commandasyncdefawrap_tool_call(self,request:ToolCallRequest,handler:Callable[[ToolCallRequest],Awaitable[ToolMessage|Command]],)-ToolMessage|Command如下所示的是FilesystemMiddleware提供的系统提示词看看我们能否撰写出同样质量的提示词来指导LLM正确地调用工具## Following Conventions - Read files before editing — understand existing content before making changes - Mimic existing style, naming conventions, and patterns ## Filesystem Tools ls, read_file, write_file, edit_file, glob, grep You have access to a filesystem which you can interact with using these tools. All file paths must start with a /. Follow the tool docs for the available tools, and use pagination (offset/limit) when reading large files. - ls: list files in a directory (requires absolute path) - read_file: read a file from the filesystem - write_file: write to a file in the filesystem - edit_file: edit a file in the filesystem - glob: find files matching a pattern (e.g., **/*.py) - grep: search for text within files ## Large Tool Results When a tool result is too large, it may be offloaded into the filesystem instead of being returned inline. In those cases, use read_file to inspect the saved result in chunks, or use grep within /large_tool_results/ if you need to search across offloaded tool results and do not know the exact file path. Offloaded tool results are stored under /large_tool_results/tool_call_id.如果涉及execute工具的使用还会加上如下这段## Execute Tool execute You have access to an execute tool for running shell commands in a sandboxed environment. Use this tool to run commands, scripts, tests, builds, and other shell operations. - execute: run a shell command in the sandbox (returns output and exit code)4. 让Agent操作你的本地文件如下的这个演示程序充分体现了FilesystemMiddleware的功能。我们创建了一个Agent并在其中注册了FilesystemMiddleware。通过调用Agent来执行一系列文件系统操作包括创建目录、写入文件、列出目录内容和读取文件内容。Agent通过FilesystemMiddleware提供的工具来与后端存储进行交互实现了一个完整的文件系统操作流程。fromlangchain.agentsimportcreate_agentfromdeepagents.middleware.filesystemimportFilesystemMiddlewarefromdeepagents.backends.local_shellimportLocalShellBackendfromlangchain_openaiimportChatOpenAIfromdotenvimportload_dotenvimportasyncio load_dotenv()agentcreate_agent(modelChatOpenAI(modelgpt-5.2-chat),middleware[FilesystemMiddleware(backendLocalShellBackend(virtual_modeTrue))],)prompt\ Execute following operations: - Remove the test_dir directory if it already exists, and then create a new empty one; - Change the current working directory to such a newly created directory; - Create a file called hello.py inside it with the content print(Hello World); - Create three files in the newly created directory: file1.txt, file2.txt, and file3.txt with any content of your choice; - List the files of the directory; - Read the content of hello.py. asyncdefmain():resultawaitagent.ainvoke(input{messages:[{role:user,content:prompt}]})result[messages][-1].pretty_print()asyncio.run(main())输出 Ai Message ✅ All requested operations have been completed. Here are the results step by step: ### Directory Setup - The directory **test_dir** was removed (if it existed) and recreated as a new empty directory. ### Files Created Inside test_dir - **hello.py** python print(Hello World) - **file1.txt** – contains: content 1 - **file2.txt** – contains: content 2 - **file3.txt** – contains: content 3 ### Directory Listing The contents of test_dir are: file1.txt file2.txt file3.txt hello.py ### Contents of hello.py print(Hello World) If you’d like to run hello.py, modify any files, or perform more filesystem operations, just let me know!