◉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, 6 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, 6 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-14Intermediate

Generate With Flash, Escalate to Deep Think Only When Unsure: A Two-Stage Pipeline

With Deep Think opening up on the API, the move is not to route every request through the heavy model but to have Deep Think verify only when Flash's output looks shaky. Here is the cost reasoning and working code.

Gemini API249Deep ThinkFlash3Cost Optimization13Quality Gate

The first thing I thought when I heard Deep Think was partly opened on the API was: "Routing everything through it raises quality, but the bill stops being realistic." A heavy reasoning model is more accurate, but its per-token price and latency are an order of magnitude away from Flash. As an indie developer running an automation pipeline every day, that gap hits the end-of-month invoice directly.

So the design I landed on generates with cheap Flash and has Deep Think verify only when the output looks shaky. Calling heavy reasoning "only when needed" rather than "always" is how I aimed to raise the floor on accuracy without blowing up cost.

Why not route every request through the heavy model

Real-world requests are not uniform in difficulty. Most return a perfectly correct answer from Flash, and only a fraction are hard cases that warrant deliberation. Routing all of them through Deep Think means paying the high rate for the easy 90% too.

The two-stage idea is "answer first with the cheap model, send only the low-confidence ones to the expensive model." The key is to have Flash itself declare whether it is confident, and use that declaration as the mechanical basis for routing. Only once escalation runs without a human watching does it work as an automated pipeline.

Stage one: have Flash emit an answer and a confidence together

The point is to have Flash's response include not just the "answer" but a structured "confidence" and "reason for doubt." Receiving JSON lets the downstream routing be written without any string parsing.

// stage1-flash.mjs
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
 
const flash = genAI.getGenerativeModel({
  model: "gemini-3.5-flash",
  generationConfig: { responseMimeType: "application/json" },
});
 
export async function draftWithConfidence(question) {
  const prompt = `Answer the question and return JSON only.
Shape: {"answer": string, "confidence": number(0-1), "uncertain_reason": string}
confidence is your certainty in your answer. Declare it lower when the question is ambiguous, under-specified, or involves calculation.
Question: ${question}`;
 
  const res = await flash.generateContent(prompt);
  return JSON.parse(res.response.text());
}

Specifying responseMimeType: "application/json" makes Flash less likely to mix in a non-JSON preamble. Self-reported confidence is not a cure-all, but Flash does declare structurally hard cases—"calculation involved," "premises missing"—reasonably honestly low. Use it as a first-pass filter for routing, not as a perfect metric.

Routing: let confidence and a threshold decide escalation

Whether to call stage two is decided mechanically by a confidence threshold. Expecting to tune it in operation, keep it in a constant.

// router.mjs
const ESCALATE_BELOW = 0.7;   // below this confidence, send to Deep Think
 
export function needsDeepThink(draft) {
  if (draft.confidence < ESCALATE_BELOW) return true;
  // even at high confidence, escalate if the reason field shows a red flag
  const risky = ["uncertain", "premise", "guess", "calculat", "latest"];
  return risky.some(w => (draft.uncertain_reason || "").toLowerCase().includes(w));
}

Not relying on the threshold alone—pairing it with keywords in uncertain_reason—is for catching "overconfident wrong answers" where confidence comes out high. In my runs, a plain 0.7 threshold escalated about 20% of the total, and questions involving calculation or fresh information concentrated there.

Stage two: ask Deep Think only to verify

What matters in stage two is having Deep Think verify whether Flash's answer is right, not re-answer from scratch. Narrowing it to a verification task keeps the output short, saves tokens, and yields a rationale for the decision.

// stage2-deepthink.mjs
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
 
const deep = genAI.getGenerativeModel({ model: "gemini-3-deep-think" });
 
export async function verify(question, draft) {
  const prompt = `Question: ${question}
First answer: ${draft.answer}
Verify whether this first answer is correct and return JSON.
Shape: {"verdict":"ok"|"fix","final":string,"why":string}
If there is an error, put the corrected answer in final.`;
 
  const res = await deep.generateContent(prompt);
  return JSON.parse(res.response.text());
}

Wiring the two: only the doubtful ones touch the expensive model

Finally, wire it together. Flash answers first, and only the ones routing flags get passed to Deep Think.

// pipeline.mjs
import { draftWithConfidence } from "./stage1-flash.mjs";
import { needsDeepThink } from "./router.mjs";
import { verify } from "./stage2-deepthink.mjs";
 
export async function answer(question) {
  const draft = await draftWithConfidence(question);
  if (!needsDeepThink(draft)) {
    return { answer: draft.answer, path: "flash-only" };
  }
  const checked = await verify(question, draft);
  return {
    answer: checked.verdict === "fix" ? checked.final : draft.answer,
    path: "escalated",
    note: checked.why,
  };
}

Keeping path in the result is so you can later tally "what share got escalated." If that ratio is higher than expected, the threshold is too strict; too low, and you may be missing wrong answers. The cost-quality balance can be monitored with this one number.

How to set the threshold, and field notes

The surest way to set the initial threshold was to prepare 20–30 harder questions, hand-label the correct answers, and decide while watching both the escalation rate and the accuracy. Tuning straight on production traffic prolongs a state where you sacrifice either cost or quality.

What paid off in operation was logging Deep Think's verification results and later analyzing where Flash tends to err. If verdict: "fix" skews toward a particular category, it is cheaper in the end to reinforce the stage-one prompt there, or send that category straight to stage two. The two-stage setup is not fixed; I treat it as a base for continuously improving routing accuracy.

Start with the minimal setup: a confidence threshold of 0.7 and the red-flag keywords only. Before routing everything through the heavy model, measure how much this two-stage approach saves, and you will get a feel for using Deep Think exactly when it counts. I hope it helps your implementation.

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 →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◈ API / SDK2026-09-08
Writing my first Gemini cost estimate in two columns, one for now and one for January
Introductory pricing for Gemini Flash ends on December 31, 2026, and standard pricing starts on January 1, 2027. Staying on an older generation does not avoid it. Here is the small, working estimator I use to see both prices at once.
◈ API / SDK2026-10-14
When Your config Raises "Extra inputs are not permitted", Check the Spelling of Every Key
If a google-genai call fails with a pydantic ValidationError (extra_forbidden) the moment you pass a config, it is almost always a one-letter typo. Here is the output I reproduced, a small function that checks keys before the call, and how to tell why response.text came back as None.
◈ API / SDK2026-10-08
When a Deprecation Has No Shutdown Date: Re-Picking an Image Model on Three Conditions
gemini-nano-banana-2.1 went GA on October 6, and gemini-3.1-flash-image was deprecated with no shutdown date. Here is a table and a short script for choosing a replacement by resolution, reference images, and search grounding.