【Amazon Connect × Amazon Lex】 Lex v2 bot の音声速度をAmazon Connectで動的制御する方法！

## はじめに

Amazon ConnectとAmazon Lexを活用したコンタクトセンター構築では、聞き取りやすい音声ガイダンスの提供が顧客体験向上の鍵となります。中でもIVR(自動音声応答)の音声速度は重要な要素の一つです。
両サービスはSSMLに対応しており、速度や抑揚、間の取り方を細かく調整可能です。
さらにAmazon Connectでは、SSMLタグ内にコンタクト属性を埋め込むことで、音声速度を一元的に管理することができます。一方、Amazon Lexボットのプロンプト再生では、SSMLタグ内に変数が使えず、音声速度を固定値でしか指定できないため、管理が複雑化し、両者の速度に差異が生じる可能性もあります。

本記事では、**Amazon Connect側で音声速度を管理**し、Amazon ConnectとLex間で音声速度の整合性を保つ具体的な方法を解説します。

:::note info
本記事の情報は2025年6月時点のものです。
最新情報については公式ドキュメントをご確認ください。
:::


## この記事のゴール

本記事では、以下のことができるようになることを目指します。

- Amazon Connectのコンタクト属性を使った音声速度の一元管理方法を理解する

## 目次

[1.動作環境](#1-動作環境)
[2.前提条件](#2-前提条件)
[3.SSMLでのテキスト読み上げ](#3-ssmlでのテキスト読み上げ)
[4.システム構成図](#4-システム構成図)
[5.Lambda関数の作成](#5-lambda関数の作成)
[6.Amazon Lex v2 ボットの作成](#6-amazon-lex-v2-ボットの作成)
[7.Amazon Connect コンタクトフロー作成](#7-amazon-connect-コンタクトフロー作成)
[8.動作確認](#8-動作確認)
[まとめ](#まとめ)

## 1. 動作環境

本記事で使用する環境は以下のとおりです。

- AWS リージョン:東京(`ap-northeast-1`)
- Amazon Lex バージョン: `v2`
- AWS Lambda ランタイム:`Node.js 22.x`

## 2. 前提条件

本記事では以下を前提とします。

- AWSアカウントが作成されていること
- AWS Lambda 関数の作成権限やAmazon Lexのボット作成権限、またそれに係る権限を持つユーザーで操作を行うこと
- Amazon Connectの初期設定(電話番号の取得やキューの設定など)が完了していること

## 3. SSMLでのテキスト読み上げ

Amazon ConnectおよびAmazon Lexでは、SSMLによる読み上げがサポートされています。
SSMLを使用することで、音声の速度や単語の発音等を細かく調整することができます。

https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/supported-ssml-tags.html

https://docs.aws.amazon.com/polly/latest/dg/supportedtags.html

### 3.1 コンタクト属性を使用した動的音声制御

Amazon Connectでは、**SSMLタグ内でコンタクト属性を参照する**ことが可能です。
この仕組みにより、音声速度等を一元管理することが可能となり、顧客属性に応じた柔軟な調整が実現できます。(下図: 通常の1.2倍速で「音声速度のテストです」と再生する際の設定例)

![フロー設定例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/2c673714-5fe8-4ca7-847f-c84503cf1ed8.png)


![属性値設定例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/3509d7af-b98c-4918-8a19-c4aee0c03338.png)


### 3.2 Amazon Lexでの動的音声制御

Amazon LexでもSSMLを使用した静的な音声制御は可能ですが、**SSMLタグ内で変数(セッション属性やスロット値など)を参照することはできません。**
したがって、Amazon Connectからセッション属性をLexへ渡すだけでは、**Amazon Connect側で音声速度を一元的に制御できません。**

-  SSMLタグ内にセッション変数を埋め込む -> **更新時にエラー発生**
![LexSSML設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/cb25f693-9990-48e3-9c01-17a1d5ce6dd6.png)

![Lex更新エラー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/022b1b83-8a3b-471d-b62d-170295e10d45.png)

> 訳: _スロットのプロンプト指定が無効です。SSML文字列が無効です。文字列を確認して、リクエストを再度実行してください。_



### 3.3 対処方法: Lambdaを使用した動的制御

Amazon Lex 単体での動的な音声速度の制御は困難ですが、**Lambda関数をフックする**ことで、動的制御を実現できます。

- 動的な音声制御処理の概要︰
    1. Amazon Connect側で音声速度を管理するためのコンタクト属性(例: `talkSpeedAC`)を設定する
    2. Amazon ConnectからLexを呼び出す時に、セッション属性の値としてコンタクト属性を渡す
    3. Lexのフック先となるLambda関数内でセッション属性(`talkSpeedLex`)を参照し、音声応答テキストを**SSML形式**で組み立てる
    4. 組み立てたSSML形式の応答テキストをLexに返し、Amazon Connect経由で音声として再生される

## 4. システム構成図

以下は、Amazon ConnectとAmazon Lex、AWS Lambda関数を組み合わせた音声対話処理のシステム構成図です。

![Qiita投稿用Lex_Connect-ページ1.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/d865fbac-90bc-4194-947b-809dba53aacc.png)


### 4.1 処理概要


#### ① 発話内容の送信(Lex呼び出し)

Amazon Connectは、顧客の音声入力をAmazon Lexへ送信します。
この際、コンタクト属性で管理している音声速度の値(例: `talkSpeedAC`)を、Lexのセッション属性(例: `talkSpeedLex`)として一緒に渡します。


#### ② 発話内容を渡す(Lambda呼び出し)

Lexは、発話内容とセッション属性を含む情報をフック先のLambda関数に渡します。Lambdaはこの情報をもとに、次に返すメッセージ内容を生成します。

#### ③ 応答メッセージを返す(スロット値を返す)

Lambda関数は、セッション属性を参照しながら、SSMLタグを含む応答メッセージを生成してLexへ返却します。

#### ④ Amazon Connectへ 回答を渡す

Lexは、Lambdaから受け取ったSSMLタグを含む応答メッセージをAmazon Connectへ返します。

### 4.2 シナリオ

本記事では、例としてユーザーから「名字」と「名前」を順に聞き取り、確認応答を行うシンプルな会話フローを作成していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/e5bde4d7-c0c8-4daa-a5e0-3567fd0ae322.png)


## 5. Lambda関数の作成

AWS Lambdaコンソール画面の「関数の作成」より、以下の設定でLambda関数を作成します。

![Lambda作成画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/7d589f8e-8549-42be-a90e-1d8ed12f5ee1.png)

- オプション: `一から作成`
- 関数名: (任意)
- ランタイム: `Node.js 22.x`
- アーキテクチャ: `x86_64`
- アクセス権限 , その他の構成: デフォルト

### 5.1 Lambda関数コード

作成した関数の`index.mjs`を以下のコードで上書きします。
このLambdaでは、Amazon Connectのコンタクト属性で設定した音声再生速度(例: `talkSpeedAC`)をLexのセッション属性(例: `talkSpeedLex`)として受け取ります。セッション属性を**テンプレートリテラルを用いて`<prosody>` タグ内に埋め込む**ことで、SSML形式の応答テキストを動的に作成しています。
これにより、Amazon Connect側で一元管理された音声速度の設定を、Lex経由の応答にも反映させることが可能となります。

```javascript
/**
 * Lex フック用 Lambda 関数
 * - Amazon Connect のコンタクト属性(セッション属性)をもとに SSML の再生速度を制御
 * - 名字 / 名前 を個別スロットで収集し、音声応答を返却
 */

// --- 環境変数読み込み ---
const DEFAULT_VOICE_RATE = process.env.DEFAULT_VOICE_RATE || "100%";
const VOICE_RATE_SESSION_ATTRIBUTE_NAME = process.env.VOICE_RATE_SESSION_ATTRIBUTE_NAME || "talkSpeedLex";

// --- 音声速度フォーマットチェック用正規表現(例: "80%","120%")---
const VOICE_RATE_REGEX = /^[0-9]{1,3}%$/;

/**
 * セッション属性から音声再生速度を取得
 */
function getVoiceRate(sessionState) {
  try {
    const sessionAttributes = sessionState?.sessionAttributes || {};
    const candidate = sessionAttributes[VOICE_RATE_SESSION_ATTRIBUTE_NAME];
    if (typeof candidate === "string" && VOICE_RATE_REGEX.test(candidate)) {
      return candidate;
    }
  } catch (e) {
    console.warn("音声速度取得時のエラー:", e);
  }
  return DEFAULT_VOICE_RATE;
}

/**
 * スロットから値を取得
 */
function getSlotValue(slots, slotName) {
  return slots?.[slotName]?.value?.interpretedValue;
}

/**
 * Lex Bot に返却する応答メッセージを生成
 */
function buildLexResponse({
  sessionState,
  intent,
  slots,
  slotToElicit,
  message,
  voiceRate,
  isFulfilled = false
}) {
  return {
    sessionState: {
      dialogAction: isFulfilled
        ? { type: "Close" }
        : { type: "ElicitSlot", slotToElicit },
      intent: {
        name: intent.name,
        slots,
        state: isFulfilled ? "Fulfilled" : "InProgress"
      }
    },
    messages: [
      {
        contentType: "SSML",
        content: `<speak><prosody rate="${voiceRate}">${message}</prosody></speak>`
      }
    ]
  };
}

/**
 * Lambda ハンドラー(Lex のコードフック)
 */
export const handler = async (event) => {
  console.debug("Lex フックイベント受信:", event);

  const sessionState = event.sessionState || {};
  const intent = sessionState.intent || {};
  const slots = intent.slots || {};

  // セッション属性から再生速度を取得
  const voiceRate = getVoiceRate(sessionState);

  // 名字のスロットが未入力
  const lastName = getSlotValue(slots, "LastName");
  if (!lastName) {
    return buildLexResponse({
      sessionState,
      intent,
      slots,
      slotToElicit: "LastName",
      message: "名字を教えてください。",
      voiceRate
    });
  }

  // 名前のスロットが未入力
  const firstName = getSlotValue(slots, "FirstName");
  if (!firstName) {
    return buildLexResponse({
      sessionState,
      intent,
      slots,
      slotToElicit: "FirstName",
      message: "名前を教えてください。",
      voiceRate
    });
  }

  // 両方のスロットが埋まっている場合は完了応答
  return buildLexResponse({
    sessionState,
    intent,
    slots,
    message: `${lastName} ${firstName}さんですね。ありがとうございます。`,
    voiceRate,
    isFulfilled: true
  });
};
```

### 5.2 環境変数の設定

作成したLambda関数には、音声再生速度に関する設定値を環境変数として指定します。
これにより、セッション属性に値が含まれていない場合や無効な値が渡された場合でも、デフォルトの再生速度で処理を継続できます。

- **「設定」** > **「環境変数」** より、以下の環境変数を設定

| 環境変数名                     | 説明                                                                 |
|-------------------------------|----------------------------------------------------------------------|
| `DEFAULT_VOICE_RATE`          | セッション属性が未設定・無効だった場合に使用される音声再生速度(例: `100%`) |
| `VOICE_RATE_SESSION_ATTRIBUTE_NAME` | 音声速度を保持するセッション属性名（例: `talkSpeedLex`)              |



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/f4f63152-cbef-4626-8a14-40f82946f0a3.png)



## 6. Amazon Lex v2 ボットの作成

[5. Lambda関数の作成](#5-lambda関数の作成)で作成したLambda関数のフック元となるチャットボットの作成をします。

チャットボットの作成方法やコードフックの有効化方法は以下の記事を参考にしてください。

https://qiita.com/yosuke-suzuki/items/14dfb323cba1e7ad9272

https://qiita.com/hirota_kodai/items/d1adcc39519f98efd017


Amazon Connectからチャットボットを作成する場合は、以下の記事を参考にボットの作成前に必要な設定・権限の付与を実施してください。

https://qiita.com/oho-kiyotaka/items/ef5798c5dd71dadb5610


### 6.1 ボットの基本設定

- ボット名 : 任意 
- インテント名: 任意
- サンプル発話: `テスト`
- スロット(2つ)
    - 1つ目
        - スロット名: `LastName`
        - スロットタイプ: `AMAZON.LastName`
        - プロンプト: `.` (※Lambdaコードフックを有効にするため、ここで設定したプロンプトは実際には再生されません)
    - 2つ目
        - スロット名: `FirstName`
        - スロットタイプ: `AMAZON.FirstName`
        - プロンプト: `.` (※Lambdaコードフックを有効にするため、ここで設定したプロンプトは実際には再生されません)


### 6.2 ボットの追加設定

**重要: 以下の設定を必ず行なってください**

- **コードフックの有効化**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/9963de9e-612c-45ad-a4aa-fbede1384445.png)


- **初期応答 > ダイアログコードフックの有効化**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/c1dd828d-b7a5-4fa6-860d-71856383b7db.png)

- **コードフック用のLambda関数の紐付け**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/d49d5044-6843-41f8-bbc8-08ae604af68c.png)

※その他の設定はデフォルトで問題ありません。


## 7. Amazon Connect コンタクトフロー作成

### 7.1 Amazon Lex v2 ボットの追加

:::note info
[Amazon Lex v2 ボットの作成](#6-amazon-lex-v2-ボットの作成)をAmazon Connectから実施した場合は、こちらの追加作業は不要です。
:::

[6. Amazon Lex v2 ボットの作成](#6-amazon-lex-v2-ボットの作成)で作成したボットをAmazon Connectのフロー内で使用するために、対象のインスタンスへ追加してください。

https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/amazon-lex.html#lex-bot-create-flow-add-bot

- **Amazon Connect コンソール** > **対象のインスタンス** > **問い合わせフロー** >**Amazon Lex**より、対象のボットを追加してください

![Lex統合.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/17eb9b08-7f0e-4a2a-b77f-be3815342d9e.png)


### 7.2 コンタクトフローの設定

全体の流れは以下のようになります。

![コンタクトフロー図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/dad4ca2d-5d13-420d-bd2d-94ac6f6b53df.png)

### 7.2.1 「コンタクト属性の設定」ブロックの設定

Amazon Connect フロー内で **音声速度を指定する属性(例: talkSpeedAC)** を定義します。
ここで設定した値は、Lex にセッション属性として引き渡され、後続の Lambda 処理で使用されます。
- 属性の名前: `talkSpeedAC`
- 属性の値: `80%`(今回の例では0.8倍速で再生)

![コンタクト属性の設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/0f0d4bb0-4c25-4f9a-922a-c9ef534b1797.png)


### 7.2.2 「顧客の入力を取得する」ブロックの設定

ユーザーの音声入力を取得するための設定を行います。ここでは、Lex ボットを呼び出す設定と、コンタクト属性をセッション属性として渡す処理を行っています。
- Lex Bot: [6. Amazon Lex v2 ボットの作成](#6-amazon-lex-v2-ボットの作成)で作成したボットを選択
- Botエイリアス: `TestBotAlias`(使用するLex Botのバージョンを指定)

:::note warn
今回は動作確認のため、`TestBotAlias`を使用しています。
本稼働に用いる場合はバージョンを発行して使用するよう推奨されています。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/03bc2686-7372-407b-aef0-1c2ba072e2a3.png)

- **読み上げるテキストの設定**

- **テキスト読み上げまたはチャットテキスト** > **手動で設定**より、以下のテキストをペーストしてください

```sh
<speak>
    <prosody rate="$.Attributes.talkSpeedAC">
テストと発話してください
    </prosody>
</speak>
```

- 「次として解釈」を **SSML** に変更してください

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/98dcbf9d-ab9d-49ce-b73b-072f5ca8a45c.png)

- **セッション属性の設定**
    - 宛先キー: `talkSpeedLex`
    - 値: `動的に設定`
    - 名前空間: `ユーザー定義済み`
    - キー: `talkSpeedAC`

→ Amazon Connect のコンタクト属性`talkSpeedAC`をLex のセッション属性`talkSpeedLex`にマッピングしています


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/c779915f-e76b-42a9-8227-0f4031921394.png)


- **インテントの設定**
[6.1 ボットの基本設定](#61-ボットの基本設定)で設定したインテント名を入力してください

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/d4df824d-71da-4705-807f-f113e5ab55b1.png)


### 7.2.3 「プロンプトの再生」ブロックの設定

`「顧客の入力を取得する」` ブロックの成功時の遷移先および**デフォルト**の遷移先に「プロンプトの再生」ブロックを追加します(任意)。

- **成功(追加したインテントの遷移先に配置)**

```sh
<speak>
    <prosody rate="$.Attributes.talkSpeedAC">
Lex実行成功
   </prosody>
</speak>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/e6512583-23b5-4cc7-8c3d-d984d8145a60.png)

- **失敗(デフォルトの遷移先に配置)**

```sh
<speak>
    <prosody rate="$.Attributes.talkSpeedAC">
申し訳ございません。
ただしく聞き取れませんでした。
恐れ入りますが、もう一度 
 </prosody>
</speak>

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/74cd842c-1121-4725-b74c-6a7c9745020f.png)


## 8. 動作確認

上記コンタクトフローの紐付け先の電話番号へ架電すると、コンタクトフロー側のプロンプトの再生速度と、Lex側のプロンプトの再生速度が一致していることを確認できます。

![AmazonConnectログ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/7c0ac456-7ebc-435c-a75f-d16f278f1d97.png)

![Lexログ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3843906/5e65f12c-e42e-47e1-b763-6a1f4c700650.png)



## まとめ

本記事では、Amazon Connectのコンタクト属性を活用し、Lex経由の音声応答でも再生速度を統一的に制御する方法を紹介しました。
次回の記事では、Lambdaコードフックによる、より柔軟なチャットボットの開発方法を紹介します。