WordPress記事のパーマリンクをAIで自動生成する ~英文スラッグ提案プラグインを作った話~

みなさんこんにちは。
WordPressで記事を作成する際、何気にちょっと面倒なのがパーマリンクの設定。

記事を的確に表すものにしたいし、SEO的に日本語スラッグは望ましくない。
そして一度設定したら変えないようにしたい… なんて悩むものです。

そこで、投稿編集画面のボタンを押すと、AIが英語スラッグの候補を提案してくれる 仕組みを作成してみました。

ダウンロード

コードはGitHubに置きましたので、宜しければご活用ください。

GitHub - kizawa2020/auto-permanent-link
Contribute to kizawa2020/auto-permanent-link development by creating an account on GitHub.

 

要件

今回は完全自動ではなく「提案型」にするという方針で計画しました。

  • 記事タイトル(と本文の冒頭)から
  • 30文字以内の英語スラッグ候補を3つ
  • 投稿画面のメタボックスにカードで表示
  • 気に入ったものを選んで「適用」

という手順です。「AIは候補を出すだけ、最終的に選んで確定させるのは人間」という線引きにしています。
またコストを考慮し、AIには Amazon Nova 2 Lite を採用しています。

 

アーキテクチャ

基本アーキティクチャ

Kiroと相談しつつ決定したアーキテクチャはこちら。(添付するでもないですが)

処理の流れは以下の通りです。

  1. 投稿画面でボタンを押す
  2. タイトル(+オプションで本文の冒頭)をプラグイン経由でLambdaへ送る
  3. Lambdaが Bedrock Nova 2 Lite を呼び、英語スラッグ候補を3件生成させる
  4. Lambda側で候補を正規化(後述)してプラグインに返す
  5. メタボックスに候補カードが並ぶので、選んで「適用」

AIによるスラッグ生成の工夫

モデルにはAmazon Nova 2 Liteを利用し、「記事タイトル(と本文冒頭)を渡すので、英語のスラッグ候補を3つ出して」と依頼します。ここでひとつ工夫が要ります。LLMに複数の候補を返させると、区切り文字ベースだと数がズレる(3つ頼んだのに2つしか返ってこない、など)ことがあることが検証の結果わかりました。

そこで、候補は番号付きのJSON構造で返させ、インデックスで対応付けるようにしました。
これで「候補が何個返ってきても、順番と対応が崩れない」状態にすることができました。

また、AIが返す文字列は、そのままではスラッグとして使えないことがあります。
大文字が混じっていたり、全角が入っていたり、長すぎたり。そこでLambda側で以下の順に正規化します。

  1. NFKC正規化(全角→半角などを揃える)
  2. 小文字化
  3. ハイフン区切りに整える
  4. 30文字以内に切り詰める(このとき、単語の途中でぶった切らず、なるべくハイフンの境界で切る)
  5. それでも空になってしまったらフォールバックの値を使う

おかわり(再生成)機能

一度出した3候補がどれもピンとこないこともあります。そのため、もう一度ボタンを押すと、前回までに表示した候補を除外して新しい候補を出す「お代わり」機能を入れました。

仕組みは、表示済みスラッグを画面側で蓄積しておき、次の生成リクエストで「これらは除外して」と exclude に渡す方式です。押すたびに既出リストが積み上がるので、毎回フレッシュな候補が出ます。

ただし現実的な上限もあります。候補はいつか出尽くしますし、exclude リストは最大50件で頭打ちにしています。出尽くしたときはエラーにせず、Lambda側のセーフティネットが近い候補を返す挙動にしてあります。なお、記事タイトルを変えるか画面をリロードすると、除外リストはリセットされて初回状態(除外なし)に戻ります。

セキュリティ

API GatewayのAPIを保護するため、二段構えのセキュリティを実装しました。

  1. 共有シークレットヘッダーで認証
    Lambda側で環境変数の値と定数時間比較し、一致しなければ401
  2. API GatewayのAPIキー + Usage Planでレート制限
    コストの暴走に上限をかける

i.で「正規の呼び出しか」を確かめ、ii.で「万一叩かれても青天井にはならない」を担保する構成です。
合わせてCORSの AllowOrigin も設定します。

 

コストと運用について

管理者・編集者がたまに押すだけの機能ですし、Amazon Nova 2 Lite を使っているため、実運用の課金額ほぼ気にならないレベルと思います。

なおLambdaのモデルなど以下のパラメーターが環境変数でチューニング可能なようになっています。

パラメータ キー 規定値
許可するCORSオリジン ALLOWED_ORIGINS  
Amazon Bedrockのリージョン BEDROCK_REGION ap-northeast-1
生成温度 GENERATION_TEMPERATURE 0.2
スラッグ候補の最大文字数 MAX_SLUG_LEN 30
生成に利用するモデル MODEL_ID jp.amazon.nova-2-lite-v1:0
一度に生成するスラッグ数 NUM_SUGGESTIONS 3

将来もっと良い・安いモデルが出たら、コードを触らずに切り替えられるかと思います。

 

インストール方法

AWS側とプラグイン側、双方への作業となります。

事前準備

ひとまず以下の準備が必要です

  • Amazon Bedrock で対象モデル(例: jp.amazon.nova-2-lite-v1:0)のモデルアクセスを有効化しておく
  • AWS CLI が設定済みで、CloudFormation / Lambda / API Gateway を作れる権限があること
  • Lambda関数(zip)の保存先S3バケットがあること
  • 共有シークレット(API_SECRET)に使うランダムな文字列を1つ用意する

シークレットは以下のように作成してください。

# 共有シークレットの生成例(この値を後でWordPress側にも登録する)
openssl rand -hex 32

AWS側(バックエンド)のデプロイ

Lambdaのコードは、いったんS3に置いてからCloudFormationで参照する方式としました。(SAM不使用)

# 1. Lambdaコードをzip化
cd aws/lambda/slug_suggester
rm -f /tmp/slug_suggester.zip  #前回の zip が残っていると追記されるため消す
zip /tmp/slug_suggester.zip *.py
cd ../../..

# 2. デプロイ用S3バケットにアップロード
aws s3 cp function.zip s3://(アップロード先バケット名)/slug_suggester.zip

# 3. CloudFormationスタックを作成(モデルIDとシークレットをパラメータで渡す)
aws cloudformation deploy \
  --template-file template.yaml \
  --stack-name auto-permanent-link \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameter-overrides \
      CodeBucket=<S3バケット名> \
      CodeKey=slug_suggester.zip \
      SharedSecret=<上記で生成した共有シークレットの値> \
      AllowedOrigins=https://example.com

CloudFormationスタックのデプロイに利用できるパラメーターは下記の通りです。

パラメータ 既定 説明
CodeBucket (必須) Lambda zip を置く S3 バケット名
CodeKey slug_suggester.zip Lambda zip の S3 キー
SharedSecret (必須・16字以上) プラグインと共有する秘密文字列(X-Api-Secret
AllowedOrigins * CORS 許可オリジン(本番サイトのオリジンに絞るようにしてください)
RateLimitPerSecond / RateBurst 5 / 10 APIレート制限とバースト
DailyQuota 500 API日次リクエスト上限(コスト暴走の上限)

デプロイが終了したら、Outputsから以下の値をメモします

  • エンドポイントURL(ApiEndpoint
  • APIキーID(ApiKeyId

なお、APIキーはIDのため、以下のコマンドで実際の値を取り出せます。

# APIキーの取得
aws apigateway get-api-key --api-key <APIキーID> --include-value --query value --output text

 

WordPressプラグインのインストール

プラグイン本体を WordPress にインストールします。

  1. wordpress/auto-permanent-link/ フォルダを丸ごとzipにする
  2. WordPress管理画面 →「プラグイン」→「新規追加」→「プラグインのアップロード」からzipを選んで有効化
  3. 管理画面の「設定」→「Auto Permanent Link」で、次の3項目を登録する
    • API エンドポイント URL
      プラグインインストール時に出力されたApiEndpoint…/prod)。末尾の /suggest は付けない(プラグインが自動付与)
    • 共有シークレット(X-Api-Secret)
      CloudFormationテンプレートデプロイ時の SharedSecret と同じ値
    • API キー(x-api-key)
      プラグインインストール時に出力されたAPIキーIDにより取り出したAPIキー値

 

動作確認

これでインストールが完了しました。

WordPress記事作成画面から(パーマリンク推奨の表示が出るようになったかと思います(管理者・編集者権限のユーザのみ)
候補を生成ボタンを押すと、パーマリンクの推奨が出てきます。

なお、切り分けのためコマンドで試験をしたい場合は以下のコマンドで確認できます。

# テスト方法
API=<ApiEndpoint名>

# 死活監視(認証不要)
curl -s "$API/health"          # → {"status":"ok"}

# スラッグ生成(X-Api-Secret と x-api-key の両方が必要)
curl -s -X POST "$API/suggest" \
  -H "Content-Type: application/json" \
  -H "X-Api-Secret: <共有シークレット>" \
  -H "x-api-key: <APIキーの値>" \
  -d '{"title":"WordPress記事タイトル"}'

正常に動作した場合、
{“suggestions”:[{“slug”:”cheap-wordpress-cloudfront”,”length”:26}, …]}
のように、JSON形式で返答があります。

 

まとめ

最近はKiroと対話しながらバイブコーディングを行うことが増えました。
本機能もKiroで実装を進めましたが、こういうプチ改善の仕組みがアイディア1つでどんどん進められるのは良い時代になりましたね。

今後も色々なアイディアを実現して行ければと思います。

コメント