みかづきブログ・カスタム

基本的にはちょちょいのほいです。

OpenAI APIを活用した文章スコアリングの実装 💯

以前、Node.jsからOpenAI APIをさささっと叩きました

blog.kimizuka.org

今回は、文章を送信したら、何かしらのスコアを返すAPIを作ってみようと思います。
この説明だとわかりにくいので、実例を交えながら解説します。

先輩風API

先日、体験型コンテンツ勉強会に参加させていただき、Electronアプリから簡単に扇風機の風力を操作できるようになったので、扇風機を音声でコントロールできるようにしてみました。

starryworks.notion.site

その際、先輩っぽいセリフを言えばいうほど、風力を上げたかったので、先輩風レベルを0〜255の256段階で判定するAPIをOpenAI APIで作成しました。

そう。先輩風を吹かせたかったのです。

getSenpaiScore

import 'dotenv/config';
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

async function getSenpaiScore(text: string): Promise<{
  score: number;
  message: string;
}> {
  const chatCompletion = await openai.chat.completions.create({
    model: 'gpt-3.5-turbo',
    messages: [{
      role: 'user',
      content: [{
        type: 'text',
        text: `
次の「」内の言葉が偉そうかどうかを0〜255の256段階で採点してください。
採点の際は、1の位も使ってなるべく細かく採点してください。ただし少数は使わないでください。
ありがとうございます。などの丁寧な言葉遣いは0点にしてください。
なんでこんなこともできないの?などの偉そうな言葉遣いは255点にしてください。
また、どうすればもっと先輩風を吹かせることができるかのアドバイスをセットで教えてください。
JSON.parseでJSONに変換したいので、返答のテンプレートは、

{"score":(採点), "message": (アドバイス)}

とJSONにしやすいようにオブジェクトにしてください。

${ text }
`
      }]
    }]
  });

  let obj = {
    score: 0,
    message: ''
  };

  try {
    obj = JSON.parse(String(chatCompletion.choices[0].message.content));
  } catch (err) {
    console.error(err);
  }

  return obj;
}

こんな感じで実装しました。
このAPIに、試しに「焼きそばパン買ってきて」を投げてみると、

(async () => {
  const obj = await getSenpaiScore('焼きそばパン買ってきて');

  console.log(obj); // => { score: 55, message: 'もう少し丁寧な言葉遣いで頼んだら、より先輩風を演出できます。例えば、「お願いだけど、焼きそばパンを買ってきてくれないかな?」という風にすると良いでしょう。' }
})();

{
score: 55,
message: 'もう少し丁寧な言葉遣いで頼んだら、より先輩風を演出できます。例えば、「お願いだけど、焼きそばパンを買ってきてくれないかな?」という風にすると良いでしょう。'
}

という感じの回答が返ってきますが、結果は一意にならず、全く同じtextを渡しても毎回異なった結果が返ってきます。

JSON.parseでJSONに変換したい旨を伝えてはいますが、一応、try...catch文で囲っています。
まだまだAIのことを信じきれていないので。

愛の言葉API

かつて、ポップコーンマシンの前で愛の言葉を叫び続けている間、釜の温度が上がっていってポップコーンが完成するというプログラムを実装したことがあります。

https://bt.imgix.net/exhibition/8692/sub/1633354043783_ecaeae3dc804d740ea613b60f8b9be65.jpg?auto=format&fm=jpg&w=1920&h=1080&fit=max&v=8

https://bijutsutecho.com/exhibitions/8692 より引用

bijutsutecho.com

当時は、愛の言葉をユーザーから募集し、投稿された言葉にマッチしたら釜の温度を上げるという処理で実装していたのですが、OpenAI APIを使えば、これをAIに判定させることもできてしまうことでしょう。

getIsLoveMessage

import 'dotenv/config';
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

async function getIsLoveMessage(text: string): Promise<{
  isLoveMessage: boolean;
  message: string;
}> {
  const chatCompletion = await openai.chat.completions.create({
    model: 'gpt-3.5-turbo',
    messages: [{
      role: 'user',
      content: [{
        type: 'text',
        text: `
次の「」内の言葉が愛の言葉に該当するか否かをtrue、falseで判定してください。
例えば、愛してる、アイラブユーなどの言葉はtrueを返してください。
嫌いです、いらっしゃいませなどの言葉はfalseを返してください。
月が綺麗ですねなどの有名な愛の言葉はtrueを返してください。
また、判定の理由をセットで教えてください。
JSON.parseでJSONに変換したいので、返答のテンプレートは、

{"isLoveMessage":(愛の言葉に該当するか否か), "message": (判定の理由)}

とJSONにしやすいようにオブジェクトにしてください。

${ text }
`
      }]
    }]
  });

  let obj = {
    isLoveMessage: 0,
    message: ''
  };

  try {
    obj = JSON.parse(String(chatCompletion.choices[0].message.content));
  } catch (err) {
    console.error(err);
  }

  return obj;
}

こんな感じで実装しました。
このAPIに、試しに「焼きそばパン買ってきて」を投げてみると、

(async () => {
  const obj = await getIsLoveMessage('焼きそばパン買ってきて');

  console.log(obj); // => { isLoveMessage: false, message: '愛の言葉とは関係ない依頼のため' }
})();

「結婚してください」を投げてみると、

(async () => {
  const obj = await getIsLoveMessage('結婚してください');

  console.log(obj); // => { isLoveMessage: false, message: '結婚の申し込みは愛の言葉として使用されることが多いため' }
})();

的なことが返ってきます。
やはり、結果が一意に決まらないので、デバイス側のデザインを工夫しないと、ものすごくわかりにくくなってしまうリスクを含んでいます。

💯

という感じで、OpenAI APIを使えば、

  • テキストにスコアをつけてもらう
  • 結果をJSONで扱いやすく整形してもらう

の2点は簡単に実装でき、診断コンテンツを簡単に作れる可能性を感じました。
今回は、文章のスコアリングでしたが、gpt-4-vision-previewを使えば画像のスコアリングもできると思います。

blog.kimizuka.org

ちなみに今回のブログのタイトルも、本文内容をもとにAIに決めてもらいました。
執筆前に僕が考えていたタイトルは「Node.jsで処理しやすいようにOpenAI APIからの戻り値をJSONに整形して診断コンテンツ用のAPIをつくる 🔨」です。
AI案の方が断然わかりやすいですね。AIを信じていこうと思います。

追記

のちに、 JSONモードについてのドキュメント を発見しました。

platform.openai.com

これに従えば、もっと簡単にJSONを取得できそうです。

async function getSenpaiScore(text: string): Promise<{
  score: number;
  message: string;
}> {
  const chatCompletion = await openai.chat.completions.create({
    model: 'gpt-3.5-turbo',
    response_format: { 'type': 'json_object' },
    messages: [{
      role: 'system',
      content: `
      次の言葉が偉そうかどうかを0〜255の256段階で採点してください。
      ありがとうございます。などの丁寧な言葉遣いは0点にしてください。
      なんでこんなこともできないの?などの偉そうな言葉遣いは255点にしてください。
      また、どうすればもっと先輩風を吹かせることができるかのアドバイスをセットで教えてください。
      返答のテンプレートは、

      {"score":(採点), "message": (アドバイス)}

      とJSONにしやすいようにオブジェクトにしてください。
      `
    },{
      role: 'user',
      content: [{
        type: 'text',
        text
      }]
    }]
  });

  let obj = {
    score: 0,
    message: ''
  };

  try {
    obj = JSON.parse(String(chatCompletion.choices[0].message.content));
  } catch (err) {
    console.error(err);
  }

  return obj;
}

これでも動きました!