- 01What I was building
- 02I sent 10 at once and the body was empty
- 03The cause: thinking also uses the output allowance
- 04A troubleshooting table for empty responses
- 05How to read usageMetadata
- 06How to set thinkingBudget
- 07thinkingBudget ranges by model
- 08The Gemini 3 series uses thinkingLevel
- 09Check first whether you can use the 2.5 series
- 10Billing for thinking tokens
- 11How to specify structured output (JSON)
- 12Splitting into groups of 5 got it through
- 13Deadlines that are not in the text sometimes get invented
- 14Discard deadlines whose date string is not in the text
- 15Key points when building this into a routine job
- 16Common mistakes and pitfalls
- 17What to check before you start
- 18Frequently asked questions
- 19Official sources I referred to
- 20Summary
Good morning, this is まさきん.
I sent 10 articles to the Gemini API in a single request, and the response came back with an empty body. Both the cause and the fix turned out to be explained in the official documentation, so I am leaving these notes here.
*This information is current as of the time of publication. Please check the official sites for the latest terms.
- When a Gemini API response is empty, look at
finishReasonandusageMetadatafirst - According to the official docs, the output token limit (maxOutputTokens) applies to the total including thinking tokens
- On gemini-2.5-flash, setting
thinkingBudgetto 0 turns thinking off (the range is 0 to 24576) - I set thinkingBudget=0 and split the batch of 10 into groups of 5, and it went through
- The model sometimes invents deadlines that are not in the text, so I check dates against the source text in post-processing
What I was building
I built a small system that fetches new articles from a members-only site every day. For each article, I let Gemini decide whether it is a deal or not and write a summary.
The notifications go to our family chat. If everything gets posted, nobody reads it, so I narrowed it down to items judged to be deals.
The model is gemini-2.5-flash. Summarizing and classifying is routine work, so I figured a fast, cheap model would be a good fit.
I sent 10 at once and the body was empty
At first, I packed the 10 newest articles into one request. I wanted to reduce the number of calls.
But the body of the response came back empty. It did not fail with an error. A response arrived, it just had nothing in it.
An error raises an exception, so you notice. An empty response slips through quietly, which can be a bit of a nuisance. When you build this into a routine job, I think this is the first pitfall.
The cause: thinking also uses the output allowance
The official documentation explains that the Gemini 3 and 2.5 series are models that use a “thinking process.” The model reasons internally before it responds.
This thinking is on by default. The official term is “dynamic thinking,” and the amount adjusts automatically to the complexity of the request.
The key point is that this thinking consumes tokens too. The docs state that max_output_tokens is a limit that includes thinking tokens.
If the limit is reached in the middle of thinking, finish_reason becomes MAX_TOKENS. And the output is said to be either cut off partway or returned empty. When you ask the model to think through 10 judgments at once, I suspect the total of thinking plus output balloons easily.
The official documentation also has a note about billing. Even if the limit is reached and the output is empty, the thinking tokens that were generated are still billed. An empty response can mean you get no result and still pay for it.
A troubleshooting table for empty responses
There are several kinds of empty response. Based on the fields in the API reference, I sorted out where to look.
| Symptom | Where to look in the response | Likely situation | Direction of the fix |
|---|---|---|---|
| Body is empty or cut off | finishReason is MAX_TOKENS | Reached the specified maximum number of tokens | Reduce thinking, reduce items per request, review the limit |
| Body is empty and thinking is heavy | usageMetadata.thoughtsTokenCount is large | Thinking is using up the output allowance | Lower thinkingBudget (2.5 series) |
| Body is empty for another reason | finishReason is SAFETY or similar | Stopped for safety or other reasons | Review the input content and the prompt |
| JSON comes back but the content is off | finishReason is STOP | The format is correct but the values are wrong | Validate the values in your application |
A finishReason of STOP means the model stopped at a natural stopping point. MAX_TOKENS is defined as having reached the maximum number of tokens specified in the request.
So I think finishReason is the first thing to check when a response is empty. It may be safest to assume you will not use the body whenever it contains anything other than STOP.
How to read usageMetadata
The response comes with usageMetadata. The API reference describes it as follows.
| Field | Meaning |
|---|---|
promptTokenCount | Number of tokens in the prompt (input) |
candidatesTokenCount | Total number of tokens in the generated response candidates |
thoughtsTokenCount | Number of tokens used for thinking by a thinking model |
totalTokenCount | Total of prompt + thinking + response candidates |
What makes this easy to follow is that the total is defined as “prompt + thinking + response.” Thinking and the response are counted as separate fields.
If the thinking number is large and the response number is small, thinking may be eating the allowance. Logging these two makes it easier to trace the cause later.
How to set thinkingBudget
In the 2.5 series, you set the amount of thinking with a parameter called thinkingBudget. The official explanation is that it gives the model a guide for how many tokens to use for thinking.
Setting it to 0 disables thinking. Setting it to -1 gives you dynamic thinking. With the Python SDK, following the official example, it looks like this.
from google import genai
from google.genai import types
client = genai.Client()
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
thinking_config=types.ThinkingConfig(thinking_budget=0)
),
)
When calling via REST, you place thinkingConfig inside generationConfig. Within that, you pass thinkingBudget as a number.
That said, the docs include a caveat. Depending on the prompt, the model may go over or under the budget. It seems best to treat it as a guide rather than a strict cap.
thinkingBudget ranges by model
The range and the default differ by model. I copied the table from the official documentation, narrowed down to the main 2.5 series models.
| Model | When not specified | Allowed range | Disabling thinking | Dynamic thinking |
|---|---|---|---|---|
| 2.5 Pro | Dynamic thinking | 128 to 32768 | Not possible | -1 (default) |
| 2.5 Flash | Dynamic thinking | 0 to 24576 | Set 0 | -1 (default) |
| 2.5 Flash Lite | No thinking | 512 to 24576 | Set 0 | -1 |
I think the important difference is that 2.5 Pro cannot stop thinking. If you carry over “the setting where I used 0 on Flash” to Pro as is, your assumptions may fall apart.
2.5 Flash Lite is a model that does not think unless you specify something. If all you need is summarizing and classifying, choosing this one is another way to go.
The Gemini 3 series uses thinkingLevel
In the official documentation as of publication, the way to specify this has changed from the Gemini 3 series onward. The recommended parameter is thinkingLevel. You specify it in steps such as minimal, low, medium, and high.
The 2.5 series, conversely, does not support thinkingLevel. The docs say to use thinkingBudget with the 2.5 series.
There were also notes on the Gemini 3 series. Gemini 3.1 Pro cannot disable thinking. Gemini 3 Flash and Flash-Lite are also described as not supporting thinking fully off. Even minimal is explained as “not a guarantee that thinking is off.”
In other words, once you move to a newer model, the trick of “set it to 0 and stop it” may no longer be available. It seems wise to expect that the settings will need rewriting when you switch.
Check first whether you can use the 2.5 series
The official model list carries a note about the 2.5 series. It says availability is limited to users who have actually used it in the past.
It also says the series is not deprecated and will continue to be offered via the API for the time being. However, new projects were advised to use the latest models.
My system runs on gemini-2.5-flash. If you are building something new from here, checking which models are available in your own project may be the first step.
Clicking this opens the Rakuten login page. Once you log in, you’ll see the campaign details.
Billing for thinking tokens
The pricing page states clearly that the output price “includes thinking tokens.” The entry for gemini-2.5-flash had the same wording.
The thinking guide has an explanation to the same effect. When thinking is on, the response price is the sum of output tokens and thinking tokens.
Even with a setting that returns only a summary of the thinking, billing is said to cover the entire original thinking tokens. It seems fair to understand that more may be counted than what you can see.
Routine jobs like summarizing and classifying run over and over every day. Each run is small, but it does not hurt to know that the thinking portion adds up.
How to specify structured output (JSON)
If your program is going to receive the judgment and the summary, having them returned as JSON is easy to handle. The Gemini API has a structured output mechanism for this.
In the API reference, you place two fields in generationConfig. You set responseMimeType to application/json. Along with that, you pass a schema in responseJsonSchema.
"generationConfig": {
"responseMimeType": "application/json",
"responseJsonSchema": {
"type": "object",
"properties": {
"is_deal": {"type": "boolean"},
"summary": {"type": "string"},
"deadline": {"type": "string"}
}
}
}
The field names are examples I made up for illustration. The reference says responseMimeType is required when you pass a schema.
Note that the SDK guide also showed examples written slightly differently. I think it is best to recheck the official examples against the SDK version you are using.
The official best practices contain one important sentence. Structured output guarantees JSON that is syntactically correct, but it does not guarantee that the values are semantically correct. It says to validate them in your application before using them.
Splitting into groups of 5 got it through
I actually did two things. I set thinkingBudget to 0. And I split the batch of 10 into groups of 5.
With both in place, the body started coming back. I have not isolated whether either one alone would have been enough.
| Point of comparison | 10 in one batch | Split into groups of 5 |
|---|---|---|
| Number of calls | Fewer | More |
| Length of one output | Longer | Shorter |
| Risk of hitting the limit | Tends to be higher | Tends to be lower |
| Impact of a failure | Redo everything | Redo only that group |
| Mixing up articles | Can happen | Easier to reduce |
A single batch looks more efficient. But when it fails, everything has to be redone. If you split it up, the scope of a failure stays within one group.
The best group size probably depends on the length of the articles and the shape of the output. Five is simply the number that happened to work for my use case.
Deadlines that are not in the text sometimes get invented
One more thing that troubled me was deadlines. Gemini sometimes produced a deadline that was not written in the article.
For deal notifications, a wrong deadline is a problem. Too early and you miss the opportunity, too late and you do not notice it has expired.
I think this connects to the caveat about structured output. Even if the JSON is shaped correctly, the values are not necessarily correct. When the schema has a “deadline” field, there may be a pull toward filling it in.
Discard deadlines whose date string is not in the text
As a countermeasure, I added post-processing. The idea is simple.
- Have Gemini return the deadline as a JSON field
- Check whether a string corresponding to that date exists in the original article text
- If it is not found in the text, discard the deadline field
- Send the notification as a deal with no deadline
I felt that just asking the model “do not make things up” was not reliable. So I check against the text on the program side.
This approach has a weakness too. If the notation in the text differs from the notation the model returns, it throws away correct deadlines as well. The design accepts that “no deadline is better than a wrong deadline.”
Key points when building this into a routine job
This episode helped me sort out how to look at putting generative AI into routine processing.
- Treat an empty response as an error (check the body length and finishReason)
- Log usageMetadata so you can trace the breakdown of thinking and output
- For tasks like summarizing and classifying, consider a setting that reduces thinking first
- Do not cram too much into one request, and keep it to a size where only one group needs redoing on failure
- Check the values the model returns against the original text before using them
The official best practices have a similar example. For simple tasks such as fact retrieval and classification, thinking should be minimal or low. Work that does not need difficult reasoning may not need deep thinking.
At work, too, I check numbers against their source before using them. Treating AI output the same way feels like the most settled approach to me.
I also wrote about having AI summarize each morning’s information in the article on having AI create my daily briefing. For permissions when running unattended, the article on why MCP gets denied with claude -p covers a related topic.
Common mistakes and pitfalls
Here are the points that are easy to trip over.
- Seeing only that a response came back, and missing that the body is empty
- Lowering maxOutputTokens to try to keep costs down
- Using settings meant for 2.5 Flash as is on a model that cannot stop thinking
- Assuming the content is correct because it came back as JSON
- Batching too many items, so one failure means redoing all of them
The second one is clearly warned about in the official docs as well. When you want to cut cost or latency, the guidance is to lower thinking rather than shrink the limit. Tightening the limit causes responses to be cut off.
The ones I stepped on were the first and the fifth. API usage and the paid plans for the app are separate matters. I summarized the app-side pricing in the article that sorts out Google AI Pro features and pricing.
What to check before you start
For anyone building a similar system, I put together a checklist.
- Confirmed that the model I want to use is available in my project
- Checked the official table to see whether thinking can be disabled on that model
- Decided how to handle cases where finishReason is anything other than STOP
- Log the thinking and output token counts from usageMetadata
- Set the number of items per request to a size that can be redone on failure
- Added a step that checks dates and numbers returned by the model against the source text
I think the third and the sixth are especially effective. Empty responses and invented values both slip through quietly.
Frequently asked questions
Q. What should I look at first when a Gemini API response is empty?
I think it is best to look at finishReason. If it is MAX_TOKENS, the specified maximum number of tokens was reached. Checking thoughtsTokenCount as well tells you how much was used for thinking.
Q. Are there models where thinkingBudget cannot be set to 0?
In the official table, 2.5 Pro is listed as unable to disable thinking. Its range is 128 to 32768. The Gemini 3 series is specified with thinkingLevel, and some models do not support thinking fully off.
Q. What is the thinkingBudget range for gemini-2.5-flash?
In the official table it is 0 to 24576. If not specified, it uses dynamic thinking. Setting 0 disables it, and -1 means dynamic thinking.
Q. Are thinking tokens billed too?
Yes. The pricing page says the output price “includes thinking tokens.” Even if the limit is reached and the output is empty, the thinking that was generated is said to be billed.
Q. Will raising maxOutputTokens solve it?
If the response is being cut off because it hits the limit, giving it more headroom is one option. However, if thinking stays heavy, cost and latency go up. The official guidance points to lowering the thinking setting.
Q. Does structured output prevent mistakes in the content?
No. The docs say that what is guaranteed is only syntactically correct JSON. The semantic correctness of the values is not guaranteed. You need validation on the application side.
Q. How many items per group is best?
I do not think there is a fixed right answer. It depends on the length of the articles and the shape of the output. Splitting 10 into groups of 5 worked for me, but that is a result for my use case.
Q. Does turning thinking off lower the quality of the judgments?
I think it depends on the task. The official docs give classification and fact retrieval as examples where low thinking is enough. I have not compared the change in quality with numbers, so if you are concerned, trying it on your own articles seems best.
Official sources I referred to
- Gemini thinking (Google AI for Developers)
- Gemini thinking, generateContent version (Google AI for Developers)
- Generating content, API reference (Google AI for Developers)
- Structured outputs, generateContent version (Google AI for Developers)
- Gemini models (Google AI for Developers)
- Gemini 2.5 Flash (Google AI for Developers)
- Gemini Developer API pricing (Google AI for Developers)
- Troubleshooting guide (Google AI for Developers)
Summary
The reason it came back empty was that thinking also uses the output allowance. Setting thinkingBudget to 0 and splitting into groups of 5 got it through.
Still, what settings can fix is only “whether something comes back.” Whether what comes back is correct had to be verified separately. The post-processing that checks deadlines against the text exists for that purpose.
If you have generative AI in a routine job, why not take a look at how you are handling empty responses?
Clicking this opens the Rakuten login page. Once you log in, you’ll see the campaign details.
This article contains affiliate advertising. If you sign up for a product or service through a link on this site, we may receive compensation from the partner company. The site operator is also an employee of Rakuten Group and may receive compensation through an employee referral program. The content and opinions here are based on the operator’s own experience and research regardless of any advertising relationship, but please read with the above in mind. See the Disclaimer & Affiliate Disclosure for details. Disclaimer & Affiliate Disclosure
This article includes machine-translated content. Please check the official Rakuten Mobile multilingual page for exact terms.
If you have concerns about Rakuten Mobile coverage or signal quality, you can consult through the official signal improvement request form .