huggingface_hub は、Hugging Faceエコシステムの基盤となるPythonクライアントです。 transformers、datasets、diffusers、sentence-transformers など数十のライブラリが、Hubとの通信に依存しています。新しいリリースを出さない週は、main ブランチに修正や機能が溜まる週となります。
以前は4〜6週間に1回のリリースを行っていました。現在は、単一のGitHub Actionsワークフローから毎週リリースしています。オープンソースツールとオープンウェイトモデルを使って構築し、判断が必要な箇所には人間を関与させています。この記事の内容は、ベンダー契約、クローズドモデル、または自分で運用できないインフラを必要としません。これは、最初から他のメンテナーが採用・カスタマイズできるワークフローを目指した設計目標でした。
この記事を読み終える頃には、ご自身で構築するために必要なすべてが揃っているはずです。
これまでの経緯
従来のプロセスは、一部自動化されていましたが、ほとんどが手作業でした。
すでにCIで自動化されていたこと:
- タグがプッシュされたらPyPIへ公開。
- リリース候補版をピン留めした状態で、下流ライブラリにテストブランチを作成。
毎回手作業で行っていたこと:
- リリースブランチの作成、
__init__.pyのバージョン更新、コミット、タグ付け、プッシュ。 - 下流CIの実行状況を監視し、失敗をトリアージ。
- 前回リリース以降にマージされた全PRを読み、テーマごとにグループ化して、手作業でリリースノートを作成(文脈を加え、
git logのダンプのような文章にならないよう配慮)。 - RC期間終了後に安定版をリリース。
- 内部Slack向けアナウンスとソーシャル投稿の下書き作成。
- リリース後に
mainを次のdev0に更新するPRを作成。
新バージョンの良いノートを書くことが最も重い作業で、異なるトピックの数十件のPRを集約する必要がありました。技術的に難しいことではありませんが、数時間の集中した作業が必要です。アナウンス作成も加えると、マイナーリリースは数日にわたって半日程度の作業となっていました。
2種類の作業
そこで全体の流れを効率化することにしました。リストを見ると、作業は2つに分けられます。
純粋に機械的な作業は自動化可能です:バージョンの更新、コミット、タグ付け、プッシュ、下流テストブランチの作成、リリース後のPR作成。これらは考える必要がなく、正しい順序で実行されればよいため、CIワークフローが得意とする領域です。
残りは異なります。リリースノートの作成、強調する内容の決定、人間向けのアナウンス文の作成:これらは頭脳労働です。この判断が必要な部分が、長年リリースを手作業に留めていた理由です。ここでAIが登場し、白紙のページを数秒でしっかりした初稿に変換します。ただし、自信たっぷりに見えて微妙に誤った下書きは、ないより悪いため、注意が必要です。
設計原則:誰でも再利用可能なオープンな部品
これを解決するにあたり、最初に1つの制約を設けました:すべての可動部品は、どのメンテナーでも自分で実行できるものでなければならない。交換できないAPIの裏側にあるクローズドモデル、独自のリリースプラットフォーム、秘伝のタレは使わない。
使用したスタックは以下の通りです:
2つ目の原則:モデルが下書きし、人間が決定する。言語モデルは30件の簡潔なPRタイトルを読みやすいリリースノートに変換するのは得意です。しかし、盲目的に信頼されることは得意ではありません。そこでワークフローは人間が監督します:モデルが初回パスを実行し、決定論的なスクリプトがその結果をチェックし、人間がレビュー・編集してから公開します(詳細は後述)。
パイプラインの概要
ワークフローは単一のファイル .github/workflows/release.yml で、Actions UIから手動でトリガーされます。入力は1つだけです:
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
そこからジョブは概ね以下の順で実行されます:
- Prepare. 次のバージョンを計算し、リリースブランチを作成または再利用、
__version__を更新、コミット、タグ付け、プッシュ。 - Publish to PyPI.
huggingface_hubをビルドしてアップロード。同時にhfCLIを独立したPyPIパッケージとしてビルド・アップロード。 - Release notes. 前回のタグ以降のコミット範囲を差分取得し、GitHub APIからPRメタデータを取得、モデルに構造化されたチェンジログの下書きを作成させる(最近の例)。ドラフトのGitHubリリースとして保存。
- Downstream test branches. RCの場合、
transformers、datasets、diffusers、sentence-transformersにRCをピン留めしたブランチを作成し、破壊的変更を素早く検知。 - Slack announcement. ノートを読み込み、チームのトーンで内部アナウンスを作成。
- Archive notes. AIの生ドラフトと人間が編集したバージョンの両方をHugging Face Bucketに並べてアップロード。
- Post-release bump. 安定版リリース後に、
mainを次のdev0に更新するPRを作成。 - Comment on shipped PRs. リリースに含まれるすべてのPRに「this shipped in vX.Y.Z」というコメントを残す。
- Sync CLI docs. 生成された
hfCLIのskillドキュメントで skills リポジトリにPRを作成。 - Report to Slack. 各ステップがスレッド返信としてステータスを投稿し、最終ジョブがルートメッセージを✅または❌で更新。
残りの手動ステップは、ドラフトリリースノートのレビューと公開、および内部Slackメッセージのレビューと投稿です。この2ステップに人間を関与させています。
信用せよ、しかし検証せよ:人間参加の核心
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が触れた 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には、ハイライトの選び方、セクションの構造化方法、docリンクの追加タイミングなどが明記されており、オンボーディング手順のように読めます。これが適切なメンタルモデルです。
人間によるチェックポイント
RCが公開された後、ドラフトのGitHubリリースにAIの初回パスが残ります。ここで人間が関与します:
- レビュアーがドラフトを読み、トーンや強調点を編集し、モデルが過大・過小評価した点を修正。
- その後で初めて
minor-release実行をトリガーし、RCを正式版に昇格。
レビュアーの時間は洗練に使われ、半日の執筆作業が15分の編集セッションに短縮されます。
また、改善のために記録を残します。AIの生ドラフト(RC時点で誰も触れていない状態)と、人間が編集したバージョン(正式リリース時にアップロード)をHugging Face Bucketに並べてアーカイブします。
# 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"
毎週両方を収集することで、「モデルが書いたもの」と「私たちが望んだもの」のデータセットが蓄積され、エージェントのskill更新に再利用できます。
オープンでセキュアな基盤
リリースプロセスの刷新は、サプライチェーン攻撃対策としてセキュリティを強化する良い機会でした。
PyPIトークンなし。 公開には Trusted Publishing を使用:PyPIはこのワークフロー専用のGitHubが発行した短命のOIDCトークンを検証し、すべての成果物に PEP 740 のattestations / Sigstore provenanceを発行します。漏洩やローテーションが必要な長期シークレットはありません。
permissions:
id-token: write # mint the OIDC token for PyPI
attestations: write # generate Sigstore provenance
# ...
- uses: pypa/[email protected]
with:
attestations: true # no password, no API token, just OIDC
エージェントランタイムは固定・検証済み。 最新のOpenCodeを curl | bash して実行することはしません。バージョンを固定し、実行前に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週間に1回から毎週に変わりました。二次的な効果も興味深いものでした:
- ノートは悪化せず、改善した。 初稿が常に存在するため、レビューの時間は洗練に使われます。グループ化がより一貫し、見落としも減りました。
- 破壊的変更が早期に表面化。 毎RCで下流テストブランチを作成することで、候補版ウィンドウ中に統合問題を検知できます。
- コントリビュータのループが短縮。 自動の「shipped in vX.Y.Z」コメントは予想以上に重要でした。クローズしたPRに誰かが問題を報告した際、どのリリースに修正が入ったかが即座にわかります。以前は手動でタグを探す必要がありました。
ご自身のものに
私たちが最も重視した部分です。ワークフローは huggingface_hub を基にしていますが、構造は汎用です。
ほぼそのまま再利用可能:
- トリガーとバージョン更新ロジック(
minor-prerelease→minor-release→patch-release)。 - 信用せよ、しかし検証せよのループ:決定論的なマニフェスト、モデル下書き、検証、再プロンプト。これは生成対象に依存しない、転用可能なアイデアです。
- OIDC Trusted Publishing、固定・チェックサム検証済みランタイム、Slackスレッディング。
- skillベースのプロンプト:テンプレートを入れ替え、構造を維持。
当社固有:
- 下流リポジトリ一覧と依存関係のピン留め形式。
- skill内の正確なセクション分類とトーン。
- Slackとbucketの宛先。
適応方法: ワークフローファイル と スクリプト をフォークし、パッケージを指定、プロジェクトのトーンに合わせて skill Markdown を書き換え、2つのリポジトリ変数(モデルIDとOpenCodeバージョン)を設定、PyPIでTrusted Publishingを設定、下流がない場合は下流テストジョブを削除します。信用せよ、しかし検証せよのループは、そのまま再利用する価値がある部分です。これが生成物を安全に公開できる理由です。
今後の予定
- 下流障害の自動トリアージ。 現在はテストブランチを開き、人間がCIを確認しています。次の明らかなステップは、失敗ログを確認して内部Slackメッセージに報告することです。
- パターンの拡張。 この大部分は汎用です。エコシステム内の他のPythonライブラリでも大部分を再利用する予定です。
要点
以前は人間が半日集中して行っていたリリース作業(ノート作成、アナウンス下書き、下流チェックの調整)は、モデルが下書きを得意とする領域です。それ以外は機械的でYAMLファイルに収まります。コツは単に「AIにやらせる」ことではありません。モデルに下書きさせ、決定論的なコードで検証し、人間に決定させることです。すべてオープンなツールとオープンウェイトで構築されているため、コストはほぼゼロで、誰でも実行可能です。
ワークフローファイルは公開されています。Pythonライブラリをメンテナンスされている方は、フォークして適応し、結果をお知らせください!


0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.