つれづれなる Agent OPS
つれづれなる Agent OPS
AgentOps

Cloudflare AI GatewayのpatchLogでLLMの評価ログを育てる

AI Gatewayのログにfeedback、score、metadataを後付けし、LLMのリクエストログを改善対象の評価データへ変えるための小さな検証メモ。

Xでシェア
Markdown を表示

はじめに

Cloudflare AI Gatewayは、LLMアプリの入口に置くだけでprovider、model、status、duration、tokens、cost、prompt、responseを見やすくしてくれます。以前の記事では、AI Gatewayを経由したLLMリクエストがどこまでログとして残るかを整理しました。

AI GatewayでLLMのリクエストとレスポンスをどこまで記録できるか試す

ただ、ログを眺められるだけでは改善サイクルになりません。運用で本当に欲しいのは、「この回答は役に立ったのか」「再生成すべきだったのか」「あとから評価データとして抽出できるのか」という評価の戻し先です。

ここで気になったのが、Cloudflare AI GatewayのログAPIに用意されている Patch Gateway Log です。公式APIリファレンスでは、AI Gatewayの個別ログに対して feedbackscoremetadata をPATCHできるようになっています。2026年7月3日時点のAPIリファレンスでは、feedback-1 から 1score0 から 100metadata は文字列、数値、真偽値のmapとして扱われます。CloudflareのCustom metadataドキュメントでは、リクエストに渡せるmetadataは最大5件で、6件目以降は保存されないとも説明されています。

これ、かなり面白い。

ログを保存して終わりではなく、LLM呼び出しの結果に評価を戻せるなら、AI Gatewayを「出口ログ」から「評価データの入口」に少し近づけられます。本記事では、patchLog 相当のPATCHリクエストを使って、AI Gatewayのログへ評価情報を戻す設計を小さく検証します。

今回の到達点

この記事で確認するのは、AI Gatewayだけで本格的なAgentOps基盤を完成させることではありません。Langfuseのようなtrace、span、generation、score、datasetを持つ観測基盤と比べると、AI Gatewayはアプリ内部の処理構造までは持ちません。

今回の到達点は、もう少し手前です。

LLM request

AI Gateway log

feedback / score / metadataをPATCH

改善対象を検索・抽出する

AI Gatewayのログにfeedback、score、metadataを戻して評価データ化する流れ

AI Gatewayのログは、LLMプロバイダへ出ていったリクエストの事実を持ちます。そこへ人間の評価、簡易採点、評価軸のmetadataを戻せるなら、少なくとも次の用途には使えます。

  • 評価が低かったLLM呼び出しだけをあとから抽出する
  • モデル変更前後でscoreの傾向を見る
  • ユーザー評価とtokens、cost、durationを同じログ上で見る
  • payloadを保存しない運用でも、評価値とmetadataだけは残す

一方で、RAGでどの文書を検索したか、Agentがどのtoolを呼んだか、回答前にどの分岐を通ったかはAI Gatewayだけでは分かりません。ここを混ぜると設計を誤ります。AI Gatewayに戻す評価は、あくまで「LLM呼び出し単位の評価」として扱うのがよさそうです。

公式APIで確認したこと

CloudflareのAPIリファレンスでは、AI Gateway Logsに次の操作が用意されています。

今回使うのは Patch Gateway Log です。エンドポイントは次の形です。

PATCH /accounts/{account_id}/ai-gateway/gateways/{gateway_id}/logs/{id}

API tokenにはAI Gateway Write権限が必要です。bodyには、必要な項目だけを入れます。

{
  "feedback": 1,
  "score": 92,
  "metadata": {
    "app": "support-bot",
    "rubric": "answer-usefulness-v1",
    "reviewed": true
  }
}

ここで大事なのは、feedbackscore の役割を分けることです。feedback はユーザーが押した高評価・低評価に近い粗い信号として扱い、score はrubricに基づく採点値として扱う方が運用しやすいです。たとえば、UIの👍は feedback: 1、👎は feedback: -1 とし、あとからバッチ評価で score: 0..100 を付与する、という分担が考えられます。

metadataは、評価の検索軸です。appenvrubricexperimentuser_segment のような値を残しておくと、あとから「本番の問い合わせBotで、回答有用性rubricのscoreが低いログ」だけを抽出しやすくなります。ただし、metadataにも個人情報や顧客情報を入れない方がよいです。ログを便利にするほど、ログ自体が管理対象になります。

もう1つ、metadataは増やしすぎない方がよいです。CloudflareのCustom metadataドキュメントでは、1リクエストあたり最大5件まで保存され、5件を超えた分は無視されると説明されています。設計上は、appenvfeaturerubricfailure_case のように、検索で本当に使うキーだけに絞る方がよさそうです。

Worker Bindingで書く場合

本記事の実検証は、Cloudflare APIを直接呼ぶREST APIベースで行っています。一方、実アプリをCloudflare Workers上に置くなら、AI bindingからログIDを受け取り、同じWorker内で patchLog() へつなぐ形の方が自然です。

CloudflareのCustom metadataドキュメントでは、Workers AI bindingの env.AI.run()gateway.metadata を渡す例が示されています。また、Workersの型定義には env.AI.aiGatewayLogIdenv.AI.gateway("...").patchLog(logId, data) が用意されています。これを組み合わせると、UIの👍👎や失敗分類をAI Gatewayログへ戻す実装は、概念的には次の形になります。

export interface Env {
  AI: Ai;
}

export default {
  async fetch(_request, env): Promise<Response> {
    const gatewayId = "patch-log-feedback-loop";

    const answer = await env.AI.run(
      "@cf/meta/llama-3.1-8b-instruct-fast",
      {
        prompt: "問い合わせへの回答案を短く作ってください。",
      },
      {
        gateway: {
          id: gatewayId,
          metadata: {
            app: "support-bot",
            env: "production",
            rubric: "answer-usefulness-v1",
          },
        },
      },
    );

    const logId = env.AI.aiGatewayLogId;

    if (logId) {
      await env.AI.gateway(gatewayId).patchLog(logId, {
        feedback: 1,
        score: 92,
        metadata: {
          app: "support-bot",
          env: "production",
          rubric: "answer-usefulness-v1",
          reviewed: true,
        },
      });
    }

    return Response.json({ answer, logId });
  },
} satisfies ExportedHandler<Env>;

ここでのポイントは、LLM呼び出しのログIDをアプリのイベントとつなげることです。たとえば、回答直後に logId をmessage idと一緒に保存しておき、ユーザーが👍👎を押したタイミングで patchLog() を呼べば、AI Gatewayのログに人間の評価を戻せます。

ただし、このWorker Binding版は本記事では未実行です。今回実際に確認したのは、この後に説明するREST API経由の Patch Gateway Log です。ここは、「patchLogという名前のAPIをWorkerではどう使うのか」を示すための実装スケッチとして扱います。

検証用スクリプトを用意する

今回の検証では、記事用に小さなNode.jsスクリプトを用意しました。これはCloudflare公式CLIではなく、patchLog に投げるリクエストbodyを安全に組み立て、実APIを叩く前に値の範囲を確認するための自作検証スクリプトです。

このスクリプトが入力として受け取るのは、AI GatewayのAccount ID、Gateway ID、Log ID、Cloudflare API token、そして feedbackscoremetadata です。--dry-run ではAPIに送らず、PATCH先の形とbodyだけを確認します。--apply を付けた場合だけ、Cloudflare APIへPATCHします。

まず、資格情報なしでdry runします。

npm run dry-run

出力は次のような形になります。

# AI Gateway patchLog dry run

case: helpful
endpoint: https://api.cloudflare.com/client/v4/accounts/<account_id>/ai-gateway/gateways/<gateway_id>/logs/<log_id>

request body:
{
  "feedback": 1,
  "score": 92,
  "metadata": {
    "app": "blog-ex",
    "env": "local",
    "rubric": "answer-usefulness-v1",
    "reviewed": true
  }
}

実際にPATCHする場合は、Cloudflare API tokenと実ログIDを設定したうえで --apply を使います。

node scripts/patch-log-feedback-loop.mjs --apply --case helpful

ただ、最初から有効な外部providerのAPI keyを使うと、今回の主題ではないprovider課金やモデル応答の論点が混ざります。そこで、この記事ではCloudflare上に検証用Gatewayを作り、あえて無効なOpenAI API keyでリクエストして401ログを作りました。その実ログに対して patchLog を実行し、再取得で feedbackscoremetadata が反映されるかを確認します。

一連の検証は、記事用に用意した自作スクリプトで実行しました。これは、Gatewayがなければ作成し、AI Gateway経由でエラーログを1件作り、そのログIDへPATCHし、最後にログ詳細を再取得するためのものです。

npm run live:patch-log

実行結果は次のとおりです。

AI Gatewayの実ログにpatchLogを適用し、feedback、score、metadataの反映を確認した結果

検証では、patch-log-feedback-loop というGatewayが作成され、AI Gateway経由のOpenAIリクエストは意図どおり 401 Unauthorized になりました。これは失敗ではなく、外部providerの有効なAPI keyを使わずにAI Gateway上へ実ログを作るための手順です。

その直後にLogs APIで直近ログを取得すると、providerはopenai、modelはgpt-4o-mini、status codeは401として記録されていました。最初のログには、リクエスト時に付けたmetadataはありましたが、feedbackscoreはまだありません。

そこへ次の評価値をPATCHしました。

{
  "feedback": -1,
  "score": 24,
  "metadata": {
    "app": "blog-ex",
    "env": "live",
    "rubric": "provider-error-observability-v1",
    "failure_case": "invalid-provider-key"
  }
}

PATCH後に同じログを再取得すると、feedback: -1score: 24metadata.failure_case: "invalid-provider-key" が反映されていました。つまり、少なくともCloudflare API経由では、AI Gatewayの実ログに評価値を後付けできることを確認できました。

今回確認したのは、Dashboard表示ではなくAPIでの再取得結果です。また、有効なprovider API keyを使った正常応答ログではなく、意図的に作った401ログに対するPATCHです。正常応答ログでも同じ設計で使えるはずですが、成功ログ、Dashboard上の表示、List Logsでのfilter条件、Datasets/Evaluations APIとの接続は別途確認する余地があります。

なぜ最初にvalidationを挟むのか

patchLog は小さなAPIですが、評価ログとして使うならvalidationをアプリ側に寄せた方がよいです。

理由は単純で、評価値はあとから集計されるからです。score101 が入ったり、metadataにネストしたオブジェクトや配列が混ざったりすると、その場では小さな実装ミスでも、後段の集計や抽出で地味に効いてきます。

今回の検証スクリプトでは、次を実行前に止めます。

項目許可する値止める値
feedback-1 から 1 の数値範囲外、数値でない値
score0 から 100 の数値範囲外、数値でない値
metadata文字列、数値、真偽値のmap。最大5件配列、オブジェクト、null、6件目以降に依存する設計

このvalidationは、AI GatewayのAPI仕様をなぞっているだけです。しかし、ここをアプリケーション側にも置くことで、評価データの形が崩れにくくなります。ログはあとから直すのが面倒です。最初に狭くしておく方が、運用では楽です。

payload保存なし運用との組み合わせ

前回の記事では、AI Gatewayでpromptとresponseのpayload保存を抑止するヘッダーも確認しました。

cf-aig-collect-log-payload: false

本文を保存しない場合、ログから具体的な失敗理由を読む力は落ちます。一方で、model、tokens、cost、duration、statusのような運用メタデータは残せます。そこへ feedbackscoremetadata を戻せるなら、「本文は残さないが、品質傾向は追う」という折衷案が作れます。

たとえば、業務データを含む問い合わせではpayloadを残さず、ユーザーが押した👍👎と、アプリ側で抽象化したカテゴリだけをmetadataに残す。

{
  "feedback": -1,
  "score": 32,
  "metadata": {
    "app": "support-bot",
    "env": "production",
    "topic": "billing",
    "rubric": "answer-usefulness-v1"
  }
}

この形なら、prompt本文を見ずに「billing系の低評価が増えている」「特定モデルに切り替えたあとscoreが落ちている」といった傾向を見られます。もちろん、原因分析には別の安全なサンプリングや、匿名化された再現データが必要です。それでも、何も残らないよりは改善の入口になります。

Langfuseとどう使い分けるか

AI Gatewayの patchLog を見て最初に思ったのは、「これでLangfuseの代わりになるのか」ということでした。

結論としては、代替というより役割が違います。

AI Gatewayは、LLMプロバイダへ出ていくリクエストのログを持ちます。provider、model、tokens、cost、duration、status、payload、metadataに強いです。patchLog でfeedbackやscoreを戻せるため、LLM呼び出し単位の評価には使えます。

一方、LangfuseのようなAgentOpsツールは、アプリケーション内部のtraceを表現できます。RAGの検索、Agentのtool call、複数stepの実行、dataset、eval run、scoreの履歴を扱うなら、そちらの方が自然です。

そのため、個人的には次の分担がよさそうだと感じています。

領域AI GatewayLangfuseなど
LLMプロバイダへの出口ログ得意連携次第
tokens、cost、duration得意実装次第
prompt / responseの確認得意得意
RAGやAgentの内部trace弱い得意
ユーザー評価の粗い付与patchLog で可能得意
dataset化、eval run管理限定的得意

AI Gatewayだけで閉じるなら、最初の評価ループはかなり軽く作れます。ただし、AgentやRAGのどの工程が悪かったかまで見たいなら、AI Gatewayのlog idをアプリ内部のtrace idやsession idとひも付けて、別の観測基盤へ渡す設計が必要になります。

使うときの設計メモ

実装に入る前に、少なくとも次の4点は決めた方がよいです。

1つ目は、feedbackscore の意味です。feedback は人間の粗い反応、score はrubricに基づく採点、と決めておくと混乱しにくくなります。

2つ目は、metadataの語彙です。appenvfeaturerubricexperiment のような共通キーを決めずに自由入力にすると、あとから検索できないログが増えます。さらに、metadataは最大5件という制約があるため、検索軸を増やす前に「本当に集計で使うか」を決める必要があります。

3つ目は、payload保存の方針です。本文を残すとデバッグは楽になりますが、ログの取り扱いは重くなります。本文を残さない場合でも、評価値とmetadataで傾向を見る設計はできます。

4つ目は、log idの受け渡しです。アプリのUIで👍👎を押したとき、どのAI Gateway logへPATCHするのかを特定できなければ、評価を戻せません。LLMリクエスト、アプリ側のmessage id、ユーザー操作、AI Gateway log idをどう対応させるかが、この方式の実装上の論点になります。

ここで止まるのか、という感じがあります。

APIとしては小さいのですが、実際に評価ループへ組み込むには、ログIDの管理、UIイベント、payload保存方針、metadata設計が一気に出てきます。これは機能紹介というより、運用設計の話です。

まとめ

Cloudflare AI Gatewayの patchLog は、LLMリクエストログに feedbackscoremetadata を後付けできる小さなAPIです。派手な機能ではありませんが、LLMの出口ログを評価データへ育てる入口としてはかなり実用的に見えます。

今回の検証では、API仕様に合わせてPATCH bodyを組み立て、feedbackscoremetadata の制約を実行前に検査したうえで、Cloudflare上の実ログにPATCHできることまで確認しました。対象は、外部providerの有効なAPI keyを使わずに作った401ログです。正常応答ログやDashboard上の表示、List Logsでのfilter、DatasetsやEvaluations APIとの接続は追加検証が必要です。

それでも、設計上の示唆はあります。

AI Gatewayは「ログを見る道具」で終わらせるより、評価を戻す口を最初から用意した方が価値が出ます。ユーザー評価、簡易採点、metadataを同じログに返せるようにしておくと、LLMアプリは後から改善しやすくなります。

正直、基本ログの記事を書いた時点では、AI Gatewayは「便利なプロキシ兼ログビューア」くらいに見ていました。でも patchLog を見ると、少し印象が変わります。軽量な評価ループの部品として使える余地があります。

残る検証範囲は、実ログIDをアプリ側に返す小さなWorkerを作り、UIの👍👎から patchLog へつなぐところです。そこまで動けば、AI Gatewayだけで始める最小のフィードバックループとして、もう一段具体的に評価できます。

DUOps

Author

DUOps(デュオプス)

LLMOps、Agent、MCP、Langfuse、Cloudflare 周辺の実装と運用を、個人で試しながら記録しています。

Xを見る

コメント

Related