Files

12 KiB
Raw Permalink Blame History

文件上传功能

概述

DeerFlow 后端提供了完整的文件上传功能,支持多文件上传,并可选地将 Office 文档和 PDF 转换为 Markdown 格式。

功能特性

  • ✅ 支持多文件同时上传,默认单条消息最多 20 个
  • ✅ 上传策略持久化到数据库,管理员可在「设置 → 上传设置」实时调整
  • ✅ 按文件类别设置格式与单文件上限,并校验扩展名和文件真实签名
  • ✅ 可选地转换文档为 Markdown(PDF、PPT、Excel、Word)
  • ✅ 文件存储在线程隔离的目录中
  • ✅ Agent 自动感知已上传的文件
  • ✅ 大文件采用流式统计和头部/中段/尾部采样,不把全文直接塞入模型上下文
  • ✅ 支持文件列表查询和删除

API 端点

1. 上传文件

POST /api/threads/{thread_id}/uploads

请求体: multipart/form-data

  • files: 一个或多个文件

网关每次上传都会读取数据库中的全局上传策略;前端也读取同一策略并在选择文件时预校验。内置默认值如下:

类别 默认格式 默认单文件上限
文档/文本 .txt、.md、.doc、.docx、.ppt、.pptx、.pdf 10 MiB
图片 .jpg、.jpeg、.png 10 MiB
表格 .xls、.xlsx、.csv 5 MiB

单条消息默认最多 20 个文件、附件合计默认不超过 100 MiB。管理员可在「设置 → 上传设置」调整数量、总量、各类别格式和大小,以及大文件采样阈值与输出字符预算。服务端始终执行最终校验;超过限制返回 413 Payload Too Large,不支持或内容签名不匹配返回 415 Unsupported Media Type。整批文件先写入暂存目录并全部验证,任一文件失败时不会留下半批结果。

响应:

{
  "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.pdf.md",
      "markdown_path": ".deer-flow/threads/{thread_id}/user-data/uploads/document.pdf.md",
      "markdown_virtual_path": "/mnt/user-data/uploads/document.pdf.md",
      "markdown_artifact_url": "/api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.pdf.md"
    }
  ],
  "message": "Successfully uploaded 1 file(s)"
}

路径说明:

  • path: 实际文件系统路径(相对于 backend/ 目录)
  • virtual_path: Agent 在沙箱中使用的虚拟路径
  • artifact_url: 前端通过 HTTP 访问文件的 URL

2. 查询和管理上传策略

所有已登录用户都可读取当前策略:

GET /api/upload-policy

管理员原子替换或恢复默认策略:

PUT  /api/upload-policy
POST /api/upload-policy/reset

策略首次启动时会将内置默认值写入数据库表 upload_policy,后续修改带递增 revision 和更新人/更新时间审计信息。各 Gateway worker 在读取或修改策略时同步刷新 harness 运行时副本。

兼容旧前端的线程级限制查询仍保留:

GET /api/threads/{thread_id}/uploads/limits

返回网关当前生效的分类限制、总量限制、大文件策略和策略版本。

响应:

{
  "max_files": 20,
  "max_file_size": 10485760,
  "max_total_size": 104857600,
  "max_files_per_message": 20,
  "max_total_bytes": 104857600,
  "groups": {
    "document": {"max_bytes": 10485760, "extensions": [".txt", ".md", ".doc", ".docx", ".ppt", ".pptx", ".pdf"]},
    "image": {"max_bytes": 10485760, "extensions": [".jpg", ".jpeg", ".png"]},
    "spreadsheet": {"max_bytes": 5242880, "extensions": [".xls", ".xlsx", ".csv"]}
  },
  "large_file": {
    "threshold_bytes": 1048576,
    "max_result_chars": 12000,
    "hard_max_result_chars": 32000
  },
  "revision": 1
}

3. 有界文本预览

GET /api/threads/{thread_id}/artifact-preview?path=/mnt/user-data/uploads/example.txt&max_chars=12000

小文件返回有界完整文本;大文件返回流式统计、开头/中段/结尾的确定性分块采样、截断标记和警告。浏览器预览不会调用无界的 response.text()。

4. 列出已上传文件

GET /api/threads/{thread_id}/uploads/list

响应:

{
  "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
}

5. 删除文件

DELETE /api/threads/{thread_id}/uploads/{filename}

响应:

{
  "success": true,
  "message": "Deleted document.pdf"
}

支持的文档格式

以下格式在显式启用 uploads.auto_convert_documents: true 时会自动转换为 Markdown:

  • PDF (.pdf)
  • PowerPoint (.ppt, .pptx)
  • Excel (.xls, .xlsx)
  • Word (.doc, .docx)
  • OpenDocument (.odt, .ods, .odp)

转换后的 Markdown 文件会保存在同一目录下,文件名为完整原文件名再追加 .md,例如 report.docx.md。这样 report.docx 和 report.pdf 不会同时覆盖同一个 report.md。前端预览仍兼容升级前的旧侧车名称。

通用部署可以关闭自动转换,以避免在网关主机上对不受信任的 Office/PDF 上传执行解析。本仓库当前 config.yaml 和纯 DeerFlow 离线部署包默认设置为 uploads.auto_convert_documents: true,用于受信任的内网部署。

Agent 集成

自动文件列举

Agent 在每次请求时会自动收到已上传文件的列表,格式如下:

<uploaded_files>
{经过 JSON 转义和字符预算裁剪的文件元数据、统计与采样摘要}
</uploaded_files>

当前轮文件可附带短摘要;历史轮次只注入元数据;自动生成的 Markdown 侧车不会作为第二个独立附件重复注入。整个 <uploaded_files> 块受数据库策略中的常规/硬字符预算约束。

使用上传的文件

Agent 在沙箱中运行,使用虚拟路径访问文件。Agent 可以直接使用 read_file 工具读取上传的文件:

# 读取原始 PDF(如果支持)
read_file(path="/mnt/user-data/uploads/document.pdf")

# 读取转换后的 Markdown(推荐)
read_file(path="/mnt/user-data/uploads/document.pdf.md")

当文本达到大文件阈值时,read_file 不会一次性返回全文,而会逐块读取并产生:总字节数/行数、空行和乱码统计、最长行、内容哈希,以及开头/中段/结尾样本。需要进一步核实时应使用明确的行区间或有界 grep。超长单行也按固定大小片段处理,内存占用不会随单行长度无限增长。

路径映射关系:

  • 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.pdf(HTTP URL)

上传流程采用“线程目录优先”策略:

  • 先写入 backend/.deer-flow/threads/{thread_id}/user-data/uploads/ 作为权威存储
  • 本地沙箱(sandbox_id=local)直接使用线程目录内容
  • 非本地沙箱会额外同步到 /mnt/user-data/uploads/*,确保运行时可见

测试示例

使用 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",
    files=files
)
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())

文件存储结构

backend/.deer-flow/threads/
└── {thread_id}/
    └── user-data/
        └── uploads/
            ├── document.pdf          # 原始文件
            ├── document.pdf.md       # 转换后的 Markdown
            ├── presentation.pptx
            ├── presentation.pptx.md
            └── ...

限制

  • 数据库策略可配置范围带服务端硬上限:文件数最多 100、单文件最多 100 MiB、单次总量最多 200 MiB;反向代理的 client_max_body_size 还必须不低于所需请求大小
  • 文件名安全性:系统会自动验证文件路径,防止目录遍历攻击
  • 文件真实性:PDF、JPG、PNG、OLE 与 OOXML 会校验签名/容器结构,OOXML 同时限制条目数、解压总量和压缩比
  • 线程隔离:每个线程的上传文件相互隔离,无法跨线程访问
  • 自动文档转换由 uploads.auto_convert_documents 控制;转换在事件循环外执行、限制并发,提取的 Markdown 还有固定字符上限
  • 模型安全硬边界:单次文件摘要/读取结果最多 32,000 字符,图片还会校验像素数并生成受限尺寸派生图

技术实现

组件

  1. Upload Router (app/gateway/routers/uploads.py)

    • 处理文件上传、列表、删除请求
    • 使用 markitdown 转换文档
  2. Uploads Middleware (packages/harness/deerflow/agents/middlewares/uploads_middleware.py)

    • 在每次 Agent 请求前注入文件列表
    • 自动生成格式化的文件列表消息
  3. Nginx 配置 (nginx.conf)

    • 路由上传请求到 Gateway API
    • 配置大文件上传支持

依赖

  • markitdown>=0.0.1a2 - 文档转换
  • python-multipart>=0.0.20 - 文件上传处理
  • 纯 DeerFlow 离线镜像还内置 python-docx、openpyxl、python-pptx、LibreOffice Headless、Pandoc、Poppler 和中文字体。旧版 DOC/XLS/PPT 及 ODT/ODS/ODP 会先经 LibreOffice 规范化,再由 MarkItDown 提取。

离线部署包可在不连接数据库的情况下运行依赖自检:

./OFFLINE_INSTALL.sh --office-check

故障排查

文件上传失败

  1. 检查文件大小是否超过限制
  2. 检查 Gateway API 是否正常运行
  3. 检查磁盘空间是否充足
  4. 查看 Gateway 日志:make gateway

文档转换失败

  1. 检查 markitdown 是否正确安装:uv run python -c "import markitdown"
  2. 查看日志中的具体错误信息
  3. 某些损坏或加密的文档可能无法转换,但原文件仍会保存

Agent 看不到上传的文件

  1. 确认 UploadsMiddleware 已在 agent.py 中注册
  2. 检查 thread_id 是否正确
  3. 确认文件确实已上传到 backend/.deer-flow/threads/{thread_id}/user-data/uploads/
  4. 非本地沙箱场景下,确认上传接口没有报错(需要成功完成 sandbox 同步)

开发建议

前端集成

// 上传文件示例
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();
}

扩展功能建议

  1. 批量删除:支持一次删除多个文件
  2. 文件搜索:支持按文件名或类型搜索
  3. 版本控制:保留文件的多个版本
  4. 图片 OCR:对上传的图片进行 OCR 识别