摘要
DeerFlow 是字节跳动开源的 LangGraph-based AI Agent 平台。在其 v2.0 架构演进中,项目团队做出了一项重要的架构决策------将原本独立运行的 LangGraph Server 进程合并至自研的 FastAPI Gateway 内部,实现了进程内直接调用 LangGraph 图引擎。本文将从架构动机、实现方案、核心组件设计等维度,深入分析这一合并的技术细节。
1. 背景与动机
1.1 原始架构的痛点
在 DeerFlow v1.x 时代,系统采用的是 双进程架构:
scss
前端 (Next.js) → nginx → LangGraph Server (langgraph-cli 启动, port 2024)
→ Backend API (FastAPI, port 8001)
该架构存在以下问题:
- 运维复杂度高:需要同时管理两个后端进程(LangGraph Server + Gateway),增加部署和监控负担。
- 功能受限:langgraph-cli 提供的 HTTP API 是标准化的 LangGraph Platform 协议,无法在其上扩展 DeerFlow 特有的业务逻辑(如 MCP 管理、Skills 系统、IM 频道集成等)。
- 数据一致性难题:Run 的生命周期管理、Token 用量统计、用户反馈等应用数据需要在两个进程间同步,增加了系统复杂性。
- 配置热加载受限 :LangGraph Server 的配置变更通常需要重启进程,而 Gateway 侧希望支持
config.yaml的 mtime 级热加载。
1.2 合并目标
DeerFlow 团队的目标是:在保持 LangGraph Platform API 协议兼容的前提下,将 LangGraph 图引擎的执行嵌入 Gateway 进程内部,从而获得:
- 单进程部署,简化运维
- 对 Run 生命周期的完全控制
- 应用数据(持久层)与图执行引擎的同进程访问
- 自定义中间件链的灵活注入
- 统一的认证/授权边界
2. 整体架构设计
合并后的架构如下:
scss
前端 (Next.js)
→ nginx (反向代理,统一为同源)
→ Gateway (FastAPI, port 8001)
├─ LangGraph Platform 兼容 API (/api/threads/*/runs/*)
├─ DeerFlow 扩展 API (/api/models, /api/mcp, /api/skills, ...)
└─ LangGraph 图引擎 (进程内直接调用)
Gateway 成为唯一的后端入口。LangGraph 图不再作为独立服务运行,而是作为 Gateway 内的运行时组件按需实例化。
3. 核心实现
3.1 应用生命周期管理
Gateway 使用 FastAPI 的 lifespan 机制管理所有运行时组件的初始化与销毁:
python
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
startup_config = get_app_config() # 加载 config.yaml 快照
async with langgraph_runtime(app, startup_config):
# 所有 LangGraph 运行时单例就绪
await _ensure_admin_user(app)
channel_service = await start_channel_service(startup_config)
yield
await stop_channel_service()
langgraph_runtime() 是一个异步上下文管理器,负责引导和销毁所有 LangGraph 相关的单例组件:
python
@asynccontextmanager
async def langgraph_runtime(app: FastAPI, startup_config: AppConfig):
async with AsyncExitStack() as stack:
# StreamBridge:Agent 任务与 SSE 端点之间的事件管道
app.state.stream_bridge = await stack.enter_async_context(make_stream_bridge(config))
# 持久化引擎(SQLite/Postgres)
await init_engine_from_config(config.database)
# Checkpointer:LangGraph 图状态的跨 turn 持久化
app.state.checkpointer = await stack.enter_async_context(make_checkpointer(config))
# Store:LangGraph KV 存储
app.state.store = await stack.enter_async_context(make_store(config))
# 应用数据仓库
app.state.run_store = RunRepository(session_factory)
app.state.feedback_repo = FeedbackRepository(session_factory)
app.state.thread_store = make_thread_store(session_factory, app.state.store)
# 事件流存储
app.state.run_event_store = make_run_event_store(run_events_config)
# RunManager:运行注册表 + 并发控制
app.state.run_manager = RunManager(store=app.state.run_store)
yield
所有单例通过 app.state 暴露,路由层通过依赖注入函数 get_xxx(request) 获取,若依赖未就绪则返回 HTTP 503。
3.2 配置热加载边界
DeerFlow 在配置管理上做了一个精妙的分层设计:
| 配置类型 | 加载方式 | 适用场景 |
|---|---|---|
| 引擎级配置(数据库连接、Checkpointer) | 启动时一次性加载 | 需要重启才能变更 |
| 应用级配置(模型列表、工具参数) | 每请求 mtime 检测热加载 | 编辑 config.yaml 立即生效 |
python
def get_config() -> AppConfig:
"""每个请求都通过此函数获取最新配置。
内部通过 mtime 检测 config.yaml 是否变更,变更时重新加载。"""
return get_app_config()
AppConfig 刻意不缓存 在 app.state 上,避免出现"分裂脑"问题------即 worker 线程看到过时的启动快照。
3.3 Run 的生命周期管理
RunManager:内存注册表 + 持久化后备
python
class RunManager:
_runs: dict[str, RunRecord] # 内存中的活跃 Run
_store: RunStore | None # SQL 持久化后备
_lock: asyncio.Lock # 所有操作加锁
并发控制策略:
RunManager.create_or_reject() 方法在单个锁内原子地完成冲突检测与 Run 创建,消除了 TOCTOU 竞态:
python
async def create_or_reject(self, thread_id, ...):
async with self._lock:
inflight = [r for r in self._runs.values()
if r.thread_id == thread_id
and r.status in (pending, running)]
if multitask_strategy == "reject" and inflight:
raise ConflictError(...)
if multitask_strategy in ("interrupt", "rollback") and inflight:
for r in inflight:
r.abort_event.set()
r.task.cancel()
# 创建新 Run 并持久化
self._runs[run_id] = record
await self._persist_new_run_to_store(record)
该设计支持三种并发策略:
reject:同一 thread 已有活跃 Run 时拒绝新请求(返回 409)interrupt:取消旧 Run 后创建新 Runrollback:取消旧 Run 并回滚 checkpoint 后创建新 Run
启动恢复机制
Gateway 重启时,通过 reconcile_orphaned_inflight_runs() 将持久化存储中残留的 pending/running 状态的 Run 标记为 error,避免前端永远显示"加载中":
python
recovered_runs = await run_manager.reconcile_orphaned_inflight_runs(
error="Gateway restarted before this run reached a durable final state.",
before=now_iso(),
)
3.4 StreamBridge:生产者-消费者桥接
StreamBridge 是实现 SSE 流式响应的关键组件,它解耦了后台 Agent 任务(生产者)与 HTTP 端点(消费者):
python
class StreamBridge(ABC):
async def publish(self, run_id: str, event: str, data: Any) -> None:
"""Agent 任务写入事件"""
async def publish_end(self, run_id: str) -> None:
"""Agent 完成,发送终止信号"""
def subscribe(self, run_id, *, last_event_id=None, heartbeat_interval=15.0):
"""SSE 端点消费事件(支持 Last-Event-ID 断线重连)"""
每个 StreamEvent 携带单调递增的 id 字段,客户端通过 Last-Event-ID 请求头即可实现断线重连,无需重新执行图。
3.5 Agent 图的按需构建
与传统 LangGraph Server 在启动时编译图不同,DeerFlow 的 Gateway 在每次请求时按需创建 CompiledGraph:
python
async def run_agent(bridge, run_manager, record, *, ctx, agent_factory, ...):
# 每次 run 创建一个全新的图实例
agent = agent_factory(config=runnable_config)
agent.checkpointer = ctx.checkpointer
agent.store = ctx.store
async for chunk in agent.astream(graph_input, config=runnable_config, stream_mode=lg_modes):
await bridge.publish(run_id, sse_event, serialize(chunk))
agent_factory 指向 make_lead_agent(),其内部完成:
- 模型解析 :根据请求中的
model_name从配置的模型列表中选择,回退到默认模型 - 工具组装:加载内置工具 + MCP 工具 + Skill 限制的白名单过滤
- 中间件链构建:15+ 个中间件按固定顺序注入
- 系统提示词生成:基于当前 agent 配置动态渲染
- 图编译 :
create_agent(model, tools, middleware, system_prompt, state_schema)返回CompiledGraph
按需创建的设计使得每次请求都能反映最新的配置(模型、工具、Skill 状态),无需重启服务。
3.6 Runtime Context 注入
LangGraph 官方 Server 通过内部机制自动为 Tool 注入 ToolRuntime.context。DeerFlow 的 Gateway 需要手动完成这一步:
python
runtime_ctx = {
"thread_id": thread_id,
"run_id": run_id,
"app_config": app_config,
"__run_journal": journal, # 内部通道:中间件写入审计事件
}
runtime = Runtime(context=runtime_ctx, store=store)
config["configurable"]["__pregel_runtime"] = runtime
Tool 在执行时通过 runtime.context 访问 thread_id、app_config 等上下文信息,从而实现虚拟路径解析、sandbox 初始化等逻辑。
3.7 LangGraph Platform API 协议兼容
为保证前端 @langchain/langgraph-sdk 的 useStream hook 无需修改即可工作,Gateway 严格遵循 LangGraph Platform 的 SSE 协议:
python
def format_sse(event: str, data: Any, *, event_id: str | None = None) -> str:
"""格式化 SSE 帧,字段顺序:event → data → id → 空行"""
payload = json.dumps(data, default=str, ensure_ascii=False)
parts = [f"event: {event}", f"data: {payload}"]
if event_id:
parts.append(f"id: {event_id}")
parts.append("")
parts.append("")
return "\n".join(parts)
响应头中包含 Content-Location 字段,指向 Run 资源的规范 URL:
python
headers={
"Content-Location": f"/api/threads/{thread_id}/runs/{record.run_id}",
}
useStream hook 通过正则从该 header 中提取 run_id,这与 LangGraph Platform 官方行为完全一致。
4. 取消与回滚机制
DeerFlow 实现了两种取消语义:
4.1 Interrupt(保留当前状态)
python
record.abort_event.set()
record.task.cancel()
# Checkpointer 中已保存的中间状态得以保留
# 后续可通过新 Run 恢复执行
4.2 Rollback(回退到运行前状态)
python
# 运行前快照 checkpoint
pre_run_checkpoint_id = ...
# 取消后,将 checkpointer 恢复到快照
await _rollback_to_pre_run_checkpoint(
checkpointer=checkpointer,
thread_id=thread_id,
pre_run_checkpoint_id=pre_run_checkpoint_id,
pre_run_snapshot=pre_run_snapshot,
)
Rollback 机制在 Run 启动前对 checkpoint 做深拷贝快照,取消时通过 checkpointer.aput() 写入一个新的 checkpoint 记录,其内容为运行前的状态。这确保了即使 LangGraph 在执行过程中已经写入了多个中间 checkpoint,rollback 也能正确恢复。
5. 统一持久层的协同
Gateway 进程内同时管理两套数据:
| 存储 | 管理者 | 用途 |
|---|---|---|
| LangGraph Checkpoint | checkpointer |
图执行状态(messages、channel_values) |
| LangGraph Store | store |
线程 KV 数据 |
| DeerFlow ORM 表 | persistence (SQLAlchemy) |
Run 元数据、Token 统计、用户反馈、事件流 |
两套数据在同一进程内直接访问,无需跨进程 RPC。例如,run_agent 的 finally 块中同时:
- 通过
journal.flush()将事件写入RunEventStore - 通过
run_manager.update_run_completion()将 Token 统计写入RunRow - 通过
checkpointer.aget_tuple()读取 title 并写入ThreadMetaRow
6. 中间件架构
合并后的一大优势是可以深度控制 Agent 的行为。DeerFlow 在 LangGraph 图内注入了 15+ 个中间件,覆盖以下关注点:
scss
请求方向 (before_model):
ToolErrorHandling → DanglingToolCall → Uploads → ThreadData →
DynamicContext → Summarization → Todo → TokenUsage → Title →
Memory → ViewImage → DeferredToolFilter → SubagentLimit →
LoopDetection → SafetyFinishReason → Clarification
响应方向 (after_model):反序执行
中间件的执行发生在 LLM 调用的前后,无需经过任何网络边界。这是进程内合并带来的天然优势------中间件可以直接访问 app_config、RunJournal、thread_store 等运行时对象。
7. 总结
DeerFlow 的 Gateway-LangGraph 合并方案展示了一种在保持协议兼容的前提下,将通用 AI Agent 运行时深度集成到业务 Gateway 中的实践路径。其核心思路可概括为:
- 进程内嵌入:LangGraph 图引擎不再作为独立服务,而是作为 Gateway 的运行时组件按需实例化。
- StreamBridge 解耦:通过内存事件管道桥接后台 Agent 任务与 HTTP SSE 端点,保持协议兼容。
- 分层配置管理:引擎级配置绑定启动快照,应用级配置每请求热加载。
- 原子并发控制:单锁保护的 RunManager 消除 TOCTOU 竞态。
- 按需图构建:每请求创建图实例,反映最新配置,无需重启。
该方案在降低运维复杂度的同时,为 DeerFlow 提供了对 Agent 行为的完全控制能力,为后续的 IM 频道集成、自定义 Agent、Skill 系统等高级功能奠定了基础。
本文基于 DeerFlow v2.0-m1 版本源码分析,项目地址:github.com/bytedance/d...