When I saw the notice that some image generation models would retire on August 17, the first thing I did was not to pick a replacement. What made me stop was a simpler, more uncomfortable fact: I couldn't say for certain which of my own scripts were calling which model, or when.
I run a few pipelines that generate images for wallpaper apps as an indie developer, and model IDs end up scattered in more places than you'd expect — a validation script from months ago, a batch that only runs at month-end, a string buried in a config file. Even if I settle on one GA model to migrate to, missing a single call site means the thing that breaks on the morning of August 17 is the one job I never noticed. So before migrating, I did an inventory. Here's the process.
Why grep alone misses call sites — three blind spots
The obvious first move is to search the whole repository for the model name. That's useful, but incomplete. Relying on static string search alone leaves three gaps.
First, model IDs loaded from environment variables, config files, or a database. The code only says os.environ["IMAGE_MODEL"], and the actual value lives only in the deployment environment. Second, IDs passed in as arguments. If you've built a wrapper around the API, the model name may come from the caller, and static analysis can't tell you which value actually arrives. Third, old jobs you assume are already dead. A schedule you thought you disabled quietly still running is the scariest pattern in any deprecation.
In short, static code search tells you where a model might be called, but not whether it's actually being called right now. Collecting those two things separately is the point of this article.
Aggregate the models that are actually being called from your logs
Request logs fill the gap that grep leaves. If you record the model ID and a timestamp on every API call, you can mechanically compute which models were called, how recently, and how often. Even if you don't log yet, if your calls pass through a single wrapper, one added line starts the record today.
Here's a minimal wrapper that records each call before running it.
import json, time, pathlib
LOG_PATH = pathlib.Path("logs/model_calls.jsonl")
LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
def call_image_model(client, model_id: str, prompt: str, *, caller: str):
"""Wrapper for image generation. Records model_id and caller before running."""
record = {
"ts": time.time(),
"model": model_id,
"caller": caller, # e.g. an identifier for the call site: "wallpaper_batch.generate"
}
with LOG_PATH.open("a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
return client.models.generate_images(model=model_id, prompt=prompt)Always passing caller is the key. Later you can trace not just "which model is used most" but "which process is calling that model," all in one pass.
Then aggregate the accumulated logs. Group the retiring model IDs and report each one's call count, last-called date, and callers.
import json, pathlib
from collections import defaultdict
from datetime import datetime, timezone
# List the model IDs retiring on August 17 (check the official deprecation list and update).
RETIRING = {
"gemini-3.1-flash-image-preview",
"gemini-3-pro-image-preview",
}
stats = defaultdict(lambda: {"count": 0, "last_ts": 0.0, "callers": set()})
for line in pathlib.Path("logs/model_calls.jsonl").read_text(encoding="utf-8").splitlines():
rec = json.loads(line)
if rec["model"] not in RETIRING:
continue
s = stats[rec["model"]]
s["count"] += 1
s["last_ts"] = max(s["last_ts"], rec["ts"])
s["callers"].add(rec.get("caller", "unknown"))
for model, s in sorted(stats.items(), key=lambda kv: kv[1]["count"], reverse=True):
last = datetime.fromtimestamp(s["last_ts"], tz=timezone.utc).strftime("%Y-%m-%d")
callers = ", ".join(sorted(s["callers"]))
print(f"{model}: {s['count']} calls / last {last} / callers: {callers}")Run this and, for each retiring model, you see at a glance whether it's still alive, how frequently, and who calls it. A count of zero means the migration is less urgent even if the code still exists. Conversely, a caller you overlooked in grep may show up here by name for the first time. In my case, a validation batch I thought I'd disabled surfaced with a last call three days prior — a quiet moment of dread.
Assign a migration priority to each call site you found
Once the inventory is in, don't try to migrate everything at once — assign priorities. The three inputs are call volume, recency, and whether it runs unattended. Unattended jobs especially deserve a higher priority even at low volume, because when they stop, no one notices right away.
| Nature of the call site | Migration priority | Reason |
|---|---|---|
| Unattended scheduled batch, called recently | Highest | A stoppage goes unnoticed and its impact spreads quietly |
| User-triggered with high call volume | High | A shutdown directly degrades the experience |
| Manually run validation or experiment scripts | Medium | Little real harm if it stops, but neglect breeds confusion later |
| Zero calls in the logs | Low (but consider deleting) | Effectively unused — a good chance to remove it from the code |
Replace the highest-priority sites with your chosen GA model first, then run the same log aggregation again. If calls to the retiring models no longer appear in the newer records, that path's migration is done. Not skipping this "confirm in the logs after switching" step is, I've found, the surest way to pass August 17 quietly.
For choosing the replacement model and the code diffs to verify during the switch, I've written up Gemini's image generation preview models retire on June 25 — the code diffs and verification steps for moving to GA. For the pipeline-side preparation that keeps the morning of a shutdown calm, What I learned on the morning a preview image model stopped — Gemini image model GA migration and a deprecation-resilient pipeline is a useful companion.
Keep model IDs in one place
The biggest lesson from this inventory was that the more model IDs are written directly throughout the code, the more searching each deprecation forces on you. To prepare for the next one, consolidate model IDs into a constants module or configuration and have call sites reference them by name — so next time, one change is enough. The thinking behind pinning model IDs and detecting changes, including the accident of a default model silently moving up, is explored in Don't let "the default model just went up" become an incident in the Gemini API — pinning model IDs and detecting default changes.
As a concrete first step you can take today, add one line of caller-tagged logging to your image generation calls. By August 17, where your code truly needs migration will be visible as data, not guesswork. Thank you for reading.