Skip to content

配置系统与 AppConfig

本章目标:

  1. 讲清 config.yaml 的四级路径解析优先级,以及 AppConfig.from_file() 从 YAML 到 Pydantic 模型的完整加载链路。
  2. 讲清 config_version 版本检测告警机制和 make config-upgrade 自动迁移脚本如何协同。
  3. 讲清 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:142get_app_config() 是带 mtime 自动热加载的缓存单例:文件被改动后无需重启进程即可生效 app_config.py:358config/ 目录下每个子配置模块(模型、沙箱、记忆、数据库等)各自定义一个 Pydantic 模型,作为字段聚合进 AppConfig;其中部分模块还维护进程级单例,加载时通过 load_*_config_from_dict() 回灌 app_config.py:185config_version 字段用于检测用户配置是否落后于 config.example.yaml,落后时打印告警提示运行 make config-upgrade

Overview

DeerFlow 是一个深度可配置的 super agent harness:模型、工具、沙箱、子代理、记忆、守卫、数据库后端等几乎每个子系统都暴露配置项。如果让每个子系统各自读文件、各自解析环境变量,会产生三个问题:配置文件位置不统一、$ENV 替换逻辑重复、配置 schema 演进后老用户无声地丢失新字段。

配置系统用三个设计决策解决这些问题:

  1. 单一聚合根 AppConfig:所有子配置都是它的一个 Pydantic 字段,一次解析、强类型校验、向下传递。config/__init__.py 只重导出少数高频入口(get_app_configget_paths 等),保持公共面收敛 config/init.py:1
  2. 集中式路径解析与 $ENV 解析:resolve_config_path() 统一四级优先级;resolve_env_variables() 递归把以 $ 开头的字符串替换成环境变量,敏感值(API key、数据库 URL)只放 .env app_config.py:269
  3. 版本检测 + 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:

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:

  1. 显式 config_path 参数(不存在则报错)。
  2. DEER_FLOW_CONFIG_PATH 环境变量(不存在则报错)。
  3. 项目根 config.yaml —— 经 existing_project_file(("config.yaml",))DEER_FLOW_PROJECT_ROOT 或 CWD 下查找 runtime_paths.py:34
  4. 旧版 monorepo 兜底:backend/config.yaml、仓库根 config.yaml,由 _legacy_config_candidates() 给出 app_config.py:52

from_file 加载管线

from_file() 串起完整加载链:解析路径 → yaml.safe_load(空文件回退 {})→ 版本检测 → $ENV 解析 → 数据库默认值填充 → 合并独立的 ExtensionsConfigmodel_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_PATHbackend/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:152load_dotenv() 在模块导入时执行,所以 .env 中的变量在解析前已进环境 app_config.py:34

缓存单例与 mtime 热加载

模块级缓存四元组 _app_config / _app_config_path / _app_config_mtime / _app_config_is_custom 加一对 ContextVar 运行时覆盖栈 app_config.py:330get_app_config() 决策顺序:先看 ContextVar 运行时覆盖 → 再看是否被 set_app_config() 注入的自定义实例 → 否则解析路径并取当前 mtime,当"未缓存 / 路径变了 / mtime 变了"任一成立就重新加载并刷新缓存 app_config.py:358reload_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() 调用在缓存失效(文件被改)时的完整时序。

加载步骤编号说明:

  1. 路径解析:四级优先级确定 config.yaml 绝对路径 app_config.py:111
  2. YAML 解析:yaml.safe_load,空文件回退 {} app_config.py:154
  3. 版本检测:在 env 解析前完成,确保用原始数据比对 config_version app_config.py:158
  4. $ENV 解析:递归替换全部 $VAR app_config.py:160
  5. 数据库默认值:database 段缺失时填充 backend=sqlitesqlite_dir=.deer-flow/data app_config.py:211
  6. 扩展配置合并:extensions 字段来自独立的 extensions_config.json app_config.py:168
  7. Pydantic 校验:model_validate 把 dict 转为强类型 AppConfig app_config.py:171
  8. 单例回灌:_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 模型定义modelsconfig/model_config.py
sandbox_config.py沙箱 provider 配置(必填)sandboxconfig/sandbox_config.py
tool_config.py工具与工具组tools / tool_groupsconfig/tool_config.py
tool_search_config.py延迟工具搜索/加载tool_searchconfig/tool_search_config.py
extensions_config.pyMCP 服务器 + skills 状态(独立 JSON)extensionsconfig/extensions_config.py
skills_config.pyskills 目录路径skillsconfig/skills_config.py
skill_evolution_config.py代理自管理技能演进skill_evolutionconfig/skill_evolution_config.py
memory_config.py长期记忆子系统(含单例)memoryconfig/memory_config.py
summarization_config.py上下文摘要(含单例)summarizationconfig/summarization_config.py
title_config.py自动标题生成(含单例)titleconfig/title_config.py
subagents_config.py子代理运行时(含单例)subagentsconfig/subagents_config.py
agents_api_config.py自定义代理管理 API(含单例)agents_apiconfig/agents_api_config.py
acp_config.pyACP 兼容代理(含单例)acp_agentsconfig/acp_config.py
guardrails_config.py守卫中间件(含单例)guardrailsconfig/guardrails_config.py
loop_detection_config.py循环检测中间件loop_detectionconfig/loop_detection_config.py
token_usage_config.pyToken 用量跟踪token_usageconfig/token_usage_config.py
database_config.py统一数据库后端databaseconfig/database_config.py
checkpointer_config.pyLangGraph checkpointer(含单例)checkpointerconfig/checkpointer_config.py
run_events_config.py运行事件存储run_eventsconfig/run_events_config.py
stream_bridge_config.pyStreamBridge(含单例)stream_bridgeconfig/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 参数FileNotFoundErrorapp_config.py:121
2DEER_FLOW_CONFIG_PATH 环境变量FileNotFoundErrorapp_config.py:126
3项目根 config.yaml(DEER_FLOW_PROJECT_ROOT 或 CWD)落到优先级 4app_config.py:132
4旧版 backend/config.yaml、仓库根 config.yamlFileNotFoundErrorapp_config.py:136

版本检测行为

场景config_version 处理行为Source
用户值缺失 / 非整数视为 0与示例比对app_config.py:231
用户版本 < 示例版本整数比较logger.warning 提示 make config-upgradeapp_config.py:261
找不到 config.example.yaml(向上 5 级)静默返回,不阻断app_config.py:247
make config-upgrade 执行写回示例版本文本 migration + 只补缺失键 + 备份 .bakscripts/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=Trueget_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 字段后,务必同步 bump config.example.yamlconfig_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

页面关系
./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 如何驱动持久化层

公司内部参考 · 由 claude-wiki-gen 基于源码自动生成的二次分析