notebook

都内でWEB系エンジニアやってます。

ChatGPTのFunction Callingを使ってブログ記事のスコアリングをしてみた

N番煎じも良いところではあるものの、やってみたので記事として残しておく

掲題のとおりChatGPTのFunctionCallingを使って今まで書いたブログ記事のスコアリング(と分類)をしてみた

  • ドキュメント

GPT - OpenAI API

platform.openai.com

記事の難易度決定や、分類をする場合、しっかり基準を決めて1つ1つ記事を読んで「この記事はこれくらいだね」というようなスコアリングをしていく

どこかでやりたいなと思っていたが現在400記事ほど書いていたのでその量を地道に分類していくのはちょっと無理かもと思っていた

そんななか、ChatGPTの波がやってきたのでこれは使えそう!ということでやってみた

指標

ChatGPTに見てもらう指標

最初スコアリングだけしようと思ったけどあれもこれもと追加したら色々見てもらうことになった

  • 記事の分類
  • 難易度
  • オリジナリティ
  • タグ
  • 評価コメント

分類

対象の記事はどのような分類の記事か

  • classify.json
[
  {
    "classify": "Tips/ハウツー",
    "description": "このカテゴリでは、ソフトウェアエンジニアリングに関する具体的なテクニックやノウハウを共有します。記事は、特定の問題を解決するためのステップバイステップのガイド、プログラミング言語の特定の機能の使い方、新しいツールの設定や導入方法など、具体的で実用的なアドバイスを含むことが一般的です。"
  },
  {
    "classify": "やってみた",
    "description": "ここでは、著者が新しい技術やツールを試したり、新しいプロジェクトを開始したり、特定の課題を克服したりするプロセスを共有します。読者には、新しいアプローチを試す際の参考になる具体的な経験と視点が提供されます。"
  },
  {
    "classify": "解説/理論",
    "description": "このカテゴリでは、特定の技術や理論に関する詳細な説明が提供されます。記事は、特定のプログラミングパラダイム、アルゴリズム、データ構造など、理解が難しいトピックの明確な理解を助けることを目指します。"
  },
  {
    "classify": "エンジニアリング文化",
    "description": "このセクションでは、エンジニアリングチームや組織の文化に焦点を当てた記事を共有します。記事は、開発プロセスの最適化、コミュニケーションの改善、メンターシップ、リーダーシップ、ダイバーシティとインクルージョンなど、より生産的で健全なエンジニアリング環境を構築する方法について述べることがあります。"
  },
  {
    "classify": "エンジニアリング倫理",
    "description": "このカテゴリでは、ソフトウェアエンジニアリングとその実践に関連する倫理的な問題を扱います。プライバシー、データの使用、AIの倫理、公平性、透明性、アカウンタビリティなどのトピックが含まれます。"
  },
  {
    "classify": "レビュー/比較",
    "description": "このカテゴリでは、特定のツール、技術、プラットフォーム、ライブラリなどをレビューしたり、それらを比較したりします。目的は、読者がそれらの選択肢の利点と欠点を理解し、自分たちのニーズに最も適したものを選択できるようにすることです。"
  },
  {
    "classify": "ツール作りました",
    "description": "ここでは、著者が自分で開発したツールやライブラリ、フレームワーク、プラグインなどを紹介します。開発の背景、目的、使用方法、そしてコミュニティにどのように貢献できるかなど、詳細に説明します。"
  },
  {
    "classify": "イベント参加レポート",
    "description": "このカテゴリでは、著者が参加した技術会議やワークショップ、ハッカソンなどのイベントについてのレポートを共有します。参加体験、学んだこと、観察したトレンド、出会った人々などを通じて、イベントのハイライトと学びを共有します。"
  },
  {
    "classify": "書評",
    "description": "ソフトウェアエンジニアリング、プログラミング、テクノロジーのトレンド、リーダーシップなど、関連する書籍のレビューを提供します。内容の要約、強み、弱み、そしてそれが読者にとってどのように有用であるかについての洞察を共有します。"
  },
  {
    "classify": "ReleaseNote",
    "description": "自身の活動をまとめたもの、技術的な取り組みや日々の話などが書いてある"
  }
]

最後のReleaseNoteを除きChatGPTに出してもらった(JSON出力もしてもらった)

この中から判断してもらう

難易度

記事で扱っている内容の難易度がどの程度なのか

1~10で判断してもらう

実際数値がどの程度なのかというのも聞いてみた

オリジナリティ

記事で扱っている内容のオリジナリティがどの程度なのか

1~10で判断してもらう

実際数値がどの程度なのかというのも聞いてみた

タグ

自身でつけているタグと比べてどんな差が出るのか見てみたかったため聞いてみることにした

差分はあって参考にはなったが本記事では深掘りしない

評価コメント

ついでに記事の評価に関して理由とかも聞いておく

本記事では深掘りしない

コード

ブログ記事を管理しているリポジトリでRubyのスクリプトを書くことが多く、Rubyの環境がすでにあったのでささっとRubyで書いた

#!/usr/bin/env ruby

require 'openai'
require 'json'
require 'awesome_print'
require 'active_support/core_ext/hash'

DIFFICULTIES = 1..10
ORIGINALITIES = 1..10
CLASSIFY_FILE = 'classify.json'

file = ARGV[0]
text = File.read(file)

def classify(text)
  client = OpenAI::Client.new(access_token: ENV['OPENAI_API_KEY'])

  classifies = JSON.parse(File.read(CLASSIFY_FILE)).map(&:symbolize_keys)
  classifies_text = classifies.map { |c| "## #{c[:classify]} \n #{c[:description]}" }.join("\n\n")
  classifies_titles = classifies.map { |c| c[:classify] }

  system_prompt = <<EOS
与えられた記事のテキストがどのような内容の記事なのか分析してください
また、評価理由をコメントしてください
EOS

  res = client.chat(
    parameters: {
      model: 'gpt-4-0613',
      temperature: 0.1,
      messages: [
        { role: 'system', content: system_prompt },
        { role: 'user', content: text }
      ],
      functions: [
        {
          name: 'classify',
          description: '抽出された特徴をJSONにして返す',
          parameters: {
            type: 'object',
            properties: {
              classify: {
                type: 'string',
                enum: classifies_titles,
                description: "記事の内容の分類\n #{classifies_text}"
              },
              difficulty: {
                type: 'integer',
                enum: DIFFICULTIES.to_a,
                description: "記事で扱っている内容の技術的な難易度、#{DIFFICULTIES.to_a.to_s}の中から選択してください"
              },
              originality: {
                type: 'integer',
                enum: ORIGINALITIES.to_a,
                description: "記事で扱っている内容のオリジナリティ、#{ORIGINALITIES.to_a.to_s}の中から選択してください"
              },
              summary: {
                type: 'string',
                description: '評価コメント'
              },
              tags: {
                type: 'array',
                items: {
                  type: 'string'
                },
                description: 'inputに対してタグを付ける場合のタグのリスト'
              }

            },
            required: ['classify', 'difficulty', 'originality', 'summary', 'tags']
          }
        }
      ],
      function_call: { 'name': 'classify' }
    })

  json = JSON.parse(res['choices'][ ]['message']['function_call']['arguments'])

  if classifies_titles.include?(json['classify']) && DIFFICULTIES.include?(json['difficulty']) && DIFFICULTIES.include?(json['originality'])
    json.symbolize_keys
  else
    raise "変な返答が返ってきたので例外 #{json}"
  end
end

result = classify(text)
puts JSON.generate(result)

こんなかんじのスクリプトを書いて公開している記事の内容テキストを投げてスコアリングした

RubyのGemだと公式ではなくコミュニティライブラリなので作法と使い方心地が公式のライブラリとは違うが

ChatGPTに投げるパラメータは他の言語のライブラリと変わらないので参考にはなるかと思う

結果

すべて1つ1つ精査したわけではないので「ちょっと違う」というのもある可能性はあるが、書いた身からすると結構納得感あるスコアが並んだと感じる

評価基準をあらかじめ出してもらったのでそれと照らしあわせてもそれなりに納得感あるかなーって感じだった

約400記事分のデータが集まったのでせっかくなので可視化してみる

分類

ほとんどが「Tips/ハウツー」「やってみた」に該当する記事と判断された

まぁ感覚としてもあっているかなという感じだった

以下記事執筆の時期をX軸にしてスコアの平均と最大値、上位数記事のデータを出している

オリジナリティ

  • 高い順

  • 低い順

難易度

  • 高い順

  • 低い順

振り返り系の記事は難易度としては低く判定されるよう

また、ReleaseNoteではない振り返りの記事もReleaseNoteとして分類されてしまっているので聞き方を変える必要がありそうだった

時系列でのグラフでは、この時期忙しかったのでちょっと軽めの記事が多いな、とかそういう振り返りはできた

まとめ

ChatGPTのAPI(Function Calling)を使ってブログ記事のスコアリングをしてみた

結構納得感ある感じの点数だったのでこれを元に分析なり今後の活動に活かしていくのはありかなと感じた

また、評価基準を独自に設定してそこに対してどうですか?っていうのもある程度できそうな気がしてきたので別の機会で試してみたい

今回2記事だけ文字数が多すぎてAPIのトークン数制限に引っかかってしまったのでその辺の調整と分類ももう少し調整して年間の振り返りに使おうと思っている

余談

全部で400記事ほど書いていたので結構お金がかかった($50ほど)

それなりに掛かるのでご利用は計画的に…