---
title: "Cloudflare AI GatewayのpatchLogでLLMの評価ログを育てる"
description: "AI Gatewayのログにfeedback、score、metadataを後付けし、LLMのリクエストログを改善対象の評価データへ変えるための小さな検証メモ。"
lang: "ja"
canonical: "https://llm-lab.dev/posts/cloudflare-ai-gateway-patch-log-feedback-loop/"
source: "https://llm-lab.dev/posts/cloudflare-ai-gateway-patch-log-feedback-loop.md"
publishedAt: "2026-07-04"
updatedAt: "2026-07-04"
category: "AgentOps"
tags:
  - "cloudflare"
  - "ai-gateway"
  - "agentops"
  - "eval"
  - "feedback-loop"
---

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

> [!NOTE]
> この記事で確認したこと
>
> 今回は、Cloudflare AI Gatewayに記録済みのLLMリクエストログへ、後から評価情報を戻せるかを検証しました。
>
> 実ログを作るために意図的な `401 Unauthorized` のproviderエラーログを作成し、そのログへAPI経由で人間の反応、採点値、検索用metadataをPATCHできることを確認しました。この軽量な評価ループはLLM呼び出し単位には使えますが、RAGの検索、tool call、複数stepの実行を見るには別のtrace設計が必要です。

## はじめに

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

[AI GatewayでLLMのリクエストとレスポンスをどこまで記録できるか試す](/posts/cloudflare-ai-gateway-llm-request-response-logging/)

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

ここで気になったのが、Cloudflare AI GatewayのログAPIに用意されている `Patch Gateway Log` です。公式APIリファレンスでは、AI Gatewayの個別ログに対して `feedback`、`score`、`metadata` をPATCHできるようになっています。2026年7月3日時点のAPIリファレンスでは、`feedback` は `-1` から `1`、`score` は `0` から `100`、`metadata` は文字列、数値、真偽値の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はアプリ内部の処理構造までは持ちません。

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

```text
LLM request
  ↓
AI Gateway log
  ↓
feedback / score / metadataをPATCH
  ↓
改善対象を検索・抽出する
```

![AI Gatewayのログにfeedback、score、metadataを戻して評価データ化する流れ](/images/posts/cloudflare-ai-gateway-patch-log-feedback-loop/01-patchlog-feedback-flow.webp)

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に次の操作が用意されています。

* [List Gateway Logs](https://developers.cloudflare.com/api/resources/ai_gateway/subresources/logs/methods/list/)
* [Get Gateway Log Detail](https://developers.cloudflare.com/api/resources/ai_gateway/subresources/logs/methods/get)
* [Patch Gateway Log](https://developers.cloudflare.com/api/resources/ai_gateway/subresources/logs/methods/edit)
* [Delete Gateway Logs](https://developers.cloudflare.com/api/resources/ai_gateway/subresources/logs/methods/delete/)

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

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

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

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

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

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

もう1つ、metadataは増やしすぎない方がよいです。CloudflareのCustom metadataドキュメントでは、1リクエストあたり最大5件まで保存され、5件を超えた分は無視されると説明されています。設計上は、`app`、`env`、`feature`、`rubric`、`failure_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.aiGatewayLogId` と `env.AI.gateway("...").patchLog(logId, data)` が用意されています。これを組み合わせると、UIの👍👎や失敗分類をAI Gatewayログへ戻す実装は、概念的には次の形になります。

```ts
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、そして `feedback`、`score`、`metadata` です。`--dry-run` ではAPIに送らず、PATCH先の形とbodyだけを確認します。`--apply` を付けた場合だけ、Cloudflare APIへPATCHします。

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

```bash
npm run dry-run
```

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

```text
# 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` を使います。

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

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

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

```bash
npm run live:patch-log
```

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

![AI Gatewayの実ログにpatchLogを適用し、feedback、score、metadataの反映を確認した結果](/images/posts/cloudflare-ai-gateway-patch-log-feedback-loop/02-live-patch-result.webp)

検証では、`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はありましたが、`feedback`と`score`はまだありません。

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

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

PATCH後に同じログを再取得すると、`feedback: -1`、`score: 24`、`metadata.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をアプリ側に寄せた方がよいです。

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

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

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

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

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

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

```http
cf-aig-collect-log-payload: false
```

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

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

```json
{
  "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 Gateway | Langfuseなど |
|---|---|---|
| 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つ目は、`feedback` と `score` の意味です。`feedback` は人間の粗い反応、`score` はrubricに基づく採点、と決めておくと混乱しにくくなります。

2つ目は、metadataの語彙です。`app`、`env`、`feature`、`rubric`、`experiment` のような共通キーを決めずに自由入力にすると、あとから検索できないログが増えます。さらに、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リクエストログに `feedback`、`score`、`metadata` を後付けできる小さなAPIです。派手な機能ではありませんが、LLMの出口ログを評価データへ育てる入口としてはかなり実用的に見えます。

今回の検証では、API仕様に合わせてPATCH bodyを組み立て、`feedback`、`score`、`metadata` の制約を実行前に検査したうえで、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だけで始める最小のフィードバックループとして、もう一段具体的に評価できます。
