每周用AI、开源工具和人工审核发布huggingface_hub
Shipping huggingface_hub every week with AI, open tools, and a human in the loop
Hugging Face 团队将 `huggingface_hub` 的发布流程从每 4-6 周一次手动发布改为每周一次自动化发布,使用 GitHub Actions 工作流编排。该流程使用开放权重模型 GLM-5.2 通过 OpenCode agent 运行时起草发布说明和 Slack 公告,由 HF Inference Providers 提供服务,并通过 PyPI Trusted Publishing 发布。关键设计是“信任但验证”:确定性脚本先提取 PR 清单作为 ground truth,模型起草后验证其完整性,缺失或多余内容会反馈给 agent 修正。人工保留对草稿说明和公告的最终审查权。一次完整发布成本约 $0.25。工作流文件、脚本和 skill 提示均公开在 GitHub 上供其他维护者复用。
](https://huggingface.co/Wauplin)
huggingface_hub 是 Hugging Face 生态系统的 Python 客户端。transformers、datasets、diffusers、sentence-transformers 以及数十个其他库都依赖它与 Hub 通信。我们每少发布一个版本,就意味着修复和功能在 main 分支上多积压一周。
很长一段时间里,我们每 4 到 6 周发布一次。现在,我们通过一个 GitHub Actions 工作流每周发布一次。我们使用开源工具和开放权重模型构建了它,并在唯一需要判断力的环节保留了人工审核。本文中没有任何内容需要供应商合同、闭源模型或你无法自行运行的基础设施。这从一开始就是我们的设计目标,因为我们希望其他维护者能够采用并调整这个工作流。
读完本文,你将拥有构建自己工作流所需的一切。
我们的起点
旧流程部分是自动化的,大部分是手动的。
已在 CI 中实现:
- 推送 tag 后自动发布到 PyPI。
- 在下游库中打开测试分支,并固定使用候选版本。
每次仍需手动操作:
- 创建发布分支,在
__init__.py中更新版本号,提交,打 tag,推送。 - 监控下游 CI 运行并排查失败原因。
- 阅读自上次发布以来合并的所有 PR,并手动编写发布说明:按主题分组,提供上下文,使用不像
git log转储的语言。 - 在候选版本期结束后发布稳定版本。
- 起草内部 Slack 公告和社交媒体帖子。
- 打开发布后 PR,将
main分支更新到下一个dev0版本。
为新版本编写好的说明是最繁重的部分,需要汇总数十个不同主题的 PR。技术上并不困难,但需要几个小时的专注。再加上公告,一个小版本发布很容易就需要几天内分散的半天工作量。
两种工作
因此,我们决定简化整个流程。看看上面的列表,工作可以分为两类。
有些步骤纯粹是机械性的,可以自动化:更新版本号、提交、打 tag、推送、打开下游测试分支、打开发布后 PR。这些步骤不需要思考。它们只需要每次都按正确顺序发生,这正是 CI 工作流擅长的。
其余部分则不同。编写发布说明、决定突出什么、为人类读者撰写公告:这是脑力工作。正是这种判断力让发布流程多年来一直保持手动。这就是 AI 发挥作用的地方,它能在几秒钟内将空白页变成扎实的初稿。这也是我们必须小心的地方,因为一份看起来自信但存在细微错误的草稿比没有草稿更糟糕。
设计原则:开放组件,人人可复用
当我们决定解决这个问题时,我们预先设定了一个约束:每个活动部件都必须能让任何维护者自己运行。不能有我们无法替换的 API 背后的闭源模型,不能有专有的发布平台,不能有秘密配方。
以下是整个技术栈:
| 部分 | 功能 |
|---|---|
| GitHub Actions | 编排整个发布流程 |
| OpenCode | 驱动模型的 agent 运行时 |
| 一个开放权重模型(目前是 Z.ai 的 GLM-5.2) | 起草发布说明和 Slack 公告 |
| HF Inference Providers | 提供模型服务 |
| PyPI Trusted Publishing | 发布包 |
第二个原则:模型起草,人类决定。语言模型擅长将三十个简洁的 PR 标题转化为可读的发布说明。它们不擅长被盲目信任。因此,工作流是人工监督的:模型进行初稿,确定性脚本检查其工作,人类在发布前审查和编辑(更多细节见下文)。
流水线概览
完整的工作流是一个文件,.github/workflows/release.yml,从 Actions UI 手动触发。它只需要一个输入:
on:
workflow_dispatch:
inputs:
release_type:
type: choice
options:
- minor-prerelease # 从 main 分支创建 RC
- minor-release # 将 RC 提升为正式版
- patch-release # 在现有发布分支上修复 bug
从这里开始,任务大致按以下顺序运行:
- 准备。 计算下一个版本号,创建或复用发布分支,更新
__version__,提交,打 tag,推送。 - 发布到 PyPI。 构建并上传
huggingface_hub。同时,构建并上传hfCLI 作为其独立的 PyPI 包。 - 发布说明。 比较自上次 tag 以来的提交范围,从 GitHub API 拉取 PR 元数据,并让模型起草结构化的变更日志(这是最近的一个例子)。保存为 草稿 GitHub release。
- 下游测试分支。 对于 RC,在
transformers、datasets、diffusers、sentence-transformers中打开一个分支,固定使用 RC 版本,以便它们的 CI 能快速告诉我们是否破坏了什么。 - Slack 公告。 读取说明并以我们团队的风格生成内部公告。
- 归档说明。 将原始的 AI 草稿和人工编辑后的版本并排上传到 Hugging Face Bucket。
- 发布后版本更新。 在稳定版本发布后,在
main分支上打开一个 PR,将其更新到下一个dev0版本。 - 在已发布的 PR 上评论。 在发布中包含的每个 PR 上留下一条“此功能已在 vX.Y.Z 中发布”的评论。
- 同步 CLI 文档。 在我们的 skills 仓库中打开一个 PR,更新重新生成的
hfCLI skill 文档。 - 报告到 Slack。 每个步骤将其状态作为线程回复发布;最后一个任务用 ✅ 或 ❌ 更新根消息。
剩余的手动步骤是审查和发布草稿发布说明,以及审查和发布内部 Slack 消息。这两个步骤是我们希望保留人工审核的地方。
信任但验证:人工审核核心
以下是每个人对 AI 生成的发布说明的担忧:模型悄悄遗漏了一个 PR,或者凭空捏造了一个不属于本次发布的 PR。一份几乎正确的变更日志比没有变更日志更糟糕,因为没人会重新检查它。
我们不信任生成的发布说明一次就能完整,我们用确定性方法验证它。在模型运行之前,一个 Python 脚本会检索所有属于本次发布的 PR,并将其存储为 ground truth。
# 确定性:从范围内的 squash-merge 提交中提取 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) # 模型写的内容 (#1234 -> 1234)
missing = expected - found # 被静默遗漏的
extra = found - expected # 属于其他发布的
如果存在任何遗漏或多余,我们不会失败,也不会发布错误的文件。我们会将差异反馈给 agent,并要求它精确修复这些 PR:
for _ in range(MAX_ITERATIONS):
missing, extra = validate(notes)
if not missing and not extra:
break # 与 manifest 完全匹配
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 加上参考模板)检入到仓库中。发布说明 skill 详细说明了如何挑选亮点、如何组织章节、何时添加文档链接等。它读起来像入职指南,这正是正确的思维模型。
人工检查点
在 RC 发布后,草稿 GitHub release 会包含 AI 的初稿。这就是人工介入的地方:
- 审阅者阅读草稿,调整语气和重点,修正模型过度或不足强调的内容。
- 只有在那之后,他们才会触发
minor-release运行,将 RC 提升为正式版。
审阅者的时间用于润色,将半天的写作变成十五分钟的编辑会议。
我们还保留记录以便持续改进。我们将两个文件并排归档到 Hugging Face Bucket:原始的 AI 草稿(在 RC 时上传,未经任何人修改)和人工编辑后的版本(在最终发布时上传)。
# 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"
每周收集两者,为我们提供了一个不断增长的“模型写了什么”与“我们希望它写什么”的数据集。然后我们可以重用这个数据集来更新 agent 的 skill。
开放且安全的管道
改造发布流程是加强安全性的好机会,特别是针对供应链攻击。
没有 PyPI token。 发布使用 Trusted Publishing:PyPI 验证 GitHub 为此特定工作流生成的短期 OIDC token,并为每个工件发布 PEP 740 认证 / Sigstore 来源证明。没有需要泄露或轮换的长期密钥。
permissions:
id-token: write # 为 PyPI 生成 OIDC token
attestations: write # 生成 Sigstore 来源证明
# ...
- uses: pypa/gh-action-pypi-publish@v1.14.0
with:
attestations: true # 无需密码,无需 API token,只需 OIDC
Agent 运行时被固定和验证。 我们不会 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 和几轮提示)在 Inference Providers 上大约花费 $0.25。使用按量付费的开放权重,每周唯一真正的问题是“是否有值得发布的内容?”,而答案总是肯定的。
实践中发生了什么变化
发布频率从每 4 到 6 周一次变为每周一次。有趣的是次要影响:
- 说明变得更好,而不是更差。 初稿始终存在,因此审阅时间用于润色。分组更加一致,遗漏的内容也更少。
- 问题更早暴露。 每个 RC 上的下游测试分支在候选版本窗口期间就能捕获集成问题。
- 贡献者循环缩短。 自动的“已在 vX.Y.Z 中发布”评论的重要性超出了我们的预期。当有人在已关闭的 PR 上报告问题时,每个人都能立即看到修复在哪个版本中。以前这需要手动查找 tag。
让它为你所用
这是最让我们关心的一点。工作流是为 huggingface_hub 量身定制的,但其结构是通用的。
几乎可以直接复用:
- 触发器和版本更新逻辑(
minor-prerelease然后minor-release然后patch-release)。 - 信任但验证循环:确定性 manifest、模型草稿、验证、重新提示。这是可迁移的思想,与你生成的内容无关。
- OIDC Trusted Publishing、固定并校验和验证的运行时、Slack 线程。
- 基于 skill 的提示:替换模板,保留结构。
特定于我们的部分:
- 下游仓库列表及其依赖固定格式。
- skill 中精确的章节分类和语气。
- Slack 和 Bucket 目标。
要适配它:fork 工作流文件 和 脚本,将其指向你的包,为你的项目风格重写 skill Markdown,设置两个仓库变量(模型 ID 和你的 OpenCode 版本),在 PyPI 上设置 Trusted Publishing,如果你没有下游项目,则删除下游测试任务。信任但验证循环是值得原样复用的部分。它使得生成的工件可以安全发布。
下一步计划
- 自动分类下游失败。 目前工作流会打开测试分支,然后由人工阅读 CI 结果。一个明显的下一步是检查失败的日志,并在内部 Slack 消息中报告它们。
- 扩展模式。 这大部分是通用的。我们期望在我们生态系统中的其他 Python 库中重用大部分内容。
要点
发布流程中那些曾经需要半天专注人工工作的部分(编写说明、起草公告、协调下游检查)正是模型擅长起草的部分。其他一切都是机械性的,适合放在 YAML 文件中。诀窍从来不是“让 AI 去做”。而是让模型起草,让确定性代码验证,让人工决定。它完全由开放工具和开放权重构建,因此成本几乎为零,任何人都可以运行。
完整的工作流文件是公开的。如果你维护一个 Python 库,fork 它,调整它,并告诉我们效果如何!