跳转到内容

Deer-Flow 的 18 层工具调用中间件链

Deer-Flow 的 18 层工具调用中间件链

学习目标

  • 理解 Deer-Flow 中间件链的组装顺序和每个中间件的职责
  • 掌握横切关注点(上下文管理、错误处理、安全、优化)在中间件中的落地
  • 学会分析中间件之间的依赖关系

前置知识

本章涉及工具调用中间件链的通用原理,建议先阅读:

下文假设你已理解上述概念,直接聚焦 Deer-Flow 的具体实现。


项目实践

中间件链的组装顺序

Deer-Flow 的 Lead Agent 中间件通过 _build_middlewares 按严格 append 顺序组装。以下是 18 个中间件的完整列表和执行顺序:

关键中间件分析

DanglingToolCallMiddleware(#4)

职责:当用户中断或 provider 异常导致工具调用未完成时,检测 AIMessage 中存在的 tool_calls 但没有对应 ToolMessage 的悬空调用,剥离 provider 级原始工具调用元数据(additional_kwargs.tool_calls),注入占位符 ToolMessage。

为什么在 LLM 错误处理之前:悬空调用恢复是历史修复,必须在下一轮模型调用前完成,否则模型会因为 malformed history 报错。

GuardrailMiddleware(#6)

职责:通过可插拔 GuardrailProvider 协议评估每个工具调用是否允许执行。拒绝时返回错误 ToolMessage。

三种 Provider 选项

  • AllowlistProvider:内置白名单,零依赖
  • OAP 策略 Provider:外部策略引擎
  • 自定义 Provider:用户实现

SummarizationMiddleware(#9)

职责:当接近 Token 限制时压缩历史对话。三种触发策略:tokens / messages / fraction of max input。

ClarificationMiddleware(#18)

职责:拦截 ask_clarification 工具调用,通过 Command(goto=END) 中断执行。

为什么必须在最后:它通过 goto=END 终止 Agent 运行,如果不在最后,后续中间件可能在此之后再次被触发。


问题与规避

中间件顺序变更的风险

新增中间件时,如果插入位置不当,可能导致:

  • 读不到前置中间件写入的状态
  • 在错误的时间点触发压缩或记忆更新

规避:在 _build_middlewares 函数中明确注释每个中间件的位置原因。新增中间件后运行 make test 验证。

中间件性能

18 个中间件在每次工具调用前后都运行。不必要的中间件会增加延迟。

规避:可选中间件(如 Summarization、TodoList、TokenUsage)仅在启用时才添加到链中。每个中间件实现 fast-path(不需要工作时直接 pass)。


设计取舍

为什么选择中间件链而非装饰器

中间件链优势

  • 中间件可以访问共享 Agent State,实现跨中间件通信
  • 组装顺序显式可控,依赖关系清晰
  • 可以动态启用/禁用,不修改代码

装饰器方案的劣势

  • 多层装饰器嵌套难以调试
  • 装饰器之间通信需要依赖闭包变量
  • 顺序由装饰器应用顺序隐式决定

参考来源

  • Deer-Flow CLAUDE.md — Middleware Chain 章节
  • packages/harness/deerflow/agents/middlewares/ — 中间件源码