单人跑通只是一半、teamai-cli 的团队配置分层决定了后面省不省事

文章导读
单人跑通只说明本机的命令、凭据和路径是对的;同事拉下同一份代码却报错、或行为不一致,差异通常不在代码里,而在配置被分成了几层、每层放了什么、谁覆盖了谁。要判断后面省不省事,可以先做一件很具体的事:把配置拆成共享层、团队层、个人层,再让两个人把当前生效配置按同一顺序导出后逐字段比对。能被比对出来的差异,才是能被修掉的差异。
📋 目录
  1. A 把两个人的生效配置按同一顺序导出并比对
  2. B 划分共享层:所有成员都必须一致的部分
  3. C 划分个人层:允许不同的部分写进本地覆盖
  4. D 处理同一字段被两层同时设置的情况
  5. E 把差异检查写进日常协作习惯
A A

单人跑通只说明本机的命令、凭据和路径是对的;同事拉下同一份代码却报错、或行为不一致,差异通常不在代码里,而在配置被分成了几层、每层放了什么、谁覆盖了谁。要判断后面省不省事,可以先做一件很具体的事:把配置拆成共享层、团队层、个人层,再让两个人把当前生效配置按同一顺序导出后逐字段比对。能被比对出来的差异,才是能被修掉的差异。

适用场景:多人共用一个 teamai-cli 仓库,出现“我这里能跑、他那边不行”。处理方向:模型来源与默认行为收敛到仓库内的共享层,组内差异放进团队层,密钥与本地路径只进个人层,并明确谁覆盖谁。验证方式:两人用同一条命令导出生效配置做文本 diff,再看日志确认最终值来自哪一层。风险边界:分层只能让配置差异可定位、可复现,不能保证模型输出完全一致;具体子命令与加载顺序以本机 `--help` 和实际日志为准。

把两个人的生效配置按同一顺序导出并比对

口头描述很容易漏,尤其当一方改的是环境变量、另一方改的是仓库外文件时。更稳的做法是约定一条导出命令,两人用同一版本、同一目录执行,输出到文件后再比对。下面命令里的子命令名,需要先按自己机器上的版本确认:

# 在仓库根目录执行,先记录版本和目录,避免把版本不同误当成配置差异
teamai-cli `--version`
pwd

# 导出当前生效配置(`--effective` 表示合并所有层之后的最终结果)
teamai-cli config show `--effective` `--format` json > /tmp/eff.$USER.json

# 若该版本没有 `--effective`,用 teamai-cli config `--help` 找等价选项

拿到两份文件后,先做遮蔽再比对,避免把密钥贴进 issue。逐字段列表可以用 jq 排序后 diff,比肉眼看更可靠:

# 遮蔽明显的密钥形态字段
sed -E 's/(sk-|key-)[A-Za-z0-9_-]+/***/g' /tmp/eff.$USER.json > /tmp/a.json

# 按 key 排序再 diff,消除键顺序造成的假差异
diff -u <(jq -S . /tmp/a.json) <(jq -S . /tmp/b.json)

# 没有 jq 时的替代:python3 -m json.tool `--sort-keys`

把 diff 结果分成三类看,方向就清楚了:

  • 同一层不同值——说明有人改了共享文件但没提交,或者两边拉到的不是同一个 commit。
  • 同一字段两层都有值——这是覆盖问题,去看加载顺序,不要反复改值。
  • 只在一侧出现——通常是文件缺失:个人覆盖文件没建、环境变量没导出,或字段里带了本机绝对路径。

划分共享层:所有成员都必须一致的部分

共享层的目标不是放最多的配置,而是放所有人必须一致、不一致就会出事的配置。建议先收敛下面这几类字段:

  • 模型来源与默认模型标识:供应商标识、模型别名、内网 endpoint 的 host 部分,不写个人 token;
  • 默认行为:默认 profile 名、超时秒数、重试次数、并发上限、输出格式;
  • 工具与能力白名单、日志级别基线;
  • 任何“改了就会让同事跑不通”的开关。

共享文件建议放在仓库内固定路径,随代码一起提交,例如 .teamai/config.toml(或团队约定的等价文件名)。不要放用户主目录,也不要靠口头同步。修改方式建议走一次评审:谁改、为什么改、影响哪些人,并附一份改动前后的 config show `--effective` diff,哪怕只是两行字段。

单人跑通只是一半、teamai-cli 的团队配置分层决定了后面省不省事
层级典型位置放什么谁维护是否提交
共享层仓库内 .teamai/config.toml模型来源、默认行为、超时重试、白名单仓库维护者提交
团队层仓库内 .teamai/team.<组名>.toml组内差异:专用 endpoint、内部模型别名、区域参数各组负责人提交(仅需要的组)
个人层~/.config/teamai/override.toml,或仓库内 .teamai/local.toml凭据来源、本地路径、个人偏好每个人自己不提交

划分个人层:允许不同的部分写进本地覆盖

个人层的判断标准很简单:这条配置换个人就该不一样,或者它本身是凭据,就放个人层。位置优先用用户级路径,这样不会和仓库的 git 状态纠缠:

# Linux/macOS
~/.config/teamai/override.toml
# Windows
%APPDATA%\teamai\override.toml

# 项目内个人覆盖(可选,同样不提交)
<仓库根>/.teamai/local.toml

覆盖写法只声明“我这一层要改什么”,不要整份复制共享文件,否则共享层以后新增字段你这边看不到:

# ~/.config/teamai/override.toml
[provider]
# 只写环境变量名,不写密钥本身
api_key_env = "TEAMAI_API_KEY"

[defaults]
model     = "my-local-alias"
timeout_s = 120

[paths]
cache_dir = "/home/me/.cache/teamai"

密钥本身走环境变量,配置文件里只出现变量名。忽略清单建议至少包含:.teamai/local.toml、.teamai/*.local.*、.env、*.key。提交前用 git status 和 git diff `--cached` 扫一遍,确认没有密钥形态的字符串。

处理同一字段被两层同时设置的情况

常见的约定是依次加载:内置默认值 → 共享层 → 团队层 → 个人层 → 环境变量 → 命令行参数,越靠后优先级越高。这个顺序需要用自己版本的 `--help` 或日志确认,不同版本可能不同,尤其是环境变量与命令行参数的相对位置。

验证优先级,最直接的办法是让工具告诉你这个值从哪来:

单人跑通只是一半、teamai-cli 的团队配置分层决定了后面省不省事
teamai-cli config explain defaults.model   # 期望输出:最终值 + 来源层
teamai-cli config get defaults.model `--source`

# 若该版本没有 explain/get,就用对照法:
TEAMAI_DEFAULTS__MODEL=xxx teamai-cli config show `--effective` | grep -i model

日志是第二个确认点。把日志级别调高再跑一次最小命令,查找加载的文件路径和覆盖记录:

TEAMAI_LOG=debug teamai-cli config show `--effective` 2>&1 | grep -iE "loaded|override|source"

改了却不生效,通常不是覆盖规则错了,而是这几个原因:改的文件不是工具真正读的那个(仓库内文件与用户目录文件混了);环境变量设成了空字符串,空值也算一次设置;键名大小写或下划线不一致,被当成两个不同的键。逐条排掉,比反复改值快。

把差异检查写进日常协作习惯

让差异检查变成习惯,后来的人遇到“我这边不行”时才能自助定位,而不是每次靠口头对配置。交接时建议过一遍这份检查项:

  • teamai-cli 版本是否一致,不一致先统一版本再谈配置;
  • 共享层文件是否已在仓库里,本地是否拉到对应 commit;
  • 个人覆盖文件是否创建,路径是否被工具真正加载;
  • 凭据走的是环境变量还是配置文件,变量名是否写对;
  • 执行命令时的当前目录是否在仓库根,相对路径配置依赖 cwd;
  • 是否已导出双方生效配置并做过 diff。

新成员首次运行,按这个顺序走一遍,出问题时也能给出可定位的信息:

  1. 安装与团队一致的版本,记录 teamai-cli `--version` 输出;
  2. 在仓库根执行 teamai-cli config show `--effective`,与仓库里的共享层文件对照,看是否有意外覆盖;
  3. 创建个人覆盖文件,密钥只填环境变量名,并导出对应变量;
  4. 跑一次不产生副作用的最小命令(例如 dry-run,或工具内置的 doctor/check 子命令)确认能读到配置;
  5. 若报错,先贴 `--version`、生效配置 diff,以及 debug 日志里与配置加载相关的几行,再讨论;
  6. 确认无误后,把这套步骤补进仓库的协作说明,避免下次重新摸索。

需要留一个边界意识:分层解决的是配置差异能不能被定位和复现,不解决模型输出完全一致。模型别名背后的实际版本、服务端行为、以及请求参数里的随机性,都可能让同样的配置产出不同结果。遇到行为不一致时,先确认配置层已对齐,再去看模型侧和服务侧,顺序不要颠倒。