`huggingface_hub` 是 Hugging Face 生态体系底层的 Python 客户端。
`transformers`
`datasets`
`diffusers`
`sentence-transformers`
以及数十个其他库都依赖它与 Hub 通信。我们每少发布一个新版本,就意味着有一周的修复和新功能被积压在
`main` 分支上。
很长一段时间里,我们每 4 到 6 周发布一次。现在,我们通过一个 GitHub Actions 工作流实现每周发布。我们使用开源工具和开放权重模型构建了它,并在唯一需要判断力的环节保留了人工审核。本文中没有任何内容需要供应商合同、闭源模型或你无法自行运行的基础设施。这从一开始就是设计目标,因为我们希望其他维护者也能借鉴并适配这个工作流。
读完本文,你将掌握构建自己工作流所需的一切。
我们的起点
旧流程部分自动化,但大部分是手动的。
已在 CI 中实现的部分:
- 推送标签后自动发布到 PyPI。
- 在下游库中打开测试分支,并固定使用候选发布版本。
但每次仍需手动完成的部分:
- 创建发布分支、在 `__init__.py` 中更新版本号、提交、打标签、推送。
- 监控下游 CI 运行并分类处理失败情况。
- 通读自上次发布以来合并的所有 PR,并手动编写发布说明:按主题分组、附上上下文、语言风格不像 git 日志转储。
- 在候选发布期结束后,切出稳定版发布。
- 起草内部 Slack 公告和社交媒体帖子。
- 打开发布后 PR,将 `main` 分支版本号更新到下一个 `dev0`。
为新版本编写高质量的说明是最繁重的部分,需要汇总数十个涉及不同主题的 PR。技术上并不困难,但需要几小时的专注投入。再加上各种公告,一次小版本发布很容易变成分散在数天内的半天工作量。
两类工作
于是我们决定精简整个流程。审视上述清单,工作可分为两类。
有些步骤纯粹是机械性的,可以自动化:更新版本号、提交、打标签、推送、打开下游测试分支、打开发布后 PR。这些步骤不需要任何人思考,只需每次按正确顺序执行——这正是 CI 工作流所擅长的。
其余部分则截然不同。撰写发布说明、决定突出哪些内容、为人类读者构思公告措辞:这些都是脑力劳动。正是这种判断力,让发布手册多年来得以沿用。这正是 AI 的用武之地,它能在几秒钟内将空白页面变成扎实的初稿。但这也是我们必须谨慎的地方,因为一份看起来自信满满却暗藏细微错误的初稿,比根本没有初稿更糟糕。
设计原则:开放部件,人人可复用
当我们决定解决这个问题时,我们预先设定了一个约束条件:每一个活动部件都必须能让任何维护者自行运行。不能有我们无法替换的封闭模型藏在 API 后面,不能有专有的发布平台,也不能有秘密配方。
以下是整个技术栈:
| 组件 | 功能 |
|---|---|
| GitHub Actions | 编排整个发布流程 |
| OpenCode | 驱动模型的智能体运行时 |
| 一个开放权重的模型(目前来自 Z.ai 的 GLM-5.2) | 起草发布说明和 Slack 公告 |
| HF 推理提供商 | 提供模型服务 |
| PyPI 可信发布 | 发布软件包 |
第二个原则:模型起草,人类决策。语言模型擅长将几十个简短的 PR 标题转化为可读的发布说明。但它们并不擅长被盲目信任。因此,工作流程是人工监督的:模型进行初稿,确定性脚本检查其工作,然后人工审核和编辑,之后才能发布任何内容(下文详述)。
流水线概览
整个工作流程是一个单一文件 `.github/workflows/release.yml`,通过 Actions 界面手动触发。它只需要一个输入:
on:
workflow_dispatch:
inputs:
release_type:
type: choice
options:
- minor-prerelease
- minor-release
- patch-release
从那里开始,任务大致按以下顺序运行:
- 准备。计算下一个版本号,创建或复用发布分支,更新 `__version__`,提交,打标签,推送。
- 发布到 PyPI。构建并上传 `huggingface_hub`。同时,构建并上传 hf CLI 作为其独立的 PyPI 包。
- 发布说明。比较自上一个标签以来的提交范围,从 GitHub API 拉取 PR 元数据,并让模型起草结构化的变更日志(这里有一个最近的例子)。保存为 GitHub 发布草稿。
- 下游测试分支。对于候选发布版本,在 transformers、datasets、diffusers、sentence-transformers 中打开一个分支,固定使用该候选版本,这样它们的 CI 就能快速告诉我们是否破坏了某些功能。
- Slack 公告。阅读笔记,并以我们团队的风格撰写一份内部公告。
- 归档笔记。将原始的 AI 草稿和人工编辑后的版本并排上传到 Hugging Face Bucket。
- 发布后版本号递增。在稳定版发布后,在主分支上创建一个 PR,将版本号递增到下一个 dev0。
- 在已合并的 PR 上留言。在发布版本中包含的每个 PR 上留下一条“此功能已在 vX.Y.Z 中发布”的评论。
- 同步 CLI 文档。使用重新生成的 hf CLI 技能文档,向我们的技能仓库提交一个 PR。
- 向 Slack 报告。每一步都会以线程回复的形式发布其状态;最终任务会用 ✅ 或 ❌ 更新根消息。
剩余的人工步骤是审阅并发布草拟的发布说明,以及审阅并发布一条内部 Slack 消息。这两个步骤是我们希望有人工参与把关的环节。
信任但要验证:人工参与的核心
以下是每个人对 AI 生成的发布说明所担心的失败模式:模型悄悄地遗漏了一个 PR,或者凭空捏造了一个不属于本次发布的 PR。一份几乎正确但又不完全正确的变更日志比没有变更日志更糟糕,因为没有人会去重新核对它。
我们不指望生成的发布说明一次就能完整无误,而是通过确定性方法进行验证。在模型运行之前,一个 Python 脚本会检索属于本次发布的所有 PR,并将其存储为基准事实。
PR_NUMBER_PATTERN = re.compile(r"\(#(\d+)\)$")
pr_numbers = [
int(m.group(1))
for commit in commits_since_last_tag
if (m := PR_NUMBER_PATTERN.search(commit.title))
]
save_manifest(pr_numbers)
然后,模型根据这些 PR 起草发布说明。完成后,我们会将模型的输出与最初的 PR 列表进行核对:
expected = set(load_manifest())
found = extract_pr_refs(notes_md)
missing = expected - found
extra = found - expected
如果发现任何遗漏或多余的内容,我们不会直接失败,也不会发布错误的文件。我们会将差异信息反馈给智能体,并要求它精确地修复那些 PR 的相关内容:
for _ in range(MAX_ITERATIONS):
missing, extra = validate(notes)
if not missing and not extra:
break
run_agent_fix(missing_prs=missing, extra_prs=extra)
这就是让整个流程值得信赖的模式:一个非确定性的模型,包裹在确定性的护栏之中。模型擅长撰写文字,但在穷举方面不可靠。因此,我们让它负责写作,而让代码来确保一致性。
约束模型,防止其凭空捏造
完整性是一方面,准确性是另一方面。一个仅根据 PR 标题来总结内容的模型,会愉快地凭空编造出一个与实际 API 不符的代码示例。
为了防止这种情况,我们在获取 PR 元数据时,也会拉取每个 PR 中实际的文档差异:即该 PR 所修改的 `docs/` 目录下任何 `.md` 文件的统一差异格式(unified diff)。
def fetch_doc_diffs(pr):
return [
{"filename": f.filename, "status": f.status, "patch": f.patch}
for f in pr.get_files()
if f.filename.startswith("docs/") and f.filename.endswith(".md") and f.patch
]
这段差异信息会进入模型的上下文窗口,因此当它撰写“这是新的 CLI 命令”时,会直接引用 PR 作者在文档中实际写下的示例。这与之前的逻辑相同:给模型提供真实的原始素材,并赋予其明确的单一任务。
提示词本身以技能(Skills)的形式存在:即存入代码仓库中的小型 Markdown 文件(SKILL.md 以及参考模板)。发布说明技能会详细说明如何挑选亮点、如何组织章节结构、何时添加文档链接等。它读起来就像一份入职指南,而这正是最恰当的思维模型。
人工检查环节
在候选发布版(RC)发布后,GitHub 上的草稿版发布页面中会包含 AI 的初稿内容。此时,人工介入的环节开始了:
- 审阅者阅读草稿,调整语气和重点,修正模型过度强调或关注不足的内容。
- 只有在完成上述步骤后,他们才会触发次要版本发布流程,将候选发布版(RC)升级为最终版本。
审阅者的时间主要用于润色,将原本需要半天的撰写工作缩短为十五分钟的编辑工作。
我们还会保留工作记录,以便持续改进。我们将两个文件并排归档至 Hugging Face 存储桶:一个是原始的 AI 草稿(在候选发布版(RC)阶段、任何人修改之前上传),另一个是人工编辑后的版本(在最终版本发布时上传)。
hf cp release_notes_raw.txt "hf://buckets/huggingface/releases/huggingface_hub/${V}/release_notes_raw.txt"
hf cp release_notes_edited.txt "hf://buckets/huggingface/releases/huggingface_hub/${V}/release_notes_edited.txt"
每周收集这两份文件,我们就能获得一个不断增长的“模型写了什么”与“我们希望它写什么”的对比数据集。这个数据集随后可用于更新智能体的技能。
开放且安全的管道
重构发布流程也是一个加强安全性的好机会,特别是针对供应链攻击。
不使用 PyPI 令牌。发布过程采用可信发布(Trusted Publishing)机制:PyPI 会验证由 GitHub 为此特定工作流生成的短期 OIDC 令牌,并为每个制品签发 PEP 740 认证 / Sigstore 来源证明。不存在需要泄露或轮换的长期密钥。
permissions:
id-token: write
attestations: write
- uses: pypa/gh-action-pypi-publish@v1.14.0
with:
attestations: true
智能体运行环境被锁定并经过验证。我们不会直接使用 `curl | bash` 命令安装最新的 OpenCode 并碰运气。我们会锁定一个版本,并在运行前检查其 SHA256 值:
curl -fsSL https://opencode.ai/install | bash -s -- --version "${OPENCODE_VERSION}"
echo "${OPENCODE_SHA256} $(which opencode)" | sha256sum -c -
开放的工具并不意味着可以粗心大意地使用工具。
那么,成本是多少?
几乎不花什么钱。一次完整发布(包括发布说明和 Slack 公告,涉及 20 到 40 个 PR 以及几轮提示词调整)在推理提供商上大约只需 0.25 美元。由于开放权重按使用量计费,每周唯一真正的问题是“有没有值得发布的东西?”,而答案总是有的。
实际发生了什么变化
发布节奏从每 4 到 6 周一次变成了每周一次。真正有趣的是那些次要影响:
- 发布说明的质量变好了,而不是变差了。初稿始终存在,因此审阅时间都花在了打磨上。内容分组更加一致,遗漏的东西也更少了。
- 问题暴露得更早了。每个候选版本的上下游测试分支都能在候选窗口期内发现集成问题。
- 贡献者的反馈周期缩短了。自动生成的“已在 vX.Y.Z 版本中发布”这条评论的重要性超出了我们的预期。当有人在已关闭的 PR 上报告问题时,每个人都能立刻看到修复代码包含在哪个版本中。以前这需要手动去查找标签。
让它为你所用
这是我们最关心的部分。这个工作流是围绕 huggingface_hub 构建的,但其结构是通用的。
几乎可以原样复用:
- 触发器和版本号递增逻辑(先预发布次版本号,再发布次版本号,最后发布补丁版本号)。
- 信任但验证循环:确定性清单、模型草稿、验证、重新提示。这是可以移植的核心思路,与你生成的内容无关。
- OIDC 可信发布、经过锁定和校验和验证的运行时、Slack 线程。
- 基于技能的提示词:替换模板,保留结构。
特定于我们的部分:
- 下游仓库列表及其依赖锁定格式。
- 技能中具体的章节分类和语气风格。
- Slack 和存储桶的目标地址。
要适配它:复制工作流文件和脚本,将其指向你的软件包,根据你项目的风格重写技能 Markdown 文件,设置两个仓库变量(模型 ID 和你的 OpenCode 版本),在 PyPI 上设置可信发布,如果你没有下游项目,则删除下游测试任务。信任但验证循环是值得原样复用的部分。正是它让生成的产物可以安全发布。
下一步计划
- 下游故障的自动分类。目前的工作流程是打开测试分支,由人工阅读 CI 日志。一个显而易见的下一步是检查失败的日志,并在内部 Slack 消息中报告它们。
- 模式的扩展。这部分大多是通用的。我们预计将在生态系统中其他 Python 库中重用其中的大部分内容。
要点总结
发布流程中那些过去需要人类专注工作半天才能完成的部分(编写发布说明、起草公告、协调下游检查),正是模型擅长起草的部分。其余的一切都是机械性的,可以放在一个 YAML 文件中。诀窍从来不只是“让 AI 去做”。而是让模型起草,让确定性代码验证,让人来做决策。它完全由开源工具和开放权重构建,因此成本几乎为零,任何人都可以运行它。
完整的工作流程文件是公开的。如果你维护一个 Python 库,可以 fork 它,进行适配,并告诉我们效果如何!
本文提及的模型 1
cli
huggingface_hub
agents