单人跑通只说明本机的命令、凭据和路径是对的;同事拉下同一份代码却报错、或行为不一致,差异通常不在代码里,而在配置被分成了几层、每层放了什么、谁覆盖了谁。要判断后面省不省事,可以先做一件很具体的事:把配置拆成共享层、团队层、个人层,再让两个人把当前生效配置按同一顺序导出后逐字段比对。能被比对出来的差异,才是能被修掉的差异。
适用场景:多人共用一个 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/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 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。
新成员首次运行,按这个顺序走一遍,出问题时也能给出可定位的信息:
- 安装与团队一致的版本,记录
teamai-cli `--version`输出; - 在仓库根执行
teamai-cli config show `--effective`,与仓库里的共享层文件对照,看是否有意外覆盖; - 创建个人覆盖文件,密钥只填环境变量名,并导出对应变量;
- 跑一次不产生副作用的最小命令(例如 dry-run,或工具内置的 doctor/check 子命令)确认能读到配置;
- 若报错,先贴
`--version`、生效配置 diff,以及 debug 日志里与配置加载相关的几行,再讨论; - 确认无误后,把这套步骤补进仓库的协作说明,避免下次重新摸索。
需要留一个边界意识:分层解决的是配置差异能不能被定位和复现,不解决模型输出完全一致。模型别名背后的实际版本、服务端行为、以及请求参数里的随机性,都可能让同样的配置产出不同结果。遇到行为不一致时,先确认配置层已对齐,再去看模型侧和服务侧,顺序不要颠倒。