AWS Step Functions（Amazon Transcribe＋Amazon Bedrock）を使って音声データを分析するうえで躓いたポイント

こんにちは、ちはらです。
約3か月間に亘って参加していたANGELDojoの中で、私が躓いたことを備忘録として書いていこうと思います。

今回はタイトルの通り、AWS Step Functionsの中でAmazon TranscribeとAmazon Bedrockを使った際に躓いたポイントをまとめています。

# Tele Talk Tutor(T3)
「T3」とは、今回のANGELDojoで私たちが開発したもので、電話練習ができるアプリです。
このアプリでは実際に利用者がAIと電話をし、利用者の応答に対してフィードバックをもらうことができます。
このフィードバックの機能を構築するうえで使用したのがAWS Step Functionsです。

# 構成図
主な構成としては以下のようになっています。
![スクリーンショット 2024-10-12 174942.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3867728/537f36e5-b4ce-814f-8b3b-369afd1750f4.png)
1. S3に保存された音声データをTranscribeの通話後分析を利用してテキストデータ化と感情の分析を行う
1. Lambdaで分析結果をJsonファイルからテキストデータに変換する
1. Bedrockを用いて分析結果をフィードバックとして出力できるように文章化する
1. DynamoDBにフィードバック内容をユーザー毎に保存する

### LambdaではなくStep Functionsを選んだ理由
構築前の段階では当初、Lambdaを使用してフィードバック機能を構築する構成で考えていました。
ですが、通話後分析の処理時間が長く15分を超える可能性があると途中で判明し、その場合Lambdaのタイムアウトの最大値を超えてしまうため、Step Functionsを採用することにしました。
また、Lambdaの場合Bedrockの処理を待っている間もコストが発生してしまいますが、Step Functionsであればそのようなことはありません。

# 躓いたポイント①

### 通話後分析が完了する前に次のステップに進んでしまう
今回初めてStep Functionsを触ったので始めの頃は仕様が分かっておらず、ステップが完了したら（今回の場合、通話後分析のステータスがCREATEDになったら）次のステップに進むのだと思っていました。
なので、最初は **通話後分析→Bedrock** という設定にしていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3867728/1b8e0821-2470-5043-9d93-4fa6a895ffba.png)



その時のNG例が以下です。

```ruby:NG例
    {
  "Comment": "A description of my state machine",
  "StartAt": "StartCallAnalyticsJob",
  "States": {
    "StartCallAnalyticsJob": {
      "Type": "Task",
      "Parameters": {
        "CallAnalyticsJobName": "MyData",
        "Media": {}
      },
      "Resource": "arn:aws:states:::aws-sdk:transcribe:startCallAnalyticsJob",
      "Next": "Bedrock InvokeModel"
    },
    "Bedrock InvokeModel": {
      "Type": "Task",
      "Resource": "arn:aws:states:::bedrock:invokeModel",
      "Parameters": {},
      "End": true
    }
  }
}
```

このように記述をすると、通話後分析が開始されたら（ステータスがIN PROGRESSのまま）すぐに次のステップに進んでしまうので、通話後分析の結果が何も出力がないままBedrockでの処理に入ってしまい、エラーになってしまいました。

この対応策として、通話後分析を開始した後に通話後分析のステータスを確認するステップを追加し、ステータスがCREATEDであればBedrockの処理に進み、IN PROGRESSであれば15秒待機して再度ステータスをチェックするという処理を追加しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3867728/b9068671-9b1a-f911-d204-7cd81df50627.png)


```ruby:OK例
    {
  "Comment": "A description of my state machine",
  "StartAt": "StartCallAnalyticsJob",
  "States": {
    "StartCallAnalyticsJob": {
      "Type": "Task",
      "Parameters": {
        "CallAnalyticsJobName": "MyData",
        "Media": {}
      },
      "Resource": "arn:aws:states:::aws-sdk:transcribe:startCallAnalyticsJob",
      "Next": "GetCallAnalyticsJob"
    },
    "GetCallAnalyticsJob": {
      "Type": "Task",
      "Parameters": {
        "CallAnalyticsJobName": "MyData"
      },
      "Resource": "arn:aws:states:::aws-sdk:transcribe:getCallAnalyticsJob",
      "Next": "Choice"
    },
    "Choice": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.GetCallAnalyticsJob.CallAnalyticsJob.CallAnalyticsJobStatus",
          "StringEquals": "COMPLETED",
          "Next": "Bedrock InvokeModel"
        },
        {
          "Variable": "$.GetCallAnalyticsJob.CallAnalyticsJob.CallAnalyticsJobStatus",
          "StringEquals": "IN_PROGRESS",
          "Next": "Wait"
        }
      ]
    },
    "Bedrock InvokeModel": {
      "Type": "Task",
      "Resource": "arn:aws:states:::bedrock:invokeModel",
      "Parameters": {},
      "End": true
    },
    "Wait": {
      "Type": "Wait",
      "Seconds": 15,
      "Next": "GetCallAnalyticsJob"
    }
  }
}
```

このように記述することで、通話後分析が完了してからBedrockの処理に入ることができました。
ただ、この状態で実行をしたところ、新たな問題に直面しました。

# 躓いたポイント②
### Transcribeの通話後分析の結果がBedrockに渡せない
躓いたポイント①のOK例のようにすることで、通話後分析が完了してからBedrockを実行することはできるようになりましたが、新たな問題が出てきました。
Bedrockに以下のように言われてしまったのです。

～～～～～
 *I apologize, but I cannot directly access or open external links or files. The URL you provided appears to be pointing to a JSON file stored in an Amazon S3 bucket. Without being able to view the contents of that file, I can't provide any specific information about what it contains.\n\nIf you have the contents of the JSON file, you could paste them here, and I'd be happy to help you interpret or analyze the data. Alternatively, if you have any specific questions about the file or its contents, feel free to ask, and I'll do my best to assist you based on the information you provide."* 
～～～～～

 **「外部リンクやファイル形式だと直接アクセスができないので、テキストデータでお願いします」** とのこと。
この問題を解決するために、LambdaでJsonファイルをテキストデータに変換するという処理を追加しました。
（Lambdaの設定については割愛します）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3867728/ce95c0a4-13e1-d43a-f42f-5c823a61ee1a.png)

```ruby:
{
  "Comment": "A description of my state machine",
  "StartAt": "StartCallAnalyticsJob",
  "States": {
    "StartCallAnalyticsJob": {
      "Type": "Task",
      "Parameters": {
        "CallAnalyticsJobName": "MyData",
        "Media": {}
      },
      "Resource": "arn:aws:states:::aws-sdk:transcribe:startCallAnalyticsJob",
      "Next": "GetCallAnalyticsJob"
    },
    "GetCallAnalyticsJob": {
      "Type": "Task",
      "Parameters": {
        "CallAnalyticsJobName": "MyData"
      },
      "Resource": "arn:aws:states:::aws-sdk:transcribe:getCallAnalyticsJob",
      "Next": "Choice"
    },
    "Choice": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.GetCallAnalyticsJob.CallAnalyticsJob.CallAnalyticsJobStatus",
          "StringEquals": "COMPLETED",
          "Next": "Lambda Invoke"
        },
        {
          "Variable": "$.GetCallAnalyticsJob.CallAnalyticsJob.CallAnalyticsJobStatus",
          "StringEquals": "IN_PROGRESS",
          "Next": "Wait"
        }
      ]
    },
    "Lambda Invoke": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "OutputPath": "$.Payload",
      "Parameters": {
        "Payload.$": "$"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "BackoffRate": 2,
          "JitterStrategy": "FULL"
        }
      ],
      "Next": "Bedrock InvokeModel"
    },
    "Bedrock InvokeModel": {
      "Type": "Task",
      "Resource": "arn:aws:states:::bedrock:invokeModel",
      "Parameters": {},
      "End": true
    },
    "Wait": {
      "Type": "Wait",
      "Seconds": 5,
      "Next": "GetCallAnalyticsJob"
    }
  }
}
```

こうすることにより、Bedrockがしっかりと通話後分析の中身をさらに分析してくれるようになりました。

～～～～～
 *この日本語は概ね正しいですが、いくつかの点で改善の余地があります。1. 文の構造：全体的に文の構造は正しいですが、少し長くなっています。2つの文に分けると、より読みやすくなります。* 
 ～～～～～

 # さいごに
 以上が私の躓いたポイントです。
 Step FunctionsのResultPathにも大分苦労したのですが、話が長くなりそうなのでまた別の機会にまとめようと思います。