DeerFlow 后端 API 参考指南:LangGraph 兼容协议与 Gateway 管理 API 的完整实战手册
DeerFlow 后端对外暴露两套彼此咬合、职责互补的 API:面向 Agent 交互的 LangGraph 兼容 API(/api/langgraph/*)与面向平台管理能力的 Gateway API(/api/*,覆盖模型、MCP、技能、文件上传与工件)。本文以仓库文档 backend/docs/API.md 为主体,结合 backend/app/gateway 源码与 config.example.yaml 配置,系统讲解认证体系、线程/运行生命周期、SSE 流式协议、PAT 个人令牌边界,以及可直接复制运行的 SDK 与 cURL 用法,帮助读者快速接入 DeerFlow 进行二次开发或机器人平台集成。
一、API 总览:两套接口的边界与统一入口
DeerFlow 后端对外提供两类 API:
- LangGraph 兼容 API —— 负责 Agent 交互、线程管理与流式输出,统一挂载在
/api/langgraph/*路径下,遵循 LangGraph SDK 的请求/响应约定; - Gateway API —— 负责模型列表、MCP 配置、技能(skills)、文件上传与 artifacts 工件等平台资源,路径前缀为
/api/*。
所有 API 均通过监听 2026 端口 的 Nginx 反向代理进入。在统一 Nginx 部署形态下,Gateway 拥有 /api/langgraph/* 前缀,并在代理层将其翻译为 Gateway 原生的 /api/* 线程/运行/流式路由。这一路径翻译逻辑可在 docker/nginx/nginx.conf 中直接看到:
# Rewrites /api/langgraph/* to /api/* before proxying to Gateway.
location /api/langgraph/ {
rewrite ^/api/langgraph/(.*) /api/$1 break;
}
也就是说,POST /api/langgraph/runs/stream 经 Nginx 改写后最终落在 Gateway 的原生路径 POST /api/runs/stream 上。
会话的两种启动方式
与 Agent 对话时,客户端可以选择两种路径:
- 先建线程、再运行:
POST /api/langgraph/threads显式创建线程,随后调用线程内的运行接口; - 无状态直达:直接调用
POST /api/langgraph/runs/stream。Gateway 会在config.configurable.thread_id缺省时自动创建线程,并把thread_id与run_id写入响应的Content-Location头中返回给客户端。
二、认证体系:从浏览器会话到平台级信任
浏览器会话通过登录时签发的 access_token 会话 Cookie 认证;程序化客户端(CI、脚本、服务端集成)则改用 Personal Access Token(PAT),以 Bearer 凭据随请求发送:
POST /api/threads/search
Authorization: Bearer dfp_...
Content-Type: application/json
{}
PAT 要求部署配置了数据库后端(SQLite/PostgreSQL);在纯内存后端上,Bearer 凭据会被拒绝,PAT 管理路由返回 503。
2.1 Personal Access Tokens(PAT)
Base URL:/api/v1/auth
PAT 的管理要求交互式会话(一个 PAT 不能管理其他 PAT,也不能修改密码),以此避免泄露的自动化令牌被用来铸造新的凭据。原始令牌只在创建时返回恰好一次,服务端仅保存其 SHA-256 摘要。
创建令牌(POST /api/v1/auth/pats):
{
"name": "ci-runner",
"scopes": ["threads:read", "runs:create", "runs:read"],
"expires_in_days": 90
}
scopes—— 路由权限的子集,可选值包括threads:read、threads:write、threads:delete、runs:create、runs:read、runs:cancel。PAT 只能收窄其所属用户的权限,绝不能放大;expires_in_days—— 可选(1–365);省略表示永不过期。
响应(201):
{
"id": "0f0c6e6a-...",
"name": "ci-runner",
"scopes": ["runs:create", "runs:read", "threads:read"],
"expires_at": "2026-11-25T10:30:00Z",
"created_at": "2026-08-27T10:30:00Z",
"token": "dfp_..."
}
请立即保存 token——之后无法再次获取。
列出令牌(GET /api/v1/auth/pats):返回调用方自己的令牌及 last_used_at/revoked_at 审计字段;绝不返回摘要或原始令牌。
吊销令牌(DELETE /api/v1/auth/pats/{pat_id}):吊销立即生效。
2.2 PAT 的约束边界
- 携带
Authorization头但校验失败的请求会得到硬性401——永不回退到会话 Cookie; - 取消(cancel)能力要求
runs:cancel出现在每一个携带该能力的请求维度上,而不只是专门的取消路由:包括POST /api/threads/{thread_id}/runs/{run_id}/stream上的?action=interrupt|rollback(无 action 的 join 保持runs:read),以及创建运行时的multitask_strategy=interrupt|rollback(默认的reject保持runs:create)。仅 join 某运行流属于纯观测——观察者断开连接从不取消该运行; - 路由级默认拒绝(default-deny):PAT 请求仅被放行到 v1 scopes 所管辖的线程/运行生命周期路由上——
POST /api/threads(创建)、POST /api/threads/search(列表)、GET/PATCH/DELETE /api/threads/{thread_id}、线程的goal/state/compact/history/branches子路由,以及已实现的/runs子路由(GET|POST /api/threads/{thread_id}/runs、仅 POST 的stream、wait、regenerate/prepare、edit-regenerate/prepare集合端点、GET /api/threads/{thread_id}/runs/{run_id}及其cancel(POST)、join/messages/events/workspace-changes(GET)、GET|POST .../runs/{run_id}/stream),外加POST /api/runs/stream|wait与GET /api/runs/{run_id}/messages|feedback。任何后续新增到/runs下的路由在被显式加入策略前一律拒绝。其余所有已认证路由——memory、agents、models、MCP/skills 配置、integrations、channels、uploads——无论 scopes 如何,对 PAT 调用者一律返回403。单独的作用域强制只能约束声明了权限的路由,因此这份白名单是外层边界;会话 Cookie 调用者不受影响; - PAT 凭据永不携带管理员能力,即使所属用户是管理员也是如此。这包括扩展(extension)贡献的管理员路由:扩展 principal 投影会为 PAT 调用者抑制全部管理员信号;
- 吊销或删除所属用户后,其 PAT 会在下一个请求上失效。
2.3 内部认证(Internal Auth):平台 HTTP 集成
面向服务到服务集成(例如飞书、企业微信机器人后端),配置环境变量即可启用:
export DEER_FLOW_INTERNAL_AUTH_TOKEN="<long-random-secret>"
| Header | 必填 | 描述 |
|---|---|---|
X-DeerFlow-Internal-Token |
是 | 必须与 DEER_FLOW_INTERNAL_AUTH_TOKEN 一致;缺失/错误 → 401 |
X-DeerFlow-Owner-User-Id |
按用户隔离时必填 | 平台用户 id(如 feishu_ou_alice、wecom_user_bob);省略 → default 桶 |
该模式不使用浏览器 Cookie 与 CSRF 令牌,也不向 users 表写入记录,而是从 owner 头直接设置 threads_meta.user_id/runs.user_id。DeerFlow 只校验平台令牌本身,不校验 owner id 是否代表真实终端用户——用户合法性完全由平台负责。相关信任边界、持久化与安全说明详见 AUTH_DESIGN.md — Internal Auth。集成时在标准 Gateway 线程/运行端点(POST /api/threads、POST /api/threads/{thread_id}/runs/stream 等)的每个请求上都附加上述两个头即可。
2.4 四种身份来源对照
DeerFlow 支持四种 HTTP 身份来源。它们共享同一套线程/运行隔离规则,区别仅在于是否在 users 表中创建记录,以及外部身份如何映射。完整设计见 AUTH_DESIGN.md。
| 模式 | 入口 | users 表 |
隔离键 |
|---|---|---|---|
| 浏览器会话 | 登录/注册后的 access_token Cookie |
是 | users.id |
| OIDC / SSO | OAuth 回调 → Cookie | 是 | users.id(见 SSO.md) |
| IM 渠道绑定 | 连接码 + channel_connections |
绑定到已注册用户 | channel_connections.owner_user_id |
| 内部认证 | X-DeerFlow-Internal-Token + X-DeerFlow-Owner-User-Id |
否 | threads_meta.user_id 上的 owner 字符串 |
IM 渠道绑定与内部认证都属于平台信任型集成:DeerFlow 信任渠道/平台完成终端用户认证。IM 绑定在 channel_connections/channel_conversations 中持久化映射并要求存在 DeerFlow users 记录;内部认证则允许平台以部署共享令牌 + 每请求 owner 头直接调用 Gateway API——无需 users 记录,但线程/运行/检查点隔离行为完全一致。
2.5 浏览器会话(默认)与 CSRF
DeerFlow 对所有非公开 HTTP 路由强制认证。公开路由仅限健康检查/文档元数据以及下列公开认证端点:
POST /api/v1/auth/initialize—— 当不存在管理员时创建第一个管理员账号;POST /api/v1/auth/login/local—— 以邮箱/密码登录并签发 HttpOnlyaccess_tokenCookie;POST /api/v1/auth/register—— 创建普通user账号并设置会话 Cookie;POST /api/v1/auth/logout—— 清除会话 Cookie;GET /api/v1/auth/setup-status—— 报告是否仍需要创建首个管理员。
已认证的认证端点:
GET /api/v1/auth/me—— 返回当前用户;POST /api/v1/auth/change-password—— 修改密码,setup 期间可顺带修改邮箱,递增token_version并重发 Cookie。
受保护的改状态请求还要求携带 CSRF 双提交令牌:把 csrf_token Cookie 的值放入 X-CSRF-Token 头。login/register/initialize/logout 属于引导认证端点,豁免于双提交令牌但仍拒绝恶意浏览器 Origin 头。
用户隔离由认证后的用户上下文强制执行:
- 线程元数据按
threads_meta.user_id隔离;搜索/读/写/删除 API 只暴露当前用户的线程; - 线程文件位于
{base_dir}/users/{user_id}/threads/{thread_id}/user-data/,在沙箱内以/mnt/user-data/暴露; - Memory 与自定义 Agent 存放在
{base_dir}/users/{user_id}/...。
需要留意的是:MCP 出站连接仍可为配置的 HTTP/SSE MCP 服务器使用 OAuth——这与 DeerFlow API 自身的认证相互独立。
三、LangGraph 兼容 API(/api/langgraph)
Base URL:/api/langgraph。该公开 API 遵循 LangGraph SDK 约定,让 LangGraph SDK 客户端与既有生态工具可以零适配接入 DeerFlow。
3.1 线程(Threads)
创建线程:
POST /api/langgraph/threads
Content-Type: application/json
{
"metadata": {}
}
响应:
{
"thread_id": "abc123",
"created_at": "2024-01-15T10:30:00Z",
"metadata": {}
}
获取线程状态:
GET /api/langgraph/threads/{thread_id}/state
响应:
{
"values": {
"messages": [...],
"sandbox": {...},
"artifacts": [...],
"thread_data": {...},
"title": "Conversation Title"
},
"next": [],
"config": {...}
}
3.2 创建运行(Create Run)
携带输入执行 Agent:
POST /api/langgraph/threads/{thread_id}/runs
Content-Type: application/json
{
"input": {
"messages": [
{
"role": "user",
"content": "Hello, can you help me?"
}
]
},
"config": {
"recursion_limit": 100,
"configurable": {
"model_name": "gpt-4",
"thinking_enabled": false,
"is_plan_mode": false
}
},
"stream_mode": ["values", "messages-tuple", "custom"]
}
Stream Mode 兼容性:
- 支持:
values、messages-tuple、custom、updates、debug、tasks、checkpoints; - 不支持的模式(含
messages、events、tools)会在创建运行前直接返回422。DeerFlow 不会用values替身静默顶替任何不支持的模式。
Run Option 兼容性:
- 支持的并发策略:
reject、rollback、interrupt; - 兼容性默认值:
if_not_exists="create",与 DeerFlow 当前行为一致; - 工件投递(artifact delivery):当某次运行在
/mnt/user-data/outputs下创建或修改了普通文件时会被自动强制启用。present_files必须呈现本次运行产出的至少一个路径(或包含它的目录),且终态 receipt 必须被持久化;只呈现无关文件不满足投递要求。没有变更输出的运行保持普通对话行为。artifact_delivery不是客户端可设置的 run option; - 不支持以下选项,返回
422:webhook、stream_resumable=true、after_seconds、feedback_keys、任何非空on_completion值(包括 SDK 值"complete"与"continue")、if_not_exists="reject"、multitask_strategy="enqueue"; stream_resumable=false可接受:它是 LangGraph SDK 的默认值,请求的正是 DeerFlow 已经提供的不可续传流;- 未声明的 SDK 选项(包括
checkpoint_during与durability)同样返回422,而非被静默丢弃。
当运行期间输出了变更时,run.delivery 事件保留 Slice 1 事实(presented、paths、by_tool),并新增 produced_paths、presented_paths、matched_paths 与显式裁定:verification、stage(presented、mismatched 或 not_started)和 satisfied。没有输出变更的运行的 receipt 保持既有形态。
3.3 Recursion Limit:默认值、钳制与天花板
config.recursion_limit 限制单次运行中 LangGraph 可执行的图步数。统一 Gateway 路径在 build_run_config 中(见 backend/app/gateway/services.py)将默认值设为 100——对 plan-mode 或子 Agent 密集的运行而言这是更安全的起点。客户端仍可在请求体中显式设置 recursion_limit;运行深层嵌套子 Agent 图时可适当调大。
从源码可以看出 Gateway 的三层设计(backend/app/gateway/services.py):
_DEFAULT_RECURSION_LIMIT = 100与_DEFAULT_MAX_RECURSION_LIMIT = 1000分别是服务端默认值与兜底天花板;_clamp_recursion_limit(value, max_limit):布尔值、非整数、非正整数值一律回退到100,合法正整数按max_recursion_limit封顶;_resolve_max_recursion_limit()从AppConfig.max_recursion_limit读取上限,配置不可加载(如无config.yaml的裸单元测试环境)时回退到1000。
因此 Gateway 绝不原样信任客户端提交的 recursion_limit——任意大的数值会让单次运行执行不受限的 LangGraph 超级步(每一步至少一次 LLM 调用),导致失控的 API 成本 / DoS。build_run_config 在把请求配置透传之后、覆写之前进行钳制并记录告警日志(clamped client recursion_limit)。
配置项位于 config.example.yaml:
# Recursion Limit — Hard ceiling for a client-supplied run recursion_limit
# A run's recursion_limit caps the number of LangGraph super-steps (each is at
# least one LLM call). The Gateway never trusts a client-supplied value
# verbatim: any value above this ceiling is clamped down to it, preventing
# runaway API cost / DoS. Invalid or non-positive client values fall back to
# the server default of 100. Raise this only if you legitimately run very
# deeply nested subagent graphs.
max_recursion_limit: 1000
调度任务(scheduled tasks)不读取客户端 body:它们使用 config.yaml 中的 scheduler.recursion_limit(默认 1000,与 Web UI 一致),并在派发时先钳制到 max_recursion_limit(见 config.example.yaml 的 scheduler: 段与 services.py 的 _resolve_scheduler_recursion_limit),保证操作者配置值永远不会作为未钳制值进入 build_run_config。任何超限或配置加载失败的路径都会产生操作者可见的告警。
补充说明:这个 100 只约束 lead 图自身的 LangGraph 超级步预算,与子 Agent 深度相互独立——一次 task() 派发把整个子 Agent 运行塞进 lead 工具节点的一个步骤里,子 Agent 自身通过 subagents.max_turns 执行自己的上限,二者不应混淆。
3.4 Configurable 选项与底层配置处理
请求体中的可配置项:
model_name(string):覆盖默认模型;thinking_enabled(boolean):为支持的模型启用扩展思考;is_plan_mode(boolean):启用 TodoList 中间件进行任务跟踪。
在 Gateway 侧(backend/app/gateway/services.py),build_run_config 还处理若干隐蔽而重要的细节:
context优先:LangGraph ≥ 0.6.0 引入context作为传递线程级数据的首选通道,且拒绝同时携带configurable与context的请求。若调用方已发送context,Gateway 会尊重它并跳过自己拼装的configurabledict(同时记录告警);- 剥离
__前缀键:调用方context中所有__开头的键会被剥离——它们是该 Harness 私有的运行上下文通道(技能密钥绑定来源、活动密钥集合、运行日志),调用者不得预置这些键伪造内部状态。合法的调用方键(secrets、user_id、模型覆盖项)从不用__前缀; thread_id永远来自 URL 路径而非调用方配置,并被镜像进configurable["thread_id"],因为检查点器无论调用方如何驱动运行,始终以configurable["thread_id"]为状态隔离键;- 自定义 Agent 名称:当
assistant_id指向默认"lead_agent"(常量_DEFAULT_ASSISTANT_ID)之外的 Agent 时,其值被归一化(小写、_→-、正则[a-z0-9-]+校验)后同时注入configurable["agent_name"]与context,使旧式 configurable 读取方与 LangGraphToolRuntime.context消费者都能感知到正确 Agent——没有它 Agent 会静默以默认 lead agent 运行。
3.5 运行历史、流式运行与无状态流
获取运行历史:
GET /api/langgraph/threads/{thread_id}/runs
{
"runs": [
{
"run_id": "run123",
"status": "success",
"created_at": "2024-01-15T10:30:00Z"
}
]
}
线程内流式运行:POST /api/langgraph/threads/{thread_id}/runs/stream,请求体与 Create Run 相同,返回 SSE 流。
无状态流式运行(Stateless Stream Run):在不先创建线程的情况下直接开聊。当 config.configurable.thread_id 缺省时 Gateway 自动创建线程,并把两个标识符都写进响应头:
POST /api/langgraph/runs/stream
Content-Type: application/json
Accept: text/event-stream
经 Nginx 后 /api/langgraph/runs/stream 被改写为原生 Gateway 路径 POST /api/runs/stream。请求体同 Create Run;省略 thread_id 即开启新对话,携带 thread_id 则续接已有对话。
响应:SSE 流并附带 Content-Location 头:
Content-Location: /api/threads/{thread_id}/runs/{run_id}
客户端应从该头解析 thread_id 与 run_id(路径以 /runs/{run_id} 结尾)。持久化 thread_id,并在下一轮通过 config.configurable.thread_id 回传,即可保持对话历史。
续接对话:
{
"input": {
"messages": [
{
"role": "user",
"content": "What did I just ask?"
}
]
},
"config": {
"configurable": {
"thread_id": "abc123",
"model_name": "gpt-4"
}
},
"stream_mode": ["values", "messages-tuple", "custom"]
}
典型 SSE 输出流帧(values/messages/end):
event: values
data: {"messages": [...], "title": "..."}
event: messages
data: {"content": "Hello! I'd be happy to help.", "role": "assistant"}
event: end
data: {}
四、Gateway API(/api)
Base URL:/api。以下路由的实现分散在 backend/app/gateway/routers 目录(models.py、mcp.py、skills.py、uploads.py、threads.py、artifacts.py 等)。
4.1 模型(Models)
列出模型:
GET /api/models
{
"models": [
{
"name": "gpt-4",
"display_name": "GPT-4",
"supports_thinking": false,
"supports_vision": true
},
{
"name": "claude-3-opus",
"display_name": "Claude 3 Opus",
"supports_thinking": false,
"supports_vision": true
},
{
"name": "deepseek-v3",
"display_name": "DeepSeek V3",
"supports_thinking": true,
"supports_vision": false
}
]
}
获取模型详情:
GET /api/models/{model_name}
{
"name": "gpt-4",
"display_name": "GPT-4",
"model": "gpt-4",
"max_tokens": 4096,
"supports_thinking": false,
"supports_vision": true
}
4.2 MCP 配置管理
获取 MCP 配置:
GET /api/mcp/config
要求已认证的管理员会话。响应中敏感的 env/header/OAuth 密钥值会被掩码;密钥容器之外的环境变量占位符以原始形态返回,确保编辑过程不会暴露或持久化其展开值。非法的操作者自定义 JSON/配置形态返回 400,而不会被当作 Gateway 故障上报。
{
"mcp_servers": {
"github": {
"enabled": true,
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "***"
},
"description": "GitHub operations"
}
}
}
整体更新 MCP 配置:
PUT /api/mcp/config
Content-Type: application/json
要求已认证的管理员会话。API 托管的 stdio MCP 服务器其 command 只允许使用受信任的可执行名(默认:npx、uvx)。当部署需要额外受信启动器时,可设置环境变量 DEER_FLOW_MCP_STDIO_COMMAND_ALLOWLIST(逗号分隔列表)。
{
"mcp_servers": {
"github": {
"enabled": true,
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "$GITHUB_TOKEN"
},
"description": "GitHub operations"
}
}
}
响应为完整掩码后的 MCP 配置(与 GET 一致)。注意此处 env 中的 $GITHUB_TOKEN:所有目标变更在写入前,Gateway 都会在副本中解析环境变量,并校验运行时将要加载的同一份展开文档,同时把原始的未展开占位符持久化下去。
更新单个 MCP 服务器状态:
PATCH /api/mcp/config
Content-Type: application/json
启用/禁用单个已配置的 MCP 服务器而不替换整个 extensions 配置,要求已认证的管理员会话。启用 stdio 服务器会校验其 command(与完整 PUT 使用同一份白名单);禁用则不需要命令在白名单内,其他服务器上的非法命令也不会阻塞本次更新。该端点保留密钥、环境变量占位符、skills、自定义服务器字段及其他顶层 extensions 配置。SSE/HTTP 目标可任选 DeerFlow 的 type 字段或 MCP 规范的 transport 字段。
{
"server_name": "semantic-scholar",
"enabled": false
}
响应为完整掩码后的 MCP 配置。未知 server_name 返回 404;尝试启用命令不在白名单的 stdio 服务器返回 400。
新增 MCP 服务器:
POST /api/mcp/config/servers
Content-Type: application/json
一次新增一个或多个服务器而不替换既有条目。Gateway 会在共享配置锁下重读文件,因此并发的兄弟变更得以保留。重名返回 409。请求体使用与完整 PUT 相同的 mcp_servers 映射。
替换单个 MCP 服务器:
PUT /api/mcp/config/server
Content-Type: application/json
完整替换一个既有服务器并保留兄弟条目。省略的普通字段会被删除或重置;显式 *** 占位符会恢复对应的已存密钥。被禁用的 stdio 替换可以保留一个白名单外的语法合法命令以便离线编辑;命令形态与代码注入类环境变量检查在保存时仍会执行,而白名单与可执行参数策略在服务器被启用时才执行。
{
"server_name": "github",
"server": {
"enabled": true,
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {"GITHUB_TOKEN": "***"}
}
}
删除单个 MCP 服务器:
DELETE /api/mcp/config/servers/{server_name}
删除单个服务器而不替换兄弟条目。服务器名作为路径参数、DELETE 请求无 body。放入 URL 前请对名字做百分号编码;路径转换器也会让遗留的空名与含斜杠名保持可寻址。
所有目标变更端点都返回完整掩码后的 MCP 配置。写前校验流程在《整体更新》一节已述:解析环境变量到副本 → 校验运行时加载的同款展开文档 → 持久化原始占位符。
重置 MCP 工具缓存:
POST /api/mcp/cache/reset
要求已认证的管理员会话。进程范围内清空缓存的 MCP 工具与持久化 MCP 会话——会影响当前 Gateway 进程中的全部线程与用户。下一次 Agent 运行或工具查找时,会从配置的 MCP 服务器重新加载工具。
{
"success": true,
"message": "MCP tools cache reset. Tools will reload on next use."
}
4.3 技能(Skills)
列出技能:
GET /api/skills
{
"skills": [
{
"name": "pdf-processing",
"display_name": "PDF Processing",
"description": "Handle PDF documents efficiently",
"enabled": true,
"license": "MIT",
"path": "public/pdf-processing"
},
{
"name": "frontend-design",
"display_name": "Frontend Design",
"description": "Design and build frontend interfaces",
"enabled": false,
"license": "MIT",
"path": "public/frontend-design"
}
]
}
获取技能详情:
GET /api/skills/{skill_name}
{
"name": "pdf-processing",
"display_name": "PDF Processing",
"description": "Handle PDF documents efficiently",
"enabled": true,
"license": "MIT",
"path": "public/pdf-processing",
"allowed_tools": ["read_file", "write_file", "bash"],
"content": "# PDF Processing\n\nInstructions for the agent..."
}
启用 / 禁用技能:
POST /api/skills/{skill_name}/enable
POST /api/skills/{skill_name}/disable
{
"success": true,
"message": "Skill 'pdf-processing' enabled"
}
安装技能:从 .skill 文件安装。
POST /api/skills/install
Content-Type: multipart/form-data
请求体字段:file —— 待安装的 .skill 文件。
{
"success": true,
"message": "Skill 'my-skill' installed successfully",
"skill": {
"name": "my-skill",
"display_name": "My Skill",
"path": "custom/my-skill"
}
}
重载技能:
POST /api/skills/reload
使当前 Gateway 进程中每个用户的技能提示缓存失效。后续运行会重新扫描配置的 public、custom 与 legacy 技能目录;已经开始运行的运行保留其既有技能快照。请求无 body,要求已认证的管理员。Cookie 认证请求需要把 CSRF Cookie 值放入匹配头:
curl -X POST http://localhost:2026/api/skills/reload \
-b cookies.txt \
-H "X-CSRF-Token: <csrf_token-cookie-value>"
{
"success": true,
"scope": "process",
"message": "Skill caches invalidated; subsequent runs in this Gateway process will rescan the latest skills."
}
需要注意 success 只代表缓存失效动作本身成功,不表示磁盘上每个文件都合法:畸形技能沿用既有解析器行为(跳过并记日志)。未认证调用者返回 401,非管理员返回 403;失效机制自身失败或进程本地后台扫描未在缓存刷新超时内完成时,返回通用 500。加载器级故障(如挂载根不可用)不会发布空目录:最后一次成功加载的进程缓存仍可用;超时的扫描会由后台 daemon worker 继续执行,完成后仍可填充进程缓存。
reload 的作用域刻意设计为进程本地。每个 Uvicorn worker 或 Kubernetes Pod 都必须被直接调用;经负载均衡 Service 的重复请求不保证触达每个实例。外部 MinIO/NFS/CSI 写入绕过了 install/edit API 所用的校验、SkillScan 与历史记录,因此挂载目录必须仅对可信操作者可写。
4.4 文件上传
上传文件(支持多文件):
POST /api/threads/{thread_id}/uploads
Content-Type: multipart/form-data
请求体字段:files —— 一个或多个待上传文件。
{
"success": true,
"files": [
{
"filename": "document.pdf",
"size": 1234567,
"path": ".deer-flow/threads/abc123/user-data/uploads/document.pdf",
"virtual_path": "/mnt/user-data/uploads/document.pdf",
"artifact_url": "/api/threads/abc123/artifacts/mnt/user-data/uploads/document.pdf",
"markdown_file": "document.md",
"markdown_path": ".deer-flow/threads/abc123/user-data/uploads/document.md",
"markdown_virtual_path": "/mnt/user-data/uploads/document.md",
"markdown_artifact_url": "/api/threads/abc123/artifacts/mnt/user-data/uploads/document.md"
}
],
"message": "Successfully uploaded 1 file(s)"
}
支持的文档格式(自动转换为 Markdown):
- PDF(
.pdf) - PowerPoint(
.ppt、.pptx) - Excel(
.xls、.xlsx) - Word(
.doc、.docx)
列出已上传文件:
GET /api/threads/{thread_id}/uploads/list
{
"files": [
{
"filename": "document.pdf",
"size": 1234567,
"path": ".deer-flow/threads/abc123/user-data/uploads/document.pdf",
"virtual_path": "/mnt/user-data/uploads/document.pdf",
"artifact_url": "/api/threads/abc123/artifacts/mnt/user-data/uploads/document.pdf",
"extension": ".pdf",
"modified": 1705997600.0
}
],
"count": 1
}
删除文件:
DELETE /api/threads/{thread_id}/uploads/{filename}
{
"success": true,
"message": "Deleted document.pdf"
}
4.5 线程清理(Thread Cleanup)
在 LangGraph 线程本身被删除后,移除 DeerFlow 托管的本地线程文件(.deer-flow/threads/{thread_id}):
DELETE /api/threads/{thread_id}
{
"success": true,
"message": "Deleted local thread data for abc123"
}
错误行为:
- 非法线程 id 返回
422; 500返回通用{"detail": "Failed to delete local thread data."},完整异常细节保留在服务端日志中。
4.6 工件(Artifacts)
获取工件:
GET /api/threads/{thread_id}/artifacts/{path}
路径示例:
/api/threads/abc123/artifacts/mnt/user-data/outputs/result.txt/api/threads/abc123/artifacts/mnt/user-data/uploads/document.pdf
查询参数:
download(boolean):为true时强制下载,附带 Content-Disposition 头。
响应:带恰当 Content-Type 的文件内容。
五、错误响应格式与 HTTP 状态码
所有 API 都以统一格式返回错误:
{
"detail": "Error message describing what went wrong"
}
HTTP 状态码:
400—— Bad Request:输入非法;404—— Not Found:资源不存在;422—— Validation Error:请求校验失败;500—— Internal Server Error:服务端错误。
六、流式支持:SSE 协议与重放语义
Gateway 的 LangGraph 兼容 API 使用 Server-Sent Events(SSE) 流式推送运行事件。
线程内流式(线程必须存在):
POST /api/langgraph/threads/{thread_id}/runs/stream
Accept: text/event-stream
无状态流式(无需预建线程,Gateway 自动创建):
POST /api/langgraph/runs/stream
Accept: text/event-stream
两个端点都返回 Content-Location: /api/threads/{thread_id}/runs/{run_id}。DeerFlow Web UI 与 LangGraph SDK 客户端都依赖该头在新建对话的首条消息上发现被分配的 thread_id 与 run_id。
SSE 重放保留与缺口(gap)
客户端可以用 Last-Event-ID 重连某运行流。重放历史受 stream_bridge.queue_maxsize(默认 256)限制;Redis 后端还受滚动 stream_ttl_seconds 约束。游标若仍在保留窗口内,会在该事件后无额外控制帧地续传。
当语法合法的游标早于已保留的水印时,服务端会在任何保留数据之前发送恰好一个 gap 事件,然后关闭该订阅且不发送 end 事件:
event: gap
data: {"code":"stream_replay_gap","run_id":"run-123","requested_event_id":"1718000000000-1","earliest_available_event_id":"1718000000100-42","latest_available_event_id":"1718000000200-84","recovery":"reload_durable_state"}
该帧刻意不带 SSE id: 字段。earliest_available_event_id 与 latest_available_event_id 均为 string | null(缓冲区无任何保留事件时为 null)。消费者必须重载持久化的线程状态与运行事件/消息,随后可从 latest_available_event_id 重连以跟随更新的在线事件;或当缓冲区为空(latest_available_event_id 为 null)时不带游标重新加入。gap 不会取消正在运行的 run。同样信号也适用于下述场景:无游标订阅者已建立空流等待,但首次 Redis 唤醒在投递前落后——此时 requested_event_id 为 null。畸形游标的处理与“合法游标但被逐出”不同,且是后端相关的。
七、限流:默认关闭,生产在 Nginx 配置
默认不实现任何限流。生产部署请在 Nginx 中配置:
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
八、SDK 用法与命令行示例
8.1 Python(LangGraph SDK)
from langgraph_sdk import get_client
client = get_client(url="http://localhost:2026/api/langgraph")
run_meta: dict[str, str] = {}
def on_run_created(meta) -> None:
# langgraph-sdk 0.3.x parses Content-Location only when this callback is set.
if meta.thread_id:
run_meta["thread_id"] = meta.thread_id
run_meta["run_id"] = meta.run_id
# Option A: stateless stream — no thread pre-creation
# Gateway auto-creates a thread and returns thread_id/run_id in Content-Location.
async for event in client.runs.stream(
None,
"lead_agent",
input={"messages": [{"role": "user", "content": "Hello"}]},
config={"configurable": {"model_name": "gpt-4"}},
stream_mode=["values", "messages-tuple", "custom"],
on_run_created=on_run_created,
):
print(event)
thread_id = run_meta["thread_id"] # persist before the next turn
# Option A (continued): same thread on the next turn
async for event in client.runs.stream(
None,
"lead_agent",
input={"messages": [{"role": "user", "content": "What did I just ask?"}]},
config={"configurable": {"thread_id": thread_id, "model_name": "gpt-4"}},
stream_mode=["values", "messages-tuple", "custom"],
on_run_created=on_run_created,
):
print(event)
# Option B: thread-scoped stream — create thread first, then stream
thread = await client.threads.create()
async for event in client.runs.stream(
thread["thread_id"],
"lead_agent",
input={"messages": [{"role": "user", "content": "Hello"}]},
config={"configurable": {"model_name": "gpt-4"}},
stream_mode=["values", "messages-tuple", "custom"],
on_run_created=on_run_created,
):
print(event)
要点:langgraph-sdk 0.3.x 只有在设置 on_run_created 回调时才会解析 Content-Location 头,因此无状态流场景务必注册该回调,并在下一轮把解析出的 thread_id 通过 config.configurable.thread_id 回传。
8.2 JavaScript / TypeScript(fetch)
// Using fetch for Gateway API
const response = await fetch('/api/models');
const data = await response.json();
console.log(data.models);
function parseRunLocation(contentLocation: string | null) {
if (!contentLocation) return null;
const match = /\/threads\/([^/]+)\/runs\/([^/]+)/.exec(contentLocation);
if (!match) return null;
return { threadId: match[1], runId: match[2] };
}
// Option A: stateless stream — no thread pre-creation
let threadId: string | undefined;
const firstResponse = await fetch("/api/langgraph/runs/stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
},
body: JSON.stringify({
input: { messages: [{ role: "user", content: "Hello" }] },
stream_mode: ["values", "messages-tuple", "custom"],
}),
});
const created = parseRunLocation(firstResponse.headers.get("Content-Location"));
threadId = created?.threadId;
console.log("thread_id:", created?.threadId, "run_id:", created?.runId);
// Option B: continue the same thread on the next turn
const followUpResponse = await fetch("/api/langgraph/runs/stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
},
body: JSON.stringify({
input: { messages: [{ role: "user", content: "What did I just ask?" }] },
config: { configurable: { thread_id: threadId } },
stream_mode: ["values", "messages-tuple", "custom"],
}),
});
// Option C: thread-scoped stream when you already have a thread_id
const streamResponse = await fetch(`/api/langgraph/threads/${threadId}/runs/stream`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
},
body: JSON.stringify({
input: { messages: [{ role: "user", content: "Hello" }] },
stream_mode: ["values", "messages-tuple", "custom"],
}),
});
const reader = streamResponse.body?.getReader();
// Decode and parse SSE frames from reader in your client code.
8.3 cURL 示例
# List models
curl http://localhost:2026/api/models
# Get MCP config
curl http://localhost:2026/api/mcp/config
# Upload file
curl -X POST http://localhost:2026/api/threads/abc123/uploads \
-F "files=@document.pdf"
# Enable skill
curl -X POST http://localhost:2026/api/skills/pdf-processing/enable
# Stateless stream — no thread pre-creation
curl -s -D - -N -X POST http://localhost:2026/api/langgraph/runs/stream \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{
"input": {"messages": [{"role": "user", "content": "Hello"}]},
"config": {
"recursion_limit": 100,
"configurable": {"model_name": "gpt-4"}
},
"stream_mode": ["values", "messages-tuple", "custom"]
}'
# Read Content-Location: /api/threads/{thread_id}/runs/{run_id} from the headers.
# Continue the same thread on the next turn
curl -s -N -X POST http://localhost:2026/api/langgraph/runs/stream \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{
"input": {"messages": [{"role": "user", "content": "What did I just ask?"}]},
"config": {
"configurable": {"thread_id": "abc123", "model_name": "gpt-4"}
},
"stream_mode": ["values", "messages-tuple", "custom"]
}'
# Thread-scoped flow — create thread first, then stream
curl -X POST http://localhost:2026/api/langgraph/threads \
-H "Content-Type: application/json" \
-d '{}'
curl -X POST http://localhost:2026/api/langgraph/threads/abc123/runs/stream \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{
"input": {"messages": [{"role": "user", "content": "Hello"}]},
"config": {
"recursion_limit": 100,
"configurable": {"model_name": "gpt-4"}
},
"stream_mode": ["values", "messages-tuple", "custom"]
}'
九、实践要点小结
- 统一走 2026 端口:无论 LangGraph 兼容 API 还是 Gateway 管理 API,客户端入口始终是
http://localhost:2026/api/...,Nginx 负责/api/langgraph/*→/api/*的路径改写; - 无状态开局、线程续聊:新对话直接
POST /api/langgraph/runs/stream,从Content-Location解析thread_id/run_id并持久化,下一轮通过config.configurable.thread_id回传即可维持上下文;Web UI 与 LangGraph SDK 客户端都依赖这一机制; - PAT 只用于线程/运行生命周期:
/api/v1/auth/pats创建令牌需交互式会话、原始令牌仅返回一次;其余管理路由对 PAT 一律403,取消运行等能力还需要对应维度上的runs:cancelscope; - 平台集成优先 Internal Auth:服务到服务的机器人后端使用
DEER_FLOW_INTERNAL_AUTH_TOKEN环境变量 + 两个专用请求头,不落users表但隔离语义一致; - recursion_limit 受服务端钳制:客户端显式值会被钳制到
config.yaml的max_recursion_limit(默认1000),非法/非正值回退到默认100;调度任务改用scheduler.recursion_limit,调参位置在 config.example.yaml; - SSE 断线重放:用
Last-Event-ID续传;游标过期会收到gap帧(无end、不取消运行),应据此重载持久状态后从latest_available_event_id重连。
如需阅读更多背景,仓库内还提供 AUTH_DESIGN.md、SSO.md 等设计文档,backend/docs/README.md 列出了完整的文档索引。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00