| @ブログ

カテゴリー検索

ふと思い立ってカテゴリーで検索できるようにしたら便利だろうと改修してみた。 Tantiny には facet_query というものがあって、カテゴリー名などは検索インデックスの型を facet にしておくと、カテゴリーだけを検索対象にできる。実装してみたところ便利。カテゴリー内の記事一覧ページを表示する機能はあるが、本文やコメントなども読み込むため遅い。タイトルだけ一覧でがっと欲しいときにカテゴリー検索は便利。

同様にタグでも検索できるようにしてみた。こちらは term_query を使っている。本文の検索は検索キーワードを形態素解析してトークナイズされた文字列で検索しているが、タグ名での検索は exact match をするようにしている。なので過不足あればヒットしない。完全一致で検索したいときに便利。

ただ、これまで記事にタグを設定してこなかったのでタグ未設定の記事が多い。ということで ChatGPT に記事の内容を読ませてタグを自動生成してもらうようにした。ちょっとへんちくりんなタグを設定することもあるが、文脈を読んでタグを設定してくれる。本文中にキーワードはないけど記事の内容に合致するタグを選んできたりもする。賢い。

ちなみに今回 ChatGPT のモデルを gpt-4o から gpt-5-mini に変更した。 GPT 5 系に移行するためには Chat API から Responses API への移行が必要だった。最初それがわからず難儀したが、 Responses API の方がパラメーターがシンプルで使いやすい。例えば JSON のフォーマットを指定したいとき、 Chat API だと JSON Schema で定義を書いて渡す必要があったが、 Response API であればプロンプト中に「こういうフォーマットでくれ」と書けばそのフォーマットで返してくれる。すごい。

require 'openai'

class EntryTagGenerator
  MODEL = 'gpt-5-mini'
  PROMPT = <<~EOF
以下のブログ記事に関して、次の作業をしてください。

1. この文章の内容を反映したタグを抽出してください。タグの数は最大で 5 個までとします。タグは極力一語で構成されるようにし、略語は避けてください。また「減量ダイエット」のような意味が重複する二つの単語で構成されるタグは付与しないでください。固有名詞の場合はその限りではありません。

レスポンスは JSON フォーマットで、以下のような形式にしてください。

{
  "tags": ["tag1", "tag2"...]
}

# ブログ記事本文

EOF

  def initialize(content)
    @content = content
    @client = OpenAI::Client.new(access_token: ENV['OPENAI_API_KEY'])
  end

  def generate
    request = @client.responses.create(
      parameters: {
        model: MODEL,
        input: input,
        reasoning: { effort: 'minimal' }
      }
    )
    JSON.parse(request.dig("output", 1, "content", 0, "text").strip)
  rescue => e
    puts "タグ生成エラー: #{e.message}"
    nil
  end

  def input
    %(#{PROMPT}#{@content})
  end
end

| @技術/プログラミング

去年の 3 月に ChatGPT を API で使いたいと思って、 OpenAI の API クレジットを $10 分買っていた。 ChatGPT の API を使っても全然クレジットが減らず、まだ $9.5 分くらい残ってたはずだが、先日買いたブログ記事の要約を作ってもらおうとしたがちゃんと動かない。どうも API がレスポンスを返していないようだ。最初はぶっ壊れたのかなと思ったが、 ChatGPT に聞いてみると以下の通りで、購入から 1 年が経ったら失効するとのことだった。

CleanShot 2025-04-24 at 00.26.51@2x

利用頻度は高くないけれど使えなくなったら不便なので取り急ぎ $5 分クレジットを購入した。

| @ブログ

OpenAI の API を利用してブログの本文の要約を自動生成する機能をつけてみた。ブログの編集画面に要約欄を追加し、チェックボックスにチェックが入っていれば OpenAI の API にリクエストを投げて記事本文の要約を生成して保存するようにした。

要約自動生成君

これも ChatGPT に設計を依頼してレビュー&手直ししながらやった。めっちゃ便利。要約生成君のコードはこんな感じ。

require 'openai'

class EntrySummarizer
  MODEL = 'gpt-4o-mini'

  def initialize(content)
    @content = content
    @client = OpenAI::Client.new(access_token: ENV['OPENAI_API_KEY'])
  end

  def summarize
    response = @client.chat(
      parameters: {
        model: MODEL,
        messages: [
          { role: "system", content: "以下のブログ記事を著者になりきって、日本語で簡潔に要約してください。文章の長さは200文字以内で、受動態表現と「ですます」調を避けて下さい。" },
          { role: "user", content: @content }
        ],
        temperature: 0.3,
        max_tokens: 150
      }
    )
    response.dig("choices", 0, "message", "content").strip
  rescue => e
    puts "要約生成エラー: #{e.message}"
    nil
  end
end

| @労働

新しくウェブ系のシステムを開発するときにやったことがいいことを書いておきます。コードを書くスキルとはちょっと別なのでこういうのは経験がないと身につかないけど超重要です。

1. システム構成図を描く

どんなシステムがあってどんな流れで処理が進むのかを絵に描く。かっちょいい設計書じゃなくてもいい。手書きの雑なやつでもいい。一個一個の処理に「〇〇君」のような名前を付けていくといい。「JSON書き出し君」みたいな感じ。

2. 誰が何をやるかを明確に

タスクを洗い出すだけではなく、誰がどこまでやるかを明確にする。1で描いた絵に担当をアサインしていくと漏れがない。

3. リリース日を決める

リリース日を決めずに開発を始めるとパリッとしない。リリース日を決めて、アプリの申請、 QA の予定などを埋めていくと、何日までに開発を終わらせておかなければならないかが逆算的にわかる。間に合わなそうであればリリース日を動かして調整する。

4. 確認環境・デモ環境を作る

ローカルではなく、本番と似た環境で動作確認できる環境をまず作る。ローカルで動いたけど本番で動かないをなくす。本番サイトを非公開にできるならリリース前からガンガンデプロイしてもいい。CSSが当たってなかったり、APIがモックでもいい。動くものを関係者間で共有し、早い段階でイメージのズレを補正することが超重要。

CI/CD の仕組みまで初期に整えておけるとなおいい。

5. リスクの高いところから開発する

システムの処理の中で重要かつ難易度が高い、もしくは実現できるかわからない(不確実性が高い)部分から開発する。簡単なところから先にやって不確実性(リスク)の解消を先送りにするのは超危険。

6. APIのJSONのフォーマットを先に決める

こうしておくことでバックエンドとフロントエンドがパラレルで開発できるようになる。誰かの作業が進まないと他の人の作業が進まないというような依存関係を作らない。

7. コードはこまめにリポジトリにプッシュ

他の人が作業状況をわかるようにする。小さくプルリクエストを作ってマージしていくとかは今更言わなくても常識ですよね?

8. 朝会か夕会をやる

超ラフにコミュニケーションしてお互いの進捗を共有する。可能ならオフラインで話す。一緒に寿司を食べるのも効果的。


↑は作るものが決まってからの話なので、作るものが合ってるか(正しいか)はまた別の話(プロダクトマネジメント)。

| @技術/プログラミング

ブログのアクセス数を集計してランキング(人気記事一覧)を表示している。

シェルスクリプトでログを集計して頑張っているが、ボットからのアクセスを除外など結構やることが複雑化してきた。また最近は主にロシア方面からのスパマーによるアクセスが多く、全然いま読まれる要素がない記事がランキング上位に入ったりしてた。スパマーは以下の 2 記事が好きなようだ。

Google Analytics でアクセス数を見るとこれらの記事は上位に入ってこないので、 Google はちゃんとスパマーからのアクセスを除外しているのだろう。

というわけで Google Analytics の API からアクセス数を取得してみることにした。

しかし調べてみた感じ、あまり情報がない。 Google の公式ドキュメントは Java とPython と Go と PHP と JavaScript のサンプルしかない。

Google が公開している Ruby のライブラリはあるが、ドキュメントがえらく貧弱で勘で使うしかない。

使い方を紹介しているブログもあるにはあるが、この Ruby 製のライブラリはアルファ版とベータ版しかなくてころころ仕様が変わるようだ。先人の情報通りに動かしてみたら全然動かなかった。

API の仕様や上述のライブラリのコードを読みつつ以下のようなコードを書いたところいい感じに使えるようになった。 Ruby で Google Analytics の API にアクセスしたいと思っている人には参考になるんじゃないかと思う。

↑のコードでは metrics は screenPageViewstotalUsers を取得している。 dimension は pagePathpageTitle だ。ほかのが必要であれば変えてあげればよい。これを Rake タスクから呼び出して必要な情報を得るようにしている。

API 呼び出しについては Google が提供している Query Explorer で確認するとよい。

また Analytics API は利用開始前に設定が必要。 Quickstart ページで API を有効化し、 GCP に IAM を作成して credential をダウンロードして Google Analytics 側でこの IAM への API アクセスを許可する必要がある。コード書く前にこの辺でくじけそうになるだろうけど頑張ってほしい。