配置写到哪里去了:路径常量与读取优先级
用 CLI 工具最烦人的一类问题不是报错,而是「我明明配过了,怎么又让我配一遍」。DeepTutor 这类问题的答案基本都能在一个函数里找到:deeptutor/runtime/home.py 的 get_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)把优先级写死成三级:
- 显式传入的
home参数 DEEPTUTOR_HOME环境变量- 当前工作目录
第三级就是本篇最该讲透的那一处。它不是”找不到就报错”,也不是”回落到用户主目录下的某个固定位置”,而是回落到你敲命令时所在的那个目录。这意味着同一台机器、同一个 Python 环境,在 <你的项目目录A> 里敲一次、在 <你的项目目录B> 里再敲一次,得到的是两套互不相干的配置。需要说清楚的是,get_runtime_home() 在这条链上只做取值——按「显式参数 → 环境变量 → 当前工作目录」的顺序拿到一个 home 就返回,代码里没有跨目录的一致性检查这一步(home.py:12-27)。至于两套配置并存时程序实际会怎样、会不会在别处提示你,我们没有安装运行过这个项目,不做断言。这里能给你的只有一条可自查的线索:落点是由你敲命令时所在的目录决定的。
get_runtime_data_root() 在此基础上再拼一层:返回 <runtime-home>/data(home.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>/user(deeptutor/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.db、logs/、settings/{interface.json, main.yaml, agents.yaml},以及 workspace/ 下的 notebook、memory、co-writer、book、chat/...。
三、这些文件是被谁创建出来的
创建动作分两段。_ensure_essential_settings() 只显式管三个文件:interface.json、main.yaml、agents.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.json、auth.json、integrations.json、model_catalog.json、interface.json、main.yaml、agents.yaml),而上面那条 ensure 链里出现的名字集合与之并不相同(多出 mineru、pageindex、llamaindex、graphrag、lightrag 五个名字)。两处不一致,以我们实读的仓库状态为准;这八个 ensure 项各自最终落成什么文件,我们没有逐个核实,也不替它补。
第二处差异在 CLI-only 模式上。CLI 包自带的那份 deeptutor_cli/README.md 第 34 行说 deeptutor init --cli「仍会创建 system.json、auth.json、integrations.json、model_catalog.json、main.yaml 和 agents.yaml」;仓库根目录那份 README.md 的 Option 4 小节也是同一份六文件清单(README.md:388)。两处清单里都没有 interface.json,而 interface.json 在 deeptutor/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 环境变量设成这个值,然后重置三个单例——PathService、RuntimeSettingsService、ModelCatalogService。
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=1(Dockerfile:326-354)。
也就是说,容器里以 JSON 配置为准,你在 docker run 上顺手加的同名环境变量会先被清掉。与之呼应的是 CONTAINERIZATION.md:377 的一句明确声明:项目根下的 .env 文件被有意忽略,不作为应用配置。别把在裸机上的那套心智模型直接搬进容器。
六、可复现的核查动作
想确认”配置到底落在哪儿”,按这个顺序查,每一步都能自己核:
- 先确认 home。 在你准备敲命令的那个目录里,看有没有
data/user/settings/这个子目录;再看当前 shell 里DEEPTUTOR_HOME是否已经有值。有值就走第二级,无值就是当前目录——顺序按home.py:12-27。 - 再确认落点。 对照上面那张后缀规则表,把代码里的
get_settings_file("x")翻译成实际文件名,去<data>/user/settings/下找同名文件。 - 比对两处清单。 把该目录下实际存在的文件名,和
CONTAINERIZATION.md:354-362的 7 文件表逐个对,差在哪一目了然。 - 看一眼当前生效值。
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),这一条本身就是判据。 - 什么情况说明不是路径问题。 如果换到别的目录、或显式指定同一个
--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 的内联写法只对紧跟其后的那一条命令生效。你也可以两边都绕开环境变量,直接用命令自带的 --home(init_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.md、
pyproject.toml 与 deeptutor/ 下的源码整理,核对日 2026-08-10,对应仓库快照 456f9c2(版本 1.5.11)。
本文内容为仓库源码与文档口径,我们没有安装、部署或运行过该项目,也没有调用过其中任何一个模型 API,
因此不涉及生成质量、响应速度与教学效果的任何描述。
参数与默认值随版本变动,请以仓库最新代码与 --help 的实际输出为准。
安全相关做法请结合自身环境评估,本文不构成安全方案建议。