●TTS — gemini-3.1-flash-tts-preview now streams speech generation via streamGenerateContent for lower latency●TRANSLATE — Gemini 3.5 Live Translate arrives, auto-detecting 70+ languages for speech-to-speech while preserving intonation●IMAGE — Nano Banana 2 Lite launches as the fastest and most cost-efficient Gemini image model●OMNI — Gemini Omni Flash enters public preview as a natively multimodal model for custom video workflows●MODEL — Gemini 3.5 Flash reaches GA and now powers gemini-flash-latest●AGENT — Managed Agents enter public preview in the Gemini API, running in isolated Google-hosted Linux sandboxes●TTS — gemini-3.1-flash-tts-preview now streams speech generation via streamGenerateContent for lower latency●TRANSLATE — Gemini 3.5 Live Translate arrives, auto-detecting 70+ languages for speech-to-speech while preserving intonation●IMAGE — Nano Banana 2 Lite launches as the fastest and most cost-efficient Gemini image model●OMNI — Gemini Omni Flash enters public preview as a natively multimodal model for custom video workflows●MODEL — Gemini 3.5 Flash reaches GA and now powers gemini-flash-latest●AGENT — Managed Agents enter public preview in the Gemini API, running in isolated Google-hosted Linux sandboxes
Catching only the deprecations that touch you — feeding the official changelog to url-context
I found out an image model was being shut down three days before the deadline. Here is a deprecation radar that reads the official changelog through url-context and surfaces only the models I actually use, with working Python and the over-alerting tuning I had to do in production.
I realized an image generation model was being retired on August 17 only three days before the deadline. As an indie developer running iOS and Android apps while also maintaining a few technical blogs, I honestly do not have the time to read the changelog of every API I touch, every day. That day I happened to open the changelog, saw the deadline staring back at me, and felt my stomach drop.
The problem was not that the information was missing. It was published, clearly and on time. I simply could not go read it every day. And if that is the real bottleneck, then the reading is what should be automated — especially if the automation grounds itself on the official page and surfaces only the deprecations that hit the models I actually use. Do that, and the notifications suddenly go quiet.
Gemini's url-context makes this straightforward to build. Below, we assemble a deprecation radar that reads the official changelog through url-context, cross-references it against your own list of models, and alerts you only on the delta. Pitfalls included, in a form you can run today.
Do not cram grounding and structured output into one call
Let me share the one design decision that matters most. Miss it, and you will end up rewriting the whole thing.
A call that enables a tool like url-context and a call that enforces strict JSON output via response_schema do not behave reliably when you put them in the same generate_content request. The path where the tool returns fetched content and the path that structures output against a schema compete with each other — the schema gets ignored, or the tool never fires. I burned half a day here.
So we split the work into two stages.
Stage
Job
Tool
Output
1
Read the official page and extract deprecation notices as plain text
url-context enabled
Plain text
2
Cross-reference that text with your manifest and structure only the impact
No tools, response_schema only
Strict JSON
Two calls looks wasteful at first, but in practice it is cheaper. Stage 1 runs fine on the lightweight gemini-flash-latest, and stage 2 only receives the already-extracted short text, so its input token count stays small. In my setup the total lands at a few cents per run, and running it three times a month is nothing.
Step 1: put the models and endpoints you actually use on one sheet
The radar's precision is decided by your own ledger — the thing you cross-reference against. Leave it vague and you get a noisy notifier that reacts to every deprecation in the world.
I keep everything my apps and sites actually call in a single YAML file. The key is to always record where each one is used. When a deprecation notice arrives, the first thing I want to know is which app I have to fix.
# manifest.yaml — the Gemini surfaces I actually useused: - id: "gemini-flash-latest" kind: model where: ["wallpaper-app-android", "labs-auto-post"] note: "Main lightweight generator. An alias, so watch for silent promotion" - id: "gemini-embedding-2" kind: model where: ["labs-related-articles"] - id: "url_context" kind: feature where: ["labs-migration-radar"] - id: "batch-api" kind: feature where: ["wallpaper-app-review-classify"]watch_pages: - "https://ai.google.dev/gemini-api/docs/changelog" - "https://ai.google.dev/gemini-api/docs/deprecations"
Writing the App Store / Google Play app name, or which blog's auto-posting pipeline, into where means I know the fix target the moment I read the alert. I even note the screens that wire into AdMob, as a reminder to not break the ad initialization order when I swap a model out.
✦
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
✦If you are tired of tracking model deprecations by hand, you will be able to point url-context at the official changelog and automatically catch only the changes that affect the models you actually run
✦You get the two-call design that keeps grounding and structured output apart, plus state management that alerts only on the delta since last run, as working Python
✦You take home the over-alerting and blind-spot fixes I learned in production: alias drift, wording of notices, and the danger of a swallowed retrieval failure
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.
Step 2: let url-context read only the changelog pages
This is stage 1. We hand the official URLs from watch_pages to url-context and ask it to extract only the text about deprecations and end-of-life, as plain bullets.
from google import genaifrom google.genai import typesclient = genai.Client(api_key="YOUR_API_KEY")def fetch_deprecation_notes(pages: list[str]) -> tuple[str, list[dict]]: url_list = "\n".join(f"- {u}" for u in pages) prompt = ( "Read the following official pages and extract only statements about " "models or features being deprecated, retired, removed, or requiring " "migration. Always include the date and the target name, as bullets. " "If there is nothing relevant, reply with only 'none'.\n" f"{url_list}" ) resp = client.models.generate_content( model="gemini-flash-latest", contents=prompt, config=types.GenerateContentConfig( tools=[types.Tool(url_context=types.UrlContext())], temperature=0, ), ) # Check retrieval status (do not swallow failures) meta = [] cand = resp.candidates[0] if cand.url_context_metadata: for u in cand.url_context_metadata.url_metadata: meta.append({ "url": u.retrieved_url, "status": str(u.url_retrieval_status), }) return resp.text, meta
The easily missed part is checking url_context_metadata. url-context can fail to fetch a page and still return a plausible-sounding answer. Trust the body without looking at retrieval status, and you get handed a false sense of "nothing today." I always log each URL's url_retrieval_status, and if any run contains something other than SUCCESS, I hold the radar's verdict. I wrote up why this swallowing is dangerous, and the verification pattern for it, in a gate that verifies retrieval status before trusting the answer.
I set temperature=0 because extraction needs no creativity. The same page should yield the same result. If this wobbles, the next delta check misfires every time.
Step 3: cross-reference the manifest and receive only the impact as JSON
This is stage 2. We pass both the stage-1 text and the step-1 manifest, and ask for structure covering only the surfaces I use. No tools — just response_schema.
from pydantic import BaseModelclass Affected(BaseModel): used_id: str # must match an id in the manifest change_type: str # deprecation / removal / migration, etc. deadline: str # deadline ("unknown" if not readable) summary: str # one line on what happens action: str # what I should doclass Radar(BaseModel): affected: list[Affected]def diff_against_manifest(notes: str, manifest_yaml: str) -> Radar: prompt = ( "Below are statements extracted from official pages about " "deprecations and migrations.\n" f"--- notices ---\n{notes}\n\n" "Below is the list of models and features I actually use.\n" f"--- usage (YAML) ---\n{manifest_yaml}\n\n" "Extract only changes related to an id in the usage list. Ignore " "deprecations that are unrelated. used_id must match an id in the " "usage list exactly; use 'unknown' if the deadline cannot be read." ) resp = client.models.generate_content( model="gemini-flash-latest", contents=prompt, config=types.GenerateContentConfig( response_mime_type="application/json", response_schema=Radar, temperature=0, ), ) return Radar.model_validate_json(resp.text)
The constraint that used_id must match a manifest id pays off quietly. With it, the next delta check can decide "is this the same item?" mechanically. Try to diff on free-form summaries alone and a slightly reworded notice looks like a brand-new item, so the alert fires twice.
Because structuring is a separate call, this stage stays protected by its schema even if the entity gemini-flash-latest points to is silently promoted. If aliases make you nervous, pair this with something that catches them ahead of time in CI. I split the roles with a setup that stops non-recommended models in CI before they bite: this radar is the "notice it" half, CI is the "block it" half.
Step 4: alert only on the delta from last time
At this point you know the impact in effect right now. But if the same deprecation notice fires every run, people learn to skip it. The radar's value is delivering only what changed since last time.
import json, hashlibfrom pathlib import PathSTATE = Path("radar_state.json")def _key(a) -> str: raw = f"{a.used_id}|{a.change_type}|{a.deadline}" return hashlib.sha1(raw.encode()).hexdigest()[:12]def new_items(radar: Radar) -> list[Affected]: prev = json.loads(STATE.read_text()) if STATE.exists() else {} now, fresh = {}, [] for a in radar.affected: k = _key(a) now[k] = a.model_dump() if k not in prev: fresh.append(a) STATE.write_text(json.dumps(now, ensure_ascii=False, indent=2)) return fresh
I make the delta key the combination of used_id, change_type, and deadline because I want to be re-alerted if the deadline moves earlier. Same target, but a moving deadline is an important change — it shifts the priority of the work. I keep the summary out of the key so wording drift does not misfire it.
After that, you just alert when new_items() returns something non-empty. I post to a chat channel I own, but anything works. What matters is that on quiet days, genuinely nothing arrives. Being able to trust that "nothing arrived" is, to me, the finished form of the radar.
Over-alerting and blind spots I only found by running it
Building it once was not the end. Run it for real and some days are noisy, while others stay silent when you wish they had not. Here are the fixes that worked.
Absorb alias drift
An alias like gemini-flash-latest and a concrete version like gemini-3.1-flash-image-preview show up mixed together inside the same notice. If your manifest id is written as the alias, you miss the concrete version's deprecation. I add "the concrete version this alias currently points to" to the item's note, and I tell the stage-1 extraction prompt to keep statements about both the alias and the concrete version. Blind spots dropped visibly.
Separate "announced" from "already done"
A changelog carries both upcoming shutdowns and reports of things already shut down. Giving the stage-2 schema a change_type and splitting removal (done) from deprecation (announced) lets me color-code urgency on the notification side. If a removal shows up on a surface I use, that is already an incident.
Do not let a fetch failure read as "none"
In the first version, runs where url-context failed to fetch a page passed silently as "none." Before I added the url_retrieval_status check in step 2, this was the scariest hole. On a failed fetch, I now hold the verdict and carry it to the next run. That single move brought the frequency of grabbing a false sense of safety close to zero.
Symptom
Cause
Fix that worked
Misses a concrete version's removal
Manifest holds only the alias
Note the concrete version and keep both in extraction
Same notice every run
Summary text was in the delta key
Fix the key to id, type, and deadline
No warning right after a shutdown
Announced and done conflated
Split by change_type and color-code urgency
Takes "none" at face value
Swallowed fetch failure
Verify retrieval status, hold and carry over
Before and after these adjustments, superfluous notifications dropped by roughly 90% in my setup. More than the number itself, what mattered was being able to trust that "if an alert arrives, it really is mine to deal with."
Wrapping up
You do not need to build a large monitoring platform. There is only one thing to do first: right now, write out the model and feature names your apps and sites are calling into the step-1 YAML. That alone gives you the foundation to mechanically check, the next time a deprecation appears, whether the impact reaches you.
What I find compelling about url-context is that it lets you ground on the "most correct source" — the official page — directly, without detours. Since I started running this, I have been freed from the daily tension of opening the changelog. If it brings a similar quiet reassurance to anyone else carrying several products on their own, I would be glad. Thank you for reading.
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.