主题
Aegra 运行时与 LangGraph
本章目标:
- 理解为什么 a-cdm 2026-04-25 把 Agent Runtime 从 LangGraph Server 切到 Aegra 0.9.4,容器名为何保留
- 看懂
RUNTIME_BACKEND三态切换:aegra / langgraph(postgres)/ langgraph(memory 应急)- 掌握两套图注册(
aegra.json文件路径 vslanggraph.json模块路径)与 wrapper 的作用
TL;DR
deer-flow-langgraph 容器(名字保留)实际跑的 Agent Runtime 由 RUNTIME_BACKEND env 决定:aegra → aegra serve(Aegra 0.9.4,MIT,数据落独立 aegra DB),langgraph → langgraph_api.cli postgres(数据落 langgraph_server DB,回滚通道),LANGGRAPH_STORAGE_MODE=memory → 内存模式(应急,重启丢数据)。Aegra 只接受 ./file.py:graph 文件路径,所以 aegra_graphs/ 下有一组 thin wrapper 把 harness 工厂 re-export;langgraph.json 用 module:func 模块路径作回滚通道。两套 server 共用同一份 acdm_auth.py Auth。
Overview(为什么从 LangGraph Server 换到 Aegra)
LangGraph Server 是 LangChain 官方的 Agent 运行时,但它的生产部署能力(LangSmith Deployments)是商业产品。a-cdm 2026-04-25 切到 Aegra 0.9.4(CLAUDE.md:14),核心动机是 Aegra 是 MIT 开源、兼容 LangSmith Deployments 协议、可自托管,避免商业依赖。
切换的工程难点是不能推翻已有架构:前端、acdm-backend SDK 调用、Caddy 路由全是按 deer-flow-langgraph:2024 写的。a-cdm 的解法是"容器名/端口/路径全保留,只换容器内进程":RUNTIME_BACKEND env 在容器启动命令里分支,默认 langgraph 作回滚通道,生产 .env 设 aegra。这样切换/回滚只改一个 env + --force-recreate,上层零感知。
Architecture:三态运行时切换
容器启动命令是一段 shell 分支(deploy/prod/deer-flow-backend/compose.yml:81-97):
| RUNTIME_BACKEND | 命令 | 数据落 | 用途 | Source |
|---|---|---|---|---|
aegra | uv run aegra serve --port 2024 | platform-db 的 aegra DB | 生产主路径(2026-04-25 起) | compose.yml:84-86 |
langgraph(默认) | langgraph_api.cli --runtime-edition postgres | langgraph_server DB | 回滚通道 | compose.yml:91-96 |
langgraph + LANGGRAPH_STORAGE_MODE=memory | langgraph dev(内存) | 内存,重启丢 | 应急(DB 也挂时) | compose.yml:87-89 |
env 注入(compose.yml:104-117):Aegra 读 DATABASE_URL(默认 postgresql://acdm:acdm@platform-db:5432/aegra)+ REDIS_URL;langgraph postgres 模式读 DATABASE_URI + REDIS_URI。两套 DB 物理隔离——Aegra 用 aegra DB,langgraph 用 langgraph_server DB,切换不会污染对方数据(compose.yml:107-111)。compose.yml:114 的默认值是 langgraph,但生产 .env 实际设 aegra。
容器还 bind mount 了 /data/erp-wiki/kingdee(compose.yml:122-126):因为 config.yaml 的 sandbox mount 把 /mnt/kingdee 映射到 host /data/erp-wiki/kingdee,而 sandbox bash 在 langgraph 容器进程内 subprocess 跑,host_path 必须容器内可达,所以做同名 bind mount。
Components:两套图注册 + wrapper
Aegra 和 LangGraph CLI 的图加载语法不同,这是 wrapper 存在的根本原因:
| aegra.json | langgraph.json | Source | |
|---|---|---|---|
| 图路径语法 | ./file.py:variable(文件路径) | module.path:func(模块路径) | deer-flow/backend/aegra.json:6-12 / langgraph.json:8-14 |
| lead_agent | ./aegra_graphs/lead_agent.py:graph | deerflow.agents:make_lead_agent | 同上 |
| pipelines | ./aegra_graphs/weekly_report.py:graph 等 | deerflow.pipelines.*:make_pipeline | 同上 |
| auth | ./packages/.../acdm_auth.py:auth | 同一个 | aegra.json:13-15 / langgraph.json:18-20 |
| checkpointer | (Aegra 自管) | ./.../checkpointer/async_provider.py:make_checkpointer | langgraph.json:15-17 |
Aegra 0.9.4 的 graph loader 只接受文件路径(aegra_graphs/lead_agent.py:1-13 注释明确),所以 deer-flow/backend/aegra_graphs/ 下有 5 个 thin wrapper:
python
# 摘自 deer-flow/backend/aegra_graphs/lead_agent.py:15
from deerflow.agents import make_lead_agent as graph # Aegra classify_factory 检测 callable+RunnableConfiglead_agent.py 直接 re-export 工厂;ba_report.py:13-17 包一层 def graph(config) 调 make_step_runner(因为 BA 报告是单步执行器,Hybrid 模式由 acdm-backend 编排,无 interrupt)。两套 server 通过 RUNTIME_BACKEND 共存,LangGraph CLI 路径是回滚通道。
Data Flow:控制面读 Aegra owner_sub
acdm-backend 和引擎 Gateway 都需要按 owner_sub 查 Aegra 的 thread 表(做 thread 归属校验)。deer-flow/backend/app/gateway/deps.py 的 _resolve_aegra_db_url()(deps.py:24-39)从 DATABASE_URI 推导 aegra DB 的 asyncpg URL:去掉 +asyncpg 标记、把库名换成 aegra(正则 re.sub(r"/[^/?]+(\?.*)?$", r"/aegra\1", base)),与 acdm-backend 的同名逻辑镜像,保证两服务指向同一 aegra DB。
aegra_pool_ctx(app)(deps.py:43-81):初始化app.state.aegra_pool(asyncpg pool,min 1 max 4)。fail-safe:env 未设或建池失败时aegra_pool=None,thread owner lookup 降级(不崩)。aegra_checkpointer_ctx(app)(deps.py:85):初始化AsyncPostgresSaver,用于 fork 端点读写 Aegra checkpointer。
run_aegra.py(run_aegra.py:1-20)是本地启动脚本:Windows 上 psycopg 需 WindowsSelectorEventLoopPolicy,必须在任何 async 代码前设;默认 DATABASE_URL/LANGGRAPH_STORE_POSTGRES 指向本地 aegra 库,AEGRA_CONFIG 指向 aegra.json,然后 uvicorn.run("aegra_api.main:app")。
Configuration(本章相关)
| env | 默认 | 含义 | Source |
|---|---|---|---|
RUNTIME_BACKEND | langgraph(compose)/ aegra(prod .env) | 运行时选择 | deploy/prod/deer-flow-backend/compose.yml:114 |
LANGGRAPH_STORAGE_MODE | postgres | memory=应急内存模式(丢数据) | compose.yml:117 |
DATABASE_URL(Aegra) | ...platform-db:5432/aegra | Aegra 数据库 | compose.yml:108 |
DATABASE_URI(LG) | ${DATABASE_URI} | langgraph postgres 模式 | compose.yml:103 |
AEGRA_CONFIG | /app/backend/aegra.json | Aegra 图/auth 配置 | compose.yml:107 |
Common Pitfalls / 实战 Tips
- 容器名
deer-flow-langgraph是误导但有意保留:它跑的可能是 Aegra。判断真实运行时看RUNTIME_BACKEND而非容器名(CLAUDE.md:14)。 - 切 RUNTIME_BACKEND 要
--force-recreate:改.env后restart不重读,必须 force-recreate 或等 cron 重启(compose.yml:15-22)。 - Aegra 与 langgraph 数据不互通:从 langgraph 切 aegra 后,旧 langgraph_server DB 里的 thread 不会出现在 aegra DB——backfill 是单独的 change(
openspec/changes/backfill-legacy-threads)。 LANGGRAPH_STORAGE_MODE=memory只在 langgraph 模式生效且丢数据:这是"DB 也挂"时的最后兜底,不是常规选项(compose.yml:87-88)。- aegra_pool fail-safe:
aegra_pool=None时 thread owner lookup 降级而非崩(deps.py:47),排查 thread 归属问题先看 pool 是否建成。
References
deploy/prod/deer-flow-backend/compose.yml:76-130— RUNTIME_BACKEND 三态切换(本章主源)deer-flow/backend/aegra.json:1-16— Aegra 图注册 + auth(文件路径形式)deer-flow/backend/langgraph.json:1-21— LangGraph 图注册(模块路径,回滚通道)deer-flow/backend/aegra_graphs/lead_agent.py/ba_report.py— Aegra wrapperdeer-flow/backend/app/gateway/deps.py:24-86— aegra_pool / checkpointer 初始化 + DSN 推导deer-flow/backend/run_aegra.py:1-45— 本地 Aegra 启动脚本
Related Pages
| Page | Relationship |
|---|---|
| 系统整体架构 | 本章是该章 deer-flow-langgraph 容器的运行时细节 |
| 鉴权与授权双层体系 | 本章两套 server 共用该章的 acdm_auth.py Auth |
| 控制面与引擎的耦合契约 | 本章 aegra_pool/owner_sub 查询是该章耦合的一部分 |
| 子代理委派与确定性 Pipeline | 本章 aegra_graphs 注册的 pipeline 在该章详解 |
| 环境变量、凭据与降级开关 | 本章 RUNTIME_BACKEND 是该章降级开关之一 |