GEMINI LABJP
NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Articles/API / SDK
API / SDK/2026-04-30Intermediate

Precise Output Control in Gemini API: maxOutputTokens and stopSequences

Combine maxOutputTokens and stopSequences in the Gemini API to shape response length exactly the way you need. Stop responses from being cut off, going over budget, or breaking JSON parsing — with production-tested patterns.

gemini-api277max-output-tokensstop-sequencespython104production140

"The response was longer than expected and blew past my Cloud Run limit." "My JSON got cut off mid-object." "I asked for a one-line answer and got three paragraphs." If you've shipped anything serious with the Gemini API, output-length issues like these probably feel familiar. The first one that bit me personally was much subtler: the same prompt would return responses of wildly different lengths from one day to the next. The root cause turned out to be confusing thinking tokens with output tokens, which cost me half a day before I caught it.

In this guide, I'll walk through how I use maxOutputTokens and stopSequences together to land response length where I actually want it. The official docs cover each parameter briefly, but the interesting behavior shows up when you combine them — so we'll spend most of our time on that combination, including the production gotchas I've personally hit.

maxOutputTokens is a hard ceiling, not a target

The most common misunderstanding is treating maxOutputTokens as "roughly how long I'd like the answer to be." It isn't. It's the strict upper limit on the number of output tokens the model can produce, and the model will be cut off the moment it hits that ceiling — even mid-sentence, even mid-JSON.

# Python SDK example
from google import genai
from google.genai import types
 
client = genai.Client()
 
response = client.models.generate_content(
    model="gemini-2.5-pro",
    contents="Summarize the following meeting notes in about 300 characters.\n---\n(meeting notes)",
    config=types.GenerateContentConfig(
        max_output_tokens=400,  # safety margin above the 300-char target
        temperature=0.3,
    ),
)
print(response.text)
print("finish_reason:", response.candidates[0].finish_reason)

The most useful habit is always checking finish_reason. STOP means a clean natural ending; MAX_TOKENS means the limit clipped the response. Logging the rate of MAX_TOKENS in production is a small change that pays back in both cost optimization and quality monitoring.

Think in tokens, not characters

For English, roughly one word equals 1.3 tokens, so a 500-word answer needs about 700 output tokens. Japanese tends to run 1–2 tokens per character. If you need precision, call client.models.count_tokens() against your actual prompt before fixing the limit.

# Measure expected input cost up front
count = client.models.count_tokens(
    model="gemini-2.5-pro",
    contents="Summarize the following meeting notes in about 300 characters.",
)
print(f"input_tokens: {count.total_tokens}")
# Knowing the input side makes total cost estimation accurate.

Thinking tokens are billed separately on 2.5 / 3 series models

This is the trap most teams fall into. On Gemini 2.5 Pro and the Gemini 3 series, the model produces internal thinking tokens before the output tokens you see. Those thinking tokens are not counted against maxOutputTokens, but they are billed. So you can set the output ceiling to 500 and still see a single call charge you for 3,000-plus tokens. If cost predictability matters, you also need to budget the thinking side — see Gemini 2.5 thinking budget optimization guide.

stopSequences: telling the model "end here"

stopSequences lets you specify up to five strings; the moment any of them appears in the generated output, the response stops cold.

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Write one sentence using these three words: cat, river, morning. Output one sentence only.",
    config=types.GenerateContentConfig(
        stop_sequences=["\n\n", "###", "Explanation:"],
        max_output_tokens=200,
        temperature=0.7,
    ),
)
print(response.text)

This stops the moment Gemini hits a double newline, a ###, or the word Explanation:. Gemini has a habit of helpfully adding "Here's why I picked those words..." after a one-sentence answer; stopSequences is the cleanest way to suppress that tendency.

The stop string itself is dropped from the response

A subtle but important detail: the stop string does not appear in the returned text. If you tell the model to wrap its reply in <answer>...</answer> and set stop_sequences=["</answer>"], the closing tag will be missing from the response. Downstream code that assumes the closing tag is present will break. I've personally had a parser blow up on this.

Watch out when combining with JSON mode

If you're using response_mime_type="application/json" for structured output, do not put a JSON syntax character like } in stopSequences — you'll truncate to an empty object. For structured output, prefer a Pydantic schema via responseSchema rather than ad-hoc stop strings.

Combining both: production patterns I actually use

In real systems, I use the two together as a defense-in-depth setup: maxOutputTokens is the safety valve, stopSequences is the intent expression.

Pattern 1: Strict-length summarization

def summarize_strict(text: str, max_chars: int = 200) -> dict:
    """Return a summary roughly within max_chars; flag it if truncation occurred."""
    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=(
            f"Summarize the following text in {max_chars} characters or fewer. "
            f"Return only the summary; no preamble.\n---\n{text}"
        ),
        config=types.GenerateContentConfig(
            max_output_tokens=int(max_chars * 1.5),  # safety margin
            stop_sequences=["\n\nSummary", "\n\nIn closing", "###"],
            temperature=0.2,
        ),
    )
    finish = response.candidates[0].finish_reason.name
    return {
        "text": response.text.strip(),
        "truncated": finish == "MAX_TOKENS",
        "finish_reason": finish,
    }
 
result = summarize_strict("(long article body)", max_chars=200)
if result["truncated"]:
    print("⚠️ Hit the ceiling — review the prompt's character target.")
print(result["text"])

Pattern 2: Lifting structured short answers

When you want fixed-format output like Category: <value>, ending the response at the structural boundary makes parsing reliable.

PROMPT = """Classify the following inquiry using this format:
Category: <technical|billing|sales|other>
Reason: <30 chars or fewer>
---
Inquiry: {query}"""
 
def classify(query: str) -> dict:
    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=PROMPT.format(query=query),
        config=types.GenerateContentConfig(
            max_output_tokens=80,
            stop_sequences=["\n---", "\n\n"],
            temperature=0.0,
        ),
    )
    out = response.text
    cat = out.split("Category:")[-1].split("\n")[0].strip()
    reason = out.split("Reason:")[-1].strip() if "Reason:" in out else ""
    return {"category": cat, "reason": reason}
 
print(classify("I can't log in"))
# → {'category': 'technical', 'reason': 'Login-related issue'}

Using stopSequences as a parsing anchor like this makes downstream code much steadier. If the model isn't stopping where you expect, my recommended order is: first tighten the prompt with explicit "do not output anything outside the format" instructions, then add stopSequences as a backstop. Doing it in that order keeps the prompt the source of truth.

Pattern 3: Defensive defaults for shared SDK wrappers

If you're building a thin wrapper around the SDK that other developers in your team will call, set defensive defaults so the worst-case caller doesn't blow your bill or your latency budget.

DEFAULTS = {
    "max_output_tokens": 1024,
    "temperature": 0.4,
    "stop_sequences": [],
}
 
def call_gemini(prompt: str, **overrides) -> dict:
    cfg = {**DEFAULTS, **overrides}
    response = client.models.generate_content(
        model=cfg.pop("model", "gemini-2.5-flash"),
        contents=prompt,
        config=types.GenerateContentConfig(**cfg),
    )
    return {
        "text": response.text,
        "finish_reason": response.candidates[0].finish_reason.name,
        "input_tokens": response.usage_metadata.prompt_token_count,
        "output_tokens": response.usage_metadata.candidates_token_count,
        "thinking_tokens": getattr(response.usage_metadata, "thoughts_token_count", 0),
    }

Returning usage_metadata alongside the text — particularly the thinking-token count on 2.5/3 series models — gives every caller the data they need to track real cost without re-instrumenting individually.

Pitfalls to watch out for

A few mistakes I see (or have made myself) repeatedly:

  • Streaming feels different: with streaming responses, hitting a stop sequence cuts the stream off immediately, so you may need a UI affordance for "this ended sooner than expected." For the broader streaming control patterns, see Streaming response chunk control.
  • Long inputs squeeze the output budget: the model's total context window covers input + output + thinking. Pump in a million tokens and there's effectively nothing left for output. For long-document summarization, either chunk the input or do a Flash-then-Pro two-pass.
  • Silently swallowing finish_reason: if you don't surface MAX_TOKENS, you get the "responses are mysteriously short some days" bug. The first thing to check when output feels short is always finish_reason. The deep dive on truncation is in Troubleshooting truncated responses.
  • Stop sequences and Unicode quirks: if your stop sequence contains a smart quote or a curly apostrophe and the model produces straight quotes, the match fails silently. Test stop sequences with a couple of generations before deploying, and prefer ASCII-only delimiters when possible.
  • Production observability is non-optional: I always log usage_metadata (input, output, thinking) along with finish_reason to a structured log. After a week of data, you can plot the distribution of output token counts per endpoint and immediately see which calls are wasting tokens versus getting clipped. That's the data that turns guesswork into evidence.

Wrapping up — the next step worth taking

Treat maxOutputTokens as a safety valve and stopSequences as intent expression, and most output-length issues become tractable. If you try one thing from this article, add finish_reason logging to your most-used Gemini call. After watching the logs for a week, you'll have a clear view of which calls are clipping at MAX_TOKENS, and you can adjust ceilings from real data instead of guesswork. That single change has saved me more debugging time than any prompt tweak.

If you want to go deeper on the prompt-side of output predictability, the chapters on instruction granularity in Prompt Engineering: A Practical Approach (or any of the recent reliable production-LLM guides) pair well with this hands-on tuning. The combination of API-side controls and prompt-side discipline is what actually moves output behavior from "mostly right" to "predictably right."

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-07-15
Sample what you already accepted — an audit budget that catches silent quality drift
A confidence gate only ever looks at output the model hesitated on. Silent drift sinks into the batch that sailed through. Working from a fixed 30-minutes-a-day review budget, this walks through deriving detection time from the binomial, reallocating the same budget across risk strata, and catching slow decay with a cumulative monitor.
API / SDK2026-07-04
When Gemini API Leaks Japanese Into Your English Output Once in a While — Field Notes on Measuring the Contamination Rate and Tightening It in Stages
You told Gemini to answer in English, and 3 out of 100 runs slip a Japanese sentence into the tail. Here is why you cannot stop that 'once in a while', and a production pattern that measures the contamination rate as an SLO and tightens it with graded recovery, with working code.
API / SDK2026-07-02
Routing Between Local Gemma 4 and the Gemini API Cut My Bill from ¥32,000 to ¥9,000 — A Production Hybrid Router Design
How I cut a ¥32,000/month Gemini API bill to the ¥9,000 range with hybrid inference: routing design, a full Python router, production pitfalls, and how Gemma 4 arriving on the Gemini API in July 2026 changes the decision.
📚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 →