← 返回主线
01

第 01 站 · 已发布

请求入口与 Agent 主链

实现 risk ●●○○○
源码锚点 · @0fb18e3

每个 Agent 系统开始回答之前,都要先解决一个朴素问题:谁负责接住请求?你在前端敲下的一句话,不能直接交给模型。它要先被登记成一次有 ID、有生命周期、可流式观察的 run,模型才有机会开口。这一章看 DeerFlow 如何接住输入、创建 run、装入运行时上下文,再把执行事件持续流回前端。

先记住一条边界:DeerFlow 先托管一次运行,才轮到 agent 做推理。 Gateway 就是这个运行时宿主:它负责接入、run 生命周期、上下文注入、事件流和结束判定。真正的推理,要到图开始执行之后才发生。

flowchart TD
U["前端 / IM / LangGraph SDK"] --> EP["POST /threads/{id}/runs/stream"]
EP --> SR["stream_run() · start_run()"]
SR --> RM["RunManager.create_or_reject()"]
RM --> CFG["build_run_config() · 兼容适配"]
CFG --> TASK["create_task(run_agent)"]
TASK -. 后台 .-> RA["run_agent()"]
RA --> RT["Runtime(context=...)"]
RT --> MK["make_lead_agent(config)"]
MK --> ASM["agent.astream(...)"]
ASM --> SB["StreamBridge"]
SR --> SSE["sse_consumer() · SSE 事件"]
SB --> SSE
一句话进来,怎么变成一次在后台执行、向前台流事件的 run

三种「运行时」,别搞混

读这一段最容易踩的坑,是把三样东西混成一样:

LangGraph         = agent 用的执行内核(图怎么跑)
LangGraph Server  = 官方可选的 HTTP 运行时
DeerFlow Gateway  = 当前主要的 HTTP 运行时,兼容 LangGraph API

backend/langgraph.json 里还登记着 lead_agent,那是为了让官方 LangGraph Server / Studio / CLI 还能照常工作。但主链上 Gateway 是直接 import 并调用图工厂的,没有经过官方 server。所以常规服务路径走的是 Gateway 这套自己实现的运行时,不是 LangGraph Server。

请求落地的四个零件

打开 thread_runs.py services.py ,你会反复碰到四个角色。别去背每个字段,先记住它们各自答的是哪个问题。

RunCreateRequest —— 请求长什么样。它带着 inputconfigcontextmetadatastream_mode,还有 interrupt_before / interrupt_aftermultitask_strategyon_disconnect 这些控制位。它是「这次 run 想怎么跑」的完整声明。

RunManager —— run 的登记处。create_or_reject() 决定这次请求能不能创建 run(比如同一 thread 已经在跑、按 multitask_strategy 该拒就拒),之后负责 set_status / cancel / 持久化 run 元数据。

StreamBridge —— 一道解耦闸。后台图在产事件,HTTP 这边在消费事件,两边不直接互相阻塞:run_agent() 往 bridge 里发,sse_consumer() 从 bridge 里取、再格式化成 SSE 事件。即使前端断开,后台任务也不会被迫中断。

run_agent() —— 真正干活的后台 worker( worker.py )。注意:它不是 agent 本身,而是托管 agent 执行的后台任务。

configurable,还是 context?

build_run_config() 干的是一件「翻译」活:把 HTTP 请求里的字段,翻成 LangGraph 认识的 RunnableConfig 形状。这里藏着整篇最该记的一处兼容债:

config["configurable"]   旧的运行时选项通道
config["context"]        新的 LangGraph runtime context 通道

DeerFlow 在兼容模式下,会把关键字段同时写进两边——model_namemodethinking_enabledreasoning_effortis_plan_modesubagent_enabledagent_nameis_bootstrap 这些白名单字段,两个通道各放一份。这不是浪费,而是兼容成本:老的 DeerFlow 代码从 configurable 读,新的 ToolRuntime.contextcontext 读;只写一边,就会有一类消费者读不到。

随后 run_agent() 还会建一个 LangGraph Runtime,把 thread_id / run_id / user_id / app_config 这些 run 级数据装进 runtime.context,再塞回 config["configurable"]["__pregel_runtime"],让下游中间件和工具都能按 LangGraph 的运行时 API 拿到它。

自己当宿主 · Gateway

  • 自己掌握鉴权 / 元数据 / 回滚 / 持久化
  • 前端、IM、SDK 多宿主统一接入
  • run 的生命周期完全可控

代价 · 兼容适配

  • 字段要翻成 LangGraph 风格的 config
  • configurable / context 双写,历史债
  • run_agent() 职责集中,局部修改成本高

会绊倒你的地方


入口这一章只做一件事:把用户输入登记成一次正在执行的 run,把上下文交给图执行,再把事件流回前端。它接住请求、创建记录、装好上下文,然后退到幕后。接下来,run 会进入 make_lead_agent():它不再只是请求数据,而要被装配成一张能运行的 agent 图。