主题
配置系统与 AppConfig
本章目标:
- 讲清
config.yaml的四级路径解析优先级,以及AppConfig.from_file()从 YAML 到 Pydantic 模型的完整加载链路。- 讲清
config_version版本检测告警机制和make config-upgrade自动迁移脚本如何协同。- 讲清
get_app_config()的 mtime 热加载缓存策略、$ENV变量解析,以及config/目录下二十多个子配置模块如何聚合进单一AppConfig。
TL;DR
DeerFlow 的运行时配置以项目根目录的 config.yaml 为单一真相源。AppConfig.from_file() 按"显式参数 → DEER_FLOW_CONFIG_PATH 环境变量 → 项目根 → 旧版 monorepo 路径"四级优先级解析文件,加载后做版本检测、$ENV 变量递归替换、数据库默认值填充,再用 Pydantic model_validate 校验成强类型对象 app_config.py:142。get_app_config() 是带 mtime 自动热加载的缓存单例:文件被改动后无需重启进程即可生效 app_config.py:358。config/ 目录下每个子配置模块(模型、沙箱、记忆、数据库等)各自定义一个 Pydantic 模型,作为字段聚合进 AppConfig;其中部分模块还维护进程级单例,加载时通过 load_*_config_from_dict() 回灌 app_config.py:185。config_version 字段用于检测用户配置是否落后于 config.example.yaml,落后时打印告警提示运行 make config-upgrade。
Overview
DeerFlow 是一个深度可配置的 super agent harness:模型、工具、沙箱、子代理、记忆、守卫、数据库后端等几乎每个子系统都暴露配置项。如果让每个子系统各自读文件、各自解析环境变量,会产生三个问题:配置文件位置不统一、$ENV 替换逻辑重复、配置 schema 演进后老用户无声地丢失新字段。
配置系统用三个设计决策解决这些问题:
- 单一聚合根
AppConfig:所有子配置都是它的一个 Pydantic 字段,一次解析、强类型校验、向下传递。config/__init__.py只重导出少数高频入口(get_app_config、get_paths等),保持公共面收敛 config/init.py:1。 - 集中式路径解析与
$ENV解析:resolve_config_path()统一四级优先级;resolve_env_variables()递归把以$开头的字符串替换成环境变量,敏感值(API key、数据库 URL)只放.envapp_config.py:269。 - 版本检测 + mtime 热加载:
config_version把"schema 演进"显式化,落后即告警;get_app_config()用 mtime 比对实现编辑即生效,Gateway 与 LangGraph 运行时读到的配置始终对齐磁盘 app_config.py:378。
Architecture
配置系统的核心代码集中在 backend/packages/harness/deerflow/config/。app_config.py 是聚合根与缓存层,其余文件多为单一子系统的 Pydantic schema。
Source:
- backend/packages/harness/deerflow/config/app_config.py —
AppConfig聚合根、路径解析、版本检测、$ENV解析、缓存单例与运行时覆盖 - backend/packages/harness/deerflow/config/init.py — 公共 API 重导出面
- backend/packages/harness/deerflow/config/runtime_paths.py — 项目根 / 运行时 home 解析,被路径与配置解析共用
- backend/packages/harness/deerflow/config/paths.py —
Paths数据目录布局单例 - backend/packages/harness/deerflow/config/extensions_config.py — MCP / skills 的独立 JSON 配置,加载时并入
AppConfig - backend/packages/harness/deerflow/config/database_config.py — 统一数据库后端配置
- config.example.yaml — 配置模板,持有当前
config_version - scripts/config-upgrade.sh — 版本迁移 + 字段合并脚本
Components / Subsystems
AppConfig 聚合根
AppConfig 是一个 extra="allow" 的 Pydantic BaseModel,把每个子系统作为一个强类型字段聚合 app_config.py:83。除少数必填字段(如 sandbox)外,绝大多数子配置都用 default_factory 提供零配置默认值,因此一个最小 config.yaml 也能启动。它还提供 get_model_config() / get_tool_config() / get_tool_group_config() 三个按名查找的便捷方法 app_config.py:293。
路径解析 resolve_config_path
四级优先级,任一级命中即返回,未找到抛 FileNotFoundError app_config.py:111:
- 显式
config_path参数(不存在则报错)。 DEER_FLOW_CONFIG_PATH环境变量(不存在则报错)。- 项目根
config.yaml—— 经existing_project_file(("config.yaml",))在DEER_FLOW_PROJECT_ROOT或 CWD 下查找 runtime_paths.py:34。 - 旧版 monorepo 兜底:
backend/config.yaml、仓库根config.yaml,由_legacy_config_candidates()给出 app_config.py:52。
from_file 加载管线
from_file() 串起完整加载链:解析路径 → yaml.safe_load(空文件回退 {})→ 版本检测 → $ENV 解析 → 数据库默认值填充 → 合并独立的 ExtensionsConfig → model_validate 校验 → ACP agent 校验 → 单例回灌 app_config.py:142。注意 extensions 字段来自 extensions_config.json 而非 config.yaml 主文件,由 ExtensionsConfig.from_file() 独立加载后注入 app_config.py:168。
config_version 版本检测
_check_config_version() 把用户 config_version 与就近的 config.example.yaml 比对:用户值缺失或非整数按 0 处理;向上最多 5 级目录搜索 config.example.yaml;用户版本低于示例版本时打印 logger.warning 提示运行 make config-upgrade;找不到示例文件或读取异常则静默返回(不阻断启动)app_config.py:223。当前示例版本为 config_version: 9 config.example.yaml:18。
config-upgrade.sh 迁移脚本
脚本先按"DEER_FLOW_CONFIG_PATH → backend/config.yaml → 仓库根 config.yaml"定位用户配置;无配置时直接拷贝示例文件;否则用内联 Python 比对版本:用户版本不低于示例则直接退出。需要升级时,先对 (user_version, example_version] 区间执行文本替换型 migration(MIGRATIONS 字典,例如 v1 把 src.* 模块路径改名为 deerflow.*),再递归只补缺失键(merge() 不覆盖已有值),最后强制写回新的 config_version,并备份原文件到 config.yaml.bak scripts/config-upgrade.sh:73。
$ENV 变量解析
resolve_env_variables() 递归遍历 dict / list / str:字符串以 $ 开头时取 os.getenv(name),变量不存在直接抛 ValueError(严格模式)app_config.py:269。注意:ExtensionsConfig.resolve_env_variables() 是另一套实现,未解析时回退为空字符串而非报错,以免 MCP 服务器收到字面量 $VAR extensions_config.py:152。load_dotenv() 在模块导入时执行,所以 .env 中的变量在解析前已进环境 app_config.py:34。
缓存单例与 mtime 热加载
模块级缓存四元组 _app_config / _app_config_path / _app_config_mtime / _app_config_is_custom 加一对 ContextVar 运行时覆盖栈 app_config.py:330。get_app_config() 决策顺序:先看 ContextVar 运行时覆盖 → 再看是否被 set_app_config() 注入的自定义实例 → 否则解析路径并取当前 mtime,当"未缓存 / 路径变了 / mtime 变了"任一成立就重新加载并刷新缓存 app_config.py:358。reload_app_config() 强制重载,reset_app_config() 清空缓存,push/pop_current_app_config() 提供按执行上下文的临时覆盖 app_config.py:390。
单例回灌 _apply_singleton_configs
部分子系统(title、summarization、memory、agents_api、subagents、tool_search、guardrails、checkpointer、stream_bridge、acp)在各自模块里维护一个进程级单例。from_file() 末尾调用 _apply_singleton_configs(),通过每个模块的 load_*_config_from_dict() 把校验后的值回灌进这些单例;若 checkpointer 配置发生变化,还会 reset_checkpointer() / reset_store() 让运行时单例下次重建 app_config.py:185。这类模块的典型形态见 checkpointer_config.py:模块级 _checkpointer_config + get/set/load_*_from_dict 三件套 checkpointer_config.py:33。
Data Flow
下图为一次 get_app_config() 调用在缓存失效(文件被改)时的完整时序。
加载步骤编号说明:
- 路径解析:四级优先级确定
config.yaml绝对路径 app_config.py:111。 - YAML 解析:
yaml.safe_load,空文件回退{}app_config.py:154。 - 版本检测:在 env 解析前完成,确保用原始数据比对
config_versionapp_config.py:158。 $ENV解析:递归替换全部$VARapp_config.py:160。- 数据库默认值:
database段缺失时填充backend=sqlite、sqlite_dir=.deer-flow/dataapp_config.py:211。 - 扩展配置合并:
extensions字段来自独立的extensions_config.jsonapp_config.py:168。 - Pydantic 校验:
model_validate把 dict 转为强类型AppConfigapp_config.py:171。 - 单例回灌:
_apply_singleton_configs把值推进各进程级单例,必要时重置 checkpointer / store app_config.py:173。
速查表
config/ 目录下的子配置模块是一组同类集合 —— 每个模块导出一个 Pydantic 模型,作为 AppConfig 的一个字段聚合;部分还附带进程级单例与 load_*_config_from_dict()。完整矩阵如下:
| 子配置模块 | 职责 | 对应 config 键 | 文件(Source) |
|---|---|---|---|
app_config.py | 聚合根 + 缓存层 + 路径/版本/env 解析 | (顶层) | config/app_config.py |
model_config.py | 可用 LLM 模型定义 | models | config/model_config.py |
sandbox_config.py | 沙箱 provider 配置(必填) | sandbox | config/sandbox_config.py |
tool_config.py | 工具与工具组 | tools / tool_groups | config/tool_config.py |
tool_search_config.py | 延迟工具搜索/加载 | tool_search | config/tool_search_config.py |
extensions_config.py | MCP 服务器 + skills 状态(独立 JSON) | extensions | config/extensions_config.py |
skills_config.py | skills 目录路径 | skills | config/skills_config.py |
skill_evolution_config.py | 代理自管理技能演进 | skill_evolution | config/skill_evolution_config.py |
memory_config.py | 长期记忆子系统(含单例) | memory | config/memory_config.py |
summarization_config.py | 上下文摘要(含单例) | summarization | config/summarization_config.py |
title_config.py | 自动标题生成(含单例) | title | config/title_config.py |
subagents_config.py | 子代理运行时(含单例) | subagents | config/subagents_config.py |
agents_api_config.py | 自定义代理管理 API(含单例) | agents_api | config/agents_api_config.py |
acp_config.py | ACP 兼容代理(含单例) | acp_agents | config/acp_config.py |
guardrails_config.py | 守卫中间件(含单例) | guardrails | config/guardrails_config.py |
loop_detection_config.py | 循环检测中间件 | loop_detection | config/loop_detection_config.py |
token_usage_config.py | Token 用量跟踪 | token_usage | config/token_usage_config.py |
database_config.py | 统一数据库后端 | database | config/database_config.py |
checkpointer_config.py | LangGraph checkpointer(含单例) | checkpointer | config/checkpointer_config.py |
run_events_config.py | 运行事件存储 | run_events | config/run_events_config.py |
stream_bridge_config.py | StreamBridge(含单例) | stream_bridge | config/stream_bridge_config.py |
tracing_config.py | 可观测性 tracing provider | (tracing) | config/tracing_config.py |
runtime_paths.py | 项目根 / 运行时 home 解析 | (环境变量) | config/runtime_paths.py |
paths.py | 数据目录布局单例 | (派生) | config/paths.py |
CircuitBreakerConfig 内联在 app_config.py 中,对应键 circuit_breaker app_config.py:45。⚠️ 本次未逐一深入 model_config.py / sandbox_config.py / tool_config.py 内部字段,详见第 05、07、20 章。
Configuration
路径解析优先级
| 优先级 | 来源 | 不存在时行为 | Source |
|---|---|---|---|
| 1 | 显式 config_path 参数 | 抛 FileNotFoundError | app_config.py:121 |
| 2 | DEER_FLOW_CONFIG_PATH 环境变量 | 抛 FileNotFoundError | app_config.py:126 |
| 3 | 项目根 config.yaml(DEER_FLOW_PROJECT_ROOT 或 CWD) | 落到优先级 4 | app_config.py:132 |
| 4 | 旧版 backend/config.yaml、仓库根 config.yaml | 抛 FileNotFoundError | app_config.py:136 |
版本检测行为
| 场景 | config_version 处理 | 行为 | Source |
|---|---|---|---|
| 用户值缺失 / 非整数 | 视为 0 | 与示例比对 | app_config.py:231 |
| 用户版本 < 示例版本 | 整数比较 | logger.warning 提示 make config-upgrade | app_config.py:261 |
找不到 config.example.yaml(向上 5 级) | — | 静默返回,不阻断 | app_config.py:247 |
make config-upgrade 执行 | 写回示例版本 | 文本 migration + 只补缺失键 + 备份 .bak | scripts/config-upgrade.sh:115 |
缓存与热加载行为
| 触发条件 | get_app_config() 行为 | Source |
|---|---|---|
ContextVar 运行时覆盖存在 | 直接返回覆盖实例,不读盘 | app_config.py:368 |
被 set_app_config() 注入自定义实例 | 直接返回缓存,不读盘 | app_config.py:372 |
| 首次调用(缓存为空) | 加载并缓存 | app_config.py:378 |
| 解析路径改变 | 重新加载 | app_config.py:378 |
| 文件 mtime 改变 | 打印 info 日志后重新加载 | app_config.py:380 |
reload_app_config() | 强制重载并刷新缓存 | app_config.py:390 |
reset_app_config() | 清空缓存,下次重载 | app_config.py:406 |
关键环境变量
| 环境变量 | 作用 | Source |
|---|---|---|
DEER_FLOW_CONFIG_PATH | 指定 config.yaml 绝对路径(优先级 2) | app_config.py:126 |
DEER_FLOW_PROJECT_ROOT | 显式指定项目根目录(影响优先级 3) | runtime_paths.py:9 |
DEER_FLOW_HOME | 可写状态目录(默认 {project_root}/.deer-flow) | runtime_paths.py:19 |
DEER_FLOW_EXTENSIONS_CONFIG_PATH | 指定 extensions_config.json 路径 | extensions_config.py:100 |
Common Pitfalls / Tips
$VAR缺失会让主配置启动失败:AppConfig.resolve_env_variables()在变量未定义时直接抛ValueError(非空字符串回退),所以config.yaml里写了$OPENAI_API_KEY就必须在.env或环境中提供 app_config.py:283。而extensions_config.json的同名方法对缺失变量回退空串,行为不同,迁移配置时勿混淆 extensions_config.py:167。$前缀是硬规则:只有以$开头的字符串才被当作环境变量;值里嵌入$(如pa$$word)不会被解析,但纯$VAR形式不支持${VAR:-default}之类的 shell 语法 app_config.py:280。- 热加载只看 mtime,不看内容:
touch config.yaml即可触发重载,即便内容未变;反之,某些文件系统 mtime 精度低时连续快速编辑可能漏检,必要时调用reload_app_config()强制重载 app_config.py:378。 set_app_config()注入后热加载失效:_app_config_is_custom=True时get_app_config()永远返回注入实例,直到reset_app_config();测试 mock 后记得复位 app_config.py:420。extensions不在config.yaml:MCP / skills 配置由独立的extensions_config.json管理,改了它需要等其各自的 mtime 缓存失效或显式reload_extensions_config(),改config.yaml的 mtime 不会带动它 extensions_config.py:210。- 版本检测不阻断启动:
config_version落后只打 warning,不会拒绝启动;CI 或新增 schema 字段后,务必同步 bumpconfig.example.yaml的config_version并(必要时)在MIGRATIONS中补迁移规则,否则老用户字段静默缺失 scripts/config-upgrade.sh:73。 - 数据库默认值是隐式注入的:
config.yaml完全不写database段时会被注入backend=sqlite/sqlite_dir=.deer-flow/data,而DatabaseConfig自身字段默认却是backend=memory;两处默认值不同,排查持久化问题时以_apply_database_defaults为准 app_config.py:211。
References
- backend/packages/harness/deerflow/config/app_config.py — 聚合根、路径解析、版本检测、
$ENV、缓存与运行时覆盖 - backend/packages/harness/deerflow/config/init.py — 配置包公共 API
- backend/packages/harness/deerflow/config/runtime_paths.py — 项目根 / 运行时 home /
existing_project_file - backend/packages/harness/deerflow/config/extensions_config.py — MCP / skills 独立配置与其差异化 env 解析
- backend/packages/harness/deerflow/config/database_config.py — 统一数据库后端 schema
- backend/packages/harness/deerflow/config/checkpointer_config.py — 子配置单例三件套范式
- backend/packages/harness/deerflow/config/paths.py — 数据目录布局
- config.example.yaml — 配置模板与当前
config_version - scripts/config-upgrade.sh — 版本迁移脚本
Related Pages
| 页面 | 关系 |
|---|---|
| ./03-系统整体架构.md | 上游:配置系统在整体架构中的位置 |
| ./05-模型配置与Model工厂.md | 下钻:models 键与 ModelConfig 字段细节 |
| ./06-MCP与Skills扩展配置.md | 下钻:extensions_config.json / ExtensionsConfig 详解 |
| ./07-沙箱与工具配置.md | 下钻:sandbox / tools / tool_groups 键 |
| ./16-持久化与存储层.md | 下钻:database / checkpointer 如何驱动持久化层 |