配置写到哪里去了:路径常量与读取优先级

2026-08-10

用 CLI 工具最烦人的一类问题不是报错,而是「我明明配过了,怎么又让我配一遍」。DeepTutor 这类问题的答案基本都能在一个函数里找到:deeptutor/runtime/home.pyget_runtime_home()

下面所有行号与路径都对应我们采集的仓库快照:HEAD 456f9c2,采集日 2026-08-10,deeptutor/__version__.py 里写的版本号是 1.5.11。你自己 clone 之后行号可能已经漂了,但常量名与目录名是稳的,照着找即可。

一、优先级链的最后一级是「当前工作目录」

deeptutor/runtime/home.py:8 定义了 DEEPTUTOR_HOME_ENV = "DEEPTUTOR_HOME"。紧接着的 get_runtime_home()home.py:12-27)把优先级写死成三级:

  1. 显式传入的 home 参数
  2. DEEPTUTOR_HOME 环境变量
  3. 当前工作目录

第三级就是本篇最该讲透的那一处。它不是”找不到就报错”,也不是”回落到用户主目录下的某个固定位置”,而是回落到你敲命令时所在的那个目录。这意味着同一台机器、同一个 Python 环境,在 <你的项目目录A> 里敲一次、在 <你的项目目录B> 里再敲一次,得到的是两套互不相干的配置。需要说清楚的是,get_runtime_home() 在这条链上只做取值——按「显式参数 → 环境变量 → 当前工作目录」的顺序拿到一个 home 就返回,代码里没有跨目录的一致性检查这一步(home.py:12-27)。至于两套配置并存时程序实际会怎样、会不会在别处提示你,我们没有安装运行过这个项目,不做断言。这里能给你的只有一条可自查的线索:落点是由你敲命令时所在的目录决定的。

get_runtime_data_root() 在此基础上再拼一层:返回 <runtime-home>/datahome.py:30-33)。所以真正决定一切落点的只有 home 这一个值。

仓库根目录的 README.md 在「Get Started」里对四条安装路径给的是同一句话:设置位于启动目录下的 data/user/settings/,或由 DEEPTUTOR_HOME / deeptutor start --home 指定(README.md:206)。这句话和代码是对得上的——“启动目录”就是上面那条链的第三级。

二、从 home 往下:谁拼出了 settings 目录

拿到 data 根之后,路径计算收口在 PathService_workspace_root 就是 data 根,_user_data_dir = <workspace_root>/userdeeptutor/services/path_service.py:82-85)。往下再分两个取文件的方法(path_service.py:206-217):

方法落点后缀规则
get_settings_dir()<data>/user/settings
get_settings_file(name)<data>/user/settings/<name>名字里不含点则补 .json
get_runtime_config_file(name)<data>/user/settings/<name>不以 .yaml 结尾则补 .yaml

这张表的用处是让你能反推:看到代码里写 get_settings_file("model_catalog"),就知道它指的是 <data>/user/settings/model_catalog.json。模型目录的常量就是这么来的——CATALOG_PATH = get_path_service().get_settings_file("model_catalog")deeptutor/services/config/model_catalog.py:20)。

有一处落点不在 user/ 里面:CLI 的知识库根目录取的是 get_path_service().project_root / "data" / "knowledge_bases"deeptutor_cli/kb.py:28-31),注意它取的是 project_root 这个属性,而 settings 那一路走的是同一个 get_path_service() 的另一个属性(_workspace_root / _user_data_dir),两者的相对位置我们没有在卡上核实,这里不替它下结论。能确定的只有一点:知识库根路径的拼法里没有 settings,找配置和找知识库要往两个不同的属性去。

至于整棵目录树长什么样,init_user_directories() 的 docstring 直接画了出来(deeptutor/services/setup/init.py:109-141):data/user/ 下有 chat_history.dblogs/settings/{interface.json, main.yaml, agents.yaml},以及 workspace/ 下的 notebookmemoryco-writerbookchat/...

三、这些文件是被谁创建出来的

创建动作分两段。_ensure_essential_settings() 只显式管三个文件:interface.jsonmain.yamlagents.yaml;其余全部交给 ensure_runtime_settings_files()deeptutor/services/setup/init.py:151-172)。后者依次 ensure 的名字有八个——system、auth、integrations、mineru、pageindex、llamaindex、graphrag、lightrag——最后再调一次 get_model_catalog_service().load()deeptutor/services/config/runtime_settings.py:953-964:490-498)。

这里有一处两份清单对不上:CONTAINERIZATION.md:354-362 的运行时配置表列的是 7 个文件(system.jsonauth.jsonintegrations.jsonmodel_catalog.jsoninterface.jsonmain.yamlagents.yaml),而上面那条 ensure 链里出现的名字集合与之并不相同(多出 mineru、pageindex、llamaindex、graphrag、lightrag 五个名字)。两处不一致,以我们实读的仓库状态为准;这八个 ensure 项各自最终落成什么文件,我们没有逐个核实,也不替它补。

第二处差异在 CLI-only 模式上。CLI 包自带的那份 deeptutor_cli/README.md 第 34 行说 deeptutor init --cli「仍会创建 system.jsonauth.jsonintegrations.jsonmodel_catalog.jsonmain.yamlagents.yaml」;仓库根目录那份 README.md 的 Option 4 小节也是同一份六文件清单(README.md:388)。两处清单里都没有 interface.json,而 interface.jsondeeptutor/services/setup/init.py:151-152 里是被显式写出的三个文件之一。另外,init 的保存分成两句:非 cli_only 才执行 runtime.save_system(system)catalog_service.save(catalog) 则两种模式都执行(deeptutor_cli/init_cmd.py:528-530)。说完差异就停,我们不推断原因。

四、--home 与那三个被重置的单例

init 命令自己带 --home(Path,默认 None),签名与 --cli 并列(init_cmd.py:542-549);顶层的 start 也有 --home,help 写的是 Runtime workspace root(deeptutor_cli/main.py:117-129)。

真正值得看的是 run_init 开头做的三件事(init_cmd.py:412-418:24-48):先用 get_runtime_home(home) 解析出 runtime_home,DEEPTUTOR_HOME 环境变量设成这个值,然后重置三个单例——PathServiceRuntimeSettingsServiceModelCatalogService

run_init 开头这段重置逻辑的注释把理由写得很直白:这些单例缓存的是上一个 PathService 算出来的路径,不清就会「静默写错地方」(init_cmd.py:24-48)。

这是本篇第二处反直觉的地方,也是读这段代码真正的收获:路径不是每次用的时候现算的,而是被进程内的单例缓存住的。所以 home 这个值一旦在进程生命周期中途改变,缓存与新值就会分叉。init 里的这段重置是针对 --home 这个具体场景写的补偿;至于其它任何”运行到一半改环境变量”的用法会怎样,代码没给承诺,我们也不做推断。

一句话结论:DEEPTUTOR_HOME 要在进程启动之前定好,不要指望中途改。

五、容器里是另一套顺序

如果你走的是容器部署,读取优先级还得再看一层。镜像的 entrypoint 会先 unset 一长串运行时环境变量(BACKEND_PORT、FRONTEND_PORT、AUTH_ENABLED、POCKETBASE_* 等共 21 个 key),再从 JSON 重新导出,并 export DEEPTUTOR_IGNORE_PROCESS_ENV_OVERRIDES=1Dockerfile:326-354)。

也就是说,容器里以 JSON 配置为准,你在 docker run 上顺手加的同名环境变量会先被清掉。与之呼应的是 CONTAINERIZATION.md:377 的一句明确声明:项目根下的 .env 文件被有意忽略,不作为应用配置。别把在裸机上的那套心智模型直接搬进容器。

六、可复现的核查动作

想确认”配置到底落在哪儿”,按这个顺序查,每一步都能自己核:

  1. 先确认 home。 在你准备敲命令的那个目录里,看有没有 data/user/settings/ 这个子目录;再看当前 shell 里 DEEPTUTOR_HOME 是否已经有值。有值就走第二级,无值就是当前目录——顺序按 home.py:12-27
  2. 再确认落点。 对照上面那张后缀规则表,把代码里的 get_settings_file("x") 翻译成实际文件名,去 <data>/user/settings/ 下找同名文件。
  3. 比对两处清单。 把该目录下实际存在的文件名,和 CONTAINERIZATION.md:354-362 的 7 文件表逐个对,差在哪一目了然。
  4. 看一眼当前生效值。 deeptutor config show 会输出 ports(backend / frontend)、llm、embedding、search、language 与 tools 列表,且所有 api_key 一律输出 "***""(not set)"deeptutor_cli/config_cmd.py:68-92,掩码见 :41:54:84)。embedding 解析抛 ValueError 时它输出的是 {"status": "not_configured", ...}config_cmd.py:57-61),这一条本身就是判据。
  5. 什么情况说明不是路径问题。 如果换到别的目录、或显式指定同一个 --home 之后,config show 的输出没有任何变化,那问题就不在 home 解析这条链上,该往具体某个 settings 文件的内容或对应服务去查了。

需要在一次命令里同时定死 home 和模式时,Linux / macOS 侧写法形如:

DEEPTUTOR_HOME=<你的项目目录> deeptutor init --cli

Windows 侧不能照抄上面这行。这种「变量写在命令前面」的内联写法是 POSIX shell 的语法,PowerShell 与 cmd 都不认,得先设变量再敲命令,分两句:

$env:DEEPTUTOR_HOME = '<你的项目目录>'
deeptutor init --cli

两段是同一个意图的两种写法:都让 get_runtime_home() 走到第二级(环境变量)而不是第三级(当前工作目录)。差别在于 PowerShell 的 $env: 赋值会留在当前这个终端会话里,后面再敲的 deeptutor 命令都吃这个值,直到你关掉窗口;而 POSIX 的内联写法只对紧跟其后的那一条命令生效。你也可以两边都绕开环境变量,直接用命令自带的 --homeinit_cmd.py:542-549),这样跨平台是同一行。

以上为按仓库中的参数语义组合的示例,未经实测,以官方文档与 --help 的实际输出为准。

七、两条边界

一是敏感数据init 向导会采集 api_key(init_cmd.py:462-470),而 config show 对所有 api_key 做掩码(config_cmd.py:41)——这说明这类值确实在配置读取链路上。<data>/user/settings/ 因此是一个本机敏感目录,把它同步进版本库或网盘之前请自行评估。具体每个密钥最终写进哪个文件,我们没有逐个核实,不做等价。

二是它会碰你本机的其它东西。这个项目里还有会驱动本机 agent CLI、执行外部程序的部分(另有专门一篇讲),本篇只谈配置落盘这一件事,不代表整体的安全边界。

最后说一句不给建议的话:home 应该指到哪里、要不要多套 workspace 并行,取决于你自己的用法,项目没有给通用值,我们也不编一个。


本文依据 DeepTutor 官方仓库(github.com/HKUDS/DeepTutor)的 README、AGENTS.mdpyproject.tomldeeptutor/ 下的源码整理,核对日 2026-08-10,对应仓库快照 456f9c2(版本 1.5.11)。 本文内容为仓库源码与文档口径,我们没有安装、部署或运行过该项目,也没有调用过其中任何一个模型 API, 因此不涉及生成质量、响应速度与教学效果的任何描述。 参数与默认值随版本变动,请以仓库最新代码与 --help 的实际输出为准。 安全相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。