Azure OpenAI × Azure Logic Apps で、LINE chatBot 作成時に 400 (Bad Request) が返る時の１例

# LINE Messaging API で 400(Bad Request) が返る場合の対処方法

## 背景

Azure Logic Apps + Azure OpenAI service を使って、簡単なLINE chatBot を作成していて、LINE側にAIからの回答を転送すると、たまに400(Bad Request) が返る場合があったので、原因と簡単な解決方法を記載しておく。

> ### おことわり
>
> LINE Messaging API で reply/push する時、400（Bad Request) が返る事象は、複数の原因で発生します。
> 例えば、「messages テキストが大きすぎる」「宛先(destination) や返信先（replyToken) が間違っている」など。
> この記事では、以下のようにリクエスト ボディのmessages[n]に原因がある 400 エラーを対象とします。
>
> ``` JSON
> {
>   "message": "The property, 'messages[0]', in the request body is invalid (line: 4, column: 347)"
> }
> ```

## 現象

[参考情報](https://qiita.com/tosumi-Qiita/items/dfdf4e1f255d2611f533#参考情報) にあるような、Azure Logic Apps を経由して、Azure OpenAI Service とLINE Messaging API とをつないで、簡単なチャットボットを作成します。  
その際、短い文章は正常に返信出来るのに、ちょっと長めの回答だと、LINE Messaging API で返信を返す時に、400 (Bad Request) のエラーが発生します。

![スクリーンショット 2023-12-05 101144.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628440/accfdc4b-a79f-9a63-b4af-2c8e6794196e.png)

## 原因

今回の原因は、改行コードにありました。（「おことわり」にあるように、400エラーの原因は複数考えられるので、あくまで「今回は」です）  
Azure OpenAI からの回答は、普通に改行コード「\n」(0x0A)が含まれていますが、これをそのままLINE Messaging APIのリクエスト ボディに渡すと、"The property, 'messages[n]', in the request body is invalid" となります。  

> ちなみに、その後「"」（ダブルクォーテーション：URIエンコードでは'%22'）でも同様に 400 (Bad Request) が発生することが分かりました。これはLINE Messaging API への返答文を作成するときに、文字列の区切りが意図しない解釈となり、本来テキストに含まれるべき引用部分がテキスト以外に解釈されてしまうためです。いずれにしても「"」もエスケープする必要があります。

## 対処方法

原因は改行コードにあるので、これをエスケープしてあげる必要があります。  
具体的には「Azure OpenAI Service からの返信テキスト内の '\n' を '\\n' に変換する」必要があります。  
ところが、Azure Logic Apps は、GUIをメインとしたローコード/ノーコードなサービスなので、デザイナー上で式としてreplace({回答テキスト}, '\n', '\\n') のように書いても、期待通りに動作しません。  
これは、デザイナー上で上のように書いた場合、内部で「\」がエスケープされてしまうためです。  
解決方法としては、以下の方法が考えられます。

- DecodeUriComponent()やDecodeBase64()などの関数を使う
- コード ビューで書き換える

それぞれ、具体例を示します。

#### DecodeUriComponent() を使う方法

HTTP アクションで、Azure OpenAI serviceを呼び出すと、回答が得られるので、「JSON の解析」アクションで一旦オブジェクト化しておく。  
その後、オブジェクトの中の、choices[0].message.content 要素に含まれるテキストを取り出し、LINE Messaging APIへの返答にするためのリクエスト ボディ（文字列）を以下のように作成する。  

```
{
  "messages": [
    {
      "text": "{AI からの回答テキスト}",
      "type": "text"
    }
  ],
  "replyToken": "{HTTP 要求の到着時に取得した Reply Token)}"
}
```

この{AI からの回答テキスト}部分を式で以下のように記載する。
> __replace__(trim(body('JSON_の解析_2')?['choices'][0]['message']['content']), __decodeUriComponent('%0A')__, '\\n')

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628440/e2cfd923-83e9-9ef5-6182-c1dd407a6fcd.png)

こうすることで、AIからの回答に含まれる改行コード（'\\n') が、エスケープ文字を含む文字列（'\\n') に置き換わる。
実行結果は以下の通り、正常終了（ステータスコード 200）となる。

![スクリーンショット 2023-12-05 114110.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628440/bd5046d6-512d-6afc-b4f4-fdbb4de8eadc.png)

#### コード ビューで書き換える方法

Azure OpenAI の回答からメッセージ部分を抜き出すところの流れは同じ。  
LINE Messaging API に返す、リクエストボディを作成するときに、式として以下を記載

> replace(trim(body('JSON_の解析_2')?['choices'][0]['message']['content']), __'\\n'__, '\\n')

![スクリーンショット 2023-12-05 111723.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628440/8e04495a-81e3-75c3-114c-65d328f9bbd8.png)

  '\n' で記載した部分は、コードビューでは自動でエスケープされて下のように変換されている。
> "@{replace(trim(body('JSON_の解析_2')?['choices'][0]['message']['content']), __'\\\\n'__, '\\\\n')}"

書き換え元の方は、「\」をエスケープされると困るので、下のように書き換える

> "@{replace(trim(body('JSON_の解析_2')?['choices'][0]['message']['content']), __'\n'__, '\\\\n')}"

「変数の初期化」アクション全体としては、こんな感じ。

```
"replyText変数を初期化する": {
    "inputs": {
        "variables": [
            {
                "name": "replyText",
                "type": "string",
                "value": "{\n  \"messages\": [\n    {\n      \"text\": \"@{replace(trim(body('JSON_の解析_2')?['choices'][0]['message']['content']), '\n', '\\n')}\",\n      \"type\": \"text\"\n    }\n  ],\n  \"replyToken\": \"@{variables('replyToken')}\"\n}"
            }
        ]
    },
```

ちなみにデザイナーで観ると、途中で途切れてるように見えるが、↓キーを押せば、改行された続きの部分が表示される。
![スクリーンショット 2023-12-05 112323.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628440/bf19ef8c-b2c9-54dc-45e4-1cbbc6844b36.png)
![スクリーンショット 2023-12-05 112749.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628440/0430a295-7ec4-9e09-818c-72d0a55c8930.png)

## まとめ

- LINE Messaging API で返信(https://api.line.me/v2/bot/message/reply) を返した時に、400 (Bad Request) が返る事があるので解析してみたら、改行コードで問題が発生していることに気が付いた。
- 今回は Azure OpenAI service からの返答に改行コードが含まれていたので、'エスケープされた改行コード'に置き換えることで対処。
- ただし、デザイナー上で改行コードを直接入力することが出来ないので、いくつか方法を試してみた。
- 結果、DecodeUriComponent()やDecodeBase64() などの関数で表現する方法や、改行だけを含んだ文字列変数を使う方法、コード ビュー上で変更する方法などいくつか見つかった。
- いずれの方法でも、エスケープされた改行コード（'\\n') に置き換えられれば、LINE Messaging API からは 200 (OK) が返るようになった。


## 参考情報
- [LINE Bot のバックエンドにできる Azure Logic Apps のフローを GitHub のボタンから 1 クリックでデプロイできるようにしてみた](https://himanago.hatenablog.com/entry/2021/12/31/102406)
- [【Azure Logic Apps】ChatGPTと話せるLINE Botを開発するハンズオン！～①テンプレートによる超爆速開発](https://zenn.dev/himanago/articles/azure-logic-app-chatgpt-linebot)
- [PowerAutomateで改行コードを扱いたい（分割したり、取り除いたり）](https://qiita.com/rsuzuki/items/5c4d95c7f1dab2952303)
- [Azure Logic Appsでつまったことまとめ](https://qiita.com/akirin103q/items/dc226e603626c88792d0)
- [Power Automate 上で複数行文字列を改行で分割する](https://qiita.com/kojisaiki/items/d7fb7932e6f20e1e6628)
- [Logic Apps ~CRLF取扱いのTips~](https://tech-blog.cloud-config.jp/2019-07-31-logic-apps-tips-crlf/)
- [Power Automate (旧称： Microsoft Flow) 、Logic Appsで文字列を改行区切りしてArray 変数に入れる方法](https://blog.tottokug.com/entry/2020/02/06/230353)
- [URLのクエリーパラメータでエスケープしなくていい文字はどれか？](https://qiita.com/aosho235/items/0581fc82f8ce2c5ac055)