Azure OpenAI Serviceをバックエンドサービスとして構成したAzure API ManagementのAPIに対し、Streamを有効にしたリクエストを投げたらHTTP 500を返してしまうことがある

このエントリは2023/09/12現在の情報に基づいています。将来の機能追加や変更に伴い、記載内容からの乖離が発生する可能性があります。

問い合わせ

いつもの人から以下のような問い合わせが届いた。

Azure API Management (以下、APIM) をAzure OpenAI Service (以下、AOAI) 組み合わせて、複数システムからの呼び出しをサポートできるようにしている(バックエンドではApplication Gatewayを使ったAOAIへの負荷分散をしている)。AOAIのStreamが有効になるようにしたリクエストでAPIMでホストしているAPIを呼び出したところ、HTTP 500が返ってきた。これを避けるにはどうしたらいいか?

AOAIのStreamモードとは、Server Sent Events (SSE) を使ってレスポンスを返すようにするもので、OpenAIにも同じ仕組みがある。

Streaming
https://platform.openai.com/docs/guides/production-best-practices/streaming

この問い合わせ主のポリシー構成は以下の通り。また、診断ログ設定で、Azure Monitorにリクエスト・レスポンスのログを出すようにしているとのこと。

<policies>
    <inbound>
        <base />
        <authentication-managed-identity resource="https://cognitiveservices.azure.com"
            output-token-variable-name="msi-access-token" ignore-error="false" />
        <set-header name="Authorization" exists-action="override">
            <value>@("Bearer " + (string)context.Variables["msi-access-token"])</value>
        </set-header>
        <set-backend-service base-url="{Endpoint for Application Gateway}" />
    </inbound>
    <backend>
        <retry condition="@(context.Response.StatusCode >= 500)" count="3" interval="0" first-fast-retry="true">
            <forward-request buffer-request-body="true" timeout="120" buffer-response="false"
                fail-on-error-status-code="true" />
        </retry>
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error />
</policies>

原因

問い合わせ主はAzure Monitorでログを取ろうとしていたが、SSEを受け取る場合にはAPIMではバッファリングしてはいけない。これが邪魔をしていて500が発生していた。APIMに詳しい方はすぐにわかったかもしれない。

APIMからリクエストを投げてから、APIMとAOAI間でHTTP通信が確立され、AOAIからのレスポンスが垂れ流しになる。このとき、APIMのOutboundセクションでバッファリングするような仕組みを使うと500が発生する。具体的には、Azure Monitorにログ出力したり、set-bodyポリシーでポリシー式を使ったり、など。ドキュメントにも記載がある。

SSE のガイドライン / Guidelines for SSE
https://learn.microsoft.com/azure/api-management/how-to-server-sent-events#guidelines-for-sse

というわけで、診断ログの設定は解除してもらった。

おまけ

上記提案をした後、以下のような更問が届いた。

prompt tokenとcompletion tokenの個数はどこに現れるのか?

非Streamの場合、usageで確認できる。

{
    "id": "chatcmpl-7xqbvCK1E147BjRaOfg1LDlr8bFYY",
    "object": "chat.completion",
    "created": 1694497943,
    "model": "gpt-35-turbo-16k",
    "prompt_annotations": [...],
    "choices": [...],
    "usage": {
        "completion_tokens": 509,
        "prompt_tokens": 19,
        "total_tokens": 528
    }
}

Streamの場合、レスポンスのusageは常にnullなのでどうしたものか、というわけ。

{
    "id": "chatcmpl-7xr2XYKrXSel2qiK2opU43Qf02OND",
    "object": "chat.completion.chunk",
    "created": 1694499593,
    "model": "gpt-35-turbo-16k",
    "choices": [
        {
            "index": 0,
            "finish_reason": null,
            "delta": {
                "content": "。"
            },
            "content_filter_results": {
                "hate": {
                    "filtered": false,
                    "severity": "safe"
                },
                "self_harm": {
                    "filtered": false,
                    "severity": "safe"
                },
                "sexual": {
                    "filtered": false,
                    "severity": "safe"
                },
                "violence": {
                    "filtered": false,
                    "severity": "safe"
                }
            }
        }
    ],
    "usage": null
}

これは、レスポンス本体、ヘッダーのいずれにもトークン数は含まれないため、レスポンスをTokenizerとよばれるライブラリに通して算出するしかない。Tokenizerの有名どころは以下のあたり。

tiktoken (Python)
https://github.com/openai/tiktoken
JTokkit (Java)
https://github.com/knuddelsgmbh/jtokkit
Tokenizer (C#, TypeScript)
https://github.com/microsoft/Tokenizer

回避策

現時点では、APIMではStreamが有効なリクエストだと、バッファリングの問題でプロンプトログの採取に失敗してしまう。これを回避策するには、APIMとAOAIの間にFunctionsなどを配置し、そのFunctionsにStreamが有効な際のAOAIの呼び出しやトークン個数の集計を任せる、という方法がある。Streamが有効か否かの判断はリクエスト本文からわかるので、APIMでStreamが有効なリクエストであれば、set-backend-serviceポリシーを使ってFunctionsをバックエンドサービスとして指定するというもの。ただこれ、「Streamを有効にした意味があるのか?🤔」となるのはその通りなので、Streamを使うのであれば、現状ではOrchestratorに類するアプリケーションでコントロールするのが望ましい。

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください