首页
/ LiteLLM 的 Claude Code 兼容性矩阵每日自动刷新:基于 GCP VM + systemd 的 cron 流水线全解

LiteLLM 的 Claude Code 兼容性矩阵每日自动刷新:基于 GCP VM + systemd 的 cron 流水线全解

2026-09-07 23:50:10作者:牧宁李

本文围绕 LiteLLM 仓库内 tests/e2e/claude_code/cron_vm/ 一套完整的"兼容性矩阵定时填充器(compatibility-matrix populator)"展开:它不跑在 GitHub Actions 上,而是每天在专有 GCP VM litellm-compatibility-matrix-populator 上,把最新稳定版 LiteLLM 网关与 Claude Code CLI 组合起来,跑一遍横跨 Anthropic、Bedrock、Vertex AI、Azure/Foundry、OpenAI 等云后端的 e2e 兼容性矩阵,再自动把结果以 PR 形式发布到 litellm-docs 文档仓库。读者读完将掌握这套生产级 cron 的完整设计(目录职责、9 步执行流程、回归门控自动合并、systemd 单元与安全加固、一次性搭建与日常运维排障),并可迁移复用到自己的"每日自动化测试 + 自动发 PR"场景。

一、为什么用一台常驻 VM 而不是 GitHub Actions

兼容性矩阵的产出物是一张"Claude Code 各功能 × 各云后端"的通过/失败表(compatibility-matrix.json),每天刷新一次。项目最初也想用 GitHub Actions cron,但最终选择了一台常驻 GCP VM(名为 litellm-compatibility-matrix-populator)来承载每日任务,权衡如下:

  • 真实 VM 便于 gh auth login:任务需要把测试结果以 PR 形式提交到 BerriAI/litellm-docs 文档仓库。在 VM 上可以直接用一个已是该文档仓库 collaborator 的账号登录 GitHub CLI,而不必去申请一个带 pull-requests: write 权限的 GitHub App 凭据。
  • 持久状态复用:VM 上长期保留一个工作树 ~/litellm-cron-worktree/ 及其 .venv。每天运行时只需做一次快速的 git checkout 换 tag + 增量式 uv sync,而不是每次从零 git clone + 冷启动装依赖。
  • 零 Docker 依赖:代理不需要容器化,直接用 uv run litellm 以子进程方式拉起即可。
  • 代价一:VM 必须开机。虽然 systemd 定时器的 Persistent=true 能在短暂停机后补跑,但连续多天宕机会让矩阵过期,直到 VM 恢复。
  • 代价二:云厂商凭据放在 VM 文件系统/etc/litellm-compat-matrix.env)而非 GitHub Secrets。因此应把该 VM 视为与 CI runner 爆炸半径相当的可信环境来治理。

需要留意目录的迁移历史:cron_vm/ 原本位于 tests/claude_code/cron_vm/(与独立的 tests/claude_code/ 套件配对),如今改为运行由仓库维护的 tests/e2e/claude_code/ 套件,pytest 环境接口也随之变化:runner 导出的环境变量从 LITELLM_PROXY_BASE_URL / LITELLM_PROXY_API_KEY 改为 LITELLM_PROXY_URL / LITELLM_MASTER_KEY;azure 列读取 AZURE_AI_API_KEY / AZURE_AI_API_BASE(旧名为 AZURE_FOUNDRY_*);GPT 列需要 OPENAI_API_KEYAZURE_API_BASE / AZURE_API_KEY——完整清单见 litellm-compat-matrix.env.example

二、目录布局:6 个文件各司其职

cron_vm/ 目录极简,职责划分非常清晰,README 用一张表说明:

文件 用途
run_daily.sh 真正的 cron 任务本体:解析版本、更新工作树、启动代理、运行 pytest、生成 JSON、打开/更新文档 PR、清扫过期的 compat-matrix PR。
build_matrix.py 一个精简的 Python CLI,封装 claude_code.matrix_builder.build_from_paths。仅因 bash 脚本需要某种方式做逐格聚合,而该构建器本来就是 Python 实现。
check_regressions.py 精简的 Python CLI,封装 claude_code.matrix_builder.find_regressions。把刚构建的矩阵与当前已发布矩阵做 diff,若出现任何单元格绿→红翻转则以退出码 3 结束,从而门控自动合并。
litellm-compat-matrix.service systemd oneshot 服务单元,负责调用 run_daily.sh
litellm-compat-matrix.timer OnCalendar=*-*-* 06:00:00 UTCPersistent=true 的定时器。
litellm-compat-matrix.env.example /etc/litellm-compat-matrix.env 的模板。

两个 Python CLI 的哲学一致:真正的逻辑都在套件侧的 matrix_builder.py(见 matrix_builder.py),CLI 只做参数解析与 I/O,并把结果映射成 bash 可以分支判断的退出码。例如 check_regressions.py 约定:0 表示无绿→红回归(可安全自动合并)、3 表示发现一个或多个绿→红回归(需人工评审)、2 为 argparse 用法错误;且 --old 文件允许不存在——首次发布没有基线可对比时直接退出 0,视为"无回归"。

由于套件内部模块是以 tests/e2e/ 作为 sys.path 根来解析的(tests/e2e/ 没有 __init__.py,而 claude_code/ 有),这两个 CLI 都在导入前通过 sys.path.insert(0, parents[2]) 自举同一个根路径(见 build_matrix.py)。

三、run_daily.sh 的 9 步核心流水线

run_daily.sh 是整条流水线的引擎(脚本以 set -Eeuo pipefail 运行,任何命令缺失都会提前报错)。其默认环境变量均可用同名环境变量覆盖,生产接线见 systemd 单元。以下是脚本注释与实现中完整保留的 9 个环节:

1. 解析最新 LiteLLM 正式版 tag

通过 GitHub Releases API(curl | jq,翻页获取)解析最新的最终版 tag(形如 vX.Y.Z,跳过 -rc.N / -dev.N 预发布)。这里的实现细节值得一提(见 run_daily.sh):

  • LiteLLM 已从旧的 vX.Y.Z-stable 命名迁移到 PEP 440 风格:正式版是裸 vX.Y.Z tag,预发布带 -rc.N/-dev.N 段(旧 -stable/-stable.patch.N 已冻结在 v1.83.x)。
  • 用正则 ^v[0-9]+\.[0-9]+\.[0-9]+$ 过滤,并用数值化的 sort_by(capture(...)) 保证 1.10 > 1.9
  • Releases API 默认每页 30 条,而 LiteLLM 每天可能发多个预发布,因此脚本逐页拉取并封顶 5 页(500 条 release),只要某一页已出现正式版 tag 就提前停。

2. 读取本机 Claude Code CLI 版本

通过 claude --version 读取。cron 不会自动升级 CLI——运营人员想测试新版本 CLI 时,需在带外手动执行 npm install -g @anthropic-ai/claude-code@latest

3. 把持久工作树更新到目标 tag,并"重塑"测试套件

工作树位于 ~/litellm-cron-worktree/,更新序列为 git fetch --tags --forcegit reset --hardgit clean -fdx -e .venv -e .uv-bin -e .uv-pythongit checkout --force <tag>。保留 .venv 是为了让随后的 uv sync --frozen 变成增量同步。随后脚本会重塑测试套件:工作树里的 tests/e2e/ 是从开发检出(dev checkout)重新拷入的——只复制 claude_code/ 套件以及它 import 的五个共享传输辅助文件(proxy_client.pye2e_http.pymodels.pye2e_config.pytransport.py),这样 cron 永远是用今天的测试去测最新的稳定代理

为什么不能直接用该 tag 自带的 tests/e2e/?因为 tag 自带的是完整 EKS e2e 测试框架,其顶层 conftest.py import 了 e2e_dblifecycleotel_client 等稳定版 venv 未安装的模块;整树拷入会让 pytest 收集阶段直接爆炸。因此重塑是一个"先 rm -rf tests/e2e/、再精确拷入 claude_code 套件 + 五个共享 helper"的过程(见 run_daily.sh)。

同一环节还处理了 uv 版本钉死:LiteLLM 的 pyproject.toml[tool.uv] required-version 字段钉死了确切 uv 版本,系统 uv 若版本不符会拒绝 sync。脚本用 awk 解析出该字段,把对应版本 uv 下载并缓存到工作树内 .uv-bin/,且先校验 Astral 官方 .sha256 摘要再解压执行(见 run_daily.sh),封堵了"把远端二进制直接管道进 tar"的供应链信任缺口。最后执行:

uv sync --frozen --group proxy-dev --extra proxy --python 3.12

其中 --extra proxy 拉入 fastapi/uvicorn 让 uv run litellm 真正能提供 HTTP 服务,--group proxy-dev 带来 pytest 等套件依赖,--python 把 venv 钉到 Python 3.12。

4. 后台拉起代理并等待就绪

代理以 setsid 后台进程运行在 4100 端口(故意避开开发者常用的 :4000,防止 SSH 进同一台 VM 的开发者代理与 cron 冲突),随后轮询 http://127.0.0.1:4100/health/liveliness 直至就绪(45 次 × 2 秒上限)。

两个安全/工程细节值得注意(见 run_daily.sh):

  • 代理只绑定 loopback--host 127.0.0.1)。因为只有本机 pytest 会访问它,而默认 LITELLM_MASTER_KEY=sk-cron-matrix 是固定可预测值,若不绑 loopback,任何能触达该 VM 端口的外部方都能通过默认 key 认证并烧掉上游云厂商凭据。
  • setsid 让代理处于独立会话/进程组,并把 pgid 写入 ${WORKDIR}/proxy.pid;退出时 cleanup() 能以负 pid 形式对整个进程组发 SIGTERM,必要时再用 pgrep 兜底 SIGKILL 掉任何仍在监听该端口的残留进程、用 ss -K 清端口、最后删除临时目录。trap cleanup EXIT INT TERM 保证正常退出、收到信号、甚至部分失败(代理已起但 pid 文件陈旧)时都能收敛。

5. 运行 pytest,失败变成 fail 单元格而非脚本错误

代理就绪后,在 tests/e2e/claude_code/ 上运行 pytest,注入以下环境:

  • LITELLM_PROXY_URL=http://127.0.0.1:${PROXY_PORT}
  • LITELLM_MASTER_KEY=sk-cron-matrix
  • COMPAT_RESULTS_PATH=${WORKDIR}/compat-results.json(conftest 钩子据此写出每个测试的结果工件)

测试的退出码有特殊语义:0 = 全绿,1 = 存在测试失败(映射成 fail 单元格,不算脚本错误);而 >=2 表示中断/内部错误/无测试收集,属于"部分运行",其缺失单元格会被发布成 not_tested,因此脚本对 PYTEST_EXIT >= 2 直接 die,拒绝发布不完整的矩阵。调试时可用 PYTEST_K 环境变量把运行收敛到某个单元格(例如 basic_messaging_non_streaming and anthropic),测试参数中还会 --ignore-glob=*_unit_tests* 跳过套件内的单元测试目录(见 run_daily.sh)。

6. 构建 compatibility-matrix.json

把 pytest 结果工件 + manifest 交给 build_matrix.py

uv run python build_matrix.py \
  --manifest tests/e2e/claude_code/manifest.yaml \
  --results compat-results.json \
  --output compatibility-matrix.json \
  --litellm-version "${LITELLM_VERSION}" \
  --claude-code-version "${CLAUDE_CODE_VERSION}"

CLI 在内部自动盖上 generated_at(UTC 时间戳),并调用 matrix_builder.pybuild_from_paths 完成纯函数式的矩阵构建。该模块被刻意设计为无网络/子进程/文件系统副作用,方便 golden-file 单测直接喂内存输入。

7. 打开或更新 litellm-docs 文档仓库的 PR

  • gh repo cloneBerriAI/litellm-docs 浅克隆进临时目录;
  • 在确定性分支名上工作:compat-matrix/<litellm版本>-<claude-code版本>-<UTC日期>
  • --force 直接把分支推送到 BerriAI/litellm-docs 本体(不是 fork)——因为 mateo-berri 机器人的 token 在该仓库有写权限,属于同仓库分支 PR;
  • gh pr create 创建 PR。若当天重跑,则快进同一分支,gh pr create 遇到 "a pull request for branch ... already exists" 视为成功(不重复开 PR);若矩阵 JSON 与文档分支字节级一致,则直接跳过推送与 PR。

PR 标题格式为 chore(compat-matrix): refresh for <litellm> + claude-code <claude>,正文包含 litellm_version / claude_code_version / generated_at 字段表以及按 feature 生成的逐格状态摘要(jq 从 JSON 现场渲染,便于评审者快速 triage)。此类机器人 PR 不再需要第二位人类评审,是否合入完全由下一步的回归门控决定。

8. 用回归检查门控自动合并

自动合并开启前,check_regressions.py 把新矩阵与当前 main 上已发布的矩阵做 diff(发布前先 cp 快照旧矩阵到临时路径,见 run_daily.sh)。仅当没有任何单元格发生绿→红翻转时才允许 gh pr merge --auto --squash

  • 允许的状态迁移:红→绿、绿→绿、红→红;
  • 预先存在的红格(例如某供应商 API 额度耗尽)属于红→红,不阻塞每日 PR 正常流动;
  • 只有 passfail 的翻转才构成回归。

当检测到回归时,PR 依然会被打开/更新(正文顶部追加 > [!WARNING] 横幅并点名违规单元格),但自动合并保持关闭——且若当天较早一次干净运行曾开启过自动合并,会被显式关闭并通过 gh pr view --json autoMergeRequest 回读确认,确保"带回归的矩阵绝不处于可自动合并状态"。

门控采用 fail-closed 策略:如果检查器自身出错(非 0 非 3 的退出码),同样扣留自动合并,防止门控的 bug 静默放行一次回归。由于 BerriAI/litellm-docs 仓库在 repo 层面禁用了 merge-commit 与 rebase、只允许 squash,AUTO_MERGE_METHOD 默认值即 squash

9. 清扫过期的 compat-matrix PR

今天的分支/PR 就绪后,脚本会列出文档仓库所有仍打开且 head 分支以 compat-matrix/ 开头的 PR,把除今天之外的全部关闭(并删除机器人所属分支),保证任意时刻最多只有一个 compat-matrix PR 处于打开状态——即最新的那个。这是非破坏性的:PR 记录与其回归报告仍可浏览。此步骤只在今天的 PR 已存在后执行(前面任何 die 都会跳过它),因此发布失败永远不会把队列清到零;清扫本身失败(限流等)也仅记 WARN,留给下次重试。

四、回归门控与单元格语义的底层原理

要理解门控为何"只盯绿→红",需要先看懂矩阵单元格是如何产生的:

单元格状态机:每个 (feature, provider) 单元格的状态取值于 {pass, fail, not_applicable, not_tested}(定义于 matrix_builder.py)。没有测试跑到、且未显式声明 not_applicable 的空单元格会被填成 not_tested

"三个模型档位全部通过"才算绿:矩阵的每个单元格实际由三次 Claude 模型调用支撑(Haiku/Sonnet/Opus 三档)。conftest 提供 compat_result fixture,测试用 .set({"status": "pass"}) 上报单结果;多模型并发测试用 .add(...) 逐档上报,让每档模型各占结果工件中的一行。聚合时若多档结果并存,单元格只有全档通过才为 pass,否则为 fail 并在 error 中浮出首个出问题的档位(见 matrix_builder.pyconftest.py 的 docstring)。因此"任一档位在某个新版本代理下坏掉"都会立刻把绿格染红,这正是门控要拦截的信号。

行列如何推断:(feature, provider) 由测试文件路径推断——父目录名即 feature_id(与 manifest.yaml 对齐),test_ 前缀之后的文件主干即 provider id(见 conftest.py)。这样避免了容易漂移的逐文件元数据。

矩阵的当前形状:manifest(见 manifest.yaml)声明了 9 个 provider 列(anthropic、bedrock_invoke、bedrock_converse、vertex_ai、azure、openai、azure_openai、bedrock_mantle、vertex_ai_gpt)与 16 个 feature 行(basic_messaging_non_streaming / _streaming、tool_use、prompt_caching_5m、vision、thinking、tool_use_streaming、thinking_with_tool_use、pdf_input、prompt_caching_1h、web_search、structured_outputs、count_tokens、tool_search、passthrough、long_context_1m)。其中 vertex_ai_gpt 是静态 not_applicable 列:GCP 不提供闭源 GPT-5.6 系列(Model Garden 只有开源的 gpt-oss),该列把这一空白显式记录下来而非标红。发布物结构可参考仓库内的 sample_compatibility-matrix.json(含 schema_versiongenerated_atlitellm_versionclaude_code_versionprovidersfeatures 等字段)。

代理的别名层:测试只认识别名(如 claude-haiku-4-5-bedrock-invoke),真正把别名映射到各家云上游 model id / region / 凭据的是 LiteLLM 代理层的 test_config.yaml。因此"新增 (feature, provider) 单元格"是测试仓库内的三步改动(manifest + 测试文件 + 别名),而"更换某个单元格所打的上游模型"在这里改一处即可、无需动测试。代理配置还开启了 forward_client_headers_to_llm_api: true,把 Claude Code 发送的供应商专属 header(如 anthropic-beta)原样转发到上游,确保被测的线缆形状与真实客户一致。

五、systemd 单元:定时器 + oneshot 服务

定时器 litellm-compat-matrix.timer

OnCalendar=*-*-* 06:00:00 UTC
Persistent=true
RandomizedDelaySec=10min
Unit=litellm-compat-matrix.service
  • 每天 06:00 UTC 触发,选这个时刻是为了让美欧时区的运营人员在一天工作开始时看到新鲜 PR(与原 GitHub Actions cron 时间一致)。
  • Persistent=true 是每日任务想要的性质:VM 关机/挂起错过的运行,会在下次定时器启动时补跑,而不是再等 24 小时。
  • RandomizedDelaySec=10min 把触发抖动 10 分钟,若将来有多条类似流水线共置同一 VM,可分散负载。

服务单元 litellm-compat-matrix.service

服务是 Type=oneshot,因为定时器语义要表达"每天跑一次、跑完即退",没有需要常驻守护的进程(见 litellm-compat-matrix.service):

[Service]
Type=oneshot
User=mateo
Group=mateo
EnvironmentFile=-/etc/litellm-compat-matrix.env
LoadCredential=github-token:/etc/litellm-compat-matrix-github-token
Environment=PATH=/home/mateo/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Environment=HOME=/home/mateo
WorkingDirectory=/home/mateo/litellm/litellm
ExecStart=/home/mateo/litellm/litellm/tests/e2e/claude_code/cron_vm/run_daily.sh
TimeoutStartSec=90min
Restart=no

关键设计:

  • 用户与路径:以 mateo 运行,路径硬编码为 /home/mateo 而非 systemd 的 %h 说明符。原因写在了单元注释里:系统单元中 %h 是按 PID 1(manager)的家目录即 /root 展开的,而不是按 User= 指令展开,这会导致 ReadWritePaths 指向不存在的 /root/.cache,命名空间设置直接以 status=226/NAMESPACE 失败。同时显式补 PATH(uvclaude 装在 mateo~/.local/bin 下)与 HOME。
  • 凭据分离:provider 凭据与 gh/PROXY_PORT 覆盖放在 EnvironmentFile=-/etc/litellm-compat-matrix.env- 前缀表示文件缺失时服务仍可启动,但没有 provider 凭据会在首个请求处失败);而发布用 PAT 通过 LoadCredential=github-token:/etc/litellm-compat-matrix-github-token 映射,故意不进 EnvironmentFile。原因见下一节的安全分析。
  • 超时与重试TimeoutStartSec=90min 为冷启动留足余量(冷运行要 clone + 对锁文件做一次全新 uv sync,2 vCPU 的 VM 上可能耗时数分钟,之后还要跑全 feature × provider 网格打多家云);Restart=no 是因为失败不该立即自动重试,下一次定时器触发才是正确的重试节奏,且同日重跑是幂等的。
  • 安全加固NoNewPrivileges=trueProtectSystem=strictProtectHome=read-onlyPrivateTmp=trueReadWritePaths 白名单只放工作树 ~/litellm-cron-worktree、uv 轮子缓存 ~/.cache、claude CLI 会话状态 ~/.claude、gh 主机配置 ~/.config/gh/tmp——流水线其余只读消费 litellm 检出与 env 文件。

六、一次性 VM 环境搭建

以下步骤在 cron VM 上以 mateo 用户执行(命令来自 cron_vm 目录 配套文档,外部下载地址以占位符示意,实际安装时替换为对应官方安装入口):

# 1. 工具链:git、node/npm(claude CLI 依赖)、jq、curl、uv、gh
sudo apt-get update
sudo apt-get install -y git nodejs npm jq curl
curl -LsSf <uv 官方安装脚本> | sh
sudo apt-get install -y gh

# 2. Claude Code CLI(cron 不会自动升级它;想测试新版 CLI 时在带外重跑此行)
sudo npm install -g @anthropic-ai/claude-code@latest

# 3. LiteLLM 检出。它被 systemd 的 WorkingDirectory 使用,同时是
#    .service / .timer 文件的来源。cron 本体则跑在独立的
#    ~/litellm-cron-worktree/ 工作树里。
mkdir -p ~/litellm
git clone <litellm 仓库地址> ~/litellm/litellm
git -C ~/litellm/litellm checkout litellm_internal_staging

# 4. gh 认证——该账号必须是 BerriAI/litellm-docs 的 collaborator
gh auth login   # 按提示操作;选 HTTPS + token 粘贴流程

# 5. 各家云厂商凭据 + 发布 token
sudo cp ~/litellm/litellm/tests/e2e/claude_code/cron_vm/litellm-compat-matrix.env.example \
        /etc/litellm-compat-matrix.env
sudoedit /etc/litellm-compat-matrix.env   # 填入真实值
sudo chmod 0600 /etc/litellm-compat-matrix.env
# mateo-berri 的 PAT 单独成文件,通过 systemd LoadCredential 映射进服务,
# 让它不进测试进程的环境(原因见 env.example 注释)。
sudo install -m 0600 /dev/null /etc/litellm-compat-matrix-github-token
sudoedit /etc/litellm-compat-matrix-github-token   # 单行内容:PAT

# 6. systemd 单元
sudo cp ~/litellm/litellm/tests/e2e/claude_code/cron_vm/litellm-compat-matrix.service /etc/systemd/system/
sudo cp ~/litellm/litellm/tests/e2e/claude_code/cron_vm/litellm-compat-matrix.timer   /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now litellm-compat-matrix.timer

环境文件需要填哪些凭据

litellm-compat-matrix.env.example 完整定义了各 provider 列对应的凭据变量:

变量 用途 说明
ANTHROPIC_API_KEY anthropic 列 Anthropic API 直连
AWS_BEARER_TOKEN_BEDROCK bedrock_invoke / bedrock_converse 列 用 Anthropic 的 Bedrock API-key passthrough(长期 bearer token),矩阵不需要 AWS_ACCESS_KEY_ID/SECRET——LiteLLM 的 invoke 与 converse 路由都会读取该变量
AWS_REGION_NAME Bedrock 默认 us-east-1
VERTEXAI_PROJECT vertex_ai / vertex_ai_gpt 列 GCP VM 上用默认服务账号的 ADC(metadata server),无需 JSON key;若在 GCP 外运行需额外导出 GOOGLE_APPLICATION_CREDENTIALS
VERTEXAI_LOCATION Vertex AI 默认 global
AZURE_AI_API_KEY / AZURE_AI_API_BASE azure 列 Azure AI Foundry 上的 Claude 部署
OPENAI_API_KEY openai GPT 列 OpenAI 直连
AZURE_API_BASE / AZURE_API_KEY azure_openai GPT 列 Azure OpenAI 部署

两个可选开关控制"按需开启"的列:COMPAT_MANTLE_CELLS=1 开启 bedrock_mantle 列(需要 AWS 账号启用 Bedrock 上的 Mantle/OpenAI-on-Bedrock 模型,否则该列记 not_tested 而非 fail);COMPAT_OPENAI_GPT_CELLS=1 开启 openai 列(并发 stage 套件下该列常触 CLI 超时,而串行 cron 通常能跑通)。可覆盖项 PROXY_PORTLITELLM_WORKTREEDOCS_REPODOCS_BRANCHDOCS_TARGET_PATH(默认 src/data/compatibility-matrix.json)、AUTO_MERGE_METHOD 均有合理默认值。

七、凭据的供应链安全:为什么 PAT 用 LoadCredential 而非环境变量

这套方案最微妙的安全设计在于发布 token 的传递方式。仓库注释解释了完整动机:

套件里有多个单元格会让"模型驱动的 claude CLI"以该用户身份读取任意文件;脚本、pytest、代理的 /proc/<pid>/environ 会把环境变量携带的 token 暴露给任何同 UID 的读取者。

因此 PAT 绝不放进 /etc/litellm-compat-matrix.env(该文件内容会落入 pytest、代理、claude CLI 的进程环境)。正确做法是:

  1. 把 PAT 单独存为 /etc/litellm-compat-matrix-github-tokenchmod 0600、单行);
  2. 服务单元用 LoadCredential=github-token:/etc/litellm-compat-matrix-github-token 映射;
  3. run_daily.sh 在启动时读取 ${CREDENTIALS_DIRECTORY}/github-token(若系统提供了凭据目录),保存为未导出的 shell 变量,每次调用按需传参(GH_TOKEN=... 前缀、curl 的 Authorization 头、push URL),永不进入任何子进程的环境
  4. 手动运行时可导出 GITHUB_TOKEN,或干脆用 SKIP_PUBLISH=1 跳过发布。

PAT 所需权限:对 BerriAI/litellm-docs 的写权限——classic repo scope(另有 workflow),或 fine-grained 的 Contents:RW + Pull requests:RW + Workflows:RW。它同时用于四个环节:(a) 解析最新稳定 release、(b) 推送每日矩阵分支、(c) 打开同仓库 PR、(d) 开启 squash 自动合并。

八、日常运维命令

流水线装好后,日常运维只需几条命令:

# 下次什么时候跑?
systemctl list-timers litellm-compat-matrix.timer

# 立即触发一次真实运行(会向 litellm-docs 发 PR)
sudo systemctl start litellm-compat-matrix.service

# 触发一次不开 PR 的运行(非常适合首次验证)
SKIP_PUBLISH=1 ~/litellm/litellm/tests/e2e/claude_code/cron_vm/run_daily.sh

# 调试时收敛到某一个单元格
SKIP_PUBLISH=1 PYTEST_K='basic_messaging_non_streaming and anthropic' \
  ~/litellm/litellm/tests/e2e/claude_code/cron_vm/run_daily.sh

# 跟踪最近一次运行
journalctl -u litellm-compat-matrix.service -f

# 查看较早的运行日志
journalctl -u litellm-compat-matrix.service --since '2 days ago'

# 暂时停用(例如排障期间)
sudo systemctl disable --now litellm-compat-matrix.timer

注意 SKIP_PUBLISH=1 模式不会发 PR,而是把矩阵 JSON 写到 ~/litellm/litellm/compatibility-matrix.json 供本地检查(见 run_daily.sh)。

九、运维 Gotchas 清单

README 末节列出的是踩坑后的经验沉淀,值得逐条保留:

  • venv 钉死 Python 3.12(CRON_PYTHON_VERSION:e2e 套件使用 PEP 695 type 别名语法,VM 系统 Python(3.11)无法解析。run_daily.sh 用 uv 拉取受管的 CPython 到 ~/litellm-cron-worktree/.uv-python/ 并据此同步 venv。版本提升后的首次运行是一次冷 venv 重建。
  • 代理端口是 4100 而非 4000:避免开发者 SSH 进同一台 VM 自带 :4000 代理时与 cron 冲突。必要时可用 env 文件里的 PROXY_PORT=... 覆盖。
  • uv sync --frozen 要求解析出的 tag 确实在 GitHub 上打标:若某次正式版发布了但 git tag 没推上去,git checkout 一步会失败——补推 tag 后重跑即可。
  • 发布 token 轮换是你自己的责任:cron 不会刷新 token。mateo-berri 的 PAT 若过期,运行会在 git push / gh pr create 处以 401 失败("Bad credentials"/"Authentication failed"),需新铸 PAT 更新该文件。
  • 升级 Claude Code CLI 后的首次运行风险最高:新版 CLI 若改变线上协议(wire format),矩阵运行可能产生系统性失败。CLI 升级后务必先用 SKIP_PUBLISH=1 跑一次,再让下一个定时任务自动触发。
  • 磁盘规划:工作树的 .venv 约 1.3 GB、.git 目录约 1 GB。VM 上至少预留 5 GB 空闲,否则 uv sync 会在运行中途失败并留下半装态的 venv。

十、可复用性总结

如果把 cron_vm/ 看作一个工程样板,它示范了一套相当完整的"无人值守每日发布"模式,五个设计点可迁移到其他项目:

  1. 版本双轨制:被测代理(LiteLLM,随每日最新稳定 tag 移动)与被测客户端(Claude Code CLI,人工带外升级)分开管理,只有前者自动化追新。
  2. 测试代码永远追新:用"开发检出的最新测试 + 持久工作树里打 tag 的稳定产品代码"这一组合,让每个新测试修复在合入当天即进入 cron,而不是等下一个 stable release。
  3. 结果工件化而非进程状态化:pytest 失败不等于流水线失败——逐测试结果落成 JSON 工件,再经纯函数构建器聚合成"语义化矩阵",把 CI 退出码从"对/错"升级为"绿/红/不适用/未测"四态网格。
  4. 发布走 PR + 门控自动合并:所有变更都留痕为可浏览的 PR,但干净时由机器人 squash 自动合入;回归时自动扣留合并并显式关闭既有的 auto-merge 请求,人类评审兜底。
  5. 凭据按敏感度分层:provider 凭据走可被测试进程继承的 EnvironmentFile,发布 token 走 systemd LoadCredential 且仅在需要时以参数形式传递,从架构上避免同 UID 进程从 /proc 窃取发布凭据。

对 LiteLLM 仓库而言,这套体系最终服务于一张公开的 Claude Code 兼容性表:它持续回答"当前稳定版 LiteLLM 网关对 Claude Code 各功能 × 各云后端的支持是否完好",并在任何格子在每日刷新中由绿转红的当天,就把带警告横幅的 PR 摆到评审者面前。相关代码均在 cron_vmclaude_code e2e 套件 目录下,可继续深入阅读源码与配套单测(如 matrix_builder 单测)做进一步验证。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
594
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
516
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388