OpenAI、Google、Anthropicの3つのAIに同じ質問をして、その結果を集約する「MAGI」システムを作る #役に立たないLLM
新世紀エヴァンゲリオンという名作アニメに、MAGIというシステムが出てきます。主人公たちが属する組織の中枢システムを担っており、3つの独立した思考回路を持っていて決議する仕組みです。
オタクとしてはMAGIに憧れがあります。
最近、Google CloudのPaLM2、AnthropicのClaudeがそれぞれ日本でも使えるようになり、API経由で利用することが出来るようになりました。AnthropicのClaudeのAPIはAWSのBedrockから利用します。ここにOpenAIのGPT-3.5-Turbo、GPT-4を合わせれば、「それぞれ独立したAI」によるMAGIを作れそうだと思ったので、さっそくやってみることにしました。
準備
私はWindows 11環境のローカルで動かしました。多分、すこし調整すればGoogle Colabでも動かせると思います。
まず、Google cloud、AWSそれぞれのCLI(コマンドラインインターフェース)をインストールして、初期設定を行い、CLI上でログインや認証を済ませておきます。このあたりは良い解説がたくさんあるので、それらに譲ります。
Google Cloudは、Vertex AIというサービス群の一部が言語AIです。以下の記事を参考に、プロジェクトを作成し、必要なAPIを有効にしておきます。
AWSでは、Bedrockが一番手軽に言語AIを利用出来るサービスです。Bedrockでは、モデル利用をリクエストしておかないと利用出来ないので、以下のページを参考にしてリクエストします。なお、リージョンはus-east-1が一番たくさんの種類のモデルを使えるのでこれがオススメです(東京リージョンはまだ使えるモデルが少ない)
(ちなみにこのAWSの解説ページは、機械翻訳の文章で意味が分かりにくいので、機会があればこの部分の解説記事も書きます…)
OpenAIのほうは、API経由での利用ができるようになっていればOKです。
必要なライブラリのインストール
venv環境を作って立ち上げた後で、以下のpipコマンドで必要なライブラリをインストールします
pip install openai gradio python-dotenv google-cloud-aiplatform boto3
openaiがOpenAIのGPT-3.5-Turbo/GPT-4、google-cloud-aiplatformがGoogle cloudのVertex AI、boto3がAWS Bedrockを、それぞれ扱う際に必要になるライブラリです。
OpenAIのAPI Keyや、Google cloudのプロジェクトIDなどは、.envファイルに保存してあるので、それを読み込むためにpython-dotenvも入れています。
ソースコード
以下の様になります。試行錯誤のあとがあるのであまり綺麗ではありませんが。
# GPT-4、Google PaLM2、Claudeに同じ質問をし、その賛成反対の多数決をとるシステム
import json
import openai
import gradio as gr
import dotenv
import vertexai
from vertexai.language_models import TextGenerationModel
import boto3
from concurrent.futures import ThreadPoolExecutor
dotenv.load_dotenv('.env')
openai_api_key = dotenv.get_key('.env', 'OPENAI_KEY')
vertexai_project = dotenv.get_key('.env', 'VERTEXAI_PROJECT')
# OpenAI GPT-3.5に質問を送信する関数
def ask_gpt35turbo(question: str) -> str:
openai.api_key = openai_api_key
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system",
"content": "あなたは答えが1つには決まらない問題に対して、"
"一つの視点を提供するアドバイザーとしての役割を持っています。"
"なので、以下の質問に対して、"
"必ず「賛成」または「反対」のどちらかの立場を表明してください。"
"最初に賛成であるか反対であるかを明示し、その後に理由を述べてください。"},
{"role": "user",
"content": question},
],
temperature=0.2,
max_tokens=512
)
answer = response.choices[0].message["content"]
print(f"Response from GPT-3.5-Turbo: {answer}")
return answer
# OpenAI GPT-4に質問を送信する関数
def ask_gpt4(final_answer: str, gpt35answer: str, palm2answer: str,
claude2answer: str, question: str) -> str:
openai.api_key = openai_api_key
response = openai.ChatCompletion.create(
model="gpt-4",
messages=[
{"role": "system",
"content": "あなたは答えが1つには決まらない問題に対して、"
"一つの視点を提供するアドバイザーとしての役割を持っています。"
f"あなたは以下の問題に対して{final_answer}であるという立場を動かさずに、"
f"以下の3人の賢者の意見を参考にしながら視点をまとめてください。"
f"賢者カスパーの意見: {gpt35answer}"
f"賢者バルタザールの意見: {palm2answer}"
f"賢者メルキオールの意見: {claude2answer}"},
{"role": "user",
"content": question},
],
temperature=0.2,
max_tokens=1024
)
answer = response.choices[0].message["content"]
return answer
# Google PaLM2に質問を送信する関数
def ask_palm2(question: str) -> str:
vertexai.init(project=vertexai_project,
location="us-central1")
parameters = {
"candidate_count": 1,
"max_output_tokens": 512,
"temperature": 0.2,
"top_p": 0.8,
"top_k": 40
}
model = TextGenerationModel.from_pretrained("text-bison")
response = model.predict(
"あなたは答えが1つには決まらない問題に対して、"
"一つの視点を提供するアドバイザーとしての役割を持っています。"
"なので、以下の質問に対して、"
"必ず「賛成」または「反対」のどちらかの立場を表明してください。"
"最初に賛成であるか反対であるかを明示し、その後に理由を述べてください:"
"====================\n"
"質問:"
+ question,
**parameters
)
print(f"Response from PaLM2: {response.text}")
return response.text
# AWS BedrockのClaudeに質問を送信する関数
def ask_claude(question: str) -> str:
bedrock = boto3.client(service_name='bedrock-runtime',
region_name='us-east-1')
body = json.dumps({
"prompt": "\n\nHuman: あなたは答えが1つには決まらない問題に対して、"
"一つの視点を提供するアドバイザーとしての役割を持っています。"
"なので、以下の質問に対して、"
"必ず「賛成」または「反対」のどちらかの立場を表明してください。"
"最初に賛成であるか反対であるかを明示し、その後に理由を述べてください:"
"====================\n"
"質問:"
f"\n{question}\n\nAssistant:",
"max_tokens_to_sample": 512,
"temperature": 0.2,
"top_k": 250,
"top_p": 1,
"stop_sequences": [
"\\n\\nHuman:"
],
"anthropic_version": "bedrock-2023-05-31"
})
kwargs = {
"modelId": "anthropic.claude-instant-v1",
"contentType": "application/json",
"accept": "*/*",
}
response = bedrock.invoke_model(body=body, **kwargs)
response_body = json.loads(response.get('body').read().decode('utf-8'))
print(f"Response from Claude: {response_body.get('completion')}")
return response_body.get('completion')
# それぞれの回答の冒頭20文字を見て、「賛成」または「反対」の文字を探す関数
def find_answer(answer: str) -> str:
first_20_chars = answer[:20]
position_agree = first_20_chars.find("賛成")
position_disagree = first_20_chars.find("反対")
if position_agree != -1 and position_disagree != -1:
# 両方が見つかった場合、最初に出現した方を返す
return "賛成" if position_agree < position_disagree else "反対"
elif position_agree != -1:
return "賛成"
elif position_disagree != -1:
return "反対"
else:
return "不明"
# それぞれの回答を集計し、多数決をとる関数
def vote(answers: list) -> str:
agree = 0
disagree = 0
for answer in answers:
if answer == "賛成":
agree += 1
elif answer == "反対":
disagree += 1
if agree > disagree:
return "賛成"
elif agree < disagree:
return "反対"
else:
return "不明"
# 質問を受け取り、それぞれのシステムに質問を送信し、回答を集計する関数
def print_result(question: str) -> str:
answers = [find_answer(ask_gpt35turbo(question)),
find_answer(ask_palm2(question)),
find_answer(ask_claude(question))]
final_answer = vote(answers)
return f"OpenAI GPT-3.5 Turbo: {answers[0]}\n" \
f"Google PaLM2: {answers[1]}\n" \
f"AWS SageMaker Claude: {answers[2]}\n" \
f"多数決: {final_answer}\n"
# 3つのAIの回答をGPT-4に渡して最終結果を得る関数
def magi_answer(question: str) -> str:
with ThreadPoolExecutor() as executor:
future_gpt35 = executor.submit(ask_gpt35turbo, question)
future_palm2 = executor.submit(ask_palm2, question)
future_claude = executor.submit(ask_claude, question)
# 各関数の実行が終了するまで待つ
gpt35answer = future_gpt35.result()
palm2answer = future_palm2.result()
claude2answer = future_claude.result()
vote_result = vote([find_answer(gpt35answer),
find_answer(palm2answer),
find_answer(claude2answer)])
final_answer = str(ask_gpt4(vote_result, gpt35answer, palm2answer,
claude2answer, question))
final_answer = ("3賢者の回答:\n"
f"カスパー(GPT-3.5-Turbo){find_answer(gpt35answer)}、 "
f"バルタザール(PaLM2){find_answer(palm2answer)}、 "
f"メルキオール(Claude instant){find_answer(claude2answer)} \n"
"多数決: " + vote_result + "\n\n"
"マギの回答:\n"
) + final_answer
return final_answer
# Gradioインターフェースの作成
iface = gr.Interface(
fn=magi_answer, # 上で定義した関数
inputs="text", # 入力はテキスト
outputs="text" # 出力もテキスト
)
# インターフェースの起動
iface.launch()
このスクリプトを起動すると、`Running on local URL: http://127.0.0.1:7860` と、Gradioのインターフェースが立ち上がります。http://127.0.0.1:7860 にアクセスすると、以下の画面がブラウザに表示されます。
このquestion部分に質問を入れると、3つのAIに質問をして、その答えをGPT-4がまとめて回答してくれます。それぞれのAPIの利用料金が発生するので、あんまり連発するとお金がかかるので慎重にやりましょう(笑)
ちなみに、Gradioの仕様なのか、Enterキーを押すと送信されてしまうので、別のテキストエディタなどで質問文を作ってからquestionのフォームに貼り付けるようにするといいです(この辺の回避方法を調査中…)
こういうシステムには、良い悪いを簡単には決められないことを聞いてみると面白い気がします。たとえば、つまらない金持ちとドキドキする貧乏のうち、貧乏と付き合いたいと言うと、賛成2、反対1という結果が出ました。
ちなみに、GradioのUI上には表示させていませんが、それぞれのAIは以下の様に回答しています。
これらをGPT-4がまとめてくれたのが、上記のスクリーンショットで得られた回答です。
別にこれが何かの役に立つことはないかもしれませんが、オタクとして、MAGIっぽいことができたのはとても楽しいです。
補足
GPT3の時代に、note CXOの深津さんがGPT3だけを使ってMAGIを作る記事もアップされていました。合わせて参考にしてみてください
宣伝
こういう、特に役に立たないけどなんだか面白いLLMの活用法を集めた技術同人誌を技術書典15に出そうと思って原稿を書いています。内容はnoteに書いた記事の再編集+noteには書かないタイプの描き下ろし記事になります。是非よろしくお願いいたします。
この記事が気に入ったらサポートをしてみませんか?