DeerFlow 2.0 发布之后,社区里讨论最多的一个词从“Deep Research”变成了“Super Agent Harness”。说白了,这个版本想解决的核心问题只有一件事:把“深度研究”这种重流程能力,从演示级的项目变成一个能嵌进业务系统、能被二次开发、能被监控和审计的工程底座。如果你看过 DeerFlow 早期版本,应该对它基于 LangGraph 的多智能体编排、规划-研究-反思-报告四段式流程有印象;而 2.0 的重点,是把这些能力解耦成一个个可替换、可观测、可对外暴露接口的模块。这篇文章会以我们团队实际改造 DeerFlow 2.0 的经历为例,从架构拆解、源码读法,到 SSE 流式接口封装、可观测性接入,把基于 DeerFlow 做 Super Agent Harness 落地的关键细节一次讲清楚。内容更适合准备做二次开发的开发者,也适合正在选型深度研究方案的架构师。
1. 从 Deep Research 到 Super Agent Harness:DeerFlow 2.0 的定位变化
1.1 旧版 DeerFlow:为“一次深度研究”而生的重流程
早期 DeerFlow 给我的感觉更像一个“重流程的研究机器”。你给它一个问题,它按顺序完成规划、执行搜索、反思、生成报告,最后输出一份长文。这种模式对“Deep Research”这个单点场景非常合适,因为它把所有可能的变数都收进了一个固定的流程图里:先规划,再研究,反思不满意就继续研究,满意了就写报告。
但这套东西一旦要接入真实业务,问题就暴露了。我们最早接 DeerFlow 时,面临的第一件事是“流程太死”。有的场景只需要“用户给个关键词,系统自动搜索并输出摘要”,不需要多轮反思;有的场景又要求在写报告之前必须人工审阅研究素材;还有的场景希望把研究结果直接塞进下游的企业知识库,而不是生成一篇好看的 Markdown。旧版 DeerFlow 的架构虽然清晰,但它的边界是写死在 LangGraph 图里的,改起来要么动源码,要么把整个流程复制一份再改,维护成本很高。
1.2 2.0 的转型:把“研究流程”变成“智能体底座”
DeerFlow 2.0 提出的 Super Agent Harness,本质上是一种“载体升级”的写法。它不再强调自己是“深度研究框架”,而是强调自己是一套能跑复杂 Agent 工作流的底座:输入、状态流转、工具调用、流式输出、人工介入、可观测性,全部做成了可插拔、可替换的接口。
用一句大白话总结:1.0 你是在“用 DeerFlow 做研究”,2.0 你是“用 DeerFlow 这个壳子,围绕你自己的业务定义 Agent 的行为”。这个壳子保留了深度研究最强的部分——规划、研究、反思、报告四个核心节点的能力沉淀;但节点的实现不再绑定某个大模型或某个搜索工具,用户完全可以替换自己的搜索源、自己的记忆模块、自己的审批流程。这种转型想解决的问题很明确:让一个人或一个小团队,不需要从零搭建多智能体编排的底层,就能在两天内拼出一个能处理复杂任务的“超级智能体”服务。
1.3 为什么我建议用“行为工厂”而不是“框架”来理解它
很多刚接触 DeerFlow 2.0 的开发者会问,它和 LangGraph、AutoGen、CrewAI 到底有什么区别。我的观点是,DeerFlow 2.0 更像一个“行为工厂”:它给常见 Agent 行为预置好了成熟的生产流水线,同时把流水线的“模具”开放给你。
你可以在里面定制行为:让 Planner 换成你自己的任务分解器,让 Researcher 换成对接内部 API 的查询器,让 Reflection 换成带评分规则的审查器,甚至把 Report 节点整个替换成“写入数据库”的动作。这和 LangGraph 的原始能力并不冲突——LangGraph 是提供状态机和图执行能力的“底层框架”,DeerFlow 2.0 是在其上构建的一层“领域模板”。理解这层关系后,二次开发时你就不会纠结“要不要自己用 LangGraph 重写一套”,因为直接把 DeerFlow 的图结构拎出来改,比从零写省太多事。
2. 架构拆解:一张图看懂 DeerFlow 2.0 的骨架
在进入源码之前,最好先对整个图的结构有一个全局认知。DeerFlow 2.0 的核心流程依然是四个阶段的循环,只是每个阶段都被抽象成了可独立替换的模块。
2.1 Planner:把用户问题拆成研究计划
Planner 负责接收用户输入,把它拆解成一系列研究子问题或者任务清单。如果你处理的是“比较三款开源向量数据库的运维成本”这类问题,Planner 会拆出“业务背景”“硬件要求”“运维复杂度”“社区活跃度”等子问题,再为每个子问题指定研究策略。
这部分在旧版中比较机械,通常是一轮 prompt 输出 JSON。2.0 里把 Planner 的结果设计成了结构化状态,也就是说你可以在计划阶段介入和修正:人工改掉某些子问题,或者直接从外部系统注入一个计划。我实际测试下来,把一个复杂问题从单轮 prompt 拆解改成“分类+细化”两段式,研究效率会有明显提升。
2.2 Researcher:真正干脏活累活的节点
Researcher 是深度研究里最累的节点。它拿到 Planner 的子任务后,会调用搜索 API、抓取网页内容、读取 PDF、汇总信息。DeerFlow 老版本已经内置了一些常用工具,2.0 最大的变化是工具接口统一:每个工具都是一个可接收上下文并返回结构化结果的函数,不再和节点强耦合。
这样带来的直接好处是:你可以把内置的“通用网页抓取工具”替换成“公司内网检索工具”,而不用动 Researcher 的决策逻辑。我们后来就把内网 Confluence 搜索封装成了一个工具节点,和公网搜索并列,Researcher 会自动根据任务类型选择走哪个工具。这一点是“Super Agent Harness”最实的体现——工具的多少决定了智能体能力的边界,而工具的可插拔性决定了智能体能否长在业务里。
2.3 Reflection:让智能体学会“不满意就重来”
Reflection 节点是 DeerFlow 区别于普通“搜索摘要器”的核心。它会把 Researcher 收集到的信息重新审视一遍,判断这些材料够不够回答规划阶段提出的问题,有没有明显矛盾,是否需要补充检索。
这个节点在 2.0 里被做成了“可配置的评判器”:既可以用一个大模型对材料打分,也可以接入规则引擎,比如“必须至少包含三个来源”“时效性必须在半年内”。打分结果会决定图的走向:如果评审通过,进入 Report 节点;不通过,则带着反思意见回到 Researcher 继续补充研究。这里我想提醒一点,反思节点是一把双刃剑,它的判断能力直接取决于模型能力和 prompt 设计,稍后我会在踩坑部分专门展开。
2.4 Report:从研究素材到结构化输出
Report 节点负责把反思通过后的材料组织成最终输出。DeerFlow 2.0 的 Report 不仅支持生成 Markdown,还可以配置输出为 JSON、XML,或者直接把结果写入数据库。这意味着它不再是一个“写文章”的节点,而是一个“交付物工厂”。
我们团队在这个节点上做过一次改造:把默认的长文生成 prompt 换成按公司模板生成“问题背景—核心结论—证据引用—待确认事项”的四段式,输出会被自动解析成结构化对象,对接下游工单系统。效果非常好,因为报告生成规则是业务里最常变化的部分,而 2.0 的节点边界让替换成本降到了最低。
2.5 状态图:LangGraph StateGraph 如何把节点串起来
DeerFlow 2.0 的图装配基于 LangGraph,核心代码结构大致是这样的:
from langgraph.graph import StateGraph, END # 全局状态 schema,所有节点的输入输出都写在这上面 class DeerFlowState(TypedDict): query: str plans: list[dict] research_log: list[dict] reflection_score: float report: str max_iterations: int iteration: int graph = StateGraph(DeerFlowState) # 注册核心节点 graph.add_node("planner", plan_node) graph.add_node("researcher", research_node) graph.add_node("reflection", reflection_node) graph.add_node("report", report_node) # 定义起点和主链路 graph.set_entry_point("planner") graph.add_edge("planner", "researcher") graph.add_edge("researcher", "reflection") # 反思循环:分数太低则回到 researcher,最多迭代 max_iterations 次 graph.add_conditional_edges( "reflection", route_after_reflection, { "revise": "researcher", "complete": "report", }, ) graph.add_edge("report", END) app = graph.compile()如果你读过老版本的代码,会发现结构变化不大,但 2.0 的关键差异在节点内部的实现方式上:所有节点都通过接口函数和状态交互,不再直接调用其他节点。这一点让整个图变成了可插拔的流水线。改动某个节点时,只要输入输出契约不变,其他节点完全不需要感知。
3. 源码拆解:从状态定义到反思循环,一份 DeerFlow 2.0 代码详解
这一节我们直接进入代码细节,讲一讲我在阅读 DeerFlow 2.0 源码时认为最值得注意的几个地方。源码给出的实现思路,也是二次开发时绕不开的几条主线。
3.1 状态数据结构的定义:一切调试的起点
DeerFlow 的节点之间靠全局状态传递数据,因此状态 schema 是整个框架的“数据库表结构”。如果状态定义不合理,后面追踪问题和扩展功能都会非常痛苦。
一个典型的 2.0 状态定义会把“过程数据”和“交付数据”分开:过程数据包括 plans、research_log、reflection_score,这些主要用于控制流程图走向和调试;交付数据包括 report、structured_output,这些是最终要落到业务系统的内容。我建议你在做二次开发时保留这种区分的习惯,不要把临时检索结果和最终交付物混在一起,否则流量一多,状态数据会快速膨胀,流式传输时也会带来不少序列化开销。
class DeerFlowState(TypedDict): # 输入:用户原始问题 query: str # 上下文:会话标识、历史摘要、外部注入的约束条件 conversation_id: str constraints: list[str] # 过程数据 plans: list[dict] research_log: list[dict] reflection_score: float iteration: int # 输出数据 report: str structured_result: dict有一个细节值得注意:iteration 和 max_iterations 一定要放进状态里,不要用全局变量或外部配置来控制循环次数。因为 LangGraph 图在并发执行时,每个会话都有自己的状态实例,如果把迭代计数放在全局,多用户并发时就会出现互相干扰,结果就是明明只让用户 A 最多迭代 5 次,用户 B 却可能因为共享变量提前结束。
3.2 工具封装:可插拔的 Fetch 与数据清洗
DeerFlow 2.0 的工具层是我看源码时觉得最有学习价值的部分。以网页抓取为例,框架不会把“请求网页”和“解析正文”耦合在同一个函数里,而是拆成两层:fetch 负责获取原始内容,parser 负责从 HTML 中提取有效正文。
这种拆法在二次开发中极其有用。比如我们接一个内部文档系统,只需要替换 fetch 层的实现(改成带鉴权的 HTTP 请求),parser 层完全复用。数据清洗逻辑里也藏了不少细节:抓下来的文本要做字符编码归一化(有些站点返回 GBK 或者带 BOM),要去除导航栏、广告、脚本标签,最好还要把长文本按段落切块,方便后续大模型处理。
class BaseTool: def run(self, params: dict, context: dict) -> list[dict]: raise NotImplementedError class HttpFetchTool(BaseTool): def run(self, params: dict, context: dict) -> list[dict]: # 这部分是一个带超时、重试、编码归一化的抓取实现 # 真实项目里还要考虑 robots、频率限制、UA 伪装等问题 return [{"type": "content", "source": params["url"], "text": cleaned_text}]我强烈建议,在封装工具时把每次工具调用的入参、出参、耗时、是否命中缓存都记录到 research_log 里。这些数据不仅在反思节点可以用到,后续做可观测性分析时更是宝贵的原始素材。
3.3 反思循环的退出条件:避免无限空转的关键
反思循环是 DeerFlow 最容易被忽视的风险点。如果你不限制迭代次数,也没有分数阈值,模型对某些冷门问题会反复进入“检索→反思→觉得不够→再检索”的循环,既烧 token 又拖慢响应。
我在源码里看到的通用做法是组合使用两类条件:第一是硬性条件,即最大迭代次数,通常 3 到 5 次封顶;第二是软性条件,即反思评分,可以是一个 0 到 1 的分数,也可以是一组布尔判断。节点的退出逻辑大概是这样的:
def route_after_reflection(state: DeerFlowState) -> str: if state["iteration"] >= state["max_iterations"]: # 已经达到硬性上限,不管质量如何都直接收尾 return "complete" if state["reflection_score"] >= 0.8: return "complete" return "revise"一个很实用的经验:反思评分不要只让大模型给一个笼统分数,建议让 Reflection 节点输出几个维度的评分,例如“覆盖度”“一致性”“时效性”,然后按业务权重算加权分。这样调节行为时,你只需要改权重,不需要反复改 prompt。我们实际项目里就遇到过“覆盖度足够但时效性很差”的研究结果,加权评分比单一分数直观得多。
4. 给智能体装上监控:可观测性与人机协同的落地
4.1 为什么 Deep Research 类的智能体特别难调试
如果你的智能体只是单轮“输入输出”,那挂个日志基本够用。但 Deep Research 是多节点、多工具、多轮反思的长时间任务,一个问题可能产生几十次工具调用。排查问题的时候,你不仅要看“模型输出了什么”,还要看“每个节点依赖了哪些中间状态”“哪个工具调用了多少次”“反思为什么反复不通过”。
这就是可观测性要解决的根本问题。DeerFlow 2.0 把节点间的状态流转做成显式数据后,天然适合接追踪系统。这里我的建议是:从第一天就接入可观测性平台,不要等项目上线再加。我见过太多团队上线后才开始追问题,结果因为缺少中间状态日志,排查一个简单 Bug 花了整整两天。
4.2 接入 LangSmith / Langfuse 追踪
LangGraph 生态里最成熟的可观测性方案是 LangSmith,也可以用 Langfuse 做开源替代。接入方式不复杂,核心是配好环境变量:
# LangSmith 接入示例 export LANGCHAIN_TRACING_V2=true export LANGCHAIN_API_KEY=your_langsmith_api_key export LANGCHAIN_PROJECT=deerflow-production如果用的是 Langfuse,通过langfuseSDK 的CallbackHandler直接挂到 LangGraph 的编译结果上:
from langfuse.callback import CallbackHandler handler = CallbackHandler( public_key="pk-...", secret_key="sk-...", host="https://cloud.langfuse.com" ) app = graph.compile(checkpointer=checkpointer) # 在每次 invoke 时传入回调 result = await app.ainvoke(initial_state, {"callbacks": [handler]})接入之后你要看的核心指标是:每个节点的耗时分布、工具调用成功率、反思循环的平均轮数、模型 token 消耗。特别是工具调用的成功率,如果某个搜索源频繁超时,你会看到 Researcher 节点耗时会异常飙升。这类问题只靠应用日志很难发现,但追踪图上会一目了然。
4.3 Human-in-the-loop:把人工审批嵌入图流程
DeerFlow 2.0 作为 Super Agent Harness,另一个被频繁提到的能力就是人机协同。研究型任务天然存在风险:模型可能抓到一个有误导性的来源,或者给出的结论敏感。此时不能让它全自动跑完,得在关键节点上停下来等人工确认。
LangGraph 提供interrupt机制,可以在节点之间插入暂停点。以我们的改造为例,在“研究完成、反思通过”之后、进入 Report 之前,我们会暂停流程,把研究素材和反思结论推送给前端,等待人工点击“通过”或“打回”。3分钟没有响应就自动挂起,而不是盲目继续。
from langgraph.types import interrupt def human_review_node(state: DeerFlowState) -> DeerFlowState: review_result = interrupt({ "type": "human_review", "payload": { "research_log": state["research_log"], "reflection_score": state["reflection_score"], }, }) # 人工返回 approve 或 reject if review_result.get("action") == "reject": state["constraints"].append(review_result.get("reason", "")) # 打回后进入反思节点,带上人工意见 return state这个设计解决了以前那个很尴尬的问题:要么全程无人审核,出错了才后悔;要么盲目相信人工,每一步都打断用户,体验非常差。正确做法是抓大放小:只在“影响结果质量”的关键节点上设置审批,其余环节保持自动。主动设置审批,而不是频繁打断,才是人机协同的正确姿势。
5. 二次开发实战:把 DeerFlow 2.0 封装成自己的智能体服务
5.1 确定封装边界:是“流程服务”还是“对话服务”
在开始写代码之前,先想清楚你的服务形态。如果你的用户只是偶尔发一个研究任务、等结果,那你直接做一个同步请求接口就够了;但如果用户希望看到“正在规划”“正在搜索”“反思未通过,正在补充材料”这样的过程反馈,那就必须上流式接口。
DeerFlow 2.0 本身调用耗时通常从几十秒到几分钟不等,同步请求很容易触发网关超时。所以我们的推荐方案是:服务端始终采用流式响应,即通过 SSE 持续推送事件;客户端拿到全部事件后自行组装结果。这样既解决了超时问题,也天然支持了过程可视化。下面我会重点讲我们封装 SSE 流式接口的整套方案。
5.2 用 FastAPI 封装 SSE 流式接口
我们选择 FastAPI 作为 HTTP 服务框架,原因很简单:异步支持好、SSE 实现成本低、OpenAPI 文档自动生成。核心思路是,把 DeerFlow 的图编译结果包装成异步生成器,每一轮的节点输出被转成 SSE 事件推送出去。
import json from fastapi import FastAPI from fastapi.responses import StreamingResponse from deerflow import build_harness app = FastAPI() harness = build_harness() async def deerflow_events(payload: dict): query = payload["query"] conversation_id = payload.get("conversation_id", "") state = { "query": query, "conversation_id": conversation_id, "plans": [], "research_log": [], "constraints": [], } async for chunk in harness.astream(state): # chunk 是当前节点的输出片段 if "plans" in chunk: yield sse_event("plan", chunk["plans"]) if "research_log" in chunk: yield sse_event("tool_call", chunk["research_log"]) if "reflection_score" in chunk: yield sse_event("reflection", {"score": chunk["reflection_score"]}) if "report" in chunk: yield sse_event("result", {"report": chunk["report"]}) yield sse_event("done", {}) def sse_event(event_type: str, data) -> str: payload = json.dumps({"type": event_type, "data": data}, ensure_ascii=False) return f"data: {payload}\n\n" @app.post("/api/research/stream") async def research_stream(payload: dict): return StreamingResponse( deerflow_events(payload), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "X-Accel-Buffering": "no", }, )这里有几个很容易踩的坑,我直接说出来:第一,X-Accel-Buffering: no很关键,如果服务前面挂着 Nginx,默认会缓冲响应,导致客户端等服务端全部算完才收到“data:”,流式就名存实亡了;第二,media_type必须是text/event-stream,否则前端 EventSource 解析会异常;第三,SSE 的每个事件块必须以空行结尾,即两个\n。
5.3 流式消息协议:status / tool_call / reflection / result 四类事件
前后端之间的通信协议一定要在第一时间定清楚,不然前端会很痛苦。我们最终采用的是一个包裹型的消息协议:每条 SSE 消息都是一个带type和data的 JSON,便于前端按事件类型分发处理。
// 前端解析 SSE 的标准流程 async function parseSSE(response) { const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const blocks = buffer.split("\n\n"); buffer = blocks.pop(); for (const block of blocks) { const line = block.trim(); if (!line.startsWith("data:")) continue; const raw = line.slice(5).trim(); if (!raw) continue; const event = JSON.parse(raw); handleEvent(event); } } } function handleEvent(event) { switch (event.type) { case "plan": renderPlan(event.data); break; case "tool_call": appendToolRecord(event.data); break; case "reflection": showReflectionScore(event.data); break; case "result": renderReport(event.data.report); break; case "done": finalizeUI(); break; default: break; } }这个协议的关键点是状态机语义清晰:前端只处理 5 种事件,后端也只发这 5 种事件。如果你将来还要支持人工审批,可以在此基础上增加human_review_request事件;前端拿到后展示一个审批弹窗,再把人工操作通过另一个 POST 接口回传。协议演进时尽量只增加新事件类型,不要随意改旧事件的字段结构。
5.4 多用户并发与会话管理
DeerFlow 图本身并不保存跨会话数据,所以多用户并发时你必须自己管理会话状态。我们采用的方案是:为每个conversation_id建立一个独立的图执行任务,任务的进度保存在内存或 Redis 中;SSE 连接只做单向推送,用户断开连接不会中断后台任务。
这里有几个经验可以分享。第一,后台任务的取消机制很重要。用户在浏览器端关闭页面后,SSE 连接断开,但后台任务可能还在跑。我们会在任务启动时绑定一个自动取消标记,如果用户 5 分钟内没有重新订阅,就终止任务。第二,要控制单任务并发度。DeerFlow 在 Researcher 阶段会并发发起多个搜索请求,如果不限流,一个人工任务可能瞬间打满你部署的搜索服务额度。第三,建议在服务层加一个简单的队列,而不是让每个请求都直接去抢模型额度。我们曾经遇到过 20 个并发研究任务同时涌入,直接把模型服务限流打爆的情况。
6. 踩坑实录:DeerFlow 改造中我遇到的 6 个常见问题
6.1 模型特定 Prompt 不匹配:换模型后效果大跌
DeerFlow 的默认 prompt 是在特定模型族上调试出来的。我们最初用默认配置接入某国产开源模型,研究质量明显下降,尤其是反思节点出现了反复“不通过”的问题。后来我们把 Reflection 节点的评分标准从“通用表述”改成了“带明确 checklist 的量化打分”,并调低了模型能力不足时的收敛阈值,才恢复正常。
提示:任何从开源项目里拿到的 prompt 都不是魔法,必须先在小样本集上做评测再上线。
6.2 网页抓取超时与编码异常:Researcher 节点耗时飙升
抓取第三方网页时,超时、反爬、编码问题是最常见的坑。我们有个阶段经常出现 Researcher 节点跑到 90 秒以上,最后发现是某个国外站点 HSTS 握手非常慢,默认的超时时间不够。解决办法是给 HTTP 客户端分别设置“连接超时”和“读取超时”,对重点域名单独调参。另外,从网页中提取正文时,一定要处理 UTF-8 之外的编码,比如 GB18030;不做编码归一化的话,进入大模型的文本会出现大量乱码,反思节点也会把乱码误判成“材料质量差”。
6.3 反思循环次数过多:Token 消耗直接翻倍
反思节点虽然保证质量,但也是最贵的节点。一旦模型打分逻辑不稳,很容易让流程陷入“检索→反思→检索”的循环,token 消耗成倍增长。我们的经验是:最大迭代次数默认设为 3,并为每个迭代轮次设计了递减的评分阈值,第一轮 0.8,第二轮 0.75,第三轮 0.7。也就是说,越到后面越“放水”,避免无限空转。
6.4 人工审批事件被流式通道吞掉
在做 Human-in-the-loop 时,我们遇到过前端已经收到human_review_request事件,但用户提交审批回传后,后端流程没有恢复的问题。排查后发现是因为我们把审批回传做成了另一个独立的 HTTP 接口,但该接口没有绑定到原来的图执行实例上,相当于给一个不存在的任务发了指令。解决方法是统一维护一个任务注册表,把所有conversation_id和graph_instance的对应关系存在里面,回传时先查注册表,确保操作的是同一个内存对象。
6.5 部署时 Nginx 缓冲把 SSE 变成了“假流式”
这个问题前面已经提过,但值得再强调一次。我们第一次部署后,前端表现完全是“等了 1 分钟,突然把所有内容一次性吐出来”。原因就是 Nginx 默认开启了缓冲。解决方式是在 Nginx 配置里对 SSE 路径添加proxy_buffering off;,同时服务端返回响应时带上X-Accel-Buffering: no头。在云厂商的 Kubernetes 环境里,还要检查 Ingress Controller 是否也有类似缓冲配置,比如 Nginx Ingress 的nginx.ingress.kubernetes.io/proxy-buffering: "off"注解。
6.6 日志量在并发场景下迅速膨胀
智能体任务的日志量远大于传统业务接口,因为每个工具调用、每次模型返回都要记录。没有做采样和分级之前,我们一天跑下来日志量达到几个 GB。后来把日志分成两级:全量写入追踪平台(LangSmith/Langfuse),应用日志只保留关键节点事件(启动、节点切换、完成、报错)。同时给日志加上 conversation_id 字段,方便在日志系统里按链路追踪。这样既保证了排查问题需要的上下文,也不会把日志成本打爆。
7. 关于二次开发,我的几点实在建议
7.1 先跑通最小闭环,再追求扩展
基于 DeerFlow 2.0 做二次开发时,最大的诱惑是一上来就想把架构改得多完美。我的建议是,第一周先原样跑通一个最小闭环:一个查询进来,经过四个节点,输出一份报告。期间把追踪系统、日志规范、SSE 接口全部搭好。只有在闭环跑通之后,再去替换 Planner 或 Researcher 的实现。因为如果你一边加业务逻辑一边改框架,出了问题你根本分不清是框架的 bug 还是你业务代码的 bug。
7.2 把状态 schema 当成 API 契约来管理
DeerFlow 的节点之间通过状态通信,因此状态字段的增删改,就是一次 API 变更。我们团队现在规定,任何改动状态 schema 的操作都必须同步更新文档和字段说明,并标注兼容性影响。有一个阶段我们随意往 research_log 里塞了一个字段,导致旧流程的序列化报错,排查花了不少时间。状态 schema 是二次开发里最容易失控的地方,一定要用契约管理。
7.3 留足人工干预的接口
即便你的目标场景没有明确的人工审批需求,我也建议在架构上预留 Human-in-the-loop 接口。原因很简单:大模型研究类应用上线后,你一定会遇到“计划外”的坏案例。届时如果没有人工干预接口,你只能靠修改 prompt 或加工具来修复,而这两者都可能引入新问题。预留一个简单的审批节点,成本很低,但能显著提升系统的容错能力。
7.4 后续可以扩展的方向
如果你问我 DeerFlow 2.0 接下来还能往哪走,我认为有几个方向很值得探索:一是把长期记忆模块接入状态机,让智能体记住用户的研究偏好;二是把工具调用从“单次 Fetch”升级成“多步任务链”,比如先检索、再根据结果决定是否需要下载附件;三是把反思结果做成可回放的数据集,用于定期评估和回归测试。这些方向本质上都在从“能用”走向“好用”,而 DeerFlow 2.0 给出的底座,让这些扩展不再需要推到重来。
我个人在实际改造中的体会是,DeerFlow 2.0 最有价值的不是它现成的几个节点,而是它展示了一个经过验证的 Agent 工作流应该长什么样。照着它的方式组织状态、工具、节点和可观测性,即便你最后没有直接使用这个框架,也能把一套稳定的多智能体系统搭起来。如果你正准备基于它做自己的 Super Agent Harness,先从最小闭环开始,把观测和人工干预接口留好,后面你会发现扩展比想象中顺利得多。