こんにちは!今回は、Alexa Skills KitとChatGPT APIを組み合わせて、
ユーザーが質問をすると回答を返すスキルを開発する方法について説明します。
このスキルは、置物となっていることが多いAlexa君を実用的なものにしてくれる、、、はず。
開発環境の設定
まずは、開発環境の設定から始めましょう。
1. Alexa Skills Kitの設定
Alexa Skills Kitを使用するためには、開発者アカウントを作成する必要がある。
アカウントが作成されたら、Alexa Developer Consoleにログインし、スキルをつくっていく。
2. ChatGPT APIの設定
ChatGPT APIを使用するためには、OpenAIのAPIキーが必要です。
OpenAIのWebサイトからAPIキーを取得し、APIエンドポイントを設定。
Alexa Skills Kit側の実装
1. 新しいスキルを作成
今回は試しにChatGPTという名前にしました。
プライマリーロケールは日本語でいいと思います。後から変更可能です。
2. インテンドの作成
インテンドはユーザーがスキルを呼び出した後に実行する処理を選択するための文字です。
例えば
「アレクサ、Amazon musicで音楽を流して」とか
「アレクサ、COOKPADでポテトサラダのレシピを教えて」とかの太字部の部分ですね。
そして、サンプル発話といわれているのが、
「COOKPADでポテトサラダのレシピを教えて」の全体の部分です。
このサンプル発話の中に{}でくくったインテンドスロット名の所を、Alexaは入力として変数に格納し、Lambdaに送信します。
ここでは、「アレクサ、ChatGPTで質問ダイエットに向いている食事は?」のような文章を想定しています。
そして、インテンド名"query"に"ダイエットに向いている食事は?"の文章を格納しています。
スロットタイプをAMAZON.SearchQueryにすることで、どのような文字でも自動的に認識して入力してくれるようにしています。
本当は”質問”というサンプル発話も無くして、ユーザが自在に音声を入力できるようにしたいのですが、Alexaの制限でできないようです。。
Alexa Skills KitとChatGPT APIの連携
Alexa Skills KitとChatGPT APIを連携するためには、以下の手順が必要。
- Alexa Skills KitからのリクエストをLambda関数で受け取る。
- リクエストからユーザーの発話を抽出する。
- ChatGPT APIに発話を送信し、生成された回答を取得する。
- Alexa Skills KitのレスポンスJSONに回答を設定して返却する。
と、やることはいたってシンプル。
次の項目で、Lambda側の実装について説明していきます。
Lambda関数の作成
Alexaからのリクエストを受け付け、ChatGPT APIに処理を渡す関数を作成していきます。
まずは全体のコード例を載せて、その後に個々の部分について解説していきます。
import openai
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.skill_builder import CustomSkillBuilder
from ask_sdk_core.utils import is_intent_name, is_request_type
from ask_sdk_model import Response
from ask_sdk_model.ui import SimpleCard
sb = CustomSkillBuilder()
class LaunchRequestHandler(AbstractRequestHandler):
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return is_request_type("LaunchRequest")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speech_text = "ChatGPTです。質問は何ですか?"
handler_input.response_builder.speak(speech_text).set_card(
SimpleCard("ハローワールド", speech_text)).set_should_end_session(
False)
return handler_input.response_builder.response
class HelloWorldIntentHandler(AbstractRequestHandler):
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return is_intent_name("作成したインテンド名")(handler_input)
def handle(self, handler_input):
openai.api_key = <"your_api_key">
try:
# Alexaからのリクエストを取得
request = handler_input.request_envelope.request
query = request.intent.slots["query"].value + "。50字以内で回答して。"
# ChatGPT APIにリクエストを送信
data = {'text': query}
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": query}
]
)
response_text = response["choices"][0]["message"]["content"]
# レスポンスを生成
handler_input.response_builder.speak(response_text).set_card(
SimpleCard("質問の回答", response_text)).set_should_end_session(True)
return handler_input.response_builder.response
except Exception as e:
logger.error(e)
traceback.format_exc()
error_response_text = "エラーが発生しました。"
handler_input.response_builder.speak(error_response_text).set_card(
SimpleCard("エラー", error_response_text)).set_should_end_session(True)
return handler_input.response_builder.response
sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(HelloWorldIntentHandler())
lambda_handler = sb.lambda_handler()
この例では、Alexaからのリクエストから発話内容を抽出し、ChatGPT APIに送信して、生成された回答を変数response_textに設定。次に、ASKのレスポンスを生成し、回答を設定している。
ただし、以下ライブラリはLambda標準では用意されていないため、別途Layerを作成する必要アリ。
- ask-sdk-core
- ask-sdk-model
- openai
レイヤーの作成方法は以下参照
まず、「アレクサ、ChatGPTを開いて」のように、スキル名だけが呼び出されたときの処理は以下(LaunchRequestHandlerクラス)
class LaunchRequestHandler(AbstractRequestHandler):
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return is_request_type("LaunchRequest")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speech_text = "ChatGPTです。質問は何ですか?"
handler_input.response_builder.speak(speech_text).set_card(
SimpleCard("ハローワールド", speech_text)).set_should_end_session(
False)
return handler_input.response_builder.response
次に、作成したインテンド名で呼び出されるのが以下。
class HelloWorldIntentHandler(AbstractRequestHandler):
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return is_intent_name("作成したインテンド名")(handler_input)
def handle(self, handler_input):
openai.api_key = <"your_api_key">
try:
# Alexaからのリクエストを取得
request = handler_input.request_envelope.request
query = request.intent.slots["query"].value + "。50字以内で回答して。"
# ChatGPT APIにリクエストを送信
data = {'text': query}
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": query}
]
)
response_text = response["choices"][0]["message"]["content"]
# レスポンスを生成
handler_input.response_builder.speak(response_text).set_card(
SimpleCard("質問の回答", response_text)).set_should_end_session(True)
return handler_input.response_builder.response
can_handle関数のis_intent_nameでインテンド名を判定し、その後のhandle関数に処理を渡しています。
スロットリングに格納された質問内容は以下で取得され、"query"変数に格納されています。
request = handler_input.request_envelope.request
request.intent.slots["query"].value
そしてここがミソ。Alexaはデフォルトで8秒レスポンスがないとタイムアウトしてしまう。
そこでChatGPTに文字制限をしてレスポンスの文章を返してもらう。(しないと平気で30秒とかかかります。)
query = request.intent.slots["query"].value + "。50字以内で回答して。"
あとは以下でChatGPT APIにリクエストを送信
data = {'text': query}
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": query}
]
)
レスポンスボディに値を設定して完了です。
response_text = response["choices"][0]["message"]["content"]
# レスポンスを生成
handler_input.response_builder.speak(response_text).set_card(
SimpleCard("質問の回答", response_text)).set_should_end_session(True)
return handler_input.response_builder.response
最後に
Alexa君の置物化がこれで何とかなればいいのですが、、
申請以降のフェーズに関しては需要があれば書こうかなぁ、、