◉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/Advanced
⬡ Advanced/2026-04-25Advanced

Building Self-Critiquing Agents with Gemini API: A Production-Ready Guide to Reflection and Critic-Refiner Patterns

A production-grade walkthrough of Reflection and Critic-Refiner patterns with Gemini 3 Pro and 3.5 Flash-Lite. Covers implementation, cost guards, over-correction defenses, and monitoring signals from real deployments.

gemini119gemini-3-pro4reflectionagent10production140self-improving

✦ Premium Article

If you've run a Gemini API workload long enough, you've probably hit the moment when single-shot quality plateaus. You tuned the prompt, dropped the temperature, added few-shot examples—and still about 10% of responses come back with hallucinated facts, broken formatting, or instruction violations. I ran into the same wall when I shipped a customer-email drafting feature in one of my apps.

The most practical way past that wall is the Reflection pattern, and its more refined cousin, the Critic-Refiner pattern. The idea is straightforward: have the LLM critique its own output, then rewrite it. Both patterns have been validated for years in research and in production. The catch is that naively bolting them onto a real workload introduces a new set of headaches—infinite loops, 3x cost spikes, and the dreaded "over-correction" failure mode where the model rewrites the parts that were already fine.

This guide walks through how I actually wire up self-critiquing agents in production using Gemini 3 Pro and 3.5 Flash-Lite. You'll get three runnable code examples, the cost guards I had to learn the hard way, and the monitoring signals that flag when your reflection setup has stopped earning its keep.

Where one-shot inference hits its ceiling

A one-shot LLM call—prompt in, response out—gets you 90% of the way for most tasks. The issue is the remaining 10%, where the failure modes are expensive enough that you can't ignore them.

Concrete examples I've seen:

  • A customer-document generator that occasionally misspells the recipient's name or omits a required field
  • A code-review agent that flags obvious vulnerabilities but misses dependency-breaking changes
  • A product description generator that lets a forbidden competitor brand slip through, despite explicit "do not mention" instructions

Trying to crush all of those with a bigger one-shot prompt usually backfires. I've watched Gemini 2.5 Pro and 3.1 Pro both start losing constraints once the prompt grows past about 30 stated rules—the model picks a subset to honor and quietly drops the rest.

The mental shift is to stop asking a single call to satisfy every constraint. Split the work into "draft → self-review → revise." That's where Reflection enters.

Reflection pattern: start with the minimum viable loop

The shape of it

The basic Reflection pattern is three steps:

  1. User request → Model A produces a first draft
  2. First draft → Model A (same model) produces a structured review listing instruction violations, factual errors, and formatting issues
  3. First draft + review → Model A produces a revised draft

Three calls instead of one. In my own A/B tests, this lifts perceived quality by roughly 20% without changing prompts much. The key detail is that the review must be a structured object, not free-form text. Vague feedback turns into vague edits, which turn into the over-correction failure mode where the revision step rewrites paragraphs that were already fine.

Code: a single-model Reflection loop with Gemini 3 Pro

What this code does: takes a task and a list of constraints, runs the three-step Reflection loop, and returns the final text.

# requirements: google-genai>=0.5.0, pydantic>=2.0
import os
from google import genai
from google.genai import types
from pydantic import BaseModel
from typing import List
 
client = genai.Client(api_key=os.environ["YOUR_GEMINI_API_KEY"])
MODEL = "gemini-3-pro"
 
class Issue(BaseModel):
    """A single problem found by the reviewer."""
    category: str  # e.g., "instruction_violation" / "factual_error" / "formatting" / "forbidden_term"
    snippet: str   # the offending span (max 80 chars)
    suggestion: str  # how to fix it
 
class Review(BaseModel):
    issues: List[Issue]
    needs_revision: bool
 
def generate_first_draft(task: str, constraints: List[str]) -> str:
    """Produce the initial draft."""
    prompt = (
        f"Write text that satisfies the following requirements.\n\nTask:\n{task}\n\n"
        f"Constraints:\n" + "\n".join(f"- {c}" for c in constraints)
    )
    resp = client.models.generate_content(
        model=MODEL,
        contents=prompt,
    )
    return resp.text
 
def review_draft(draft: str, constraints: List[str]) -> Review:
    """Review the draft and return a structured list of issues."""
    prompt = (
        "Review the following text against the listed constraints.\n"
        "Only list real problems—ignore stylistic preferences.\n"
        "If you find no issues, set needs_revision to false.\n\n"
        f"Constraints:\n" + "\n".join(f"- {c}" for c in constraints) +
        f"\n\nText:\n{draft}"
    )
    try:
        resp = client.models.generate_content(
            model=MODEL,
            contents=prompt,
            config=types.GenerateContentConfig(
                # Determinism comes from schema enforcement and explicit instructions,
                # not from sampling settings (now deprecated)
                response_mime_type="application/json",
                response_schema=Review,
            ),
        )
        return Review.model_validate_json(resp.text)
    except Exception as e:
        # Fail safe: if JSON parsing breaks, treat as "no revision needed"
        print(f"[review_draft] fallback: {e}")
        return Review(issues=[], needs_revision=False)
 
def revise_draft(draft: str, review: Review) -> str:
    """Apply only the listed fixes and leave everything else alone."""
    issues_text = "\n".join(
        f"- [{i.category}] {i.snippet} -> {i.suggestion}" for i in review.issues
    )
    prompt = (
        "Rewrite the following text by addressing ONLY the listed issues.\n"
        "Do not change any part that was not flagged.\n\n"
        f"Fixes to apply:\n{issues_text}\n\nOriginal text:\n{draft}"
    )
    resp = client.models.generate_content(
        model=MODEL,
        contents=prompt,
    )
    return resp.text
 
def reflect_once(task: str, constraints: List[str]) -> str:
    """Run a single Reflection cycle and return the final text."""
    draft = generate_first_draft(task, constraints)
    review = review_draft(draft, constraints)
    if not review.needs_revision:
        return draft
    return revise_draft(draft, review)
 
# Expected output: a revised draft that respects the constraints more strictly than the first draft.
if __name__ == "__main__":
    final = reflect_once(
        task="Draft a 200-character invitation email for a new SaaS product.",
        constraints=[
            "Stay strictly under 200 characters",
            "Address the recipient as 'Mr. Hirokawa'",
            "Never mention the competitor 'Acme Corp'",
            "Use a warm, professional tone throughout",
        ],
    )
    print(final)

Why this shape: the review is a Pydantic model, not a paragraph of prose, and the revise step is told to touch only the flagged spans. If you let the revise step "rewrite the whole thing in light of the review," it will. That's where over-correction comes from. Constraining the revision target is the silent safety belt of the entire pattern.

✦

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 your one-shot prompts have plateaued, you'll walk away with working Reflection loops and a clear rule for when to split into Critic and Refiner
✦You'll learn how to combine Gemini 3 Pro and 3.5 Flash-Lite to lift quality while keeping the cost increase inside 1.5–2x, plus the monitoring metrics that prove it's still working
✦You'll get the guard rails that stop self-critique loops from runaway costs, over-correction, and Critic bias—and the production signals that reveal when reflection has stopped paying off
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

⬡ Advanced2026-04-27
Self-Healing Architecture for Gemini Computer Use — Production Patterns That Keep Browser Automation Alive Beyond Day Three
Gemini Computer Use looks magical in demos but breaks daily in production: vanishing elements, surprise modals, network jitter, off-by-four-pixel clicks. This guide builds a five-layer self-healing architecture in Python that classifies failures and recovers them automatically, with working code you can drop into your agent loop today.
⬡ Advanced2026-04-24
Safely Migrating Gemini Model Versions with Shadow Traffic — A Production Pattern for Measuring Output Drift
Stop treating Gemini model migrations as a coin flip. This guide walks through a production-ready shadow traffic architecture — duplicate real inputs to the new model, quantify output drift, and cut over progressively. Includes Python and Cloud Tasks code you can ship today.
◈ API / SDK2026-05-11
Personalized Push Notifications at Scale: Gemini 3 Pro × Firebase Cloud Messaging for Indie App Developers
If your push notification open rates have plateaued, this guide shows how to combine Gemini 3 Pro with Firebase Cloud Messaging to generate per-user copy. Includes the production architecture, working code, PII guardrails, and real cost numbers from a live indie app.