LoginSignup
18
22

More than 1 year has passed since last update.

AlexaとChatGPT APIを組み合わせて質問に回答するスキルを開発しよう!

Last updated at Posted at 2023-03-22

こんにちは!今回は、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エンドポイントを設定。

参考: OpenAI API キーを取得する方法

Alexa Skills Kit側の実装

1. 新しいスキルを作成

今回は試しにChatGPTという名前にしました。
image.png
プライマリーロケールは日本語でいいと思います。後から変更可能です。

2. インテンドの作成

インテンドはユーザーがスキルを呼び出した後に実行する処理を選択するための文字です。
例えば
「アレクサ、Amazon musicで音楽を流して」とか
「アレクサ、COOKPADでポテトサラダのレシピを教えて」とかの太字部の部分ですね。

そして、サンプル発話といわれているのが、
COOKPADでポテトサラダのレシピを教えて」の全体の部分です。

このサンプル発話の中に{}でくくったインテンドスロット名の所を、Alexaは入力として変数に格納し、Lambdaに送信します。
image.png

ここでは、「アレクサ、ChatGPTで質問ダイエットに向いている食事は?」のような文章を想定しています。
そして、インテンド名"query"に"ダイエットに向いている食事は?"の文章を格納しています。
スロットタイプをAMAZON.SearchQueryにすることで、どのような文字でも自動的に認識して入力してくれるようにしています。

本当は”質問”というサンプル発話も無くして、ユーザが自在に音声を入力できるようにしたいのですが、Alexaの制限でできないようです。。

Alexa Skills KitとChatGPT APIの連携

Alexa Skills KitとChatGPT APIを連携するためには、以下の手順が必要。

  1. Alexa Skills KitからのリクエストをLambda関数で受け取る
  2. リクエストからユーザーの発話を抽出する。
  3. ChatGPT APIに発話を送信し、生成された回答を取得する。
  4. 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

レイヤーの作成方法は以下参照

AWS Lambda Layers作成から紐付けまでさらっと解説

まず、「アレクサ、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君の置物化がこれで何とかなればいいのですが、、
申請以降のフェーズに関しては需要があれば書こうかなぁ、、

18
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
22