アレクサスキルで自由入力の手順 【2024/11版】

# 誰向けか
アレクサスキルで自由入力をしたいが、詰まっている人

# 記載しないこと
長くなってしまうので、以下は割愛します
※後述の「参考にした記事」に説明がありますので、興味があればそちらを参照してください
- アレクサスキルのゼロからの作成手順
  - 本記事ではIntentの作成、既存Lambdaの関数に処理を追記する形です
- そもそも自由入力とはなにか
- 自由入力の本来の使い方、仕組み

# 自由入力の使用の背景
アレクサ経由で「簡単にAIに質問や相談」ができるシステムを個人開発(以下)で作成
ただし、「キャリアフレーズ」という仕様があるおかげで使い勝手がとても悪かった

https://qiita.com/parayan0429/items/06c5350788afe6655171

※当時は「質問」を「キャリアフレーズ」に設定していました
例：質問 {プログラミングのFlutterって何？}
　↓
{}の中身がバックエンド側のLambdaに連携され、AIにリクエストとして送信、レスポンスをそのまま返し、レスポンス内容をアレクサが喋ってくれる

# 記事執筆の背景
以下の記事がとても参考になりましたが、「私のやり方が悪かった」のか「当時から仕様が変わった」のか分かりませんが、2024/11時点で成功しませんでした
※「スキルがリクエストに正しく応答できませんでした」と言われてしまいました

悪戦苦闘した結果「自由入力」ができるようになりましたので、私の対応方法を残し、悩んでいる人の一助になりたかった

## 参考にした記事
「自由入力の概念」、「最終的に使えるようになった足がかり」にもなりました。
この場を借りてお礼申し上げます。

https://qiita.com/hirokiwa/items/28426af6f051845b1a1f

https://zenn.dev/kun432/scraps/904ebc3874e0df

# 自由入力の手順
## ①アレクサスキル側の対応

### コンソールの手順
jsonだけ欲しい人は読み飛ばしてください
※余談ですが、このコンソールで日本語設定してるのにほぼ英語なのは私だけですかね？
　数ヶ月前は日本語だったんですが...

#### 1. Intentの追加
ここでは名前を「ChatGPTIntent」とします

:::note warn
名前は任意ですが、Lambdaのコードでこの名前を指定する箇所があります
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569056/718b8f6a-1bcb-ce78-b834-4c8318410b70.png)

#### 2. slotの設定
:::note warn
重要！！　Sample Utterancesは何も設定しません
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569056/b17a2439-0cd6-7977-4714-8b72ef627d4c.png)

slot名を設定し、＋アイコンをクリック
ここでは名前を「prompt」とします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569056/8da93ec5-8ce4-da81-4fcc-0e01387ca4ab.png)

typeは「AMAZON.SearchQuery」です
Edit Dialogをクリック(↓へ続く)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569056/f9d88e99-c3c3-c1d6-0d56-d26a19387693.png)

#### 3. Dialogの設定

Dialogタブでそれぞれ以下を入力後、＋アイコンをクリック
すべて設定できたら画面左上のSaveボタンをクリック
- Is this slot required to fulfill the intent?
    - ONに変更
- Alexa speech prompts
    - 必須なのでなにかしら入力
    - ※本来はアレクサがslotを収集する際に使用しますが、今回はLambdaで「アレクサの収集処理」をカット(行わないように)するため、実際には使用されません
- User utterances
    - ↑で作成したslot名を{}付きで入力

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569056/c215863e-3a32-06a2-b0de-a28397019bff.png)

#### 3. Dialog Delegation Strategyの設定
「disable auto delegation」に変更

:::note warn
重要！！
ここで「自動デリゲートを無効」にしておかないと、Lambdaでアレクサのslot収集がカットできず、動作確認でアレクサに「スキルがリクエストに正しく応答できませんでした」と言われます
ちなみに私はここで躓き、10時間ほど時間を溶かしました
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569056/0a478e34-0e8c-9ff4-885b-ddc33757c078.png)

#### 4. 保存とビルド
↑がすべて設定できたら画面左上のSaveボタンをクリックし、「Build skill」をクリック

:::note warn
重要！！
ビルドしないとスキルに反映されないため、ハマりやすいです
:::

## json
↑のコンソール手順を行った結果、以下のようなjsonが作成されます

流用する際、invocationNameは任意のスキル名にしてください
※promptsのidは全く同じだと上手くいかないかもです
　その場合は↑の「コンソールの手順」でお願いします
<details><summary>サンプル</summary>

```
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "{任意のスキル名}",
            "intents": [
                {
                    "name": "ChatGPTIntent",
                    "slots": [
                        {
                            "name": "prompt",
                            "type": "AMAZON.SearchQuery",
                            "samples": [
                                "{prompt}"
                            ]
                        }
                    ],
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": [
                        "バイバイ",
                        "終わり"
                    ]
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.NavigateHomeIntent",
                    "samples": []
                }
            ],
            "types": []
        },
        "dialog": {
            "intents": [
                {
                    "name": "ChatGPTIntent",
                    "delegationStrategy": "SKILL_RESPONSE",
                    "confirmationRequired": false,
                    "prompts": {},
                    "slots": [
                        {
                            "name": "prompt",
                            "type": "AMAZON.SearchQuery",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.1311813009944.922337381744"
                            }
                        }
                    ]
                }
            ],
            "delegationStrategy": "ALWAYS"
        },
        "prompts": [
            {
                "id": "Elicit.Slot.1311813009944.922337381744",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "なにか言ってみてください。"
                    }
                ]
            }
        ]
    }
}
```
</details>

# ②Lambda側の手順
## 前提
LambdaはPythonでデコレータを使用しています
※大した量ではないため、ノーデコレータの方やTypeScriptでも参考になると思います

## コード
### 1. ライブラリのimport
```python
from ask_sdk_model.dialog import ElicitSlotDirective
from ask_sdk_model import Intent, IntentConfirmationStatus, Slot, SlotConfirmationStatus
```

### 2. 自由入力の初期化
LaunchRequestと自由入力を処理するhandlerで使い回すため、グローバル変数として宣言します

```python
# インテント名、スロット名はAlexaスキル側の設定と合わせる
intent_name = "ChatGPTIntent"
slot_name = "prompt"
directive = ElicitSlotDirective(
    slot_to_elicit=slot_name,
    updated_intent=Intent(
        name=intent_name,
        confirmation_status=IntentConfirmationStatus.NONE,
        slots={
            slot_name: Slot(
                name=slot_name,
                value="",
                confirmation_status=SlotConfirmationStatus.NONE,
            )
        },
    ),
)
```

### 3. LaunchRequestで自由入力の初期化
「.add_directive(directive)」することが重要です
他のコードは何でもOKです
これにより、スキルを起動した時点で「自由入力」を受け付ける状態になります
```python
@sb.request_handler(can_handle_func=is_request_type("LaunchRequest"))
def launch_request_handler(handler_input):
    # type: (HandlerInput) -> Response
    speech_text = "エーアイモードに入ります。"

    return (
        handler_input.response_builder.speak(speech_text)
        .ask("なにか質問ですか")
        # 自由入力の初期化
        .add_directive(directive)
        .response
    )
```

### 4. 自由入力の処理
ここでも最後に「.add_directive(directive)」することが重要です
これにより、レスポンスを返却した後も常に「自由入力」を受け付ける状態になります
```python
@sb.request_handler(can_handle_func=is_intent_name("ChatGPTIntent"))
def chat_gpt__intent_handler(handler_input):
    # type: (HandlerInput) -> Response

    # スロットからAIにリクエストする文言を取得
    user_utterance = get_user_utterance(handler_input)

    # AIの処理を呼び出し
    chat_response = get_chat_response(user_utterance)

    # Alexaのレスポンスを生成
    return (
        handler_input.response_builder.speak(chat_response)
        # 自由入力の初期化
        .add_directive(directive).response
    )


# ユーザーの発話を取得
def get_user_utterance(handler_input: HandlerInput) -> str:
    # スロットからAIにリクエストする文言を取得
    user_utterance = handler_input.request_envelope.request.intent.slots["prompt"].value
    # 不要な空白を削除
    user_utterance.strip()

    return user_utterance
```

# 余談
Amazon自体でもアレクサでAIを使えるようにするプロジェクトが動いてるようです
2024/10リリースなんて噂もありましたが、特になかったですね
有料プラン(5$～10$)らしいですが、リリースされたら1回使ってみようかなと思います
※自分でAIのスキル作ったは良いものの、スキルの初回起動がコンスタントに遅い(4～5秒)ので使用頻度かなり低いですww

# おわりに
以上になります！
質問などありましたらコメントしていただければ、分かる範囲で答えます！