◉GEMINI LABJP
●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 5 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 5 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…
Articles/API / SDK
◈ API / SDK/2026-06-03Advanced

Recording Provenance for Gemini Output — Designing for Reproducibility and Audit

Before you lose track of which model and prompt produced an output months later: how to stamp provenance metadata onto Gemini generations so quality investigations and model migrations stay reproducible.

Gemini API250provenancereproducibilityproduction140indie developer12

✦ Premium Article

About six months after I started using Gemini to auto-classify wallpapers in one of my apps, a user reported that an image had been filed under "abstract" instead of "night view." I wanted to fix it — but I couldn't tell which model had made that call, or what prompt I had sent. The output was there in my logs; the provenance was not.

I have run apps as a solo developer since 2014. Across more than 50 million cumulative downloads, my wallpaper, relaxation, and mindfulness apps now lean on Gemini under the hood. Once you put generative AI into production, these "explain yourself after the fact" moments are inevitable. And what saves you then is not the output itself, but the record of when, with which model, prompt, and settings it was born. Here is a design — at the implementation level — for stamping provenance onto generated output so that, even half a year later, you can reconstruct the exact conditions.

The minimal fields worth stamping

Provenance bloats your storage if you record too much, and becomes useless for reproduction if you record too little. The minimal set that proved neither excessive nor lacking in my classification and summarization features is this.

For generation conditions: the model name (an ID like gemini-2.5-flash), the model generation at that point in time, and the sampling parameters — temperature, thinking_budget, and seed. For content fingerprints: the prompt template ID and its canonical hash, a hash of the full input, and a hash of the full output. For environment: the SDK version and the generation timestamp. With just these you can answer both "can I rebuild the same conditions?" and "has the output drifted from what it was?"

One thing matters here: do not store the full prompt text. Prompts often mix in user input, which risks carrying PII. Keep a hash rather than the body, and version the template separately.

import hashlib
import json
import time
from dataclasses import dataclass, asdict
 
def canonical_prompt(template_id: str, variables: dict) -> str:
    # Remove key-ordering noise before joining, so equivalent inputs map to the same string
    normalized = json.dumps(
        variables, ensure_ascii=False, sort_keys=True, separators=(",", ":")
    )
    return f"{template_id}\x1f{normalized}"
 
def sha256_short(text: str) -> str:
    return hashlib.sha256(text.encode("utf-8")).hexdigest()[:16]
 
@dataclass
class Provenance:
    model: str
    model_generation: str
    prompt_template_id: str
    prompt_hash: str
    temperature: float
    thinking_budget: int | None
    seed: int | None
    input_hash: str
    output_hash: str
    sdk_version: str
    created_at: float

What it means to "pin a prompt by hash"

The heart of provenance is the prompt hash. But naively running the raw prompt string through SHA-256 invites a subtle bug: the same meaning produces a different hash. Variables in a different order, one extra space, a stray newline character. To absorb this drift, insert a canonicalization step before hashing.

The canonical_prompt above joins the template ID with a key-sorted JSON of the variables. That makes {"style": "night", "lang": "en"} and {"lang": "en", "style": "night"} hash identically. Keep the template body under separate management, and adopt the rule that any change to the body bumps the template ID. That single discipline avoids the worst case: "the prompt body changed but the hash stayed the same."

I embed a version number in the template ID, like wallpaper-classify-v3, and version the body in the repository. Change one character of the body, bump the version. My grandfathers were temple carpenters, and they left ink-marked guidelines so the next generation could trace the same work. A prompt hash feels like exactly that kind of traceable ink mark.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦The minimal set of provenance fields to stamp on every generation: model version, prompt hash, temperature, seed, and input/output hashes
✦Where to draw the line between what is and is not reproducible on Gemini, even at temperature 0, based on real measurements
✦How a provenance sidecar let me reproduce a wallpaper-classification misfire in three minutes
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◈ API / SDK2026-05-15
Three Places Gemini API Embedding Broke on Me — and What Actually Fixed Them
Notes from wiring Gemini API Embedding into an auto-categorization pipeline: INVALID_ARGUMENT from dimension mismatches, 429 rate limits that lost 400 items of work, and weak retrieval quality — plus normalization, checkpointing, and a recall@5 harness that made improvements measurable.
◈ API / SDK2026-05-04
Solving Gemini API Cold Starts — Production-Grade Startup Optimization for Cloud Run, Lambda, and Workers
When you put Gemini API on serverless, the first request takes six seconds. This guide breaks down where the time goes and shows concrete startup-optimization patterns for Cloud Run, AWS Lambda, and Cloudflare Workers — with real numbers, runnable code, and cost trade-off advice.
◈ API / SDK2026-04-26
Defending Gemini API Responses with Schema Validation — Never Let Unexpected Formats Reach Production
Gemini's structured output is convenient, but in production the day always comes when an unexpected format slips through. This piece walks through layered Zod/Pydantic validation, repair prompts, and graceful degradation — the defense lines I run on my own apps.