このエントリは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
AsandAsFormUrlEncodedContent()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 thepreserveContentparameter totrue, as shown in examples for theset-bodypolicy.既定では、
AsとAsFormUrlEncodedContent()のメソッド:
– 元のメッセージ本文ストリームを使います。
– 戻った後は使用不可とレンダリングします。
これを回避し、本文ストリームのコピーでメソッドを実行するには、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スコープなどでまとめて設定できるので、逐一オペレーションごとに設定しなくてもよい点も都合がよい。