GeminiとAlexaの連携

# はじめに
Alexaスキルの制作が初めてだったり、作り始める直前にGeminiのアップデートが入ったりで、なかなか苦戦しました。結構いろんな記事を参考に作成したので、まとめておきます。
# AWS lambdaとAlexa skillの連携
https://www.kuretom.com/alexa-skill-lambda/

この記事のとおりにやっていたのですが、コードをそのまま貼ったらうまくいきませんでした。未だになぜかはよくわからないのですが、とりあえず以下の公式チュートリアルのオプション1を見て、コードを作りました。

https://developer.amazon.com/ja-JP/docs/alexa/alexa-skills-kit-sdk-for-python/develop-your-first-skill.html

```lambda_function.py
from ask_sdk_core.skill_builder import SkillBuilder

sb = SkillBuilder()

from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.utils import is_request_type, is_intent_name
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model import Response
from ask_sdk_model.ui import SimpleCard

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 = "ようこそ、アレクサスキルキットへ。こんにちは、と言ってみてください。"

        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("HelloWorldIntent")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speech_text = "こんにちは"

        handler_input.response_builder.speak(speech_text).set_card(
            SimpleCard("ハローワールド", speech_text)).set_should_end_session(
            True)
        return handler_input.response_builder.response

class HelpIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_intent_name("AMAZON.HelpIntent")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speech_text = "こんにちは。と言ってみてください。"

        handler_input.response_builder.speak(speech_text).ask(speech_text).set_card(
            SimpleCard("ハローワールド", speech_text))
        return handler_input.response_builder.response

class CancelAndStopIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return (is_intent_name("AMAZON.CancelIntent")(handler_input) or 
                is_intent_name("AMAZON.StopIntent")(handler_input))

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        speech_text = "さようなら"

        handler_input.response_builder.speak(speech_text).set_card(
            SimpleCard("ハローワールド", speech_text)).set_should_end_session(True)
        return handler_input.response_builder.response

class SessionEndedRequestHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_request_type("SessionEndedRequest")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        # クリーンアップロジックをここに追加します

        return handler_input.response_builder.response

from ask_sdk_core.dispatch_components import AbstractExceptionHandler

class AllExceptionHandler(AbstractExceptionHandler):

    def can_handle(self, handler_input, exception):
        # type: (HandlerInput, Exception) -> bool
        return True

    def handle(self, handler_input, exception):
        # type: (HandlerInput, Exception) -> Response
        # Cloudwatchログに例外を記録します
        print(exception)

        speech = "すみません、わかりませんでした。もう一度言ってください。"
        handler_input.response_builder.speak(speech).ask(speech)
        return handler_input.response_builder.response

sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(HelloWorldIntentHandler())
sb.add_request_handler(HelpIntentHandler())
sb.add_request_handler(CancelAndStopIntentHandler())
sb.add_request_handler(SessionEndedRequestHandler())

sb.add_exception_handler(AllExceptionHandler())

handler = sb.lambda_handler()

```
基本的にはペタペタ貼り付けただけのものですが、一箇所だけ修正した箇所が以下です。
```lambda_function.py
class CancelAndStopIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_intent_name("AMAZON.CancelIntent")(handler_input)
                or is_intent_name("AMAZON.StopIntent")(handler_input)
```
これだと構文エラー？を、起こすみたいです。よく分かってないんですけど……。ここを以下のように変更しています。
```lambda_function.py
class CancelAndStopIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return (is_intent_name("AMAZON.CancelIntent")(handler_input) or 
                is_intent_name("AMAZON.StopIntent")(handler_input))
```

# Geminiを使うためのライブラリをLambdaで使えるようにする
lambdaで直接pipを使うことは出来ないので、まずlambdaに依存関係やライブラリをアップロードする必要があります。

そのzipファイルをwindowsで作っても上手くいかないそうなので、WSLを使います。また、lambda関数のランタイムに合わせてpython3.9のインストールも行います。
### WSLのインストールと起動
https://self-development.info/%e3%80%90windows-11%e3%80%91wsl%e3%82%92%e7%94%a8%e3%81%84%e3%81%9fubuntu%e3%81%ae%e3%82%a4%e3%83%b3%e3%82%b9%e3%83%88%e3%83%bc%e3%83%ab/

この記事の通りにインストールしましたが、私の環境では```wsl```だけ打っても起動ができませんでした。原因はdockerをインストールしていたためだと思われます。```wsl -l -v```のコマンドで、複数出てきた場合は、どれを起動するか指定しないといけないと思われます。以下のコマンドを打ちます。
```
wsl -d Ubuntu-20.04
```
### python3.9のインストール
以下を見ながらやって、特に問題なく行きました。

https://ja.linux-console.net/?p=16324

### lambdaにpythonライブラリをインストール
https://self-development.info/%e3%80%902024%e5%b9%b4%e3%80%91aws-lambda%e3%81%b8%e3%81%aepython%e3%83%a9%e3%82%a4%e3%83%96%e3%83%a9%e3%83%aa%e3%81%ae%e3%82%a4%e3%83%b3%e3%82%b9%e3%83%88%e3%83%bc%e3%83%ab/

この記事で、```/usr/local/bin/python3.12 -m venv venv```というコマンドがありますが、pythonのバージョンを書き換えるだけではうまくいきませんでした。ディレクトリ構成が違ったようです。python3.9がどこにあるかは、以下のコマンドで調べました。
```
which python3.9
```
後は、```pip install setuptools -t python```のsetuptoolsのところを、 google-genaiに書き換えればOKです。
```
$ /usr/bin/python3.9 -m venv venv
$ /usr/bin/python3.9 -m venv tmp_venv
$ source tmp_venv/bin/activate
$ mkdir python
$ pip install google-genai -t python
$ zip -r setuptools_layer.zip python
```
geminiを使うためのライブラリは、ついこの間まで、google-generativeaiでした。たまに変わるみたいなので、バージョンが違うものを使う場合は、確認しておきましょう。
後は、記事通りにすればいいです。

# lambda関数にgeminiのチャットを実装する
### gemini APIキーを環境変数に登録
lambda関数の設定から、環境変数へ行き、キーと値を登録します。
Gemini APIキーはhttps://aistudio.google.com/
のGet API keyから、"APIキーを作成"で作れます。
### lambda関数にコードを貼り付ける
これをコードの先頭に貼ります(先頭でなくてもいいけど、Handlerより前のところに貼ってください)
```lambda_function.py
import os
from google import genai
from google.genai import types
from google.genai.types import (
    Content,
    Part,
)

YOUR_API_KEY = os.environ["GEMINI_API_KEY"]
client = genai.Client(api_key=YOUR_API_KEY)
# 履歴をhistoryとして作成
my_history = [
    Content(role="user", parts=[Part(text="簡潔にお答えください")]),
    Content(role="model", parts=[Part(text="了解")]),
]

chat = client.chats.create(
    model="gemini-2.0-flash",
    history=my_history )
```
LaunchRequestHandlerクラスの後ろに、以下を貼り付けます。
```lambda_function.py
class GeminiIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        return is_intent_name("GeminiIntent")(handler_input)

    def handle(self, handler_input):
        slots = handler_input.request_envelope.request.intent.slots
        question = slots["question"].value

        try:
            response = chat.send_message(question)
            speak_output = response.text
        except:
            # Handle Error
            speak_output = question#response.text

        

        speech_text = speak_output
        return (
            handler_input.response_builder
            .speak(speech_text)
            .ask("他に質問がありますか？")
            .response
        )
```
ちなみに、
```lambda_function.py
# 履歴をhistoryとして作成
my_history = [
    Content(role="user", parts=[Part(text="簡潔にお答えください")]),
    Content(role="model", parts=[Part(text="了解")]),
]
```
の部分についてですが、これの"簡潔にお答えください"の部分は、いい感じに書き換えてください。音声対話で長々話されてもな…という理由でこのように書いていますが、これだけだと色々と対応できないこともあります。
もう少し具体的な文字数制限を掛けたり、聞き間違いに対応できるようになにかプロンプトを考えたほうがいいと思います。
### Alexa developer consoleの設定
Intentsで、+ Add Intentを押してGeminiIntentを作成します。
Sample Utterancesに{question}と入力して＋をクリック。
Intent SlotsのNAMEにquestionと入力して＋をクリックし、SLOT TYPEでAMAZON Languageを選択。
Build skillを押してビルドします。
# テスト
Lambda関数のテストからテストをして成功したら、Alexa developer consoleでもテストをしましょう。
# 参考にさせてもらった記事
記事内ではリンクを貼っていませんでしたが、以下の記事も参考にしました。

https://www.kuretom.com/alexa-chatgpt/

https://zenn.dev/mkj/articles/061494e83b848b#1.7-%E4%BC%9A%E8%A9%B1%E5%B1%A5%E6%AD%B4%E3%81%AE%E7%B6%9A%E3%81%8D%E3%81%8B%E3%82%89%E4%BC%9A%E8%A9%B1%E3%81%95%E3%81%9B%E3%82%8B%E5%A0%B4%E5%90%88

https://qiita.com/N_H_tennis/items/b30ed6370ccf959407f5