* fix(test): exclude blocking I/O suite from make test Keep make test-blocking-io as the dedicated suite owner. Add regression coverage for the Makefile contract. Refs #5088 * test: pin blocking I/O workflow ownership Document both targets required for full offline validation. Keep the dedicated workflow and Makefile target under contract coverage. Refs #5088 * docs(test): align blocking-I/O test guidance
24 KiB
DeerFlow 后端
语言: English | 简体中文
DeerFlow 是一个基于 LangGraph 的 AI 超级智能体,具备沙箱执行、持久记忆和可扩展工具集成能力。后端使 AI 智能体能够执行代码、浏览网页、管理文件、将任务委派给子智能体,并在多轮对话之间保留上下文——所有操作都在按线程隔离的环境中进行。
架构
┌──────────────────────────────────────┐
│ Nginx(端口 2026) │
│ 统一反向代理 │
└───────┬──────────────────┬───────────┘
│
/api/langgraph/* │ /api/*(其他)
重写为 /api/* │
▼
┌────────────────────────────────────────┐
│ Gateway API(8001) │
│ FastAPI REST + 智能体运行时 │
│ │
│ 模型、MCP、技能、记忆、上传、 │
│ 产物、线程、运行、流式传输 │
│ │
│ ┌────────────────────────────────────┐ │
│ │ 主智能体 │ │
│ │ 中间件链、工具、子智能体 │ │
│ └────────────────────────────────────┘ │
└────────────────────────────────────────┘
请求路由(通过 Nginx):
/api/langgraph/*→ 与 LangGraph 兼容的 Gateway API——智能体交互、线程和流式传输/api/*(其他)→ Gateway API——模型、MCP、技能、记忆、产物、上传和线程本地数据清理/(非 API)→ 前端——Next.js Web 界面
核心组件
主智能体
唯一的 LangGraph 智能体(lead_agent)是运行时入口,通过 make_lead_agent(config) 创建。它组合了:
- 支持思考和视觉能力的动态模型选择
- 处理横切关注点的中间件链(9 个中间件)
- 包含沙箱、MCP、社区工具和内置工具的工具系统
- 用于并行执行任务的子智能体委派
- 注入技能、记忆上下文和工作目录指导的系统提示词
中间件链
中间件按照严格顺序执行,每个中间件负责一个特定关注点:
| # | 中间件 | 用途 |
|---|---|---|
| 1 | ThreadDataMiddleware | 为每个线程创建独立目录(工作区、上传、输出) |
| 2 | UploadsMiddleware | 将新上传的文件注入对话上下文 |
| 3 | SandboxMiddleware | 获取用于代码执行的沙箱环境 |
| 4 | SummarizationMiddleware | 接近 Token 限制时压缩上下文(可选) |
| 5 | TodoListMiddleware | 在计划模式中跟踪多步骤任务(可选) |
| 6 | TitleMiddleware | 在第一次交互后自动生成对话标题 |
| 7 | MemoryMiddleware | 将对话加入异步记忆提取队列 |
| 8 | ViewImageMiddleware | 为支持视觉的模型注入图像数据(有条件启用) |
| 9 | ClarificationMiddleware | 拦截澄清请求并中断执行(必须放在最后) |
沙箱系统
按线程隔离执行,并提供虚拟路径转换:
- 抽象接口:
execute_command、read_file、write_file、list_dir - 提供程序:
LocalSandboxProvider(文件系统)和AioSandboxProvider(Docker,位于community/)。异步运行时路径使用异步沙箱生命周期钩子,使启动、就绪轮询和释放操作不会阻塞事件循环。AioSandboxProvider会在获取或复用期间验证活跃缓存和预热池中的容器,移除已确定失效的条目,使线程能够在容器意外退出后创建新沙箱,同时让get()保持为内存查询。后端健康检查失败会被视为状态未知,而不是容器已失效;在发现过程中无法验证的容器不会被采用,获取流程会继续创建容器,而不是直接失败。 - 虚拟路径:
/mnt/user-data/{workspace,uploads,outputs}→ 线程专属的物理目录 - 技能路径:
/mnt/skills→deer-flow/skills/目录 - 技能加载:递归发现
skills/{public,custom}下嵌套的SKILL.md文件,并保留其嵌套容器路径 - SkillScan:安装技能或由智能体写入技能时,原生离线确定性扫描会先于 LLM 技能扫描器执行;
CRITICAL级别的问题会阻止操作,警告则会成为 LLM 上下文 - 文件写入安全:
str_replace按照(sandbox.id, path)对“读取—修改—写入”操作进行串行化,因此即使虚拟路径相同,彼此隔离的沙箱仍能保持并发 - 工具:
bash、ls、read_file、write_file、str_replace(write_file默认覆盖文件,并提供append以在文件末尾追加内容;使用LocalSandboxProvider时,bash默认禁用;如需隔离的 Shell 访问,请使用AioSandboxProvider)
子智能体系统
支持并发执行的异步任务委派:
- 内置智能体:
general-purpose(完整工具集)和bash(命令专家,仅在 Shell 访问可用时提供) - 并发限制:每轮最多 3 个子智能体,超时时间为 15 分钟
- 执行方式:后台线程池,并提供状态跟踪和 SSE 事件
- 流程:智能体调用
task()工具 → 执行器在后台运行子智能体 → 轮询完成状态 → 返回结果
记忆系统
由 LLM 驱动、可跨对话保留上下文的持久记忆:
- 自动提取:分析对话中的用户上下文、事实和偏好
- 作用域安全写入:中间件提取过程只存储持久且具有描述性的用户级事实;全局摘要同样需要描述权限。当缺少作用域元数据,或者内容仅限于某个任务或项目时,矛盾移除和事实合并将采用失败关闭策略
- 原子替换:与替换项关联的矛盾移除,只有在替换项通过作用域和置信度门控、去重及事实数量裁剪后才会执行
- 结构化存储:用户上下文(工作、个人、当前关注事项)、历史记录以及带置信度评分的事实
- 防抖更新:批量处理更新以减少 LLM 调用次数(等待时间可配置)
- 系统提示词注入:将最重要的事实和上下文注入智能体提示词
- 运行级记忆标识:
GET /api/threads/{thread_id}/runs/{run_id}/events?event_types=context:memory返回实际隐藏记忆块的 SHA-256 标识,而不会把记忆文本复制进事件存储 - 存储方式:JSON 文件,并基于 mtime 实现缓存失效
工具生态系统
| 类别 | 工具 |
|---|---|
| 沙箱 | bash、ls、read_file、write_file、str_replace |
| 内置 | present_files、ask_clarification、view_image、task(子智能体) |
| 社区 | Tavily(网页搜索)、Jina AI(网页获取)、Crawl4AI(网页获取)、Firecrawl(网页抓取)、fastCRW(网页抓取)、DuckDuckGo(图片搜索) |
| MCP | 任意 Model Context Protocol 服务器(stdio、SSE、HTTP 传输) |
| 技能 | 通过系统提示词注入的领域专用工作流 |
Gateway API
FastAPI 应用程序,为前端集成提供 REST 接口:
| 路由 | 用途 |
|---|---|
GET /api/models |
列出可用的 LLM 模型 |
GET/PUT /api/mcp/config |
管理 MCP 服务器配置 |
POST /api/mcp/cache/reset |
重置缓存的 MCP 工具,使其在下次使用时重新加载 |
GET/PUT /api/skills |
列出并管理技能 |
POST /api/skills/install |
从 .skill 归档文件安装技能 |
GET /api/memory |
获取记忆数据 |
POST /api/memory/reload |
强制重新加载记忆 |
GET /api/memory/config |
获取记忆配置 |
GET /api/memory/status |
获取组合后的配置和数据状态 |
GET /api/threads/{id}/runs/{run_id}/events |
获取某次运行的调试或审计事件;可使用 event_types=context:memory 筛选实际记忆标识 |
POST /api/threads/{id}/uploads |
上传文件(自动将 PDF、PPT、Excel、Word 转换为 Markdown;拒绝目录路径;自动重命名单次请求中的重复文件名) |
GET /api/threads/{id}/uploads/list |
列出已上传的文件 |
DELETE /api/threads/{id} |
删除 LangGraph 线程后,清除由 DeerFlow 管理的本地线程数据;意外错误会记录在服务端,并返回通用的 500 错误信息 |
GET /api/threads/{id}/artifacts/{path} |
提供生成产物的访问 |
即时通信渠道
即时通信桥接支持飞书、Slack 和 Telegram。Slack 和 Telegram 仍然使用最终的 runs.wait() 响应路径;飞书现在通过 runs.stream(["messages-tuple", "values"]) 进行流式传输,在渠道管理器内部对同一线程中快速连续到达的请求进行串行化,并针对每条源消息原地更新线程内的同一张卡片。
对于飞书卡片更新,DeerFlow 会按每条入站消息保存运行中卡片的 message_id,并持续更新同一张卡片直到运行结束,同时保留现有的 OK / DONE 表情回应流程。当现有飞书话题中的上一轮仍在运行时收到后续消息,新消息会在映射的 DeerFlow thread_id 上等待,在对应的源消息上显示排队中或运行中的卡片,并在后续更新中保留简洁的源消息引用块,使快速连续提出的问题仍然容易区分。
快速开始
前置条件
- Python 3.12+
- uv 包管理器
- 所选 LLM 提供商的 API 密钥
安装
cd deer-flow
# 复制配置文件
cp config.example.yaml config.yaml
# 安装后端依赖
cd backend
make install
配置
编辑项目根目录中的 config.yaml:
models:
- name: gpt-4o
display_name: GPT-4o
use: langchain_openai:ChatOpenAI
model: gpt-4o
api_key: $OPENAI_API_KEY
supports_thinking: false
supports_vision: true
- name: gpt-5-responses
display_name: GPT-5 (Responses API)
use: langchain_openai:ChatOpenAI
model: gpt-5
api_key: $OPENAI_API_KEY
use_responses_api: true
output_version: responses/v1
supports_vision: true
设置 API 密钥:
export OPENAI_API_KEY="your-api-key-here"
运行
完整应用程序(从项目根目录运行):
make dev # 启动 Gateway、Frontend 和 Nginx
仅运行后端(从 backend 目录运行):
# Gateway API + 嵌入式智能体运行时
make dev
直接访问:Gateway 位于 http://localhost:8001
终端工作台(TUI)——基于嵌入式 Harness 的终端原生界面, 无需运行任何服务:
uv pip install 'deerflow-harness[tui]' # 可选的 textual 依赖
deerflow # 启动 TUI
deerflow --print "summarize this repo" # 无界面的单次运行
deerflow --recursion-limit 250 --print "run a longer task"
在 TUI 中打开的会话会出现在 Web UI 侧边栏中(它会在本地默认用户下写入共享的 threads_meta 存储)。详见 docs/TUI.md。
项目结构
backend/
├── packages/harness/ # deerflow-harness 包(导入路径:deerflow.*)
│ └── deerflow/
│ ├── agents/ # 智能体系统
│ │ ├── lead_agent/ # 主智能体(工厂、提示词)
│ │ ├── middlewares/ # 中间件组件
│ │ ├── memory/ # 记忆提取与存储
│ │ └── thread_state.py # ThreadState 数据结构
│ ├── sandbox/ # 沙箱执行
│ │ ├── local/ # 本地文件系统提供程序
│ │ ├── sandbox.py # 抽象接口
│ │ ├── tools.py # bash、ls、read/write/str_replace
│ │ └── middleware.py # 沙箱生命周期
│ ├── subagents/ # 子智能体委派
│ │ ├── builtins/ # general-purpose、bash 智能体
│ │ ├── executor.py # 后台执行引擎
│ │ └── registry.py # 智能体注册表
│ ├── tools/builtins/ # 内置工具
│ ├── mcp/ # MCP 协议集成
│ ├── models/ # 模型工厂
│ ├── skills/ # 技能发现与加载
│ ├── config/ # 配置系统
│ ├── runtime/ # 嵌入式运行执行(RunManager、StreamBridge)
│ ├── persistence/ # Checkpointer/Store 引擎和数据库结构迁移
│ ├── guardrails/ # 工具调用前的授权提供程序
│ ├── tracing/ # Tracer 工厂和追踪元数据
│ ├── uploads/ # 上传管理器
│ ├── tui/ # 终端 UI(`deerflow` 控制台脚本)
│ ├── community/ # 社区工具和提供程序
│ ├── reflection/ # 动态模块加载
│ └── utils/ # 工具函数
├── app/ # FastAPI Gateway + 即时通信渠道(导入路径:app.*)
│ ├── gateway/ # Gateway API
│ │ ├── app.py # 应用程序配置
│ │ └── routers/ # 路由模块
│ └── channels/ # 即时通信渠道集成
├── docs/ # 文档
├── tests/ # 测试套件
├── langgraph.json # 用于工具和 Studio 兼容性的 LangGraph 图注册表
├── pyproject.toml # Python 依赖
├── Makefile # 开发命令
└── Dockerfile # 容器构建文件
langgraph.json 并不是默认的服务入口。脚本和 Docker 部署使用 Gateway 嵌入式运行时;保留该文件是为了兼容 LangGraph 工具、Studio 或直接运行 LangGraph Server。
配置
主配置(config.yaml)
将其放置在项目根目录。以 $ 开头的配置值会被解析为环境变量。
主要配置节:
models——包含类路径、API 密钥、思考和视觉标志的 LLM 配置tools——包含模块路径和分组的工具定义tool_groups——工具的逻辑分组sandbox——执行环境提供程序skills——技能目录路径title——自动生成标题的设置summarization——上下文摘要设置subagents——子智能体系统(启用或禁用)memory——记忆系统设置(启用状态、存储方式、防抖和事实数量限制)
提供商说明:
models[*].use通过模块路径引用提供商类,例如langchain_openai:ChatOpenAI。- 如果缺少某个提供商模块,DeerFlow 会返回包含安装指导的可操作错误信息,例如
uv add langchain-google-genai。
扩展配置(extensions_config.json)
在同一个文件中配置 MCP 服务器和技能状态:
{
"mcpServers": {
"github": {
"enabled": true,
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {"GITHUB_TOKEN": "$GITHUB_TOKEN"}
},
"secure-http": {
"enabled": true,
"type": "http",
"url": "https://api.example.com/mcp",
"oauth": {
"enabled": true,
"token_url": "https://auth.example.com/oauth/token",
"grant_type": "client_credentials",
"client_id": "$MCP_OAUTH_CLIENT_ID",
"client_secret": "$MCP_OAUTH_CLIENT_SECRET"
}
},
"postgres": {
"enabled": false,
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"],
"description": "PostgreSQL database access",
"routing": {
"mode": "prefer",
"priority": 50,
"keywords": ["orders", "users", "SQL", "database", "table"]
},
"tools": {
"query": {
"routing": {
"priority": 100,
"keywords": ["query database", "orders table", "metrics"]
}
}
}
}
},
"skills": {
"pdf-processing": {"enabled": true}
}
}
routing 会向智能体提示词添加柔性的 MCP 偏好提示。它能帮助模型在处理匹配的请求时优先选择配置的 MCP 工具,同时不会禁止其他工具。当 tool_search.enabled=true 延迟加载 MCP Schema 时,匹配的路由元数据可以在模型调用前自动提升最多 tool_search.auto_promote_top_k 个延迟 Schema。
环境变量
DEER_FLOW_CONFIG_PATH——覆盖 config.yaml 的位置DEER_FLOW_EXTENSIONS_CONFIG_PATH——覆盖 extensions_config.json 的位置- 模型 API 密钥:
OPENAI_API_KEY、ANTHROPIC_API_KEY、DEEPSEEK_API_KEY等 - 工具 API 密钥:
TAVILY_API_KEY、GITHUB_TOKEN等
LangSmith 追踪
DeerFlow 内置了 LangSmith 可观测性集成。启用后,所有 LLM 调用、智能体运行、工具执行和中间件处理过程都会被追踪,并可在 LangSmith 控制台中查看。
配置步骤:
- 在 smith.langchain.com 注册并创建一个项目。
- 将以下内容添加到项目根目录的
.env文件:
LANGSMITH_TRACING=true
LANGSMITH_ENDPOINT=https://api.smith.langchain.com
LANGSMITH_API_KEY=lsv2_pt_xxxxxxxxxxxxxxxx
LANGSMITH_PROJECT=xxx
**旧版变量:**为了向后兼容,仍然支持 LANGCHAIN_TRACING_V2、LANGCHAIN_API_KEY、LANGCHAIN_PROJECT 和 LANGCHAIN_ENDPOINT。当两组变量同时设置时,优先使用 LANGSMITH_* 变量。
Langfuse 追踪
DeerFlow 同样支持使用 Langfuse 观察与 LangChain 兼容的运行过程。
将以下内容添加到 .env 文件:
LANGFUSE_TRACING=true
LANGFUSE_PUBLIC_KEY=pk-lf-xxxxxxxxxxxxxxxx
LANGFUSE_SECRET_KEY=sk-lf-xxxxxxxxxxxxxxxx
LANGFUSE_BASE_URL=https://cloud.langfuse.com
如果使用自托管的 Langfuse 部署,请将 LANGFUSE_BASE_URL 设置为你的 Langfuse 主机地址。
同时使用两个提供商时的行为
如果同时启用了 LangSmith 和 Langfuse,DeerFlow 会初始化并附加两者的回调,使相同的运行数据同时上报给两个系统。
如果明确启用了某个提供商,但缺少必要凭据,或者无法初始化该提供商的回调,DeerFlow 会在模型创建期间初始化追踪时抛出错误,而不是静默禁用追踪。
**Docker:**在 docker-compose.yaml 中,追踪默认处于禁用状态(LANGSMITH_TRACING=false)。如需在容器化部署中启用追踪,请在 .env 中设置 LANGSMITH_TRACING=true 和/或 LANGFUSE_TRACING=true,并同时提供所需凭据。
开发
命令
make install # 安装依赖
make dev # 运行 Gateway API 和嵌入式智能体运行时(端口 8001)
make gateway # 运行不带热重载的 Gateway API(端口 8001)
make lint # 运行代码检查器(ruff)
make format # 格式化代码(ruff)
make detect-blocking-io # 统计可能阻塞后端事件循环的阻塞式 IO
make migrate-rev MSG="..." # 根据当前 ORM 模型自动生成新的 Alembic 修订
数据库结构迁移
DeerFlow 的应用程序表(runs、threads_meta、feedback、users、run_events 和 channel_* 表)由 Alembic 管理。Gateway 启动时会通过 bootstrap_schema(engine, backend=...) 自动运行 alembic upgrade head,因此运维人员不需要在生产环境中手动运行 alembic。初始化过程支持并发安全(跨进程使用 PostgreSQL advisory lock;单个 SQLite 进程内按引擎使用 asyncio.Lock),并能针对已有数据库结构(空数据库、旧版数据库或已带版本的数据库)保持幂等。
添加或修改 ORM 模型时,请在 packages/harness/deerflow/persistence/migrations/versions/ 下提交新的迁移版本:
make migrate-rev MSG="add foo column to runs"
该目标会调用 scripts/_autogen_revision.py。脚本先在一个新的临时 SQLite 数据库上迁移到 head,然后将当前模型与其进行比较,因此全新检出的代码不需要预先存在的 ./data/deerflow.db。提交前请检查生成的文件,并将原始的 op.add_column / op.drop_column 调用替换为 migrations/_helpers.py 中的幂等辅助函数。项目有意不提供 make migrate / make migrate-stamp 目标——Gateway 启动是唯一的迁移执行路径,从而避免运维误操作。完整设计请参阅 backend/CLAUDE.md 中的“Schema Migrations”部分。
代码风格
- 代码检查器/格式化工具:
ruff - 行长度:240 个字符
- Python:3.12+,使用类型提示
- 引号:双引号
- 缩进:4 个空格
测试
# 默认离线后端测试套件(排除调用外部真实 API 和阻塞式 I/O 的测试)
make test
# 严格的阻塞式 I/O 测试套件
make test-blocking-io
# 显式运行使用真实 API 的 DeerFlowClient 集成测试套件
make test-live
实时测试套件需要有效的根目录 config.yaml 和 API 凭据。它可能产生 API 费用,或创建本地沙箱、产物和文件,因此不属于默认测试流程或 CI。直接通过 pytest 运行 tests/test_client_live.py 时,也需要设置 DEER_FLOW_RUN_LIVE_TESTS=1。
make detect-blocking-io 会静态扫描后端业务代码,寻找可能在后端事件循环上运行且不受测试覆盖范围限制的阻塞式 IO。它会输出便于人工审查的简明摘要,并把完整的 JSON 结果写入仓库根目录的 .deer-flow/blocking-io-findings.json,无论该目标是在仓库根目录还是 backend/ 目录中调用。JSON 结果同时包含宽泛的 IO 分类和面向审查的字段,例如 priority、location、blocking_call、event_loop_exposure、reason 和 code。priority 只是根据操作类型生成的确定性审查顺序,并不能证明存在缺陷。对于同一文件中通过裸名称调用的函数,扫描器按函数名解析,因此当一个文件中存在同名辅助函数时,可能会保守地高估异步可达性。
技术栈
- LangGraph(1.0.6+)——智能体框架和多智能体编排
- LangChain(1.2.3+)——LLM 抽象和工具系统
- FastAPI(0.115.0+)——Gateway REST API
- langchain-mcp-adapters——Model Context Protocol 支持
- agent-sandbox——沙箱化代码执行
- markitdown——多格式文档转换
- tavily-python / firecrawl-py——网页搜索和抓取
文档
许可证
请参阅项目根目录中的 LICENSE 文件。
参与贡献
贡献指南请参阅 CONTRIBUTING.md。