0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Gemini 3.6 Flashのusage契約を検査するPython

0
Posted at

Gemini 3.6 Flashのusage契約を検査するPython

Gemini 3.6 Flash へ切り替えるなら、モデルの回答だけでなく、利用量ログのキーも先に検査した方がいい。新しい Interactions API は usage.total_input_tokens のような snake_case を返す。一方、従来の GenerateContent API のログは usageMetadata.promptTokenCount のような camelCase だ。集計側が前者だけ、あるいは後者だけを前提にしていると、コストのダッシュボードが静かに 0 になる。

今回は、両方の JSON を一つの形式に寄せ、未知の形は例外にする小さな契約テストを作る。

Q. なぜ移行前に利用量を見るのか

7月21日に出た Gemini 3.6 Flash は、Google の発表では 3.5 Flash より出力トークンを17%減らし、入力100万トークンあたり $1.50、出力100万トークンあたり $7.50 としている。ここは魅力的だけど、アプリ側で見るべき数値はモデル名や請求単価だけじゃない。実際にどれだけ入力、出力、thinking、キャッシュ、ツール利用が出ているかだ。公式のトークン説明も、レスポンスの usage から入力、出力、thinking、キャッシュ、ツール利用、合計を取得できるとしている。

自分は移行用のログを見ていて、旧形式の promptTokenCount を読む集計が残っているのに気づいた。エラーにはならない。辞書の get()0 を返すだけで、グラフだけが妙にきれいになる。この手の壊れ方が一番見つけにくい。

Q. 何を契約にするのか

Google は現在、最新機能とモデルには Interactions API を推奨している。同じ公式ドキュメントの Python 例でも gemini-3.6-flash に対して interaction.usage を読む形になっている。旧 API の履歴をすぐ消せないチームでは、二つの形を受け入れる境界を一か所に置くのが扱いやすい。

次のコードは API を呼ばない。保存済みのレスポンス JSON を受け取り、集計に渡せる Usage に正規化する。total_tokens を各項目の足し算で再計算しない点も意図的だ。キャッシュ済みトークンは入力トークンに含まれる扱いになることがあるため、雑に足すと二重計上になる。API が返した合計をそのまま記録する。

from dataclasses import dataclass

@dataclass(frozen=True)
class Usage:
    input_tokens: int
    output_tokens: int
    thought_tokens: int
    cached_tokens: int
    tool_use_tokens: int
    total_tokens: int

def as_non_negative_int(value: object, name: str) -> int:
    if not isinstance(value, int) or value < 0:
        raise ValueError(f"{name} must be a non-negative integer: {value!r}")
    return value

def normalize_usage(response: dict) -> Usage:
    if "usage" in response:  # Interactions API
        raw = response["usage"]
        keys = {
            "input_tokens": "total_input_tokens",
            "output_tokens": "total_output_tokens",
            "thought_tokens": "total_thought_tokens",
            "cached_tokens": "total_cached_tokens",
            "tool_use_tokens": "total_tool_use_tokens",
            "total_tokens": "total_tokens",
        }
    elif "usageMetadata" in response:  # GenerateContent API
        raw = response["usageMetadata"]
        keys = {
            "input_tokens": "promptTokenCount",
            "output_tokens": "candidatesTokenCount",
            "thought_tokens": "thoughtsTokenCount",
            "cached_tokens": "cachedContentTokenCount",
            "tool_use_tokens": "toolUsePromptTokenCount",
            "total_tokens": "totalTokenCount",
        }
    else:
        raise ValueError("usage or usageMetadata is required")

    if not isinstance(raw, dict):
        raise ValueError("usage must be an object")

    values = {
        field: as_non_negative_int(raw.get(key, 0), key)
        for field, key in keys.items()
    }
    if values["total_tokens"] < values["input_tokens"] + values["output_tokens"]:
        raise ValueError("total tokens is smaller than input + output")
    return Usage(**values)

legacy = {"usageMetadata": {
    "promptTokenCount": 1200, "candidatesTokenCount": 280,
    "thoughtsTokenCount": 200, "cachedContentTokenCount": 900,
    "toolUsePromptTokenCount": 0, "totalTokenCount": 1680,
}}
interaction = {"usage": {
    "total_input_tokens": 1200, "total_output_tokens": 280,
    "total_thought_tokens": 200, "total_cached_tokens": 900,
    "total_tool_use_tokens": 0, "total_tokens": 1680,
}}

assert normalize_usage(legacy) == normalize_usage(interaction)
print(normalize_usage(interaction))

手元の Python 3.9.6 で実行した出力はこれだった。

Usage(input_tokens=1200, output_tokens=280, thought_tokens=200, cached_tokens=900, tool_use_tokens=0, total_tokens=1680)

raw.get(key, 0) は、任意項目が省略されたレスポンスを許すために使っている。ただし必須にしたい項目があるなら、ここを raw[key] に変えればよい。重要なのは、キー名の変更を集計 SQL や可視化コードまで漏らさないこと。ここを通らないレスポンスは例外にして、モデル切り替えの PR で止める。

Q. このテストだけで移行してよいか

よくない。これは利用量の契約だけを守るテストだ。次に、実サービスの代表リクエストを旧モデルと 3.6 Flash へ同じ条件で送り、正答率、p95 レイテンシ、Usage を同じ行に保存する。公式発表の17%は比較の出発点であって、自分のプロンプトやツール呼び出し回数まで保証しない。

モデル更新では「回答が返った」を合格にしがちだが、運用コストの観測が壊れた状態では比較そのものができない。レスポンス境界を一つ作っておくと、次の API 移行でも直す場所が決まる。小さいコードだけど、コスト最適化はここから始めるのが堅いと思う。

参考: Google の Gemini 3.6 Flash 発表Gemini API のトークン説明

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?