Bedrock(Claude 4)でToolsがどのタイミングPrompt cachingされるか試してみた(MCP利用)

Bedrock(Claude 4)でToolsがどのタイミングPrompt cachingされるか試してみた(MCP利用)

Clock Icon2025.06.09

はじめに

Grafana MCPを使ったAIチャットボットを検討する際に、MCPのツール一覧はどのようにPrompt cachingされるのか気になったので検証しました。

MCPのツール一覧は以下のような特徴が当てはまる場合、Prompt cachingすることでコスト削減が見込めます。

  • トークン数が多い
  • 静的な内容である

後者の 静的な内容である に関しては、多くのMCPサーバーの特徴だと思います。

Claudeモデルを利用するので、Claudeの公式ドキュメントを見ると以下の記述があります。

https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching

Prompt caching caches the full prefix
Prompt caching references the entire prompt - tools, system, and messages (in that order) up to and including the block designated with cache_control.

(翻訳)
Prompt caching は完全なプレフィックスをキャッシュします
Prompt caching では、ツール、システム、メッセージ(この順序で)から、cache_control で指定されたブロックまでのプロンプト全体を参照します。

Prompt cachingはtools, system, userの順のprefixの完全一致によって実現されることがわかります。完全な前方一致は、LLMのアテンション機構からも納得がいきます。

これはtoolsやsystemが静的な内容で運用されるAIチャットボットでは非常に良い仕様で、tools, systemまでの内容でcacheした場合、全て会話でsystemまでのキャッシュを活用すること出来ます。

一方でuserメッセージが追加されていくと、その会話のみでしかキャッシュが効かないので、ターン数の多い会話を続けないとヒットしません。(prefix完全一致のため他のユーザーがたまたまそのキャッシュを引く確率は非常に低いと考えられます。)

結論

前述の通りの仕様が確認できました。tools->systemでキャッシュポイントを設定すれば、ユーザーメッセージを変更してもtools->systemまでキャッシュが適用されます。

toolsのトークン数が多いかつ静的なチャットボットではsystemまでのキャッシュポイントで効率的なPrompt cachingが可能です。

検証

前提

利用する環境は以下です。

  • Bedrock(us.anthropic.claude-sonnet-4-20250514-v1:0)
  • ai-sdk の streamText経由で確認

※ あえてai-sdkを利用する理由は、このSDKを通して開発する例が多いと思いますので今後のロギング含めて参考になると思ったためです。

評価内容は以下です。

  1. systemにキャッシュポイントを設定して、6985(tools) + 94(system) = 7079 が キャッシュされることを確認
  2. userメッセージを変更して、1のtools, systemのキャッシュが利用されていることを確認
type 内容 キャッシュポイント トークン数(概算)
tools Grafana MCPのツール一覧(省略) 6985
system あなたはGrafanaのダッシュボードを操作するためのアシスタントです。
あなたの主な機能は、ユーザーにGrafanaのダッシュボードを操作するためのツールを提供することです。
Grafanaのダッシュボードを操作するためのMCPを使用してください。
94
user あなたはどんなことができますか? 19
user あなたはどんなことができますか?具体的に教えてください。 28

検証

検証スクリプトは以下です。

Prompt caching検証スクリプト
スクリプト
import path from "node:path";
import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock";
import { fromNodeProviderChain } from "@aws-sdk/credential-providers";
import {
  type CoreMessage,
  type CoreSystemMessage,
  type CoreUserMessage,
  experimental_createMCPClient,
  streamText,
} from "ai";
import { Experimental_StdioMCPTransport } from "ai/mcp-stdio";
import { logger } from "./utils/logger";

const AWS_REGION = process.env.AWS_REGION || "us-east-1";
const GRAFANA_API_KEY = process.env.GRAFANA_API_KEY!;

const bedrock = createAmazonBedrock({
  region: AWS_REGION,
  credentialProvider: fromNodeProviderChain(),
});

const modelID = {
  apac: "apac.anthropic.claude-sonnet-4-20250514-v1:0",
  us: "us.anthropic.claude-sonnet-4-20250514-v1:0",
};

const model = bedrock(modelID.us, {
  additionalModelRequestFields: {},
});

const mcpClient = await experimental_createMCPClient({
  transport: new Experimental_StdioMCPTransport({
    command: path.join(process.cwd(), "bin/.mcp-grafana/mcp-grafana"),
    args: [],
    env: {
      GRAFANA_URL: "http://localhost:8085",
      GRAFANA_API_KEY,
    },
  }),
});

const tools = await mcpClient.tools();

const processMessages = async () => {
  try {
    const systemContent = `あなたはGrafanaのダッシュボードを操作するためのアシスタントです。
あなたの主な機能は、ユーザーにGrafanaのダッシュボードを操作するためのツールを提供することです。
Grafanaのダッシュボードを操作するためのMCPを使用してください。`;

    const systemMessage: CoreSystemMessage = {
      role: "system",
      content: systemContent,
      providerOptions: {
        bedrock: { cachePoint: { type: "default" } },
      },
    };

    const userMessage: CoreUserMessage = {
      role: "user",
      content: "あなたはどんなことができますか?",
    };

    const messages: CoreMessage[] = [systemMessage, userMessage];

    const { textStream } = streamText({
      model,
      messages: messages,
      tools,
      experimental_toolCallStreaming: true,
      onFinish: (event) => {
        logger.info(
          {
            usage: {
              ...event.usage,
              // @ts-ignore
              bedrock: event.providerMetadata?.bedrock.usage ?? null,
              // NOTE: erimental_providerMetadataはdeplicate
              // experimental_bedrock: event.experimental_providerMetadata?.bedrock.usage ?? null,
            },
          },
          "Stream completed with token usage",
        );

        logger.info({
          output: {
            text: event.text,
          },
        });
      },
      onError: ({ error }) => {
        logger.error({ error }, "Stream error occurred");

        throw error;
      },
    });

    for await (const textPart of textStream) {
      process.stdout.write(textPart);
    }
  } catch (error) {
    logger.error({ error }, "Error processing chat request");
    throw new Error(error instanceof Error ? error.message : "An error occurred.");
  }
};

await processMessages();
実行方法
export GRAFANA_API_KEY="glsa_***"
export AWS_REGION="us-east-1"

npx tsx ./src/index.ts

まず初回実行します。

$ npx tsx ./src/index.ts
time=2025-06-09T11:31:39.777+09:00 level=INFO msg="Starting Grafana MCP server using stdio transport"
time=2025-06-09T11:31:39.777+09:00 level=INFO msg="Using Grafana configuration" url=http://localhost:8085 api_key_set=true
私はGrafanaのダッシュボードやモニタリングシステムを操作するためのアシスタントです。以下のような機能を提供できます:

## 📊 ダッシュボード操作
- **ダッシュボードの検索・取得**: タイトルやキーワードでダッシュボードを検索
- **ダッシュボードの作成・更新**: 新しいダッシュボードの作成や既存のものの更新
- **パネル情報の取得**: ダッシュボード内のパネルのクエリやデータソース情報を確認

## 🔍 データクエリ・分析
- **Prometheusクエリ**: PromQLを使用したメトリクス取得
- **Lokiログクエリ**: LogQLを使用したログ検索・分析
- **ラベル・メトリクス情報**: 利用可能なラベルやメトリクス名の一覧取得
- **エラーパターン分析**: ログから異常なエラーパターンを検出
- **スロークエリ検出**: パフォーマンス問題の特定

## 🚨 アラート・インシデント管理
- **アラートルール管理**: アラートルールの一覧取得・詳細確認
- **インシデント管理**: インシデントの作成・更新・一覧取得
- **通知設定**: コンタクトポイントの管理

## 👥 チーム・オンコール管理
- **オンコール管理**: スケジュール確認・当番者の特定
- **チーム管理**: チーム情報の検索・管理
- **ユーザー管理**: ユーザー情報の取得

## 🔧 システム管理
- **データソース管理**: Prometheus、Loki、Tempoなどのデータソース情報取得
- **アサーション確認**: エンティティの状態確認
- **調査・分析**: Siftを使用した詳細な調査

何か具体的にお手伝いできることがあれば、お気軽にお聞かせください!例えば:
- 「特定のサービスのダッシュボードを探して」
- 「最近のエラーログを調べて」
- 「現在のオンコール担当者を教えて」
- 「アラートが発火している項目を確認して」

などのリクエストに対応できます。{"level":"INFO","time":"2025-06-09T02:31:59.284Z","pid":67265,"hostname":"HL01142.local","usage":{"promptTokens":153,"completionTokens":707,"totalTokens":860,"bedrock":{"cacheReadInputTokens":0,"cacheWriteInputTokens":7191}},"msg":"Stream completed with token usage"}
{"level":"INFO","time":"2025-06-09T02:31:59.285Z","pid":67265,"hostname":"HL01142.local","output":{"text":"私はGrafanaのダッシュボードやモニタリングシステムを操作するためのアシスタントです。以下のような機能を提供できます:\n\n## 📊 ダッシュボード操作\n- **ダッシュボードの検索・取得**: タイトルやキーワードでダッシュボードを検索\n- **ダッシュボードの作成・更新**: 新しいダッシュボードの作成や既存のものの更新\n- **パネル情報の取得**: ダッシュボード内のパネルのクエリやデータソース情報を確認\n\n## 🔍 データクエリ・分析\n- **Prometheusクエリ**: PromQLを使用したメトリクス取得\n- **Lokiログクエリ**: LogQLを使用したログ検索・分析\n- **ラベル・メトリク ス情報**: 利用可能なラベルやメトリクス名の一覧取得\n- **エラーパターン分析**: ログから異常なエラーパターンを検出\n- **スロークエリ検出**: パフォーマンス問題の特定\n\n## 🚨 アラート・インシデント管理\n- **アラートルール管理**: アラートルールの一覧取得・詳細確認\n- **インシデント管理**: インシデントの作成・更新・一覧取得\n- **通知設定**: コンタクトポイントの管理\n\n## 👥 チーム・オンコール管理\n- **オンコール管理**: スケジュール確認・当番者の特定\n- **チーム管理**: チーム情報の検索・管理\n- **ユーザー管理**: ユーザー情報の取得\n\n## 🔧 システム管理\n- **データソース管理**: Prometheus、Loki、Tempoなどのデータソース情報取得\n- **アサーション確認**: エンティティの状態確認\n- **調査・分析**: Siftを使用した詳細な調査\n\n何か具体的にお手伝いできることがあれば、お気軽にお聞かせ ください!例えば:\n- 「特定のサービスのダッシュボードを探して」\n- 「最近のエラーログを調べて」\n- 「現在のオンコール担当者を教えて」\n- 「アラートが発火している項目を確認して」\n\nなどのリクエストに対応できます。"}}
Bedrockログ(初回)
{
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0",
    "timestamp": "2025-06-09T02:31:39Z",
    "accountId": "111111111111",
    "identity": {
        "arn": "arn:aws:sts::111111111111:assumed-role/shuntaka/1749427297374859000"
    },
    "region": "us-east-1",
    "requestId": "ced3c99a-0c8a-41c5-82fe-26ecbff20d3c",
    "operation": "ConverseStream",
    "modelId": "us.anthropic.claude-sonnet-4-20250514-v1:0",
    "input": {
    (中略)
        "inputTokenCount": 153,
        "cacheReadInputTokenCount": 0,
        "cacheWriteInputTokenCount": 7191
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "output": {
                "message": {
                    "role": "assistant",
                    "content": [
                        {
                            "text": "私はGrafanaのダッシュボードやモニタリングシステムを操作するためのアシスタントです。以下のような機能を提供できます:\n\n## 📊 ダッシュボード操作\n- **ダッシュボードの検索・取得**: タイトルやキーワードでダッシュボードを検索\n- **ダッシュボードの作成・更新**: 新しいダッシュボードの作成や既存のものの更新\n- **パネル情報の取得**: ダッシュボード内のパネルのクエリやデータソース情報を確認\n\n## 🔍 データクエリ・分析\n- **Prometheusクエリ**: PromQLを使用したメトリクス取得\n- **Lokiログクエリ**: LogQLを使用したログ検索・分析\n- **ラベル・メトリクス情報**: 利用可能なラベルやメトリクス名の一覧取得\n- **エラーパターン分析**: ログから異常なエラーパターンを検出\n- **スロークエリ検出**: パフォーマンス問題の特定\n\n## 🚨 アラート・インシデント管理\n- **アラートルール管理**: アラートルールの一覧取得・詳細確認\n- **インシデント管理**: インシデントの作成・更新・一覧取得\n- **通知設定**: コンタクトポイントの管理\n\n## 👥 チーム・オンコール管理\n- **オンコール管理**: スケジュール確認・当番者の特定\n- **チーム管理**: チーム情報の検索・管理\n- **ユーザー管理**: ユーザー情報の取得\n\n## 🔧 システム管理\n- **データソース管理**: Prometheus、Loki、Tempoなどのデータソース情報取得\n- **アサーション確認**: エンティティの状態確認\n- **調査・分析**: Siftを使用した詳細な調査\n\n何か具体的にお手伝いできることがあれば、お気軽にお聞かせください!例えば:\n- 「特定のサービスのダッシュボードを探して」\n- 「最近のエラーログを調べて」\n- 「現在のオンコール担当者を教えて」\n- 「アラートが発火している項目を確認して」\n\nなどのリクエストに対応できます。"
                        }
                    ]
                }
            },
            "stopReason": "end_turn",
            "metrics": {
                "latencyMs": 18555
            },
            "usage": {
                "inputTokens": 153,
                "cacheReadInputTokens": 0,
                "cacheWriteInputTokens": 7191,
                "outputTokens": 707,
                "totalTokens": 8051
            }
        },
        "outputTokenCount": 707
    },
    "inferenceRegion": "us-west-2"
}

2回目の実行します。

$ npx tsx ./src/index.ts
time=2025-06-09T11:32:41.050+09:00 level=INFO msg="Starting Grafana MCP server using stdio transport"
time=2025-06-09T11:32:41.050+09:00 level=INFO msg="Using Grafana configuration" url=http://localhost:8085 api_key_set=true
私はGrafanaのダッシュボードやモニタリングシステムを操作するためのアシスタントです。以下のような機能を提供できます:

## 📊 ダッシュボード操作
- **ダッシュボードの検索・取得**: タイトルやキーワードでダッシュボードを検索
- **ダッシュボードの作成・更新**: 新しいダッシュボードの作成や既存のものの更新
- **パネル情報の取得**: ダッシュボード内のパネルのクエリやデータソース情報を確認

## 🔍 データクエリ・分析
- **Prometheusクエリ**: PromQLを使用したメトリクス取得
- **Lokiログクエリ**: LogQLを使用したログ検索・分析
- **ラベル・メトリクス情報**: 利用可能なラベルやメトリクス名の一覧取得
- **エラーパターン分析**: ログから異常なエラーパターンを検出
- **スロークエリ検出**: 遅いリクエストの特定

## 🚨 アラート・インシデント管理
- **アラートルール管理**: アラートルールの一覧取得・詳細確認
- **インシデント管理**: インシデントの作成・更新・詳細確認
- **通知設定**: コンタクトポイントの管理

## 👥 チーム・オンコール管理
- **オンコール管理**: スケジュール確認・当番者の特定
- **チーム管理**: チーム情報の検索・確認
- **ユーザー管理**: ユーザー情報の取得

## 🔧 システム管理
- **データソース管理**: Prometheus、Loki、Tempoなどのデータソース情報取得
- **アサーション確認**: エンティティの状態確認

何か具体的にやりたいことがあれば、お気軽にお聞かせください!例えば:
- 「特定のサービスのダッシュボードを探したい」
- 「エラーログを調べたい」
- 「現在のアラート状況を確認したい」
- 「オンコール担当者を知りたい」

などなど、お手伝いできます。{"level":"INFO","time":"2025-06-09T02:32:57.661Z","pid":67891,"hostname":"HL01142.local","usage":{"promptTokens":153,"completionTokens":679,"totalTokens":832,"bedrock":{"cacheReadInputTokens":7191,"cacheWriteInputTokens":0}},"msg":"Stream completed with token usage"}
{"level":"INFO","time":"2025-06-09T02:32:57.661Z","pid":67891,"hostname":"HL01142.local","output":{"text":"私はGrafanaのダッシュボードやモニタリングシステムを操作するためのアシスタントです。以下のような機能を提供できます:\n\n## 📊 ダッシュボード操作\n- **ダッシュボードの検索・取得**: タイトルやキーワードでダッシュボードを検索\n- **ダッシュボードの作成・更新**: 新しいダッシュボードの作成や既存のものの更新\n- **パネル情報の取得**: ダッシュボード内のパネルのクエリやデータソース情報を確認\n\n## 🔍 データクエリ・分析\n- **Prometheusクエリ**: PromQLを使用したメトリクス取得\n- **Lokiログクエリ**: LogQLを使用したログ検索・分析\n- **ラベル・メトリク ス情報**: 利用可能なラベルやメトリクス名の一覧取得\n- **エラーパターン分析**: ログから異常なエラーパターンを検出\n- **スロークエリ検出**: 遅いリクエストの特定\n\n## 🚨 アラート・インシデント管理\n- **アラートルール管理**: アラートルールの一覧取得・詳細確認\n- **インシデント管理**: インシデントの作成・更新・詳細確認\n- **通知設定**: コンタクトポイントの管理\n\n## 👥 チーム・オンコール管理\n- **オンコール管理**: スケジュール確認・当番者の特定\n- **チーム管理**: チーム情報の検索・確認\n- **ユーザー管理**: ユーザー情報の取得\n\n## 🔧 システム管理\n- **データソース管理**: Prometheus、Loki、Tempoなどのデータソース情報取得\n- **アサーション確認**:  エンティティの状態確認\n\n何か具体的にやりたいことがあれば、お気軽にお聞かせください!例えば:\n- 「特定のサービスのダッシュボードを探したい」\n- 「エラーログを調べたい」\n- 「現在のアラート状況を確認したい」\n- 「オンコール担当者を知りたい」\n\nなどなど、お手伝いできます。"}}
Bedrockログ(2回目)
{
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0",
    "timestamp": "2025-06-09T02:32:41Z",
    "accountId": "111111111111",
    "identity": {
        "arn": "arn:aws:sts::111111111111:assumed-role/shuntaka/1749427297374859000"
    },
    "region": "us-east-1",
    "requestId": "0805ba0b-1909-493f-a8b3-7a7b461c7b5d",
    "operation": "ConverseStream",
    "modelId": "us.anthropic.claude-sonnet-4-20250514-v1:0",
    "input": {
    (中略)
        "inputTokenCount": 153,
        "cacheReadInputTokenCount": 7191,
        "cacheWriteInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "output": {
                "message": {
                    "role": "assistant",
                    "content": [
                        {
                            "text": "私はGrafanaのダッシュボードやモニタリングシステムを操作するためのアシスタントです。以下のような機能を提供できます:\n\n## 📊 ダッシュボード操作\n- **ダッシュボードの検索・取得**: タイトルやキーワードでダッシュボードを検索\n- **ダッシュボードの作成・更新**: 新しいダッシュボードの作成や既存のものの更新\n- **パネル情報の取得**: ダッシュボード内のパネルのクエリやデータソース情報を確認\n\n## 🔍 データクエリ・分析\n- **Prometheusクエリ**: PromQLを使用したメトリクス取得\n- **Lokiログクエリ**: LogQLを使用したログ検索・分析\n- **ラベル・メトリクス情報**: 利用可能なラベルやメトリクス名の一覧取得\n- **エラーパターン分析**: ログから異常なエラーパターンを検出\n- **スロークエリ検出**: 遅いリクエストの特定\n\n## 🚨 アラート・インシデント管理\n- **アラートルール管理**: アラートルールの一覧取得・詳細確認\n- **インシデント管理**: インシデントの作成・更新・詳細確認\n- **通知設定**: コンタクトポイントの管理\n\n## 👥 チーム・オンコール管理\n- **オンコール管理**: スケジュール確認・当番者の特定\n- **チーム管理**: チーム情報の検索・確認\n- **ユーザー管理**: ユーザー情報の取得\n\n## 🔧 システム管理\n- **データソース管理**: Prometheus、Loki、Tempoなどのデータソース情報取得\n- **アサーション確認**: エンティティの状態確認\n\n何か具体的にやりたいことがあれば、お気軽にお聞かせください!例えば:\n- 「特定のサービスのダッシュボードを探したい」\n- 「エラーログを調べたい」\n- 「現在のアラート状況を確認したい」\n- 「オンコール担当者を知りたい」\n\nなどなど、お手伝いできます。"
                        }
                    ]
                }
            },
            "stopReason": "end_turn",
            "metrics": {
                "latencyMs": 15733
            },
            "usage": {
                "inputTokens": 153,
                "cacheReadInputTokens": 7191,
                "cacheWriteInputTokens": 0,
                "outputTokens": 679,
                "totalTokens": 8023
            }
        },
        "outputTokenCount": 679
    },
    "inferenceRegion": "us-west-2"
}

3回目の実行します。 userメッセージを以下のように変更します。

$ git diff
diff --git a/packages/tools-prompt-cache/src/index.ts b/packages/tools-prompt-cache/src/index.ts
index 0d2d727..c132b4d 100644
--- a/packages/tools-prompt-cache/src/index.ts
+++ b/packages/tools-prompt-cache/src/index.ts
@@ -57,7 +57,7 @@ Grafanaのダッシュボードを操作するためのMCPを使用してくだ

     const userMessage: CoreUserMessage = {
       role: "user",
-      content: "あなたはどんなことができますか?",
+      content: "あなたはどんなことができますか?具体的に教えてください。",
     };

     const messages: CoreMessage[] = [systemMessage, userMessage];

キャッシュされませんでした。これはBedrockのcacheWriteInputTokensされたinferenceRegionであるus-west-2でなくus-east-2にクロスリージョン推論プロファイルからルーティングされた結果だと推測しています。

❯ npx tsx ./src/index.ts
time=2025-06-09T11:33:44.275+09:00 level=INFO msg="Starting Grafana MCP server using stdio transport"
time=2025-06-09T11:33:44.275+09:00 level=INFO msg="Using Grafana configuration" url=http://localhost:8085 api_key_set=true
私はGrafanaの包括的な操作を支援するアシスタントです。以下のような機能を提供できます:

## 📊 ダッシュボード操作
- **ダッシュボードの検索**: キーワードでダッシュボードを検索
- **ダッシュボードの取得**: UIDを使って特定のダッシュボードの詳細を取得
- **ダッシュボードの作成・更新**: 新しいダッシュボードの作成や既存のものの更新
- **パネルクエリの確認**: ダッシュボード内の各パネルのクエリ情報を取得

## 🔍 データソース管理
- **データソース一覧**: 利用可能なデータソース(Prometheus、Loki、Tempoなど)の表示
- **データソース詳細**: 名前やUIDでデータソースの詳細情報を取得

## 📈 メトリクス・ログクエリ
### Prometheus
- **メトリクス検索**: メトリクス名の一覧取得と検索
- **ラベル操作**: ラベル名・値の取得
- **PromQLクエリ**: 即座クエリや範囲クエリの実行
- **メタデータ取得**: メトリクスのメタデータ情報

### Loki
- **ログクエリ**: LogQLを使ったログ検索
- **ラベル管理**: ログのラベル名・値の取得
- **統計情報**: ログストリームの統計(エントリ数、バイト数など)

## 🚨 アラート・インシデント管理
- **アラートルール**: アラートルールの一覧表示と詳細取得
- **インシデント管理**: インシデントの作成、一覧表示、詳細取得
- **インシデント活動**: インシデントへのノート追加
- **通知設定**: コンタクトポイントの管理

## 👥 OnCall・チーム管理
- **OnCallスケジュール**: スケジュールの管理と当番確認
- **チーム管理**: チームの検索と管理
- **ユーザー管理**: OnCallユーザーの管理

## 🔧 高度な分析機能
- **エラーパターン分析**: Lokiログから異常なエラーパターンを検出
- **遅延リクエスト分析**: Tempoを使った遅いリクエストの特定
- **アサーション確認**: エンティティのアサーション状況確認
- **Sift調査**: 調査の作成・管理・分析結果の取得

## 使用例
`` `
「エラーログを調べて」
「CPUメトリクスを確認したい」
「新しいインシデントを作成して」
「今日の当番は誰?」
「ダッシュボードを更新したい」
`` `

何か具体的にやりたいことがあれば、お気軽にお聞かせください!{"level":"INFO","time":"2025-06-09T02:34:05.554Z","pid":68439,"hostname":"HL01142.local","usage":{"promptTokens":7353,"completionTokens":879,"totalTokens":8232,"bedrock":null},"msg":"Stream completed with token usage"}
{"level":"INFO","time":"2025-06-09T02:34:05.554Z","pid":68439,"hostname":"HL01142.local","output":{"text":"私はGrafanaの包括的な操作を支援するアシスタントです。以下のような機能を提供できます:\n\n## 📊 ダッシュボード操作\n- **ダッシュボードの検索**: キーワードでダッシュボードを検索\n- **ダッシュボードの取得**: UIDを使って特定のダッシュボードの詳細を取得\n- **ダッシュボードの作成・更新**: 新しいダッシュボードの作成や既存のものの更新\n- **パネルクエリの確認**: ダッシュボード内の各パネルのクエリ情報を取得\n\n## 🔍 デ ータソース管理\n- **データソース一覧**: 利用可能なデータソース(Prometheus、Loki、Tempoなど)の表示\n- **データソース詳細**: 名前 やUIDでデータソースの詳細情報を取得\n\n## 📈 メトリクス・ログクエリ\n### Prometheus\n- **メトリクス検索**: メトリクス名の一覧取得 と検索\n- **ラベル操作**: ラベル名・値の取得\n- **PromQLクエリ**: 即座クエリや範囲クエリの実行\n- **メタデータ取得**: メトリクスのメタデータ情報\n\n### Loki\n- **ログクエリ**: LogQLを使ったログ検索\n- **ラベル管理**: ログのラベル名・値の取得\n- **統計情報**:  ログストリームの統計(エントリ数、バイト数など)\n\n## 🚨 アラート・インシデント管理\n- **アラートルール**: アラートルールの一覧表示と詳細取得\n- **インシデント管理**: インシデントの作成、一覧表示、詳細取得\n- **インシデント活動**: インシデントへのノート追加\n- **通知設定**: コンタクトポイントの管理\n\n## 👥 OnCall・チーム管理\n- **OnCallスケジュール**: スケジュールの管理と当番確認\n- **チーム管理**: チームの検索と管理\n- **ユーザー管理**: OnCallユーザーの管理\n\n## 🔧 高度な分析機能\n- **エラーパターン分析**: Lokiログから異常なエラーパターンを検出\n- **遅延リクエスト分析**: Tempoを使った遅いリクエストの特定\n- **アサーション確認**: エンティ ティのアサーション状況確認\n- **Sift調査**: 調査の作成・管理・分析結果の取得\n\n## 使用例\n```\n「エラーログを調べて」\n「CPUメト リクスを確認したい」\n「新しいインシデントを作成して」\n「今日の当番は誰?」\n「ダッシュボードを更新したい」\n```\n\n何か具体的に やりたいことがあれば、お気軽にお聞かせください!"}}
Bedrockログ(3回目)
{
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0",
    "timestamp": "2025-06-09T02:33:44Z",
    "accountId": "111111111111",
    "identity": {
        "arn": "arn:aws:sts::111111111111:assumed-role/shuntaka/1749427297374859000"
    },
    "region": "us-east-1",
    "requestId": "eb200a15-75ed-4be6-b328-021ebc70e8b4",
    "operation": "ConverseStream",
    "modelId": "us.anthropic.claude-sonnet-4-20250514-v1:0",
    "input": {
    (中略)
        "inputTokenCount": 7353,
        "cacheReadInputTokenCount": 0,
        "cacheWriteInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "output": {
                "message": {
                    "role": "assistant",
                    "content": [
                        {
                            "text": "私はGrafanaの包括的な操作を支援するアシスタントです。以下のような機能を提供できます:\n\n## 📊 ダッシュボード操作\n- **ダッシュボードの検索**: キーワードでダッシュボードを検索\n- **ダッシュボードの取得**: UIDを使って特定のダッシュボードの詳細を取得\n- **ダッシュボードの作成・更新**: 新しいダッシュボードの作成や既存のものの更新\n- **パネルクエリの確認**: ダッシュボード内の各パネルのクエリ情報を取得\n\n## 🔍 データソース管理\n- **データソース一覧**: 利用可能なデータソース(Prometheus、Loki、Tempoなど)の表示\n- **データソース詳細**: 名前やUIDでデータソースの詳細情報を取得\n\n## 📈 メトリクス・ログクエリ\n### Prometheus\n- **メトリクス検索**: メトリクス名の一覧取得と検索\n- **ラベル操作**: ラベル名・値の取得\n- **PromQLクエリ**: 即座クエリや範囲クエリの実行\n- **メタデータ取得**: メトリクスのメタデータ情報\n\n### Loki\n- **ログクエリ**: LogQLを使ったログ検索\n- **ラベル管理**: ログのラベル名・値の取得\n- **統計情報**: ログストリームの統計(エントリ数、バイト数など)\n\n## 🚨 アラート・インシデント管理\n- **アラートルール**: アラートルールの一覧表示と詳細取得\n- **インシデント管理**: インシデントの作成、一覧表示、詳細取得\n- **インシデント活動**: インシデントへのノート追加\n- **通知設定**: コンタクトポイントの管理\n\n## 👥 OnCall・チーム管理\n- **OnCallスケジュール**: スケジュールの管理と当番確認\n- **チーム管理**: チームの検索と管理\n- **ユーザー管理**: OnCallユーザーの管理\n\n## 🔧 高度な分析機能\n- **エラーパターン分析**: Lokiログから異常なエラーパターンを検出\n- **遅延リクエスト分析**: Tempoを使った遅いリクエストの特定\n- **アサーション確認**: エンティティのアサーション状況確認\n- **Sift調査**: 調査の作成・管理・分析結果の取得\n\n## 使用例\n```\n「エラーログを調べて」\n「CPUメトリクスを確認したい」\n「新しいインシデントを作成して」\n「今日の当番は誰?」\n「ダッシュボードを更新したい」\n```\n\n何か具体的にやりたいことがあれば、お気軽にお聞かせください!"
                        }
                    ]
                }
            },
            "stopReason": "end_turn",
            "metrics": {
                "latencyMs": 20268
            },
            "usage": {
                "inputTokens": 7353,
                "outputTokens": 879,
                "totalTokens": 8232
            }
        },
        "outputTokenCount": 879
    },
    "inferenceRegion": "us-east-2"
}

再度前回(3回目)と同様の内容を、4回目として実行します。

❯ npx tsx ./src/index.ts
time=2025-06-09T11:34:16.563+09:00 level=INFO msg="Starting Grafana MCP server using stdio transport"
time=2025-06-09T11:34:16.563+09:00 level=INFO msg="Using Grafana configuration" url=http://localhost:8085 api_key_set=true
私はGrafanaの包括的な操作を支援するアシスタントです。以下のような機能を提供できます:

## 📊 ダッシュボード操作
- **ダッシュボードの検索**: キーワードでダッシュボードを検索
- **ダッシュボードの取得**: UIDを使って特定のダッシュボードの詳細を取得
- **ダッシュボードの作成・更新**: 新しいダッシュボードの作成や既存のものの更新
- **パネルクエリの確認**: ダッシュボード内の各パネルのクエリ情報を取得

## 🔍 データソース管理
- **データソース一覧**: 利用可能なデータソース(Prometheus、Loki、Tempoなど)の表示
- **データソース詳細**: 名前やUIDでデータソースの詳細情報を取得

## 📈 メトリクス・ログクエリ
### Prometheus
- **メトリクス検索**: メトリクス名の一覧取得と検索
- **ラベル操作**: ラベル名・値の取得
- **PromQLクエリ**: 即座クエリや範囲クエリの実行
- **メタデータ取得**: メトリクスのメタデータ情報

### Loki
- **ログクエリ**: LogQLを使ったログ検索
- **ラベル管理**: ログのラベル名・値の取得
- **統計情報**: ログストリームの統計(エントリ数、バイト数など)

## 🚨 アラート・インシデント管理
- **アラートルール**: アラートルールの一覧表示と詳細取得
- **インシデント管理**: インシデントの作成、一覧表示、詳細取得
- **インシデント活動**: インシデントへのノート追加
- **通知設定**: コンタクトポイントの管理

## 👥 OnCall・チーム管理
- **OnCallスケジュール**: スケジュールの管理と当番確認
- **チーム管理**: チームの検索と管理
- **ユーザー管理**: OnCallユーザーの管理

## 🔧 高度な分析機能
- **エラーパターン分析**: Lokiログから異常なエラーパターンを検出
- **遅延リクエスト分析**: Tempoを使った遅いリクエストの特定
- **アサーション確認**: エンティティのアサーション状況確認
- **Sift調査**: 調査の作成・管理・分析結果の取得

## 使用例
`` `
「エラーログを調べて」
「CPUメトリクスを確認したい」
「新しいインシデントを作成して」
「今日の当番は誰?」
「ダッシュボードを更新したい」
`` `

何か具体的にやりたいことがあれば、お気軽にお聞かせください!{"level":"INFO","time":"2025-06-09T02:34:38.687Z","pid":69074,"hostname":"HL01142.local","usage":{"promptTokens":162,"completionTokens":879,"totalTokens":1041,"bedrock":{"cacheReadInputTokens":7191,"cacheWriteInputTokens":0}},"msg":"Stream completed with token usage"}
{"level":"INFO","time":"2025-06-09T02:34:38.687Z","pid":69074,"hostname":"HL01142.local","output":{"text":"私はGrafanaの包括的な操作を支援するアシスタントです。以下のような機能を提供できます:\n\n## 📊 ダッシュボード操作\n- **ダッシュボードの検索**: キーワードでダッシュボードを検索\n- **ダッシュボードの取得**: UIDを使って特定のダッシュボードの詳細を取得\n- **ダッシュボードの作成・更新**: 新しいダッシュボードの作成や既存のものの更新\n- **パネルクエリの確認**: ダッシュボード内の各パネルのクエリ情報を取得\n\n## 🔍 デ ータソース管理\n- **データソース一覧**: 利用可能なデータソース(Prometheus、Loki、Tempoなど)の表示\n- **データソース詳細**: 名前 やUIDでデータソースの詳細情報を取得\n\n## 📈 メトリクス・ログクエリ\n### Prometheus\n- **メトリクス検索**: メトリクス名の一覧取得 と検索\n- **ラベル操作**: ラベル名・値の取得\n- **PromQLクエリ**: 即座クエリや範囲クエリの実行\n- **メタデータ取得**: メトリクスのメタデータ情報\n\n### Loki\n- **ログクエリ**: LogQLを使ったログ検索\n- **ラベル管理**: ログのラベル名・値の取得\n- **統計情報**:  ログストリームの統計(エントリ数、バイト数など)\n\n## 🚨 アラート・インシデント管理\n- **アラートルール**: アラートルールの一覧表示と詳細取得\n- **インシデント管理**: インシデントの作成、一覧表示、詳細取得\n- **インシデント活動**: インシデントへのノート追加\n- **通知設定**: コンタクトポイントの管理\n\n## 👥 OnCall・チーム管理\n- **OnCallスケジュール**: スケジュールの管理と当番確認\n- **チーム管理**: チームの検索と管理\n- **ユーザー管理**: OnCallユーザーの管理\n\n## 🔧 高度な分析機能\n- **エラーパターン分析**: Lokiログから異常なエラーパターンを検出\n- **遅延リクエスト分析**: Tempoを使った遅いリクエストの特定\n- **アサーション確認**: エンティ ティのアサーション状況確認\n- **Sift調査**: 調査の作成・管理・分析結果の取得\n\n## 使用例\n```\n「エラーログを調べて」\n「CPUメト リクスを確認したい」\n「新しいインシデントを作成して」\n「今日の当番は誰?」\n「ダッシュボードを更新したい」\n```\n\n何か具体的に やりたいことがあれば、お気軽にお聞かせください!"}}

4回目で無事に、inferenceRegionus-west-2 にルーティングされました!初回で書き込まれた cacheWriteInputTokens 7191トークンが cacheReadInputTokens トークンとして読み込まれていることが確認できました!

Bedrockログ(4回目)
{
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0",
    "timestamp": "2025-06-09T02:34:16Z",
    "accountId": "111111111111",
    "identity": {
        "arn": "arn:aws:sts::111111111111:assumed-role/shuntaka/1749427297374859000"
    },
    "region": "us-east-1",
    "requestId": "69973c60-ffcc-4904-863e-75b6616aa554",
    "operation": "ConverseStream",
    "modelId": "us.anthropic.claude-sonnet-4-20250514-v1:0",
    "input": {
    (中略)
        "inputTokenCount": 162,
        "cacheReadInputTokenCount": 7191,
        "cacheWriteInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "output": {
                "message": {
                    "role": "assistant",
                    "content": [
                        {
                            "text": "私はGrafanaの包括的な操作を支援するアシスタントです。以下のような機能を提供できます:\n\n## 📊 ダッシュボード操作\n- **ダッシュボードの検索**: キーワードでダッシュボードを検索\n- **ダッシュボードの取得**: UIDを使って特定のダッシュボードの詳細を取得\n- **ダッシュボードの作成・更新**: 新しいダッシュボードの作成や既存のものの更新\n- **パネルクエリの確認**: ダッシュボード内の各パネルのクエリ情報を取得\n\n## 🔍 データソース管理\n- **データソース一覧**: 利用可能なデータソース(Prometheus、Loki、Tempoなど)の表示\n- **データソース詳細**: 名前やUIDでデータソースの詳細情報を取得\n\n## 📈 メトリクス・ログクエリ\n### Prometheus\n- **メトリクス検索**: メトリクス名の一覧取得と検索\n- **ラベル操作**: ラベル名・値の取得\n- **PromQLクエリ**: 即座クエリや範囲クエリの実行\n- **メタデータ取得**: メトリクスのメタデータ情報\n\n### Loki\n- **ログクエリ**: LogQLを使ったログ検索\n- **ラベル管理**: ログのラベル名・値の取得\n- **統計情報**: ログストリームの統計(エントリ数、バイト数など)\n\n## 🚨 アラート・インシデント管理\n- **アラートルール**: アラートルールの一覧表示と詳細取得\n- **インシデント管理**: インシデントの作成、一覧表示、詳細取得\n- **インシデント活動**: インシデントへのノート追加\n- **通知設定**: コンタクトポイントの管理\n\n## 👥 OnCall・チーム管理\n- **OnCallスケジュール**: スケジュールの管理と当番確認\n- **チーム管理**: チームの検索と管理\n- **ユーザー管理**: OnCallユーザーの管理\n\n## 🔧 高度な分析機能\n- **エラーパターン分析**: Lokiログから異常なエラーパターンを検出\n- **遅延リクエスト分析**: Tempoを使った遅いリクエストの特定\n- **アサーション確認**: エンティティのアサーション状況確認\n- **Sift調査**: 調査の作成・管理・分析結果の取得\n\n## 使用例\n```\n「エラーログを調べて」\n「CPUメトリクスを確認したい」\n「新しいインシデントを作成して」\n「今日の当番は誰?」\n「ダッシュボードを更新したい」\n```\n\n何か具体的にやりたいことがあれば、お気軽にお聞かせください!"
                        }
                    ]
                }
            },
            "stopReason": "end_turn",
            "metrics": {
                "latencyMs": 21265
            },
            "usage": {
                "inputTokens": 162,
                "cacheReadInputTokens": 7191,
                "cacheWriteInputTokens": 0,
                "outputTokens": 879,
                "totalTokens": 8232
            }
        },
        "outputTokenCount": 879
    },
    "inferenceRegion": "us-west-2"
}

実行結果まとめ

3回目と4回目は、userメッセージを1回目と2回目で変更しています。

promptTokens completionTokens totalTokens cacheReadInputTokens cacheWriteInputTokens inferenceRegion
1 153 707 860 0 7191 us-west-2
2 153 679 832 7191 0 us-west-2
3 7353 879 8232 null null us-east-2
4 162 879 1041 7191 0 us-west-2

初回でキャッシュに乗った7191トークンは、tools + systemだと思われます。(Anthropicのトークナイザで計算した際には6985(tools) + 94(system) = 7079でしたが微妙にトークナイザがバージョンが違うのかなと推測しています)

1回目と2回目では1回目のtools + system分のプロンプトキャッシュが効いていることが分かります。

  • 1回目
    • 153(promptTokens) + 707(completionTokens) + 7191(cacheWriteInputTokens) = 8051
  • 2回目
    • 153(promptTokens) + 679(completionTokens) + 7191(cacheReadInputTokens) = 8023

1回目と2回目は全て(tools+system+user)同じですが、completionTokensは実行結果(assistant)のトークン数28トークン差があります。これはLLMが同じキャッシュを使っても、おそらく乱数を組み合わせた単語の選択処理を内部で行っているため全く同じ回答には至らないためトークン数が変わっていると考えられます。

次にユーザーメッセージを変えた3回目と4回目です。

3回目は、恐らくクロスリージョン推論プロファイルで別リージョンあたり、キャッシュすらされていません。せめてcacheWriteInputTokensしてもいいと思うのですが。。ここら辺の挙動は自分にはよくわかっていません。

tools + system + userの値が、promptTokensの7353に当たると考えられます。初回の153(promptTokens) + 7191(cacheWriteInputTokens) = 7344 で 差分は9トークンです。これは変更したユーザーメッセージの増分で、Anthropicのトークナイザ(28 - 19 = 9)と一致します。

4回目はcacheReadInputTokensに7191がカウントされ、初回にcacheWriteInputTokensした値がそのまま使われています。先ほど変更さいたユーザーメッセージの9トークン分が、1回目のpromptTokensと比較して増加しています。想定通りの挙動ですね。

さいごに

Prompt cachingは非常にコスト削減が見込める機能です。そしてツール連携は肥大化しがちで、肥大化した際にはtools -> systemまででキャッシュを作れば、そのツールが固定のAIチャットボットでは全てのユーザーのリクエストがキャッシュに乗ります。コストメリットは大きそうです!活用していきたいですね!

検証スクリプト全体の公開前に力尽きてしまったので、@shuntaka_jpまでリクエスト頂ければ対応します!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.