本記事は「Amazon Bedrock Advent Calendar 2023」、12月20日の記事です。
はじめに
こんにちは、KDDIアジャイル開発センターのはしもと(仮名)です。
Amazon Bedrock 大好き!Amazon Bedrock 大好き!
アドベントカレンダーを書いているということは今年ももうすぐ終わりというわけです。早いですね。
IT業界で今年最も話題になったものといえば、間違いなく「生成AI」でしょう。
一時は OpenAI・Microsoft 連合の一強かと思われましたが、ビッグテック各社、独自の大規模言語モデルを開発するだけでなく、自社サービスに生成AIを統合するといった動きも盛んで、まだまだ競争が収まる気配はありません。
待望の Amazon Bedrock の登場
登場...?
ChatGPT が巷を席巻した今年4月、AWS は生成AIサービスである Amazon Bedrock を発表しました。
全国のAWSエンジニアは 来たか...! と胸を躍らせたのではないでしょうか。
ただ....ええ....あの...なかなかGAされなくて.......
AWSさーん...おーい....Amazon Bedrockまだうちに来てないです.......
Azureつらいのではやくしてくれないかなー...なんて.....
待ちに待ちすぎて街ができました
登場!!!!!
re:Invent まで引っ張るつもりかと思い始めた2023年9月28日、Amazon Bedrock が正式リリースとなりました!!!
選択できる基盤モデルには、Amazon が開発したものだけでなく、Stable Diffusion で有名な Stability AI, Llama2 を開発した Meta, 元OpenAI のスタッフが立ち上げた Anthropic の Claudeなど、分野を牽引する企業のものから自由に選択して使用することができます。
基盤モデルだけではありません。毎年11-12月ごろに開催される AWS re:Invent では、大注目される中、Bedrock のさまざまなアップデートが発表されました。
12月に入る前にこれだけアップデートがあれば、ブログのネタには困りませんね。
今年のアドベントカレンダーは大丈夫そうです、おつかれっした!
アドカレで書くことない( ^ q ^ )
気づいておりました。AWSエンジニア、Bedrock に興味津々なのです。
毎日投稿される検証ブログやSNSの投稿はもちろん、勉強会のテーマになれば数百人が参加するほどの大盛況。
このアドベントカレンダーも公開されるやいなや続々とエントリーが入り、気づけばシーズン2ができています(!)。
サービスの入門記事、re:Invent 2023 で発表された新機能の解説、Bedrock で使用できる言語モデルのプロンプトのお作法などなど。
ここで私はアドベントカレンダーに正面から挑むことを諦めました。
ビッグテックの中の人ができないことをやろう
稚拙だとかネタだとか思われてもいい、誰もやっていないことをやろう。
もっといえばつよつよエンジニアができないことをやろう。
そして思いつきました、OpenAI(Microsoft) や Google の 言語モデルも使ってしまおうと。
Agents for Amazon Bedrock 使うからアドベントカレンダーのテーマ的にもきっとセーフ!
やること
1つの質問に対して AWS (Claude V2), OpenAI (GPT-4), Google (Gemini Pro) の超精鋭LLMたちから回答を取得できるようにします。
各言語モデルへのアクセスは、新機能「Agents for Amazon Bedrock」を使ってよしなにやってもらいます!!!
構成図
- 操作は AWSマネジメントコンソール > Bedrock のサービス画面から行います。
- Claude V2 をベースの言語モデルとしてエージェントを構成します。
- このエージェントは必要に応じてLambda関数を実行し、Claude V2, GPT-4, Gemini ProのAPIを呼び出し、得られた回答を返します。
- S3バケット内には、APIスキーマを記述したyamlファイルを事前に準備し、アクショングループ作成時にこのスキーマを利用するよう、オブジェクトのURIを指定しておきます。
最終成果物
3つのLLMたちに不労所得について質問した様子です、なぜこんなことになったのか。
「Gemini」という文字列を出力させたかったのですが、<REDACTED>
にマスクされてしまいました。
大人の事情でしょうか。
実装
Gemini Pro さ〜〜〜ん!!!
初めに、各LLMにリクエストを送信するためのLambda関数を作成します。
リクエスト、レスポンスのフォーマットが決まっているのに注意し、公式ドキュメントや他の方の検証記事を参考に実装を行います。
実装は以下のとおりです。
apiPath
が /ask-gemini
の場合に、Gemini Pro にリクエストを送信します。
※必要なライブラリは別途レイヤーを作成し、関数に追加しています
Lambda実行スクリプトとAPIスキーマ
import os
PROMPT = """
Answer the following question in Japanese.
Question: {}
"""
def lambda_handler(event, context):
print(event)
api_path = event["apiPath"]
question = event["requestBody"]["content"]["application/json"][
"properties"
][0]["value"]
content = PROMPT.format(question)
if api_path == "/ask-gemini":
import google.generativeai as genai
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
model = genai.GenerativeModel("gemini-pro")
response = model.generate_content(content)
response_body = {"application/json": {"body": response.text}}
action_response = {
"actionGroup": event["actionGroup"],
"apiPath": event["apiPath"],
"httpMethod": event["httpMethod"],
"httpStatusCode": 200,
"responseBody": response_body,
}
api_response = {"messageVersion": "1.0", "response": action_response}
print(api_response)
return api_response
実装は Google の公開しているドキュメントを参考にしています
https://ai.google.dev/tutorials/python_quickstart
openapi: 3.0.0
info:
title: "get answer from external LLMs"
version: 1.0.0
paths:
/ask-gemini:
get:
summary: "Ask Gemini"
description: "You can get answer from Gemini, which is a large language model created by Google."
operationId: gemini
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
question:
type: string
description: question to ask Gemini
required:
- question
responses:
"200":
description: "success"
content:
application/json:
schema:
type: object
properties:
body:
type: string
Lambda実行ログ
Bedrock Agent の設定を行った上で、日本の大都市を3つ教えて
と聞いてみます。
Lambdaの実行ログに、eventの内容と、LLMが生成した回答を含むレスポンスが確認できます。やったね!!!!
このあたりからある感情が湧いてきます。私はなにをやっているんだろうと。
GPT-4 さ〜〜〜ん!!!
この調子で進めます。後述する Action Group を別で作成した上で、Lambda関数の実装とAPIスキーマを修正します。
GPT-4へのリクエストに対応したLambda関数の実装と、新たに作成したAPIスキーマの詳細はこちらです。
apiPath
が/ask-gpt4
の場合の処理を追加しています。
Lambda実行スクリプトとAPIスキーマ
(中略)
if api_path == "/ask-gpt4":
from openai import OpenAI
client = OpenAI()
client.api_key = os.environ.get("OPENAI_API_KEY")
messages = [
{"role": "user", "content": content},
]
response = client.chat.completions.create(
model="gpt-4", messages=messages
)
response_body = {
"application/json": {"body": response.choices[0].message.content}
}
action_response = {
"actionGroup": event["actionGroup"],
"apiPath": event["apiPath"],
"httpMethod": event["httpMethod"],
"httpStatusCode": 200,
"responseBody": response_body,
}
api_response = {"messageVersion": "1.0", "response": action_response}
print(api_response)
return api_response
openapi: 3.0.0
info:
title: "get answer from external LLMs"
version: 1.0.0
paths:
/ask-gpt4:
get:
summary: "Ask GPT-4"
description: "You can get answer from GPT-4, which is a large language model created by OpenAI."
operationId: gpt4
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
question:
type: string
description: question to ask GPT-4
required:
- question
responses:
"200":
description: "success"
content:
application/json:
schema:
type: object
properties:
body:
type: string
Lambda実行ログ
先ほどと同様の質問をしてみます。
ask-gemini
とask-gpt4
両方のアクショングループが呼び出されています。セッションIDが同一なので、Bedrock Agent の一連の処理の中でこれらが連続して実行されたようです。
Claude V2 さ〜〜〜ん!!!
最後はおなじみの Claude V2 です。
説明は割愛し、Pythonスクリプトの追加箇所およびAPIスキーマのみ記載します。
Lambda実行スクリプトとAPIスキーマ
(中略)
if api_path == "/ask-claudev2":
import boto3
import json
bedrock_runtime = boto3.client(
service_name="bedrock-runtime", region_name="us-east-1"
)
modelId = "anthropic.claude-v2"
accept = "application/json"
contentType = "application/json"
content = "\n\nHuman:" + content + "\n\nAssistant:"
body = json.dumps({"prompt": content, "max_tokens_to_sample": 200})
response = bedrock_runtime.invoke_model(
body=body, modelId=modelId, accept=accept, contentType=contentType
)
decoded_response = response.get("body").read().decode()
response_body = {
"application/json": {
"body": json.loads(decoded_response)["completion"]
}
}
action_response = {
"actionGroup": event["actionGroup"],
"apiPath": event["apiPath"],
"httpMethod": event["httpMethod"],
"httpStatusCode": 200,
"responseBody": response_body,
}
api_response = {"messageVersion": "1.0", "response": action_response}
print(api_response)
return api_response
openapi: 3.0.0
info:
title: "get answer from external LLMs"
version: 1.0.0
paths:
/ask-claudev2:
get:
summary: "Ask claudev2"
description: "You can get answer from ClaudeV2, which is a large language model created by Anthropic."
operationId: claudev2
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
question:
type: string
responses:
"200":
description: success
content:
application/json:
schema:
type: object
properties:
body:
type: string
Lambda実行ログ
再度同じ質問を投げると、ClaudeV2にもリクエストしていることが分かります。
何やら不穏ですね。ただやりたかったことはこれでできました!
Bedrock Agents の設定
Bedrock Agents の設定についても、抜粋して記載します。
全体の構成について
Action groups
を使用する言語モデル単位で作成しています。
当初、ひとつのアクショングループで試していたのですが、複数APIの実行がうまくいかなかったためこのようになりました。
このあたりの仕様について記載されているドキュメント等あれば教えていただけるとありがたいです。
Knowledge basesは不要のため何も設定していません。
Post-processing template の上書き
出力フォーマットに関する指示を Agent の Instruction に書いたのですが、うまく効きませんでした。
そこで、Advanced prompts
から、Post-processing
のテンプレートに一部追記する形でフォーマットの指示を与えたところ、それっぽい出力を得ることができました。
まとめ
Agents for Amazon Bedrock を使って(無理やり)複数のLLMを呼んでみました。
お手軽便利機能だと思っていましたが、ちゃんと触ってみると結構複雑で、一見問題なさそうな入力でも弾かれてしまうなど、使いこなせるようになるにはなかなか骨が折れそうだなと思いました。
まだまだ謎多き Agents for Amazon Bedrock。
アドベントカレンダーにも同機能に関する記事がたくさんあるので、年末年始はじっくり勉強しようと思います。
はしもと(仮名)でした。
Appendix
何もコメントはしませんが、これを貼って締めたいと思います。
太平の世は訪れるのだろうか...