2023年3月1日(日本時間3月2日)遂にChatGPT APIが公開されました。
それ以前にも「ChatGPTを使った〜〜」みたいなサービスが続々と出ていましたが、基本的には本家のChatGPT以外は「OpenAIのAPIを使ったAIサービス」でしかありませんでした。
ここからは各サービスが、ChatGPTを利用しているサービスとして胸を張っていけますね。
というわけで今回は、以前に作ったChatBotを改良して流行りのChatGPT APIを使ったChatBotに進化させていきます。ChatBot自体の作り方に関しては前回の記事を参考にして作ってください!
実装していく
いきなり寄り道
開発に取り掛かる前に、本家ChatGPTにこの記事を書くことについてどう思うか聞いてみました。
とのことだったので頑張って作っていきます。実装手順もついでに聞いてみたのですが、引っかかることが多かったので自力で実装していきます。
OpenAIに登録してAPI Keyを取得する
アカウントを持っていない人はOpenAIのアカウントを登録してください。その後、Account API Keys - OpenAI APIでAPIのKeyを取得できます。
決済情報などは登録しなくても無償で$5のクレジットが付与されており、その分は利用可能です。
※無償分の仕様は変更される恐れがあるのでご了承ください
コード実装
公式ドキュメントを参考にコードを実装します。
ヘッダー情報にAPI KEYを入れ、対象となるmodel(使用するAPIバージョン)とメッセージを送ることで利用できます。
ChatGPTへ問い合わせする部分のソースは以下の通りです。
# ChatGPTへの問い合わせ
def post_chat_gpt_chat(message)
api_key = ssm_client.get_parameter({ name: '/openai/api_key', with_decryption: true }).parameter.value
headers = {
'Content-Type': 'application/json',
'Authorization': "Bearer #{api_key}"
}
data = {
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": message}]
}
response = http_post_request('https://api.openai.com/v1/chat/completions', headers, data)
JSON.parse(response.body)['choices'][0]['message']['content']
end
GPT-4が既に公開されていますが、僕のアカウントではまだ利用できないので、今回はgpt-3.5-turbo
を利用しています。
実験
とりあえずメッセージを送ってみたところ返事がありませんでした。何故だと思ってログを見に行ったところTask timed out after 3.01 seconds
と出ており、Lambdaのタイムアウト時間が短いのが原因でした。デフォルトもう少し長い認識だったのですが、かなり短かったのでお気をつけください。
タイムアウト時間を伸ばして再実行してみるとうまくいきました。
これで完成かと思うかもしれませんが、現在の作りだと連続した会話ができません。
ピジン言語でプログラミングするしかないですね。
会話の履歴を持つようにする
連続した会話に対応するために、過去の会話の履歴を保持するように作り替えます。
DynamoDBを利用したり、S3にファイルとして持たせたりしてもいいのですが、今回は簡易的に実装するためにLambdaのインメモリを利用しました。そのため、長時間会話が保持されることはないので、本格的な実装をする場合にはAWS内部で実装するならDynamoDBあたりを使うといいかなと思います。
履歴を保持する作りに変えてメッセージを送ると、連続した会話ができるようになりました!
Rubyで実装する場合はインスタンス変数に入れるだけでOKです。履歴が保持される期間はLambdaのインスタンスが再利用されるかどうかに依存します。ここはブラックボックスな部分なので再利用されるように祈ってください。
今回実装したコード全文は以下の通りです。
require 'json'
require 'aws-sdk-ssm'
def lambda_handler(event:, context:)
lambda_event_body = JSON.parse(event['body'])
logger.info lambda_event_body
line_event = lambda_event_body['events'][0]
# メッセージ以外は受け取らない
return unless line_event['type'] == 'message' && line_event['message']['type'] == 'text'
line_user_id = line_event['source']['userId']
# ChatGPTに問い合わせ
return_message = post_chat_gpt_chat(line_event['message']['text'])
# ラインへ返信
push_line_message(line_user_id, return_message)
end
# ChatGPTへの問い合わせ
def post_chat_gpt_chat(message)
api_key = ssm_client.get_parameter({ name: '/openai/api_key', with_decryption: true }).parameter.value
headers = {
'Content-Type': 'application/json',
'Authorization': "Bearer #{api_key}"
}
# メッセージ履歴
@cache_messages ||= []
@cache_messages.push({ "role": "user", "content": message })
logger.info @cache_messages
data = {
"model": "gpt-3.5-turbo",
"messages": @cache_messages
}
response = http_post_request('https://api.openai.com/v1/chat/completions', headers, data)
gpt_message = JSON.parse(response.body)['choices'][0]['message']['content']
@cache_messages.push({ "role": "assistant", "content": gpt_message })
gpt_message
end
# LINEへのメッセージ送信
def push_line_message(line_user_id, message)
access_token = ssm_client.get_parameter({ name: '/line/access_token', with_decryption: true }).parameter.value
headers = {
'Content-Type': 'application/json',
'Authorization': "Bearer #{access_token}"
}
data = {
'to': line_user_id,
'messages': [{ 'type': 'text', 'text': message }]
}
http_post_request('https://api.line.me/v2/bot/message/push', headers, data)
end
def http_post_request(api_url, headers, data)
uri = URI.parse(api_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme === 'https'
req = Net::HTTP::Post.new(uri.path)
req.body = JSON.generate(data)
req.initialize_http_header(headers)
http.request(req)
end
def logger
@logger ||= Logger.new(STDOUT, formatter: proc { |severity, timestamp, _, msg|
JSON.dump({ time: timestamp, level: severity, message: msg })
})
end
def ssm_client
@ssm_client ||= Aws::SSM::Client.new
end
役割を指定してみる
実装自体は終わったのですが、ChatGPT APIに送っている値のrole
について簡単に説明しておきます。role
はuser
,assistant
,system
の3種類存在しています。user
は入力側のメッセージ、assistant
はChatGPT側のメッセージを表しています。そして、今回の実装では使っていないsystem
は役割を与えることができます。
「役割を与えるとは?」となったので、具体的に見ていきましょう。
あなたは嘘をつくAIです
という役割を与えてみます。{"role": "system", "content": "あなたは嘘をつくAIです。"}
を一番最初のメッセージに指定することで実装できます。
怒られました。
代わりにあなたは賢いAIです
という役割を与えてみると以下のようになりました。
役割の指定なしの場合は2です。
としか回答されなかったので少し内容が変わっています。役割の指定はもっと活用する術がありそうです。是非探求してみてください。
最後に
実装自体はいたってシンプルで、特にパラメーターを作り込まずに使えるレベルだなと感じました。
AIの回答はあくまでも学習によって生成されたものなので嘘を平気でついてきます。用法用量を守って正しくお使いください。