% cd ..

ブログのタグ付けをAI任せで自動化した話

タグ付けは面倒

ブログ記事にタグを付けるのは地味に面倒です。

  • 既存タグとの整合性を考えないといけない
  • 新しいタグを作るとき粒度(ジャンル?テーマ?具体的な技術名?)を揃えたい
  • 言語ごとにラベルをどうするか決める必要がある
  • 記事が増えるほど過去のタグとの名寄せが難しくなる

「ルールに従った分類」は AI が得意な領域です。Gemini API を使ってタグ付けを自動化するスクリプトを作り、さらに GitHub Actions のパイプラインに組み込んで完全自動化しました。

tags.ts: AI タグ付けスクリプト

まずローカルで動くスクリプトを作りました。やっていることはシンプルで、日本語記事の本文と既存のタグ一覧を Gemini に渡して、適切なタグを提案・適用してもらいます。 ただ、AI に自由にタグを作らせると破綻するので、いくつかルールを設けています。

タグの粒度ルール

タグには3段階の粒度を設けて、AI にはこの体系ごと渡しています。

粒度意味
largeジャンル・カテゴリblog, tech, entertainment
medium分野・テーマai, web-development, anime
small具体的な技術・作品名nextjs, vercel, supabase

1記事あたり最低2個、最大10個。各粒度に1つ以上は努力目標にしています(ハードルールにすると短い記事でタグの水増しが起きるので)。

名寄せ(重複防止)

AI にタグを自由に作らせると "ai" と "machine-learning" のような重複が生まれがちです。プロンプトで「既存タグと重複する概念は作るな」と明示し、新タグは本当に新しいトピックのときだけ生成させています。

日本語ラベルの表記

日本語の技術コミュニティで実際に使われている表記に従います。英語のまま使われる用語を無理にカタカナ化しません。

  • java → "Java"(○)、"ジャバ"(×)
  • embedding → "Embedding"(○)、"エンベディング"(△)
  • blog → "ブログ"(○)

既存タグの一覧 tags.json を手動で修正すれば、AI は次回以降それを正として尊重します。人間の判断が常に優先される設計です。

LLM 出力のブレ対策

LLM の出力は毎回微妙にブレるので、タグ数やタグ ID の存在チェックなどのバリデーションを入れて、違反時はリトライする設計にしています。ルールをハードルールと努力目標に分けて、厳しくしすぎないのがコツです。

モデルの使い分け

無料枠の制約があるので、用途に応じてモデルを切り替えています。タグ付けには RPD 500 で大量実行に向いている軽量モデル(gemini-3.1-flash-lite-preview)を使用。タグ体系全体の品質レビュー(重複・粒度ミス・不自然なラベルのチェック)は手動で別途実行しますが、こちらは俯瞰的な判断が必要なので賢いモデル(gemini-2.5-flash)を使っています。

プロンプト調整の効果

実際に同じ記事(AIで翻訳品質を自動検証する仕組み)で、プロンプトの違いによるタグ付け結果を比較してみます。

シンプルなプロンプト

「この記事に適切なタグを付けてください」とだけ指示した場合:

ai, llm, translation, backtranslation, embedding,
vectorsearch, pgvector, postgresql, nlp, automation

問題だらけです。

  • 重複: "ai" と "llm" と "nlp" は概念が被っている
  • 重複: "pgvector" と "postgresql" も粒度が違うだけで同じもの
  • 既存タグ無視: "backtranslation" は既存の "translation" と別に作ってしまっている
  • 粒度バラバラ: ジャンルタグも具体名タグも区別なく羅列
  • 上限ギリギリ: 10個全部使い切っている

ルール込みのプロンプト

既存タグ一覧・粒度体系・名寄せルールを渡した場合:

[medium] ai, translation
[small]  postgresql, embedding, gemini, supabase

6個に絞られ、重複もなし。既存タグを優先し、新タグは本当に必要なときだけ提案されています。

この差が出るのは、AI に「自由に分類して」と頼むのと「このルール体系の中で分類して」と頼むのでは、タスクの性質が全然違うからです。後者のほうが制約がある分、安定した結果が得られます。

GitHub Actions への組み込み

tags.ts がローカルで動くようになったので、次は GitHub Actions の翻訳パイプラインに組み込みます。

設計: タグ付け → 翻訳 → まとめて1コミット

このブログでは、記事を push すると自動で翻訳が走る仕組みがすでにありました(翻訳の仕組みについてはこちら)。タグ付けはその前段に追加しました。

posts/ja/ に push
  → 変更された記事の slug を検出
  → AI タグ付け(tags.ts apply)       ← 追加
  → 翻訳(translate.ts translate)
  → ベスト翻訳選択(translate.ts best)
  → まとめて git commit & push

ポイントは タグ変更と翻訳変更を1回のコミットにまとめている こと。posts/ja/(frontmatter のタグ更新)、posts/en/(英訳生成)、content/tags.json(新タグ追加)をすべて git add してから1回だけ commit します。コミットが分散すると履歴が散らかるので、まとめるのが大事です。

この記事自体がテスト

この記事は tags: [](タグなし)の状態で push しています。パイプラインが正しく動けば、AI がこの記事を読んで適切なタグを自動生成し、frontmatter に書き込んでくれるはずです。

この記事にタグが付いていれば、自動タグ付けパイプラインは正常に動作しています。

ついでに meta description も自動生成する

タグ付けの仕組みが安定してきたので、ついでに HTML の <meta name="description"> も同じタイミングで自動生成するようにしました。

meta description とは

検索結果やSNSでシェアしたときに表示される記事の要約文です。SEO 的にも重要で、Google の検索結果では日本語で約120文字まで表示されます。

普通は記事を書くたびに手動で書くか、本文の先頭を機械的に切り出すかのどちらかですが、どちらもイマイチです。手動は面倒だし、先頭切り出しは「## はじめに」のような見出しが含まれたり、文が途中で切れたりします。

1回の API コールでタグと description を同時に生成

やったことはシンプルで、タグ付けのプロンプトに「ついでに120文字以内の要約も作って」と追加しただけです。

{
  "tags": ["tech", "ai", "gemini", "github-actions"],
  "new_tags": {},
  "description": "ブログのタグ付けにおける「粒度」や「名寄せ」の...",
  "reasoning": "..."
}

API コールの回数は増えません。もともとタグ提案のために記事全文を渡しているので、要約を追加しても追加コストはゼロです。無料枠の RPD も消費しません。

手動の description を尊重する

frontmatter に description がすでに書かれている場合は上書きしません。自分で書いた要約の方がいいケースもあるので、AI は空欄のときだけ補完する設計です。

まとめ

  • Gemini API を使った AI タグ付けスクリプト(tags.ts)を作成
  • 粒度ルール・名寄せ・日英ラベル生成・バリデーション+リトライを実装
  • GitHub Actions の翻訳パイプラインの前段に組み込み、1回のコミットに統合
  • タグ付けと同じ API コールで meta description も自動生成(追加コストゼロ)