●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Rendering Gemini's Thought Summaries in a Next.js UI — A Production Pattern for Explainable AI
A production walkthrough for surfacing Gemini 2.5 / 3 thought summaries in a Next.js UI. Covers the SDK configuration, Server-Sent Events, a React collapsible component, observability, and the UX judgement calls you face when you decide how much of the AI's reasoning to show.
After six months of running Gemini in production apps, I keep hearing the same question from users: "Why did it answer that way?" Saying "because the model decided so" erodes trust, whether you are building a solo indie product or a B2B SaaS. Support tickets go up, refund requests go up, and the lifetime value of your customers gradually drifts down. Retrieval citations and source links help, but they only show what the model was given, not how it reasoned. For tasks that do not touch external information at all — summarization, classification, draft generation, comparative judgement — you cannot even offer citations, so the transparency gap is even wider.
Gemini 2.5 Pro, 2.5 Flash, and 3 Pro expose thought summaries — short, structured glimpses of the model's intermediate reasoning. I have found that surfacing these in the UI visibly reduces support requests that start with "why". On one of my apps the refund rate fell from roughly 0.8% to 0.3% after the feature shipped. This article walks through the whole pipeline end-to-end: pulling summaries from the SDK, streaming them via Server-Sent Events from a Next.js Route Handler, rendering them with a collapsible React component, and — the part most guides skip — the UX judgement calls that decide whether your users actually benefit. It also covers the observability, multi-turn conversation, and mobile UX details that always bite you the week after launch.
Trust does not accumulate while the reasoning stays hidden
From the user's point of view, an LLM-powered app is a black box with a text input and a text output. RAG citations and source chips tell you what information was retrieved, but they do not tell you how the model chose between competing possibilities. The reasoning itself stays opaque. This is especially painful for tasks where no external retrieval happens at all: the model is pulling from its internal weights, and you literally have nothing external to cite.
Thought summaries change that dynamic. The model internally generates many reasoning tokens, but exposing the full thinking trace tends to overwhelm users rather than reassure them. Gemini's summary feature distills those thoughts into short, structured snippets before returning them. Setting include_thoughts=True is enough for the API to mix in chunks where part.thought = True. Turning that into a production experience takes a bit of plumbing, so let's take it step by step.
One caveat worth internalizing early: thought summaries are summaries of reasoning for display, not the model's actual internal computation. They are not what researchers use when they discuss mechanistic interpretability. Treat them as a user-facing explanation layer, not as ground truth about the model's inner state. If you skip this distinction, internal design discussions tend to stall on "but did the model really think this?" — which is the wrong question for an application-layer feature.
Start with the smallest possible thought-summary loop
Before you involve Next.js, React, or streaming, make sure you can pull thought summaries out of Gemini from a vanilla Python script. If this layer is broken, everything downstream is broken too. Personally, I always start every new SDK feature with a "smallest possible smoke test" before I touch any full-stack code. Diving straight into streaming and UI tends to burn hours on issues that are easy to isolate at the script level.
# gemini_thought_demo.py# Purpose: verify that we can separate the answer from the thought summary.import osfrom google import genaifrom google.genai import typesclient = genai.Client(api_key=os.environ["GEMINI_API_KEY"])def ask_with_thoughts(question: str) -> dict: """Return the answer text and thought summaries as a dict.""" try: response = client.models.generate_content( model="gemini-2.5-pro", contents=question, config=types.GenerateContentConfig( thinking_config=types.ThinkingConfig( include_thoughts=True, # ← ask for thought summaries thinking_budget=-1, # ← auto (>=128 is a safe floor on Pro) ), ), ) except Exception as e: # Catch network, quota, and safety-filter errors in one place. return {"answer": "", "thoughts": [], "error": str(e)} answer_parts, thought_parts = [], [] for candidate in response.candidates or []: for part in candidate.content.parts: if getattr(part, "thought", False): thought_parts.append(part.text or "") elif part.text: answer_parts.append(part.text) return { "answer": "".join(answer_parts), "thoughts": thought_parts, "error": None, }if __name__ == "__main__": r = ask_with_thoughts("Give me 3 reasons Japanese public holidays tend to shift to Mondays.") print("--- THOUGHTS ---") for t in r["thoughts"]: print(t) print("\n--- ANSWER ---") print(r["answer"])
Running GEMINI_API_KEY=... python gemini_thought_demo.py prints one to three short summaries under --- THOUGHTS --- — things like "Group reasons into economic, cultural, and legal" — followed by the actual answer. If the thoughts array is empty, you are almost certainly on a non-thinking model like gemini-2.5-flash-lite. Switch to gemini-2.5-pro or gemini-2.5-flash and retry. This is the single most common cause of "thoughts not appearing" reports I see.
The thinking_budget value of -1 (auto) is fine in almost all real-world scenarios. If you do want to set it explicitly, a lower bound of 128 on Pro and 64 on Flash tends to keep the summaries from shrinking to a useless trickle. If you need an upper bound for cost control near end-of-month, capping at 512–1024 avoids runaway spending while still giving you usable reasoning. For the deeper story on model choice between Pro and Flash, Gemini 2.5 Pro thinking budget reasoning control guide will save you a lot of trial and error.
✦
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
✦Developers who cannot explain why their Gemini-powered app gave a particular answer will be able to ship a UI that surfaces the reasoning today.
✦You will get complete, runnable code for pulling thought summaries from the SDK, streaming them over SSE, and rendering them as a collapsible React component.
✦You will learn the UX heuristics for deciding how much reasoning to expose and how to measure whether it actually improves user trust.
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.
Stream thoughts and answers separately to the client
Returning the entire response in one shot gives users a long spinner followed by a wall of text. Streaming thoughts as they are finalized, then the answer tokens as they flow, feels far more natural. In Next.js 15's App Router, a Route Handler that emits Server-Sent Events is the shortest path.
// app/api/ask/route.ts// Purpose: stream thoughts and answer tokens to the browser as distinct SSE events.import { GoogleGenAI } from "@google/genai";export const runtime = "nodejs"; // @google/genai dependencies are unreliable on Edgeconst ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY\! });export async function POST(req: Request) { const { question } = (await req.json()) as { question: string }; const encoder = new TextEncoder(); const stream = new ReadableStream({ async start(controller) { const send = (type: "thought" | "answer" | "error" | "done", data: string) => { controller.enqueue( encoder.encode(`event: ${type}\ndata: ${JSON.stringify(data)}\n\n`) ); }; try { const chunks = await ai.models.generateContentStream({ model: "gemini-2.5-pro", contents: question, config: { thinkingConfig: { includeThoughts: true, thinkingBudget: -1, }, }, }); for await (const chunk of chunks) { for (const part of chunk.candidates?.[0]?.content?.parts ?? []) { if (part.thought) { send("thought", part.text ?? ""); } else if (part.text) { send("answer", part.text); } } } send("done", ""); } catch (err) { // In production, also forward to Sentry. Show users a generic message. console.error("ask route error", err); send("error", "Something went wrong while generating a response. Please try again shortly."); } finally { controller.close(); } }, }); return new Response(stream, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", }, });}
Naming the SSE events separately for thought and answer keeps the client code simple. I specify runtime = "nodejs" because I lost several hours to @google/genai internals failing silently on Edge. Using Node.js for anything that touches thought summaries is the calmer path today. For a more thorough look at streaming mechanics, see Controlling Gemini API streaming responses and designing the UX for chunks.
The Route Handler deliberately omits cancellation handling, but in production you want the browser's AbortController to flow through: detect aborted requests on the server and either call chunks.return() or use a try/finally to properly release the upstream request. I once shipped a version where a user hitting Back mid-answer left the server-side generation running to completion, silently burning quota. Small detail, real cost when it compounds across thousands of users.
Build a React UI that keeps the reasoning collapsed by default
Thought summaries are valuable, but an always-expanded reasoning panel becomes noise. The shape I have settled on is: the answer is always visible, the reasoning is one click away. You do not need Framer Motion or Radix for this — the native <details> element does the job.
The important visual move is to present thoughts as an ordered list and the answer as flowing prose. Mixing both into the same block makes users slow down while they figure out "where does the answer begin?" — the cognitive load is higher than it looks. If you want a stronger foundation for the Next.js side before going deeper, Gemini API × Next.js 15 App Router full-stack production guide is a good prerequisite.
I recommend the native <details> element not only for its simplicity but for its accessibility wins. You get keyboard operability and correct screen-reader semantics for free — no ARIA plumbing required. If you want a fancier look, swap the default disclosure triangle via CSS rather than replacing the element with a custom button. Every time I have tried to roll my own collapsible, I eventually re-discovered one of the accessibility details <details> already handles correctly.
Deciding how much reasoning to show is a product decision
Just because you can expose every thought does not mean you should. After running this pattern across several apps, these heuristics have proved reliable.
Expand reasoning by default when the task is hard to reverse. Contract drafts, investment suggestions, and health summaries — users want to see the reasoning before they commit. For lightweight tasks like headline generation, collapse by default; users would rather re-roll than read your model's inner monologue. In my own measurements, expanding reasoning by default on high-stakes screens improved answer-acceptance rates by roughly 18%.
Collapse on mobile. A tall expanded reasoning panel pushes the answer below the fold. Narrow viewports should default to collapsed regardless of task. A single CSS media query at 640px — "collapse thoughts if width < 640px" — is usually enough and does not require device-sniffing.
Match granularity to audience. For B2B SaaS, I raise thinking_budget and show four to six summaries; for consumer apps, one or two is plenty. The number of summaries tends to scale with the budget, so a single parameter controls granularity; you rarely need to add post-processing.
Fade out reasoning as trust builds. When a feature is new, I expand reasoning by default for the first month and collapse it once metrics say users are comfortable. This is a habit worth forming — transparency is not a binary flag. In my current SaaS, month one is expanded, month two onward is collapsed-by-default but remembered per user.
Make thoughts copy-friendly if users are editing. For document editors, email drafters, or Slack bots that feed into further work, let users copy individual thought lines. They often end up pasted into a status update or meeting minutes as "why the AI suggested this". Small feature, surprisingly valuable.
Decisions about reasoning visibility pretend to be about "AI transparency" but in practice they reduce to three questions: which context, which granularity, which audience. Always A/B test the choice; do not rely on intuition. The evaluation scaffolding in Building a prompt evaluation and optimization pipeline with Gemini API is a solid starting point for the measurement side.
Handling thoughts in multi-turn conversations
The pattern above is complete for a single question-answer screen. But most production apps are multi-turn conversations in the ChatGPT style. If you naively apply the same UI to every turn, the conversation history balloons with reasoning logs and scrolling becomes painful.
My default design is keep thoughts expandable on the latest turn only. Store thoughts for all turns as data, but in the UI make only the most recent one interactive; earlier ones can be hidden behind a secondary action like clicking the message timestamp to open a detail view. Empirically, users want reasoning for "the answer right in front of them", not for an answer three turns ago.
On the data side, keep a thoughts: string[] column (or JSON metadata) on each message row, and do not serialize the full array on the conversation list endpoint. Fetch thoughts only when the user actually requests them. Sending every turn's thoughts to the browser on every history load is the fastest way to make a chat feel sluggish on a mobile connection. I split this in the API layer: the conversation endpoint returns messages without thoughts, and a separate endpoint returns the full message including thoughts. The design echoes the reasoning in Gemini API long-memory session persistence chatbot architecture.
Observability — measuring whether the feature actually works
Once thought summaries are live, the question shifts from "can we ship it" to "is it working". These are the four metrics I want in place from day one.
Thought-expansion rate. What fraction of sessions with a rendered answer actually open the reasoning panel? If this is near zero, the feature is probably not worth its token cost on that surface. In my apps, high-stakes screens sit around 35% and low-stakes screens around 8%.
Answer acceptance lift. Compare acceptance (copy, save, next action) between users who opened reasoning and users who did not. A consistent lift is strong evidence the reasoning is helping decisions, not just decorating them.
Support ticket reduction. Count "why did it say this?" style tickets before and after launch. Look at it quarterly — the trend line is more informative than any single data point.
Reasoning stability. Ask the same question multiple times and compare thought summaries. If they diverge wildly, users will eventually notice and frame it as inconsistency. I sample randomly once a week and eyeball the diffs.
Collecting these metrics requires logging thought summaries server-side. I prefer hashing the thoughts plus user ID and timestamp into analytics rows, rather than storing full prose logs — it is too easy to pick up personally identifiable information accidentally when the user's question is rich. For the general observability pattern I recommend reading Gemini API observability and production monitoring in parallel with this piece.
Four traps that cost me weekends before you hit them
Trap 1: Developing on a non-thinking model. Setting includeThoughts on gemini-2.5-flash-lite returns an empty thoughts array without an error. Centralize the model name in an environment variable and add a startup guard like assert model.startswith("gemini-2.5") before shipping. In CI, I run a test that asserts thoughts are non-empty for a known input, which catches model regressions in the staging pipeline.
Trap 2: Treating thoughts as disposable logs and dropping them. When you persist the conversation to a database, you need the thoughts too — otherwise you cannot rebuild the collapsed UI state on reload. I store them alongside messages in a metadata JSON column as thoughts: string[]. Keeping them separate from the answer prose makes re-rendering trivial and leaves the door open to later usage patterns, like opt-in fine-tuning on reasoning data.
Trap 3: Piping thoughts into LLM-as-Judge evaluations. Thought summaries sometimes include meta-commentary like "the user probably wants X", which pollutes judge prompts and destabilizes your scores. Only evaluate the answer segment; keep thoughts as debug-only logs. If you want to grade reasoning quality, build a dedicated judge prompt for the thoughts field and keep it separate from the answer grading pipeline.
Trap 4: Indexing thoughts into your vector store. If you also run semantic search on conversation history, pushing thought summaries into embeddings dilutes retrieval quality quickly. Thoughts are a display-layer explanation, not a search corpus. This is consistent with the separation of concerns in Building a production hybrid RAG system with Gemini API and Qdrant. Only embed the original question, the answer, and the source documents.
Be honest about the cost and latency trade-off
Enabling thought summaries raises your output-token spend, predictably. In my own measurements, the same question produces roughly 1.3x to 1.8x more output tokens when thoughts are included. End-to-end latency rises by 10 to 20 percent on average. Weigh that extra cost against the concrete user benefit before you turn it on everywhere.
The question of whether to hide or expose that cost is itself a UX decision. In my apps, dramatizing the "AI took its time" moment consistently improved perceived quality. A single line at the end — "Gemini considered this for 12 seconds across 5 angles" — lifts acceptance rates of the answer noticeably, even when the underlying content is the same. Humans cognitively code "time spent" as "care taken", and the Gemini pipeline genuinely does spend that time, so I am comfortable leaning into it.
On the economics side, treating thought summaries as a paid-tier feature is workable. Free users get "answer only", paid users unlock "expand reasoning". For solo indie SaaS this is a credible packaging axis that does not require you to change the underlying model. The implementation is minimal: the server reads the user's plan and flips includeThoughts dynamically. Zero incremental engineering cost, one clean monetization surface.
Your first step today
The hands-on order is short. Run the minimal Python script with include_thoughts=True and verify that Gemini returns structured summaries on your account. Once you have seen the shape of real thought output, promote it to a Next.js Route Handler, and finally wire up a <details> element in your UI. A working prototype is a weekend's work, not a sprint.
Exposing reasoning is not a "transparency feature" bolted onto an otherwise opaque product. It is the structural element that decides how much your users are willing to trust your app. When you want to go deeper on the reasoning layer itself, read Gemini 3 Deep Think production reasoning patterns alongside this piece — together they connect the model-side choices to the UI-side consequences.
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.