Hugging Face 如何用 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 工作流驱动,使用 OpenCode 作为智能体运行时、GLM-5.2 等开放权重模型起草发布说明和 Slack 公告,模型经 HF Inference Providers 提供服务。
huggingface_hub 是 Hugging Face 生态系统底层的 Python 客户端。transformers、datasets、diffusers、sentence-transformers 以及数十个其他库都依赖它与 Hub 通信。我们每少发布一个新版本一周,就意味着有一周的修复和功能被卡在 main 上。
很长一段时间里,我们每 4 到 6 周发布一次。现在我们通过一个 GitHub Actions 工作流每周发布。我们使用开源工具和开放权重模型构建了它,并在唯一需要判断力的地方保留了人工介入。本文中的任何内容都不需要供应商合同、闭源模型或你无法自行运行的基础设施。这从一开始就是一个设计目标,因为我们希望其他维护者能够接手并改编这套工作流。
读完本文,你将拥有构建自己工作流所需的一切。
我们从哪里开始
旧流程部分是自动化的,大部分是手动的。
已经在 CI 中:
- 推送标签后发布到 PyPI。
- 在下游库中打开测试分支,并固定发布候选版本。
每次仍然需要手动完成:
- 创建发布分支,在
__init__.py中更新版本号,提交,打标签,推送。 - 观察下游 CI 运行并分类处理失败。
- 阅读自上次发布以来合并的每个 PR,并手写发布说明:按主题分组,附带上下文,语气不能读起来像
git log的堆砌。 - 在 RC 期结束后发布稳定版本。
- 起草内部 Slack 公告和社交媒体帖子。
- 打开发布后 PR,将
main更新到下一个dev0。
为新版本撰写好的说明是最繁重的部分,需要汇总数十个不同主题的 PR。技术上并不难,但需要几个小时的专注。再加上公告,一个小版本发布很容易变成分散在几天里的半天工作量。
两类工作
所以我们决定简化整个流程。看看那份清单,工作分为两类。
有些步骤纯粹是机械性的,可以自动化:更新版本号、提交、打标签、推送、打开下游测试分支、打开发布后 PR。这些不需要任何人思考。它们只需要每次都按正确的顺序发生,而这正是 CI 工作流擅长的。
其余的就不同了。撰写发布说明、决定突出什么、为人类受众措辞公告:那是脑力工作。正是这种判断力让发布多年来一直保持手动。这就是 AI 发挥作用的地方,它能在几秒钟内把一张白纸变成扎实的初稿。这也是我们必须小心的地方,因为一份看起来自信但微妙地错误的初稿,比完全没有初稿更糟糕。
设计原则:开放组件,任何人都可复用
当我们决定解决这个问题时,我们一开始就设定了一个约束:每个活动部件都必须是任何维护者都能自行运行的东西。没有我们无法替换的 API 背后的闭源模型,没有专有发布平台,没有秘密配方。
以下是整个技术栈:
| 部分 | 作用 |
|---|---|
| GitHub Actions | 编排整个发布流程 |
| OpenCode | 驱动模型的智能体运行时 |
| 开放权重模型(目前是来自 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 # cut an RC from main
- minor-release # promote the RC to final
- patch-release # bugfix on an existing release branch
从那里开始,各个作业大致按以下顺序运行:
- 准备。计算下一个版本,创建或复用发布分支,更新
__version__,提交、打标签、推送。 - 发布到 PyPI。构建并上传
huggingface_hub。同时,将hfCLI 作为独立的 PyPI 包构建并上传。 - 发布说明。对比自上一个标签以来的提交范围,从 GitHub API 拉取 PR 元数据,让模型起草一份结构化的变更日志(这是最近的一份)。保存为 草稿 GitHub 发布。
- 下游测试分支。对于 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 技能文档。 - 向 Slack 报告。每个步骤都将其状态作为线程回复发布;最后一个作业用 ✅ 或 ❌ 更新根消息。
剩余的手动步骤是审查和发布草稿发布说明,以及审查和发布内部 Slack 消息。这两个步骤是我们希望有人类参与的地方。
信任但验证:人在回路的核心
这是每个人都担心的 AI 生成发布说明的失败模式:模型悄悄漏掉一个 PR,或者编造一个不在本次发布中的 PR。一份几乎正确的变更日志比没有变更日志更糟糕,因为没有人会重新检查它。
我们不信任生成的发布说明在第一次尝试时就是完整的,我们以确定性的方式验证它。在模型运行之前,一个 Python 脚本检索属于该发布的所有 PR,并将它们存储为基准真相。
# Deterministic: extract PR numbers from squash-merge commits in the range.
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) # the source of truth
然后模型根据它们起草说明。完成后,我们将其输出与初始 PR 列表进行核对:
expected = set(load_manifest()) # what should be there
found = extract_pr_refs(notes_md) # what the model wrote (#1234 -> 1234)
missing = expected - found # silently dropped
extra = found - expected # belongs to a different release
如果有任何缺失或多余,我们不会失败,也不会发布错误的文件。我们将差异交还给代理,并要求它精确修复那些 PR:
for _ in range(MAX_ITERATIONS):
missing, extra = validate(notes)
if not missing and not extra:
break # matches the manifest exactly
run_agent_fix(missing_prs=missing, extra_prs=extra)
这就是让整个事情值得信赖的模式:一个非确定性模型被包裹在确定性护栏中。模型擅长写散文,但不擅长做到详尽无遗。所以我们让它写,让代码来强制一致性。
为模型提供依据,使其不会编造内容
完整性是一半。准确性是另一半。一个仅根据标题总结 PR 的模型会兴高采烈地编造一个与实际 API 不匹配的代码示例。
为了防止这种情况,在获取 PR 元数据时,我们还会从每个 PR 中拉取实际的文档差异:即该 PR 触及的 docs/ 下任何 .md 文件的统一差异。
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 的第一版内容。这就是人工介入的地方:
- 审阅者阅读草稿,调整语气和重点,修正模型过度或不足强调的内容。
- 只有在这之后,他们才触发
minor-release运行,将 RC 提升为最终版本。
审阅者的时间用于润色,将半天的写作变成十五分钟的编辑会议。
我们还保留纸质记录以便随时间改进。我们将两个文件并排归档到 Hugging Face Bucket:原始 AI 草稿,在 RC 时上传,在任何人触碰之前;以及人工编辑版本,在最终发布时上传。
# at RC time: straight from the model, untouched
hf cp release_notes_raw.txt "hf://buckets/huggingface/releases/huggingface_hub/${V}/release_notes_raw.txt"
# at release time: after the human review
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 # mint the OIDC token for PyPI
attestations: write # generate Sigstore provenance
# ...
- uses: pypa/gh-action-pypi-publish@v1.14.0
with:
attestations: true # no password, no API token, just OIDC
代理运行时被固定并验证。 我们不会 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 上报告问题时,每个人都能立即看到修复在哪个版本中。这曾经是手动查找标签。
让它成为你的
这是我们最关心的部分。工作流围绕 huggingface_hub 塑造,但结构是通用的。
几乎可以直接复用:
- 触发器和版本号递增逻辑(
minor-prerelease然后minor-release然后patch-release)。 - 信任但验证的循环:确定性清单、模型草稿、验证、重新提示。这是可迁移的核心思路,与你生成的内容无关。
- OIDC 可信发布、固定并校验和验证的运行时、Slack 线程。
- 基于技能的提示词:替换模板,保留结构。
我们特有的部分:
- 下游仓库列表及其依赖固定格式。
- 技能中确切的章节分类和语气。
- Slack 和存储桶目标位置。
要适配它:fork 工作流文件和脚本,指向你的包,按你项目的语气重写技能 Markdown,设置两个仓库变量(模型 ID 和你的 OpenCode 版本),在 PyPI 上设置可信发布,如果没有下游就删除下游测试任务。信任但验证的循环是值得原样复用的部分。它让生成的产物可以安全发布。
下一步
- 自动分诊下游失败。目前工作流会打开测试分支,由人工查看 CI。一个明显的下一步是检查失败的日志,并在内部 Slack 消息中报告。
- 扩展这一模式。其中大部分是通用的。我们预计会在生态中的其他 Python 库中复用大部分内容。
要点
发布中过去需要半天专注人工工作的部分(撰写说明、起草公告、协调下游检查)正是模型擅长起草的部分。其余一切都是机械性的,可以放进一个 YAML 文件。诀窍从来不只是“让 AI 来做”。而是让模型起草,让确定性代码验证,让人来决定。它完全由开放工具和开放权重构建,因此成本几乎为零,任何人都可以运行。
完整的工作流文件是公开的。如果你维护一个 Python 库,fork 它,适配它,并告诉我们进展如何!
来源:Hugging Face Blog · huggingface.co