🐬

OpenAI のテクノロジーを使ってカイル君を 2023 年に召喚する

2023/02/17に公開
4

はじめに

思いついたので作ってみました。OpenAI のテクノロジーを使ってかつての Office アシスタントであるカイル君をチャットボットとして 2023 年に召喚します。

記事を書いた時点ではまだ自然な会話に特化した ChatGPT の API が利用可能な状態ではありませんでしたので、今回は GPT-3 (具体的には GPT-3.5 と呼ばれるモデルのひとつであるtext-davinci-003) をチャットエンジンとして使います。

今後もし ChatGPT API や 新しい Bing の API が公開されれば、より自然な会話やチャット検索ができるボットが簡単に作れるようになるかもしれません。

【追記】2023 年 3 月 1 日に ChatGPT API が公開されましたので、新しいバージョンを作りました。
https://zenn.dev/ryo117/articles/10c4aa5c77c14a

作り方

以下のサービスを組み合わせて作ります。トライアル期間を使い切っていない場合はすべて無料で済ませることもできます。

サービス 役割 無料要素
LINE Messaging API UI・ボット フリープラン
DALL-E 2 アイコンデザイン フリークレジット
Azure Functions ロジック Azure の無料アカウント
OpenAI API チャットエンジン フリートライアル

1. アイコン

ダリ先生にボットの顔であるアイコンを描いてもらいます。

1.1. DALL-E 2 サインアップ

Google アカウントか Microsoft アカウントでサインアップします。

1.2. 画像生成

サインアップが完了したらプロンプトエンジニアリングを繰り返して画像を生成します。プロンプトは英語で入力する必要がありますので、必要に応じて DeepL 先生に手伝ってもらいます。
試行錯誤の結果、似た感じのイルカが出てきたのでこれを使います。本当はカイル君が持っていた「ホタテ貝の形の PC」も再現したかったのですが、ダリ先生がなかなか理解してくれなかったので諦めました。

2. LINE ボット

2.1. ベース部分

LINE Messaging APIAzure Functions でまずはオウム返し LINE ボットを作ります。手順については以下の記事が今回使う言語とやりたいことにとてもマッチしていましたのでそのまま参考にさせていただきました。

チャネルアイコン

途中、チャネルアイコンにはダリ先生に描いてもらった画像を設定します。

あいさつメッセージ

友だち追加時のあいさつメッセージも設定しておきます。

2.2. テスト

完成したら友だち追加して会話してみます。この段階ではまだこちらの入力をそのまま返してくるだけです。

3. チャットエンジン

カイル君が実際に会話できるようにチャットエンジンを組み込んでいきます。

3.1. OpenAI サインアップ

OpenAI にGoogle アカウントか Microsoft アカウントでサインアップします。

3.2. OpenAI API キー取得

以下を参考にして OpenAI の API キーを取得します。

3.3. Azure Functions 環境変数設定

取得した API キーは LINE チャネルアクセストークン / チャネルシークレットと同じ要領で Azure Functions の環境変数に OPENAI_API_KEY という名前で設定します。

3.3. 関数実装

LINE ボットの Webhook に設定した Azure Functions の関数 (オウム返し) を以下のように変更して上書きデプロイします。

requirements.txt

azure-functions
line-bot-sdk
openai

__init__.py

import logging
import os
import azure.functions as func
import openai
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage

channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
openai_api_key = os.getenv('OPENAI_API_KEY', None)
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)
prohibited_sentences = [
    'お前を消す方法',
    'お前をけす方法',
    'おまえを消す方法',
    'おまえをけす方法',
    'おまえをけすほうほう'
]

def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')
    signature = req.headers['x-line-signature']
    body = req.get_body().decode('utf-8')
    logging.info('Request body: ' + body)
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        func.HttpResponse(status_code=400)
    return func.HttpResponse('OK')

def generate_response(prompt):
    openai.api_key = openai_api_key
    response = openai.Completion.create(
        engine='text-davinci-003',
        prompt=prompt,
        max_tokens=1000,
        temperature=0.5
    )
    return response['choices'][0]['text'].replace('\n', '')

@handler.add(MessageEvent, message=TextMessage)
def message_text(event):
    if (event.message.text in prohibited_sentences):
        response = ''
    else:
        response = generate_response(event.message.text)
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=response)
    )

generate_response 関数はユーザーからの入力をモデル (text-davinci-003) に渡して生成されたテキストを受け取ります。

モデルの temperature パラメータは 0 ~ 2 の間で設定します。小さくするほど毎回同じような内容を生成するようになり、大きくするほどランダムな内容を生成するようになります。今回はややランダム性を持たせたかったので、とりあえず 0.5 に設定しました。

max_tokens パラメータは生成されるテキストの最大トークン数を設定しています。日本語の 1,000 トークンは画面のサイズにもよりますが大体 LINE の一画面がいっぱいになるくらいの文量になります。

本筋ではありませんが、トークンについては以下でまとめています。

会話

検証

会話してみます。こちらの入力に対しておおむね自然な返しができています。突然中国語で Excel の話を始めたりと、かつての Office アシスタントの名残を感じます。
ちなみに、背後で動いている text-davinci-003 が多言語対応モデルですので、日本限定アシスタントだったカイル君も多言語を扱うことができるようになっています。

OpenAI について聞いてみました。モデルの temperature を高めに設定していますので毎回微妙に答えが変わります。Q&A ボットのように使いたい場合は temperature を低めにすると良いかもしれません。
ただし、あくまでも Text Completion によって入力 (プロンプト) に対するそれらしい続き部分を生成しているだけですので、回答内容が正確とは限りません。(今回は正しいことを言っているようです)

かつての仕事もこなせるようです。

例のホタテ貝の形のパソコンのことを英語で聞いてみたところ、数年前に売ってしまって今は持っていないそうです。

Office アシスタント引退後のことを聞いてみたところ、ちょっとテンションが上がりすぎてしまいました。投資に相当な思い入れがあったようです。

チューニング

少々チューニングを行います。generate_response 関数を以下のように変更して上書きデプロイします。ここではモデルのパラメータに frequency_penalty を追加しています。frequency_penalty は -2 ~ 2 の間で設定します。正の値を設定すると同じトークンが出現する頻度に対してペナルティが課され、同じ内容を繰り返しづらくなります。上記のようなことになって欲しくないですので、今回はとりあえず 1 に設定します。

__init__.py

# (略)
def generate_response(prompt):
    openai.api_key = openai_api_key
    response = openai.Completion.create(
        engine='text-davinci-003',
        prompt=prompt,
        max_tokens=1000,
        temperature=0.5,
        frequency_penalty=1
    )
    return response['choices'][0]['text'].replace("\n", "")
# (略)

参考

frequency_penalty と似たパラメータに presence_penalty があります。こちらも -2 ~ 2 の間で設定を行います。正の値を設定すると、そのトークンが既に出現している場合 (頻度ではなく出現しているか否か) にペナルティが課されます。必然的に既出でないトークンを使おうとするため、新しいトピックについて生成を促したい場合に設定します。いま気になるのは繰り返しですので、今回は特に設定を行いません。

再検証

再度同じ質問を投げかけてみます。今度はしっかりとした受け答えができるようになりました。引退後は色々なことをしていたようです。

これでカイル君の召喚は完了です。

おわりに (お前を消す方法)

以上です。🍵

Discussion

MSKMSK

はじめまして!
記事のカイル君面白かったです!

ryohtakaryohtaka

読んでいただきありがとうございます。面白いと言っていただけて嬉しいです!

bunniesfieldbunniesfield

いろんな意味でインターネット接続必至な時代ですよね。ネット接続してないPCでは使い物にならない感じでしょうか?

ryohtakaryohtaka

用途によるとは思いますが、大規模言語モデルを使っている機能の多くはオンライン前提になってくるかもしれませんね。