Azure API Managementを通るリクエスト・レスポンスをカスタムログとして出力したい

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

問い合わせ

いつもの人がAzure OpenAI Service (以下AOAI) を使い始めたそうで、その関係で以下のような問い合わせが届いた。

AOAIのプロンプトやレスポンスなどを把握するため、カスタムログとして出力したい。どうすればよいか。

相変わらず雑な問い合わせだったので、詳細を尋ねてみた。

  • 現在社内でAOAIを使ったシステムを構築中。
  • AOAIに対する問い合わせ(プロンプト)とレスポンスに問題がないか(妙なプロンプトを送信していないか)を監視するため、バックエンドサービスAPIのリクエスト・レスポンスを取得したい。
  • とはいえAOAIのログではペイロードを取得できないので、Azure API Management (以下APIM) でAOAIをバックエンドサービスとして構成し、リクエスト・レスポンスのペイロードをログとして確保しようと考えている。
  • 下記URLの内容に従って、APIMの診断設定を構成しているが、Log Analytics WorkspaceやApplication Insightsには最大8,192Byteまでしか出力されない。
  • gpt-35-turboなど、8,192Byteに収まるトークン数のモデルであれば問題ないが、gpt-35-turbo-16kのように、8,192Byteで収まらないモデルの場合でも、何らかの形でペイロードを取得したいのだが、どうすればよいか?

Azure OpenAI モデルのログと監視を実装する / Implement logging and monitoring for Azure OpenAI models
https://learn.microsoft.com/azure/architecture/example-scenario/ai/log-monitor-azure-openai

問い合わせ主の懸念

上記の通りAPIMには診断設定があり、これを使うとリクエスト・レスポンスの本文やヘッダーをApplication InsightsやLog Analytics Workspaceに書き出すことができる。ただ、出力サイズの上限は最大8,192byteという制約があり、宛先がApplication InsightsやLog Analytics Workspaceでその制約に違いはない。このことを問い合わせ主は気にしている。

Tip 301 – How to log request/response payload in Application Insights
https://microsoft.github.io/AzureTipsAndTricks/blog/tip301.html

もちろん、診断設定でログをEvent Hubsやストレージアカウントに流す、というのでもかまわないのだが、カスタムログとして出せるとありがたい、ということらしい。

解決策の一例

今回説明した方策はこちら。

  • APIMのoutboundセクションで出力したい形式に整形し、ログとしてEvent Hubsに投げ込む
  • Event HubsをリスニングしているFunctionsやLogic Appを使って永続領域に書き出す
  • APIごとに同一設定を使うよう、この設定をAPIスコープで定義する

もちろん、Event Hubsの代わりに別APIを呼び出す、というのでもかまわないが、いずれにしてもoutboundセクションでまとめて取得したい情報を吐き出す、という大方針に違いはない。Event Hubsであれば、Basic SKUでも256 KBまで対応(Standard/Premiumなら最大1MB)するので、十分対応できると考えられる。

実際にやってみる

Event Hubsの場合、APIMにlog-to-eventhubというポリシーがあるので、これを使う。

イベント ハブにログを記録する / Log to event hub
https://learn.microsoft.com/azure/api-management/log-to-eventhub-policy

1. inboundセクション

リクエストのヘッダー、ボディをinboundセクションでコンテキスト変数に保持する。inboundセクションで設定したコンテキスト変数は、outboundセクションから利用できる。以下はリクエスト本文と、リクエストヘッダーにあるapi-keyを取得する例。

<set-variable name="request" value="@(context.Request.Body.As<JObject>(preserveContent: true))" />
<set-variable name="api-key" value="@(context.Request.Headers.GetValueOrDefault("api-key",""))" />

なおcontext.Request.Body.As<T>()で値を取得すると、以下に記載の通り、その値はバックエンドが利用できなくなってしまう。値を残したい場合には、preserveContent: trueを指定しておく必要がある。

By default, the As and AsFormUrlEncodedContent() methods:
– Use the original message body stream.
– Render it unavailable after it returns.

To avoid that and have the method operate on a copy of the body stream, set the preserveContent parameter to true, as shown in examples for the set-body policy.

既定では、AsAsFormUrlEncodedContent() のメソッド:
– 元のメッセージ本文ストリームを使います。
– 戻った後は使用不可とレンダリングします。
これを回避し、本文ストリームのコピーでメソッドを実行するには、set-body ポリシーの例で示すように preserveContent パラメーターを true に設定します。

IMessageBody
https://learn.microsoft.com/azure/api-management/api-management-policy-expressions#ref-imessagebody
2. outboundセクション

同様に、バックエンドサービスからの応答をコンテキスト変数に保持する(もちろん直接ボディストリームを扱ってもいい)。

<set-variable name="response" value="@(context.Response.Body.As<JObject>(preserveContent: true))" />
<set-variable name="id" value="@(context.Response.Headers.GetValueOrDefault("apim-request-id",""))" />

その後、log-to-eventhubポリシーを使って整形したログをEvent Hubsに出力する。今回はJSON形式で送りたいので、JObjectに詰め込んでいる。

<log-to-eventhub logger-id="logicojp-eventhubs">@{
    return new JObject(
        new JProperty("id", context.Variables["id"]),
        new JProperty("api-key", context.Variables["api-key"]),
        new JProperty("request-body", context.Variables["request"]),
        new JProperty("response-body", context.Variables["response"])            
    ).ToString();
    }
</log-to-eventhub>

logger-idは以下のドキュメントを参照して設定する必要がある。Azure CLIではまだ構成できないのが少々あれではあるが、接続文字列でEvent Hubsと接続する場合はPowerShellでも可能。Managed Identityを使ったRBACを構成している場合には、REST APIやBicep、ARM Templateでのみ構成可能である点には注意が必要(REST API使うならBearer token取得が面倒なのでaz rest一択)。

Azure API Management で Azure Event Hubs にイベントを記録する方法 / How to log events to Azure Event Hubs in Azure API Management
https://learn.microsoft.com/azure/api-management/api-management-howto-log-event-hubs
Logger – Create Or Update
https://learn.microsoft.com/rest/api/apimanagement/current-preview/logger/create-or-update?tabs=HTTP

以上で完了。

テスト

今回はEvent HubsからLogic Appsでデータを取り出し、最終的にCosmos DBに書き出す構成にした(もちろん、Stream Analyticsでやってもいいので、お好みで)。バックエンドサービスとしては本来ならAOAIにすべきではあるが、8,192Byteの制約がないことを確認するため、単純に9,000Byteの文字列を返すLogic Appsで作ったモックサービスを設定している。そしてEvent Hubsに入ったカスタムログは、Logic Appsが仲介してCosmos DBに書き出している。このLogic AppsはEvent HubsのトリガーアクションとCosmos DBのアクションを使ったシンプルなもの。

AOAIのAPI呼び出しをシミュレートするため、以下のリクエスト本文をつけてAPIMがホストするAPIを呼び出してみたところ、

{
  "prompt": "Generate a summary of the below conversation in the following format:\nCustomer problem:\nOutcome of the conversation:\nAction items for follow-up:\nCustomer budget:\nDeparture city:\nDestination city:\n\nConversation:\nUser: Hi there, I'm off between August 25 and September 11. I saved up 4000 for a nice trip. If I flew out from San Francisco, what are your suggestions for where I can go?\nAgent: For that budget you could travel to cities in the US, Mexico, Brazil, Italy or Japan. Any preferences?\nUser: Excellent, I've always wanted to see Japan. What kind of hotel can I expect?\nAgent: Great, let me check what I have. First, can I just confirm with you that this is a trip for one adult?\nUser: Yes it is\nAgent: Great, thank you, In that case I can offer you 15 days at HOTEL Sugoi, a 3 star hotel close to a Palace. You would be staying there between August 25th and September 7th. They offer free wifi and have an excellent guest rating of 8.49/10. The entire package costs 2024.25USD. Should I book this for you?\nUser: That sounds really good actually. Please book me at Sugoi.\nAgent: I can do that for you! Can I help you with anything else today?\nUser: No, thanks! Please just send me the itinerary to my email soon.\n\nSummary:",
  "temperature": 0.0,
  "top_p": 1,
  "frequency_penalty": 0,
  "presence_penalty": 0,
  "max_tokens": 350,
  "stop": null
}

Cosmos DBには想定通りの情報が入っていた(黄色でマークした部分はリクエストメッセージボディ、緑色でマークした部分は9,000Byteのメッセージが入っている)。

この設定はAPIスコープなどでまとめて設定できるので、逐一オペレーションごとに設定しなくてもよい点も都合がよい。

コメントを残す

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