
DeerFlow 文件上传全链路解析API 端点、沙箱同步与 Agent 上下文注入机制【免费下载链接】deer-flowAn open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of tasks that could take minutes to hours.项目地址: https://gitcode.com/GitHub_Trending/de/deer-flowDeerFlow 后端为每个会话线程thread提供了线程隔离的文件上传能力支持多文件上传、可选的 Office/PDF 转 Markdown、以及让 Agent 在沙箱内通过统一虚拟路径感知并读取附件。读完本文你可以完整掌握上传 API 的请求/响应契约、config.yaml中uploads配置段的全部参数、文件在三套路径体系宿主路径 / 沙箱虚拟路径 / HTTP artifact URL间的映射关系以及UploadsMiddleware将当前消息附件注入 Agent 上下文的底层实现。功能特性总览DeerFlow 后端提供的文件上传功能具备以下能力支持多文件同时上传multipart/form-data字段名固定为files可选地将文档转换为 MarkdownPDF、PPT、Excel、Word文件存储在线程隔离的目录中线程之间互不可见Agent 自动感知当前消息中附带的文件历史附件按需通过list_uploaded_files工具查询支持文件列表查询和删除网关在应用层限制上传规模默认最多10 个文件、单文件 50 MiB、单次请求总计 100 MiB超过限制时后端返回413 Payload Too Large。这三个默认值硬编码在 uploads.py 中DEFAULT_MAX_FILES 10 DEFAULT_MAX_FILE_SIZE 50 * 1024 * 1024 DEFAULT_MAX_TOTAL_SIZE 100 * 1024 * 1024可通过config.yaml的uploads.max_files、uploads.max_file_size、uploads.max_total_size调整样例见 config.example.yaml前端会读取同一组限制并在选择文件时提示。API 端点四个端点全部挂载在 uploads.py 定义的路由前缀下router APIRouter(prefix/api/threads/{thread_id}/uploads, tags[uploads])每个端点还通过require_permission装饰器做了权限收敛上传要求threads:write且owner_checkTrue列表/限制查询要求threads:read删除要求threads:delete见 uploads.py。1. 上传文件POST /api/threads/{thread_id}/uploads请求体multipart/form-datafiles: 一个或多个文件响应UploadResponse{ success: true, files: [ { filename: document.pdf, size: 1234567, path: .deer-flow/threads/{thread_id}/user-data/uploads/document.pdf, virtual_path: /mnt/user-data/uploads/document.pdf, artifact_url: /api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.pdf, markdown_file: document.md, markdown_path: .deer-flow/threads/{thread_id}/user-data/uploads/document.md, markdown_virtual_path: /mnt/user-data/uploads/document.md, markdown_artifact_url: /api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.md } ], message: Successfully uploaded 1 file(s) }响应模型UploadedFileInfouploads.py还包含两个文档未强调的字段original_filename当文件名被去重重命名为name_1.ext时保留原始文件名和skipped_files因不安全文件名被跳过的文件列表同时会使success为false。三套路径说明字段含义使用者path实际文件系统路径相对于backend/目录运维/排障virtual_pathAgent 在沙箱中使用的虚拟路径Agent 工具调用artifact_url前端通过 HTTP 访问文件的 URL浏览器前端其中virtual_path与artifact_url由 manager.py 统一生成文件名会做 percent-encoding保证空格、#、?等特殊字符安全def upload_artifact_url(thread_id: str, filename: str) - str: return f/api/threads/{thread_id}/artifacts{VIRTUAL_PATH_PREFIX}/uploads/{quote(filename, safe)} def upload_virtual_path(filename: str) - str: return f{VIRTUAL_PATH_PREFIX}/uploads/{filename}2. 查询上传限制GET /api/threads/{thread_id}/uploads/limits返回网关当前生效的上传限制供前端在用户选择文件前提示和拦截。响应UploadLimits{ max_files: 10, max_file_size: 52428800, max_total_size: 104857600 }从源码看限制解析函数_get_upload_limit兼容历史配置键max_file_count、max_single_file_size且对 0或非法值自动回退到默认值并打警告日志uploads.py因此写错配置不会导致上传功能不可用只会退回默认限制。3. 列出已上传文件GET /api/threads/{thread_id}/uploads/list响应UploadListResponse{ files: [ { filename: document.pdf, size: 1234567, path: .deer-flow/threads/{thread_id}/user-data/uploads/document.pdf, virtual_path: /mnt/user-data/uploads/document.pdf, artifact_url: /api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.pdf, extension: .pdf, modified: 1705997600.0 } ], count: 1 }列表由 manager.py 的list_files_in_dir生成按文件名排序、只收常规文件follow_symlinksFalse、并自动过滤网关上传暂存文件.upload-*.part前缀因此前端不会看到上传中断留下的临时文件。4. 删除文件DELETE /api/threads/{thread_id}/uploads/{filename}响应{ success: true, message: Deleted document.pdf }删除逻辑在 manager.py 的delete_file_safe中先做路径遍历校验若文件扩展名属于可转换集合会连带删除转换生成的同名.md伴生文件missing_okTrue。文件不存在返回404检测到路径遍历返回400。配置说明uploads 配置段config.example.yaml 中给出的完整配置段uploads: # Application-level upload limits enforced by the gateway and exposed to the # frontend before file selection. max_files: 10 max_file_size: 52428800 # 50 MiB max_total_size: 104857600 # 100 MiB # Automatic Office/PDF conversion runs on the backend host before sandbox # isolation applies. Keep this disabled unless uploads come from a fully # trusted source and you intentionally accept host-side parser risk. auto_convert_documents: false # Controls which PDF-to-Markdown converter is used whenever PDF conversion # runs. Automatic upload conversion is gated separately by # auto_convert_documents. pdf_converter: auto各参数说明参数默认值作用max_files10单次请求最多文件数max_file_size52428800 (50 MiB)单文件上限字节max_total_size104857600 (100 MiB)单次请求总大小上限字节auto_convert_documentsfalse是否自动将 Office/PDF 转换为 Markdownpdf_converterautoPDF 转换策略auto/pymupdf4llm/markitdownauto_convert_documents的解析实现uploads.py同时接受布尔值和字符串1/true/yes/on大小写不敏感任何解析异常都回退为False——即安全默认是关闭。文档中的安全说明值得强调自动转换默认关闭是为了避免在网关主机上对不受信任的 Office/PDF 上传执行解析解析发生在沙箱隔离生效之前。只有在受信任部署中明确接受此风险时才应设为true。pdf_converter的三种取值在 file_conversion.py 中校验非法值会警告并回退auto。auto模式的策略是优先使用pymupdf4llm需另行安装若输出稀疏度异常少于 50 字符/页判定为图片型或加密 PDF则回退到 MarkItDown未安装pymupdf4llm时直接用 MarkItDown。支持的文档格式与转换策略显式启用uploads.auto_convert_documents: true时以下格式会自动转换为 MarkdownPDF (.pdf)PowerPoint (.ppt,.pptx)Excel (.xls,.xlsx)Word (.doc,.docx)这组扩展名由 file_conversion.py 的CONVERTIBLE_EXTENSIONS常量定义。转换后的 Markdown 文件保存在同一目录下文件名为原文件名 .md扩展名若转换失败原文件仍然保留仅日志记录错误转换函数返回None时路由不写入markdown_*字段。从源码看还有两个工程细节大文件异步转换超过 1 MB 的文件通过asyncio.to_thread放到线程池转换避免阻塞事件循环file_conversion.py。伴生.md的命名去重上传路由在写.md之前先通过claim_unique_filename预留名称防止转换输出静默覆盖同请求内的其他文件转换失败时再释放该名称。Agent 集成当前消息的文件上下文发送消息时前端会把该消息附带的上传文件元数据放入HumanMessage.additional_kwargs.files每个条目含filename、size、path、status。UploadsMiddlewareuploads_middleware.py只把当前消息中的文件注入 Agent 上下文current_uploads The following files were uploaded in this message: - document.pdf (1.2 MB) Path: /mnt/user-data/uploads/document.pdf To work with these files: - Read from the file first — use the outline line numbers and read_file to locate relevant sections. - Use grep to search for keywords when you are not sure which section to look at. - Use glob to find files by name pattern. /current_uploads中间件的实际注入比文档示例更丰富before_agent钩子uploads_middleware.py每个文件条目除名称、大小、路径外还会附带文档大纲通过extract_outline_for_file提取的标题及行号提示 Agent 用read_file按行号定位章节无结构标题的文档则给出开头预览文本并建议用grep(patternkeyword, path/mnt/user-data/uploads/)搜索每条上下文最多列出 10 个文件_MAX_FILES_PER_CONTEXT_SECTION超出部分只汇总类型统计并提示用glob列出全部文件名、路径、大纲标题等用户可控内容都经过neutralize_untrusted_tags净化防止恶意构造的文件名在可信的current_uploads包裹内注入指令标签。以前轮次上传的文件不会在每次请求中重复注入这是控制 token 消耗的关键设计见 uploads_middleware.py 的模块注释。Agent 可按需调用list_uploaded_files工具实现位于 list_uploaded_files_tool.py查询历史上传如果已知文件名也可直接用read_file或grep访问/mnt/user-data/uploads/下的文件。中间件还提供了abefore_agent异步钩子把目录枚举、stat、大纲读取等阻塞式文件 IO 通过run_in_executor派发到工作线程避免拖慢事件循环。使用上传的文件路径映射与存储结构Agent 在沙箱中运行使用虚拟路径访问文件可直接用read_file工具读取# 读取原始 PDF如果支持 read_file(path/mnt/user-data/uploads/document.pdf) # 读取转换后的 Markdown推荐 read_file(path/mnt/user-data/uploads/document.md)路径映射关系Agent 使用/mnt/user-data/uploads/document.pdf虚拟路径实际存储backend/.deer-flow/threads/{thread_id}/user-data/uploads/document.pdf前端访问/api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.pdfHTTP URL文件存储结构backend/.deer-flow/threads/ └── {thread_id}/ └── user-data/ └── uploads/ ├── document.pdf # 原始文件 ├── document.md # 转换后的 Markdown ├── presentation.pptx ├── presentation.md └── ...上传流程采用线程目录优先策略对应 uploads.py 的upload_files实现先写入backend/.deer-flow/threads/{thread_id}/user-data/uploads/作为权威存储本地沙箱sandbox_idlocal直接使用线程目录内容默认情况下非本地沙箱通过acquire_async路由内为try_acquire_sandbox_for_request获取沙箱后再额外把文件update_file同步到/mnt/user-data/uploads/*确保运行时可见。同步前会通过_make_file_sandbox_writable放宽文件权限位——Docker 沙箱中网关以 root 写文件0o600容器内非 root 进程需要 group/other 读写位如果 Gateway 与远端沙箱保证挂载同一份线程 user-data例如正确对齐的共享 PVC、NFS 或 hostPath可设置sandbox.thread_data_mounts: true上传路由检测到沙箱提供者的uses_thread_data_mounts为真时会跳过 sandbox acquire 和逐文件同步不确定挂载关系时应省略该配置并保留自动检测。错误地设为true会导致文件只存在于 Gateway 存储、沙箱内不可见。源码中还有一个权限细节值得注意当调用方被授权体系拒绝sandbox:execute时上传本身仍会成功文件留在线程 uploads 目录只是跳过沙箱同步——因为被拒绝沙箱执行的 Agent 本就无法消费这些文件。安全设计文件名校验与写入防护文档提到系统会自动验证文件路径防止目录遍历攻击manager.py 给出了完整的实现证据链normalize_filename只保留 basename拒绝空名、./..、含反斜杠的名称并限制文件名为 255 字节UTF-8validate_upload_destination确认目标是 uploads 目录内的常规文件且无多硬链接再做resolve().relative_to(base)的遍历校验写入路径使用open_upload_file_no_symlinkPOSIX 下以O_NOFOLLOW打开防止上传目录被沙箱进程预埋符号链接后、网关以自身权限覆盖目录外文件上传采用暂存文件模式.upload-*.part先写临时文件再os.replace原子落位中途失败自动回滚删除已写文件网关硬崩溃遗留的暂存文件由cleanup_stale_upload_staging_files在启动时清理。测试与验证使用 curl 测试# 1. 上传单个文件 curl -X POST http://localhost:2026/api/threads/test-thread/uploads \ -F files/path/to/document.pdf # 2. 上传多个文件 curl -X POST http://localhost:2026/api/threads/test-thread/uploads \ -F files/path/to/document.pdf \ -F files/path/to/presentation.pptx \ -F files/path/to/spreadsheet.xlsx # 3. 列出已上传文件 curl http://localhost:2026/api/threads/test-thread/uploads/list # 4. 删除文件 curl -X DELETE http://localhost:2026/api/threads/test-thread/uploads/document.pdf使用 Python 测试import requests thread_id test-thread base_url http://localhost:2026 # 上传文件 files [ (files, open(document.pdf, rb)), (files, open(presentation.pptx, rb)), ] response requests.post( f{base_url}/api/threads/{thread_id}/uploads, filesfiles ) print(response.json()) # 列出文件 response requests.get(f{base_url}/api/threads/{thread_id}/uploads/list) print(response.json()) # 删除文件 response requests.delete( f{base_url}/api/threads/{thread_id}/uploads/document.pdf ) print(response.json())仓库内有对应的自动化测试test_uploads_manager.py 覆盖存储管理器逻辑文件名归一化、去重、路径遍历、安全删除test_uploads_router.py 覆盖 HTTP 端点的限制与错误码。限制与 Nginx 配置最大文件大小100MB可在 nginx.conf 中配置client_max_body_size文件名安全性系统会自动验证文件路径防止目录遍历攻击线程隔离每个线程的上传文件相互隔离无法跨线程访问自动文档转换默认关闭如需启用需在config.yaml中显式设置uploads.auto_convert_documents: trueNginx 侧的实际配置在 nginx.conf# Custom API: Uploads endpoint location ~ ^/api/threads/[^/]/uploads { proxy_pass http://$gateway_upstream; proxy_http_version 1.1; ... # Large file upload support client_max_body_size 100M; proxy_request_buffering off; # Disable response buffering to avoid permission errors }要点是client_max_body_size 100M与proxy_request_buffering off关闭请求缓冲大文件边收边转发、不落 nginx 临时盘。注意 nginx 层的 100M 略大于应用层max_total_size100 MiB实际生效的瓶颈以应用层限制为准。技术实现组件Upload Routeruploads.py处理文件上传、列表、删除、限制查询请求使用 markitdown可选 pymupdf4llm转换文档Uploads Middlewareuploads_middleware.py读取当前消息的additional_kwargs.files在 Agent 请求前生成并注入current_uploads文件上下文历史上传由list_uploaded_files按需查询不会每轮自动注入共享管理器manager.py纯业务逻辑无 FastAPI 依赖Gateway 和 Client 两侧共用——文件名安全、暂存/原子落盘、列举、安全删除与路径 URL 生成都在此Nginx 配置nginx.conf正则路由上传请求到 Gateway API配置大文件上传支持。依赖markitdown0.0.1a2— 文档转换python-multipart0.0.20— 文件上传处理故障排查文件上传失败检查文件大小是否超过限制对比GET /uploads/limits返回值检查 Gateway API 是否正常运行检查磁盘空间是否充足查看 Gateway 日志make gateway若走 Nginx 反代注意413 Request Entity Too Large可能来自 nginx 层client_max_body_size而非应用层文档转换失败检查 markitdown 是否正确安装uv run python -c import markitdown查看日志中的具体错误信息某些损坏或加密的文档可能无法转换但原文件仍会保存响应中不出现markdown_*字段Agent 看不到上传的文件确认UploadsMiddleware已在 agent 组装流程中注册检查thread_id是否正确确认文件确实已上传到backend/.deer-flow/threads/{thread_id}/user-data/uploads/中间件会校验文件在磁盘上真实存在缺失条目会被静默跳过并打 info 日志非本地沙箱场景下确认上传接口没有报错需要成功完成 sandbox 同步若误设了sandbox.thread_data_mounts: true而挂载实际未对齐也会出现网关有文件、沙箱看不到的现象前端集成// 上传文件示例 async function uploadFiles(threadId: string, files: File[]) { const formData new FormData(); files.forEach(file { formData.append(files, file); }); const response await fetch( /api/threads/${threadId}/uploads, { method: POST, body: formData, } ); return response.json(); } // 列出文件 async function listFiles(threadId: string) { const response await fetch( /api/threads/${threadId}/uploads/list ); return response.json(); }生产集成时建议先调用GET /uploads/limits获取当前网关限制在文件选择阶段就拦截超限输入避免上传到一半收到413。小结DeerFlow 的文件上传子系统是一条HTTP 网关 → 线程隔离存储 → 沙箱可见 → Agent 上下文的完整链路应用层限制在 uploads.py 中逐字节校验并返回413存储落位由 manager.py 的安全写入原语保证沙箱可见性依赖线程目录优先 按需同步/挂载策略可用sandbox.thread_data_mounts显式声明共享挂载Agent 侧则由 uploads_middleware.py 将仅当前消息的附件含文档大纲注入current_uploads上下文历史附件交给list_uploaded_files工具按需发现。理解这套三路径映射宿主路径 / 虚拟路径 / artifact URL与当前轮注入、历史按需查的上下文策略是排查上传类问题的核心抓手。【免费下载链接】deer-flowAn open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of tasks that could take minutes to hours.项目地址: https://gitcode.com/GitHub_Trending/de/deer-flow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考