Skip to main content

DeerFlow:字节把 LangGraph 用成了什么样

一句话:这是本专题里唯一一个能正面回答「大厂产品到底用不用现成框架」的样本——答案是用,但在上面糊了四十多层中间件。

一、拿的是哪份代码

git clone --depth 1 https://github.com/bytedance/deer-flow.git
仓库bytedance/deer-flow
Star80,280
版本backend/pyproject.tomlversion = "2.1.0"
拉取时间2026-08-19

二、先看这个文件:它确实跑在 LangGraph 上

backend/langgraph.json

backend/langgraph.json
{
"$schema": "https://langgra.ph/schema.json",
"python_version": "3.12",
"dependencies": ["."],
"env": "../.env",
"graphs": {
"lead_agent": "deerflow.agents:make_lead_agent"
},
"auth": {
"path": "./app/gateway/langgraph_auth.py:auth"
},
"http": {
"app": "./app/gateway/langgraph_studio.py:langgraph_app"
},
"checkpointer": {
"path": "./packages/harness/deerflow/runtime/checkpointer/async_provider.py:make_checkpointer"
}
}

四条信息量很大:

  1. 唯一的入口图叫 lead_agent —— 直接就是 orchestrator 命名,跟 Anthropic 的 lead agent 是同一个词。
  2. 自定义 checkpointer —— 没用 LangGraph 自带的,自己写了一个异步 provider。
  3. 挂了 auth —— 说明是多租户在线服务,不是本地 CLI。
  4. 接了 LangGraph Studio —— 官方调试 UI 直接可用。

中间件基类来自 langchain.agents.middleware.AgentMiddleware,也就是说它吃的是 LangChain 1.x 的中间件体系 + LangGraph 的运行时

所以「大厂产品都自研不用框架」这个说法是不成立的。 至少字节这个 80k star 的项目是老老实实站在 LangGraph 上的。真正的问题是——站上去之后还要自己补什么。

答案在下一节。

三、四十多个中间件:一份生产事故清单

packages/harness/deerflow/agents/middlewares/ 下的文件名,我按功能分组列出来(这是全文最值钱的一张表):

中间件干什么的
子 Agent 治理subagent_limit_middleware.py限制子 Agent 的数量和并发
delegation_ledger.py委派台账:记录每次派活和结果
成本刹车token_budget_middleware.py单次运行的 token 总预算
token_usage_middleware.py用量统计
tool_output_budget_middleware.py单个工具输出的长度预算
tool_output_synopsis.py工具输出过长时生成摘要
死循环防护loop_detection_middleware.py检测模型在原地打转
dangling_tool_call_middleware.py处理悬空的工具调用
model_length_finish_reason_middleware.py处理因长度截断的响应
model_length_termination_detectors.py同上的判定器
上下文管理summarization_middleware.py上下文压缩
durable_context_middleware.py持久化上下文
dynamic_context_middleware.py动态上下文注入
system_message_coalescing_middleware.py合并多条 system 消息
memory_middleware.py长期记忆
安全与净化input_sanitization_middleware.py输入净化
tool_result_sanitization_middleware.py工具结果净化
safety_finish_reason_middleware.py安全策略触发的终止
safety_termination_detectors.py同上
sandbox_audit_middleware.py沙箱审计
行为约束read_before_write_middleware.py强制先读后写
skill_tool_policy_middleware.py技能的工具权限策略
skill_activation_middleware.py技能激活
deferred_tool_filter_middleware.py延迟加载的工具过滤
mcp_routing_middleware.pyMCP 路由
交互clarification_middleware.py澄清提问
todo_middleware.pytodo 列表
tool_progress_middleware.py工具进度
terminal_response_middleware.py终端响应
view_image_middleware.pyuploads_middleware.py多模态与上传
错误处理llm_error_handling_middleware.pyLLM 层错误
tool_error_handling_middleware.py工具层错误

这份清单几乎可以直接当成「把 Agent 放到生产上会遇到什么」的目录。

有三个特别值得说:

tool_output_budget_middleware.py —— 给单个工具的输出上限。一个 grep 打出 5 万行,不做拦截就直接把上下文冲垮了。这是最常见也最容易被忽略的翻车点。

read_before_write_middleware.py —— 强制模型改文件前必须先读过。这是从无数次「模型凭想象覆盖文件」里长出来的规则。

loop_detection_middleware.py —— 检测模型原地打转。Kimi CLI 靠 max_steps_per_turn=1000 一刀切,DeerFlow 多做了一层模式识别。步数上限只能兜底,检测不出「它在第 30 步和第 50 步做了同一件事」。

四、子 Agent:只有两个内建工种

subagents/builtins/ 下只有两个文件:

subagents/builtins/general_purpose.py(节选)
GENERAL_PURPOSE_CONFIG = SubagentConfig(
name="general-purpose",
...
tools=None, # Inherit all tools from parent
disallowed_tools=["task", "ask_clarification", "present_files"], # Prevent nesting and clarification
...
max_turns=150,
)
subagents/builtins/bash_agent.py(节选)
BASH_AGENT_CONFIG = SubagentConfig(
name="bash",
...
tools=["bash", "ls", "read_file", "write_file", "str_replace"], # Sandbox tools only
disallowed_tools=["task", "ask_clarification", "present_files"],
max_turns=60,
)

对照 Kimi CLI 的三个工种(coder / explore / plan),DeerFlow 只有两个:一个万能的、一个只能操作沙箱的。分工更粗,但每个的轮数上限给得很足(150 和 60,Kimi 单个子 Agent 是 1 小时超时)。

disallowed_tools 里的注释直接写了 # Prevent nesting

subagents/config.py:38
disallowed_tools: list[str] | None = field(default_factory=lambda: ["task"])

默认就禁掉 task 工具,也就是禁止子 Agent 再派子 Agent。Kimi CLI 的 if role != "root" 殊途同归,和 Claude Code 默认允许 3 层 相反。

本专题拆到这里,「禁止递归」已经是三比一的多数派了。

还有一个细节:三个被禁的工具

ask_clarificationpresent_files 也被禁了。前者是「向用户提问」——子 Agent 不该直接跟用户说话,这跟 Kimi CLI 的 ROLE_ADDITIONAL prompt("Do not directly ask the end user questions")是同一件事,只是 Kimi 靠 prompt,DeerFlow 靠工具剥离。后者更硬。

五、三层限流:这是「生产级」最直白的证据

第一层:并发数

config/subagents_config.py:11-15
DEFAULT_MAX_TOTAL_SUBAGENTS_PER_RUN = 6
MIN_TOTAL_SUBAGENTS_PER_RUN = 1
MAX_TOTAL_SUBAGENTS_PER_RUN = 50
MIN_CONCURRENT_SUBAGENT_CALLS = 1
MAX_CONCURRENT_SUBAGENT_CALLS = 4
subagents/executor.py:1261
MAX_CONCURRENT_SUBAGENTS = 3

同时最多 3 个子 Agent 在跑,一次响应里最多派 4 个。

对照 Manus Wide Research 的「上百个」——差了两个数量级。原因也不难理解:Manus 一个子 Agent 一台 VM,横向扩展只受基础设施限制;DeerFlow 的子 Agent 跑在同一个 LangGraph 运行时里,并发数受进程资源约束。

架构选择决定了并发天花板。 这一条比任何抽象讨论都能说明「隔离粒度」这个维度的实际后果。

第二层:单次运行的子 Agent 总数

默认 6 个,可配范围 [1, 50]。

注意这跟并发是两件事:并发 3 是「同时几个」,总数 6 是「整轮加起来几个」。派完 6 个之后,中间件会往上下文里追加这句话:

agents/middlewares/subagent_limit_middleware.py:31-35
_TOTAL_LIMIT_STOP_MSG = (
"[SUBAGENT LIMIT REACHED] The subagent delegation limit for this run has been reached. "
"Continue using the subagent results already collected, execute remaining simple work "
"directly, or summarize the remaining work instead of launching more subagents."
)

又是「错误消息即 prompt」——不只是拒绝,还告诉模型接下来该怎么办(用已有结果、简单活自己干、剩下的总结一下)。这跟 Kimi CLI 的 "Please try splitting the task into smaller subtasks" 是同一种手艺。

第三层:token 预算

这段 docstring 值得整段引用,因为它把权衡过程写出来了:

config/subagents_config.py:28-55
def default_subagent_token_budget(*, summarization_enabled: bool = False) -> TokenBudgetConfig:
"""Default per-run token budget for subagents (#3875 Phase 2 → Phase 3 coupling).

Enabled by default so the pathological-token-burn backstop actually
engages (per umbrella #3857 point 4 — backstops must engage, not just
exist). ``max_tokens`` is **coupled to whether subagent summarization is
on**:

- ``summarization_enabled=True``: **1M** — tighter ceiling still
covers legitimate deep research while catching degenerate runs earlier.
- ``summarization_enabled=False``: **2M** — ... legitimate deep-research runs
(``max_turns=150``, no summarization) "can genuinely accumulate >1M
cumulative input," so a 1M ceiling without compaction would prematurely
cap them.
"""
max_tokens = 1_000_000 if summarization_enabled else 2_000_000
return TokenBudgetConfig(enabled=True, max_tokens=max_tokens, warn_threshold=0.7)

「pathological-token-burn backstop」——病态 token 燃烧的兜底。这个词组和后面引用的 issue 编号(#3875、#3857)说明:线上真的烧穿过。

三个数字:

场景单次运行 token 上限
开了上下文压缩100 万
没开压缩200 万
告警阈值用到 70% 时告警

而且注释里明确说了「backstops must engage, not just exist」——兜底机制必须真的会触发,不能只是摆着好看。 这句话应该刻在每个做 Agent 平台的人的显示器上。

对照 Anthropic 说的「多 Agent 是普通聊天的 15 倍 token」,就明白为什么必须有这层刹车了。

六、委派台账:确定性截断,不用 LLM 总结

delegation_ledger.py 处理「子 Agent 的结果怎么回到父 Agent 上下文里」:

agents/middlewares/delegation_ledger.py(节选)
_RESULT_BRIEF_CAP = 2000
_DESCRIPTION_CAP = 200
_LEDGER_RENDER_CHAR_BUDGET = 6000
_LEDGER_ENTRY_RESULT_RENDER_CAP = 120
_STATUS_ONLY_RESULT_BRIEFS = {
"failed": "Task failed.",
"cancelled": "Task cancelled by user.",
"timed_out": "Task timed out.",
"polling_timed_out": "Task polling timed out.",
}

def _bound_text(text: str, cap: int = _RESULT_BRIEF_CAP) -> str:
"""Deterministic head/tail truncation. This is not an LLM summary."""
if len(text) <= cap:
return text
if cap <= 0:
return ""
head = cap * 2 // 3
omitted_marker = "\n...\n"

"This is not an LLM summary." 这句注释是特意写的。

截断策略是头 2/3 + 尾 1/3,中间挖掉插一个省略标记。为什么不用 LLM 总结?因为总结要多花一次调用、有延迟、而且结果不确定——同样的输入两次总结可能不一样,调试时会疯掉。

这跟 Kimi CLI 的做法正好相反。 Kimi 是「摘要不到 200 字就再叫模型写一遍」(用 LLM,不确定),DeerFlow 是「超过 2000 字就掐头去尾」(不用 LLM,确定)。

Kimi CLIDeerFlow
方向太短,逼它写长太长,直接截断
手段再跑一轮 LLM纯字符串操作
确定性不确定完全确定
成本多一次调用

两种做法针对的是同一个问题的两端。理想做法大概是两个都要:下限用 LLM 保证信息量,上限用确定性截断保证不炸上下文。

台账的整体渲染预算是 6000 字符,单条结果在台账里只渲染 120 字符——也就是说台账是个索引,不是全文。父 Agent 想看细节要另外取。

七、五维打分

对照 01 的五个维度

维度DeerFlow 的答案
D1 隔离单位LangGraph 运行时里的独立分支 + 独立工具集,同进程
D2 通信拓扑星型,无兄弟通信
D3 结果回收委派台账,确定性头尾截断(2000 字上限),不用 LLM 总结
D4 递归深度单层disallowed_tools 默认含 task
D5 生命周期max_turns 150/60,超时 30 分钟,有 checkpointer 支持中断续跑

一句话总结:架构上最保守(单层星型、并发只有 3),但运行时治理是本专题里最厚的——四十多个中间件、三层限流、确定性台账。它把力气全花在了「别炸」上。

八、这篇最该带走的东西

如果你正在把一个 Agent 往生产上放,DeerFlow 的中间件清单可以直接当 checklist 用:

  • 单个工具输出有没有长度上限?(tool_output_budget
  • 整轮运行有没有 token 总预算?兜底会不会真的触发?(token_budget,「backstops must engage」)
  • 有没有原地打转的检测?光靠步数上限够不够?(loop_detection
  • 子 Agent 的并发和总数分别限了没有?(并发 3 / 总数 6)
  • 限流触发时,给模型的消息里有没有告诉它接下来该干什么?
  • 子 Agent 的结果回传是确定性的还是要过一次 LLM?
  • 模型改文件之前强制读过没有?(read_before_write
  • 子 Agent 能不能直接找用户说话?(应该不能)

九、参考