2026年7月6日から、Interactions API の対応する呼び出しの実行ログが、Google AI Studio のダッシュボードで確認できるようになりました。API 経由のリクエストが、コンソール上でそのまま追える。エージェントの各実行ステップや、ツール呼び出しの連なりを目で見て切り分けられる。デバッグの入口としては、率直にありがたい変更です。
私自身、Interactions API へ寄せた自動処理のデバッグで、まずここを開くようになりました。手元のログを grep するより速く、失敗した呼び出しの周辺が視覚的に見える。挙動の当たりをつける「速報レンズ」としては、これで十分に機能します。
ただ、便利さの手触りと、記録としての信頼性は別物です。ここを混同したのが、冒頭の失敗でした。
ダッシュボードを「正本」にしてはいけない理由
観測の道具には、性質のまったく異なる二種類があります。ひとつは、いま何が起きているかを速く掴むための「速報レンズ」。もうひとつは、後からいつでも同じ答えを返せる「正本(system of record)」です。
AI Studio の開発者ログは、前者として設計された道具だと捉えるのが安全です。マネージド側のダッシュボードは、保持期間・表示範囲・サンプリングの方針が提供側の都合で将来変わりえます。今日たまたま3週間前まで見えたとしても、それは仕様として保証された正本ではありません。私が辿れなかったのは、まさにこの前提を取り違えていたからです。
import sqlite3from dataclasses import asdictDDL = """CREATE TABLE IF NOT EXISTS interactions( interaction_id TEXT PRIMARY KEY, prev_interaction_id TEXT, prompt_fingerprint TEXT, feature TEXT, -- どの機能からの呼び出しか model TEXT, input_tokens INTEGER, output_tokens INTEGER, latency_ms INTEGER, status TEXT, created_at REAL);CREATE INDEX IF NOT EXISTS idx_created ON interactions(created_at);CREATE INDEX IF NOT EXISTS idx_status ON interactions(status);"""class SqliteSink: """ホット層の正本。INSERT OR IGNORE で再送・リトライを冪等に受ける。""" COLS = ("interaction_id", "prev_interaction_id", "prompt_fingerprint", "feature", "model", "input_tokens", "output_tokens", "latency_ms", "status", "created_at") def __init__(self, path: str): self.con = sqlite3.connect(path, check_same_thread=False) self.con.execute("PRAGMA journal_mode=WAL") self.con.executescript(DDL) def append_only(self, record) -> None: row = asdict(record) if not isinstance(record, dict) else record placeholders = ",".join(f":{c}" for c in self.COLS) self.con.execute( f"INSERT OR IGNORE INTO interactions VALUES({placeholders})", {c: row.get(c) for c in self.COLS}, ) self.con.commit()
INSERT OR IGNORE にしているのは、リトライで同じ interaction_id が二度届いても正本が二重にならないようにするためです。追記専用と冪等は相性がよく、この一語で突合の前提が安定します。
月末に API の請求を見て、「どの機能が使ったのか」を知りたくなる。ダッシュボードでは実行を1件ずつ眺めることはできても、機能ごとに束ねて合計する視点は得られません。正本が手元にあると、これが1本のクエリで済みます。
SELECT feature, model, COUNT(*) AS calls, SUM(input_tokens) AS in_tok, SUM(output_tokens) AS out_tok, ROUND(AVG(latency_ms)) AS avg_ms, ROUND(100.0 * SUM(status <> 'ok') / COUNT(*), 2) AS fail_pctFROM interactionsWHERE created_at >= :period_startGROUP BY feature, modelORDER BY in_tok + out_tok DESC;
100,000 行に対してこの集計を走らせて 85.8 ms でした。月次のレポートを作るのに待たされる速度ではありません。単価は変動しますので、ここで出したトークン数に最新の料金表を掛けて按分する、という手順にしています。単価をコードに埋め込まないのは、料金改定のたびに過去の集計が壊れないようにするためです。