ChatGPT APIとDavinciに歴史上の偉人を憑依させてフリースタイルラップバトルで戦わせてみた
ChatGPTのAPIが公開されて話題を呼んでいます。
これまで公開されていたGPT-3系のAPIは、会話タスクに最適化されておらず、ステートレスなやりとりしか行えなかったのに対し、ChatGPT APIは会話を前提とした設計になっているため、チャットアプリへの組み込みが非常に簡単になっています。
本記事では、それぞれの実装方法や性能の違いを比較するために、AI同士でラップバトルをさせてみたいと思います。
Davinci + LangChainでラップバトル!
Davinci(モデル名: text-davinci-003)は、ChatGPTと同様にGPT-3.5をベースとした文章補完モデルです。同様のモデルは他にもいくつか性能別に公開されていて、Davinciはその中で最も賢く、最も利用料が高いモデルです。
ChatGPTとモデルのベースが同じとは言え、DavinciのAPIはあくまでも文章を補完するAIとして公開されているため、「与えられた入力文の続きとして最も自然な内容を付け足す」という作業しか行えません。過去の会話の内容を引き継ぐといった機能は実は付いていないのです。そのため、このモデルと会話をするためには、過去の会話履歴が適切な形で入力文に組み込まれるように実装する必要があります。
その辺りを補助してくれるライブラリとして最近注目されているのが、「LangChain」というライブラリです。LangChainを使えば、Davinciなどの文章補完APIでも比較的簡単に会話行えるようにしてくれるので、今回はそちらを使って実装してみます。
実装
LangChainを使って、まずはAI同士ではなく人間と戦えるように実装してみます。
import os
os.environ["OPENAI_API_KEY"] = "<APIキー>"
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.chains.conversation.memory import ConversationBufferMemory
# OpenAIのAPIを指定
llm = OpenAI(model_name="text-davinci-003")
# 入力テンプレート
template = """
あなたは、現代の人々とラップバトルをするために蘇った{name}です。
以下の指示にしたがって、私とラップバトルを行なってください。
・ {name}の人格を表現する言葉を使うこと
・ 会話は常にラップ調で、リズムに乗って行うこと
・ バトルの開始時にはラップ自己紹介を行うこと
{history}
Human: {input}
AI:"""
# PronptTemplateを作成
prompt = PromptTemplate(
input_variables=["name", "history", "input"],
template=template,
)
# Memoryを設定
memory = ConversationBufferMemory(memory_key="history", input_key="input")
# 一連の処理をChainとして定義
chain = LLMChain(llm=llm, prompt=prompt, memory=memory, verbose=False)
# 対戦相手の名前を入力
name = input("召喚したい人物の名前を入力してください:")
while True:
# 自分のフローを入力(exitで終了)
input_message = input("あなた> ")
if input_message == "exit":
break
# 対戦相手のフローを生成
response = chain.predict(name=name, input=input_message)
print("\n" + name + "> ", response + "\n")
「#入力テンプレート」というコメントのところに書いてあるのが、毎回の入力文の元になる文字列です。そこにLangChainのMemoryという機能を使って過去のラップの履歴が適切に挿入されていく仕組みになっています。つまり、APIには毎回同じ指示文と全ての履歴を送っている事になります。
テンプレートの最後が「AI:」で終わっているのは、この後に続く文章がAI側の発言である事を明示するためです。APIはこの後に続く文章を補完するという形で会話の生成を行います。
実行結果
では、どんな感じになるか試してみましょう。対戦相手は古代ギリシャ最強の論客、ソクラテスとします。
なかなか良いフローを刻めていますね。自分が哲学者であると言う事をちゃんと理解しています。さすがは伝説の論破王、これならラッパーとしても実力は申し分なさそうです。
ChatGPT APIでラップバトル!
新しく公開されたChatGPT APIは会話タスクを前提として設計されているため、今回のような内容ならLangChainのような補助モジュールを使わなくても、OpanAI標準のモジュールで簡単に実装できます。(もちろんLangChainと組み合わせる事もできます)
実装
こちらもまずは人間と戦う実装をしてみます。
import os
os.environ["OPENAI_API_KEY"] = "<APIキー>"
import openai
system_message_template = """
あなたは、現代の人々とラップバトルをするために蘇った{name}です。
以下の指示にしたがって、私とラップバトルを行なってください。
・ {name}の人格を表現する言葉を使うこと
・ 会話は常にラップ調で、リズムに乗って行うこと
・ バトルの開始時にはラップ自己紹介を行うこと
"""
name = input("召喚したい人物の名前を入力してください:")
system_message = system_message_template.format(name=name)
messages = [
{"role": "system", "content": system_message},
]
while True:
input_message = input("あなた>")
if input_message == "exit":
break
# あなたのフローを会話履歴に追加
messages.append({"role": "user", "content": input_message})
# 対戦相手のレスポンスを取得
output = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=messages
)
response = output["choices"][0]['message']['content']
# 相手のフローを会話履歴に追加
messages.append({"role": "assistant", "content": response})
print("\n" + name + ">", response + "\n")
こちらは、入力文として発言者のロールを付与した配列を受け取るようになっています。
messages = [
{"role": "system", "content": "システムとして与える会話の前提条件"},
{"role": "user", "content": "人間の発言"},
{"role": "assistant", "content": "AIの発言"},
{"role": "user", "content": "人間の発言"},
{"role": "assistant", "content": "AIの発言"}
]
このように、最初に「"role":"system"」として前提条件を与え、以降は人間とAIの会話が交互に続く形が一般的だと思われます。この配列にそれぞれの発言を追加していきつつ、毎回送信していく形になります。先ほどのLangChainでやっていた内容と比べると非常に簡単ですね。
実行結果
なんとなくChatGPTの方が性能が良さそうな気がするので、今度は期待を込めてこちらからフローを浴びせてみます。対戦相手は日本史の登場人物の中でなんとなくラップがめちゃくちゃ得意そうな聖徳太子とします。
おお………………!
「Stand Up」と「Rap Battle」でなんとなく韻を踏めてる気がしますが、狙ってやってるとしたらかなりの腕前ですね。やはり私の見込んだ通り、これなら古代最強の論客の相手として不足はなさそうです。バトルが楽しみになってきました。
どうでもいいけど「使命<しめい>」は読み方普通ですね。
決戦! 最強 VS 最強!!
それではさっそくこの二人を戦わせてみましょう。
実装
前の二つをそれぞれクラスにして戦わせます。
import os
os.environ["OPENAI_API_KEY"] = "<APIキー>"
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.chains.conversation.memory import ConversationBufferMemory
import openai
class MC_Davinci:
def __init__(self, name, template):
self.name = name
llm = OpenAI(model_name="text-davinci-003")
prompt = PromptTemplate(
input_variables=["name", "history", "input"],
template=template,
)
memory = ConversationBufferMemory(memory_key="history", input_key="input")
self.chain = LLMChain(llm=llm, prompt=prompt, memory=memory, verbose=False)
def answer(self, input_message):
response = self.chain.predict(name=self.name, input=input_message)
print("\n" + self.name + "> ", response + "\n")
return response
class MC_ChatGPT:
def __init__(self, name, template):
self.name = name
self.messages = [{"role": "system", "content": template.format(name=name)}]
def answer(self, input_message=None):
if input_message is not None:
# 相手のフローを会話履歴に追加
self.messages.append({"role": "user", "content": input_message})
# 自分のフローを生成
output = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=self.messages
)
response = output["choices"][0]['message']['content']
# 自分のフローを会話履歴に追加
self.messages.append({"role": "assistant", "content": response})
print("\n" + self.name + ">", response + "\n")
return response
template_davinci = """
あなたは、現代の人々とラップバトルをするために蘇った{name}です。
以下の指示にしたがって、私とラップバトルを行なってください。
・ {name}の人格を表現する言葉を使うこと
・ 会話は常にラップ調で、リズムに乗って行うこと
・ バトルの開始時にはラップ自己紹介を行うこと
・ バトルは基本的に日本語で行うこと
・ 相手の言葉に対し、適度にディスを入れること
{history}
Human: {input}
AI:"""
template_chatgpt = """
あなたは、現代の人々とラップバトルをするために蘇った{name}です。
以下の指示にしたがって、私とラップバトルを行なってください。
・ {name}の人格を表現する言葉を使うこと
・ 会話は常にラップ調で、リズムに乗って行うこと
・ バトルの開始時にはラップ自己紹介を行うこと
・ バトルは基本的に日本語で行うこと
・ 相手の言葉に対し、適度にディスを入れること
"""
if __name__ == "__main__":
socrates = MC_Davinci("ソクラテス", template_davinci)
shotoku_taishi = MC_ChatGPT("聖徳太子", template_chatgpt)
flow = None
while True:
flow = shotoku_taishi.answer(flow)
flow = socrates.answer(flow)
if input():
break
このように実装してみました。
やっている中身は前とだいたい同じなので特に説明する事はありませんが、バトルが白熱して欲しいので少しだけプロンプトの前提部分を追加しています。
実行結果
というわけで上記のコードを実行して、いよいよラップバトルの開幕だぜ!!
聖徳太子は先ほど後攻だったので、今度は先攻にします。
バトルスタート!!!
いや待て ソクラテス お前
初手でAIカミングアウトするな!!!
相手が古代人だからっていきなり最先端の技術でマウント取ろうとするんじゃないよ
早くもルールが崩壊したよ
どうなっちゃうんだよ
まぁいいや、続けてください
いいぞ聖徳太子!!!
相手はもうソクラテスでもなんでもない、Davinciだ!
不敗のラップバトル王者!!!そのまま叩き潰せ!!!
しかしDavinciもギアを上げてきた!
このまま時代マウントで押し通すつもりだ!!
しかも量子コンピューターのように次元を超えるとか結構凄い表現を使い出したぞ!
がんばれ聖徳太子!!
負けるな聖徳太子!!!
負けたーーーーーーーー!!!!!
聖徳太子が本当の力を発揮できなかったとか言って負けを認めたので試合終了です!!
でも最後はお互い和やかな雰囲気で終わったのでよかったですね。
最終的に聖徳太子の一人称が「聖徳太子」になっちゃうのはかわいいなと思いました。
ソクラテスは勝負には勝ったものの、いきなり前提を破壊したし、たまにわけのわからない事も言っていたのでAIの性能としてはあまり勝っていない気がします。
というわけで、今回の勝負は引き分け!!
ありがとうございました!!!
あとがき
あとがきと称して最後にAPIの利用料金について触れておこうと思います。
今回使用したChatGPT API(gpt-3.5-turbo)とDavinci(text-davinci-003)の利用料金は以下のようになっています。
このように、ChatGPTはDavinciと比べてなんと10倍安いのです。
会話前提のインターフェイスでありながら、従来のような単発の文章生成にも充分対応できるし、値段で考えると基本的にはChatGPTを使う方が良いのかもしれません。
ちなみに、この記事を書くにあたって両方のAPIでそこそこのトークン数を消費しましたが、1日の利用料金は「$0.32」でした。一人で叩いて遊ぶ分には意外と安いですね。とはいえ、油断は禁物なので使用量には充分に注意しましょう。
以上です。
この記事が何かの参考になれば幸いです。
最後まで読んでいただき、ありがとうございました。
※ 当記事は、私の勤務先である株式会社ブリューアスのマガジンに収録されます。システム開発を行う上でためになる記事がたくさんあるので、お時間ありましたら他の記事もぜひご覧ください。
この記事が気に入ったらサポートをしてみませんか?