GEMINI LABJP
IMGEOL — The older image generation models are deprecated and shut down on August 17. Move to the newer stable or preview endpoints before thenREGION — Gemini 3.5 Flash disappears from the global region in the Gemini Enterprise app on August 4, so check any config that pins region and model togetherGROK — The Grok 4.1 model family on the Gemini Enterprise Agent Platform is deprecated and will be shut down on August 20FSMM — File Search now handles multimodal search through gemini-embedding-2, letting you index and query images natively instead of transcribing them firstFLASH36 — Gemini 3.6 Flash and 3.5 Flash-Lite are generally available. 3.6 Flash improves token efficiency and agentic planning while sitting below 3.5 Flash on priceSAMPLING — The temperature, top_p, and top_k sampling parameters are now deprecated, so any output control built on them needs a different approachIMGEOL — The older image generation models are deprecated and shut down on August 17. Move to the newer stable or preview endpoints before thenREGION — Gemini 3.5 Flash disappears from the global region in the Gemini Enterprise app on August 4, so check any config that pins region and model togetherGROK — The Grok 4.1 model family on the Gemini Enterprise Agent Platform is deprecated and will be shut down on August 20FSMM — File Search now handles multimodal search through gemini-embedding-2, letting you index and query images natively instead of transcribing them firstFLASH36 — Gemini 3.6 Flash and 3.5 Flash-Lite are generally available. 3.6 Flash improves token efficiency and agentic planning while sitting below 3.5 Flash on priceSAMPLING — The temperature, top_p, and top_k sampling parameters are now deprecated, so any output control built on them needs a different approach
Articles/API / SDK
API / SDK/2026-08-01Advanced

When Sampling Parameters Were Deprecated, Diversity Broke Before Determinism Did

Deprecating temperature, top_p and top_k hurt the diversity-generating side of my pipeline, not the deterministic side. Counting real call sites, moving diversity to the input layer, and measuring effective diversity.

Gemini API200temperature3migration7regression testingcost design7

Premium Article

I was working through the August 17 image model shutdown notice when a short line caught my eye much further down the same changelog. temperature, top_p and top_k had been deprecated.

Compared to the entries with hard shutdown dates attached, it was placed very quietly.

I did not brace for it. Most of the Gemini calls in the pipeline I run as an indie developer are extraction paths, and those have been pinned to temperature: 0 for a long time. If I can no longer write zero, I simply stop writing it. That was roughly my reaction.

Once I started actually touching the code, the order turned out to be reversed.

What wobbled was not the side where I had pushed the value down. It was the side where I had pushed it up.

Count call sites, not declarations

The first job was scoping. Running grep -r temperature is easy, but what it returns is where you wrote the value, not where it takes effect.

The size of the migration is determined by how many generate_content calls the value flows into. If your defaults live in one shared module, you get one declaration and dozens of affected calls. Meanwhile values that live in config files never show up in a code grep at all.

So I wrote a scanner that resolves one hop of indirection. It collects identifiers that carry sampling parameters outward, treats any layer that merely re-wraps a known carrier as a carrier itself, and propagates that to a fixed point.

#!/usr/bin/env python3
"""scan_sampling.py — count the call sites affected by deprecated
sampling parameters, rather than the places they were declared."""
import re
import sys
from pathlib import Path
 
SAMPLING_KEYS = ("temperature", "top_p", "topP", "top_k", "topK")
CODE_EXT = {".py", ".ts", ".tsx", ".js", ".mjs"}
CONF_EXT = {".json", ".yaml", ".yml", ".toml"}
CALL_RE = re.compile(r"generate_?[Cc]ontent")
 
CARRIER_RE = [
    re.compile(r"^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_]\w*)\s*[:=]", re.M),
    re.compile(r"^\s*([A-Z][A-Z0-9_]*)\s*=", re.M),                    # Python constants
    re.compile(r"^\s*(?:export\s+)?function\s+([A-Za-z_]\w*)", re.M),
    re.compile(r"^\s*def\s+([a-z_]\w*)", re.M),
]
 
 
def carriers_in(text, seeds):
    """Names exported out of a block that contains sampling keys,
    or that references an already-known carrier."""
    found = set()
    for rx in CARRIER_RE:
        for m in rx.finditer(text):
            name = m.group(1)
            tail = text[m.start(): m.start() + 400]
            if any(k in tail for k in SAMPLING_KEYS):
                found.add(name)
            elif any(re.search(rf"\b{re.escape(s)}\b", tail) for s in seeds):
                found.add(name)   # a layer that only re-wraps a carrier is one too
    return found
 
 
def scan(root):
    decl, conf, carriers = [], [], set()
    files = [p for p in root.rglob("*") if p.is_file()]
 
    for p in files:
        if p.suffix not in CODE_EXT | CONF_EXT:
            continue
        text = p.read_text(encoding="utf-8", errors="ignore")
        hits = [k for k in SAMPLING_KEYS if k in text]
        if not hits:
            continue
        rel = str(p.relative_to(root))
        if p.suffix in CONF_EXT:
            conf.append((rel, sorted(set(hits))))
        else:
            decl.append((rel, sorted(set(hits))))
            carriers |= carriers_in(text, set())
 
    # propagate to a fixed point: defaults -> buildConfig -> cfg
    code_files = [(p, p.read_text(encoding="utf-8", errors="ignore"))
                  for p in files if p.suffix in CODE_EXT]
    for _ in range(5):
        grown = set(carriers)
        for _p, text in code_files:
            grown |= carriers_in(text, carriers)
        if grown == carriers:
            break
        carriers = grown
 
    direct, indirect = [], []
    for p in files:
        if p.suffix not in CODE_EXT:
            continue
        text = p.read_text(encoding="utf-8", errors="ignore")
        lines = text.splitlines()
        rel = str(p.relative_to(root))
        for i, line in enumerate(lines, 1):
            if not CALL_RE.search(line):
                continue
            window = "\n".join(lines[max(0, i - 6): i + 6])
            if any(k in window for k in SAMPLING_KEYS):
                direct.append(f"{rel}:{i}")
            elif any(re.search(rf"\b{re.escape(n)}\b", window) for n in carriers):
                indirect.append(f"{rel}:{i}")
    return decl, conf, sorted(carriers), direct, indirect
 
 
if __name__ == "__main__":
    root = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve()
    decl, conf, carriers, direct, indirect = scan(root)
    print(f"[declarations] code   : {len(decl)}")
    for rel, ks in decl:
        print(f"  - {rel}  {','.join(ks)}")
    print(f"[declarations] config : {len(conf)}")
    for rel, ks in conf:
        print(f"  - {rel}  {','.join(ks)}")
    print(f"[carriers]            : {len(carriers)} -> {', '.join(carriers)}")
    print(f"[call sites] direct   : {len(direct)} -> {', '.join(direct)}")
    print(f"[call sites] indirect : {len(indirect)} -> {', '.join(indirect)}")
    print(f"[total] to migrate    : {len(direct) + len(indirect)}")

Here is the output against a reduced tree that mirrors my own layout: two extraction files, one paraphrase file, two shared-defaults files, two config files.

[declarations] code   : 4
  - src/variation/paraphrase.py  temperature,top_k,top_p
  - src/shared/defaults.ts  temperature,topK,topP
  - src/extract/receipt.py  temperature
  - src/extract/tags.py  temperature
[declarations] config : 2
  - config/profiles.json  temperature,topK,topP
  - config/batch.yaml  temperature,top_k
[carriers]            : 9 -> BASE, CFG, GEN_DEFAULTS, buildConfig, call, cfg, extract, run, variants
[call sites] direct   : 3 -> src/variation/paraphrase.py:4, src/extract/receipt.py:4, src/extract/tags.py:2
[call sites] indirect : 1 -> src/shared/agent.ts:3
[total] to migrate    : 4

The second-to-last line is the one that matters. src/shared/agent.ts does not contain the string temperature, top_p or top_k anywhere. It just calls buildConfig(). Deprecated values still reach that call, and a work list built purely from grep leaves it stranded until the very end.

How you countCountWhat it actually measures
Files matched by grep6Where the keyword was typed, including config
Direct call sites3A key appears within 6 lines of the call
Indirect call sites1Reached via a carrier. Invisible to grep
Total to migrate4Places that genuinely need editing

Six declarations against four call sites. The numbers are close, but the sets do not overlap. Closing that gap first is what let me track the rest of the work as "how many are left."

The extraction paths barely moved

I started with the extraction paths, assuming they were the main event: pulling amounts and merchant names out of receipt images, and tagging images.

I dropped the zero and watched what changed.

Almost nothing did. In hindsight the reason is plain — both paths were already passing response_schema.

Temperature adjusts how far into the tail of the output distribution you reach. A schema closes the set of outputs you can produce at all. If an enum restricts a field to five values, no shift in the distribution produces a sixth. If a numeric field is declared integer, the only room to wobble is inside the digits.

Stability in those paths was carried by the schema, not by the temperature. The zero was a comfort setting layered on top.

That was the opposite of what I expected. I had planned to handle these paths with the most care, and in the end I deleted one line from a config dict.

The more structured output you already have, the smaller this deprecation is for you. Implementations that tightened their output using temperature alone, without a schema, have some design to walk back.

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
grep reported 6 declaration sites but only 4 call sites actually needed changes, and one of them lived in a file that never mentions temperature
Moving diversity to the input layer cut the monthly bill from $125.28 to $82.75 at a 75% cache discount, and changed nothing at all at 0%
Round-robin stratification balanced the axes but produced 5 duplicates out of 8 prompts; a greedy balanced selector reached 0 duplicates with a max skew of 1
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-07-19
Still image or short clip? Deciding feature placement from the cost gap between Nano Banana 2 Lite and Omni Flash
When I froze over whether a wallpaper app's hero asset should be a still image or a short moving loop, the deciding factor was not taste but the order of magnitude of the cost. Here is how to normalize Nano Banana 2 Lite and Omni Flash onto the same footing, down to a working decision function.
API / SDK2026-07-17
A Gemini stream drops halfway — restart it, or have the model continue?
Most apps silently restart a dropped stream. Here is the arithmetic behind continuing from the partial output instead, and where to put the threshold.
API / SDK2026-07-05
Splitting Bulk Image Generation Cost in Two with Nano Banana 2 Lite: A Draft-and-Render Design
A two-tier cost design that routes first-pass generation to Nano Banana 2 Lite and final renders to the standard Nano Banana 2, with a minimal Python router you can adapt.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →