Gemini Deep Research API: From Automatic Search Query Generation to Structured Reports, How to Handle It in an Asynchronous Backend Pipeline
When I first read the Deep Research API documentation, I honestly thought, "Isn't this just Gemini with search bolted on?" But after actually connecting to it, I changed my mind. This is not a single-shot inference model — it's an agent with a built-in multi-step search pipeline. You throw in one prompt, and it decomposes the query into sub-questions, generates and executes search queries for each one, appends follow-up queries wherever it spots gaps, and finally synthesizes a report with cited sources. Because it rides on a loop that Google has pre-built, it's less "fully autonomous" and more "managed multi-step research orchestration."
For backend developers, this means one thing: execution time is measured in minutes. If you connect it directly to a synchronous HTTP endpoint the way you would with generateContent, you'll hit a timeout. It cannot be used in production without an asynchronous architecture.
This post covers how the Interactions API works, patterns for managing interaction_id in an async backend, conceptual code for streaming intermediate thinking over SSE, and a dual pipeline for extracting the final report as structured JSON. As of 2026, the Interactions API has reached GA and is stable for production use. The code examples below reconstruct the conceptual flow from the official documentation; actual SDK signatures, class names, and event fields may differ across versions, so please verify against the official Deep Research documentation.
What Actually Happens Inside Deep Research
generateContent is single-shot inference: request → response, done. Deep Research is different. The agent receives a single prompt and internally runs a multi-step loop.
The interface that drives this loop is the Interactions API. Unlike the conventional generateContent, it is designed to handle long-running tasks asynchronously. The minimum SDK version changes over time, so when adopting it in a project it's safer to check the official release notes. Note that the older google-generativeai package is already deprecated, so you should migrate to the google-genai / @google/genai family.
Choosing a Model ID
As of 2026, the Deep Research family is broadly split into a standard preview and a highest-precision preview (Deep Research Max). The model ID string you pass to the API is updated with each release, so copy the latest string directly from the official documentation.
| Tier | Characteristics |
|---|---|
| Standard Preview | Fast, interactive research. Best when responsiveness matters. |
| Max Preview | Highest-precision version with significantly more exploration depth and iterations. For cases where thoroughness matters, such as financial or legal due diligence. |
What Background Execution Means
Setting background=True causes the API to return an interaction_id immediately. The agent keeps running on the server side, and the client polls with this ID or reconnects to the stream. This single parameter determines the entire architecture.
Why an Async Architecture Is Mandatory
Complex queries can take several minutes or more. Connecting via a synchronous HTTP request makes timeouts inevitable. A pattern that works in real production looks roughly like this.
The client receives a response as soon as it gets the interaction_id, and then receives the agent's progress in real time via an SSE connection. Even if the network drops, the stream can be resumed with Last-Event-ID, making it well-suited for long-running tasks.
Three Scenarios in Code
All three snippets below are conceptual examples. The actual SDK's class names (InteractionConfig, AgentConfig, UploadFileConfig, etc.), event field names, and parameter specifications vary by SDK version. Rather than copying and running them directly, treat them as a reference for the flow and verify the final signatures against the SDK source and official documentation.
1. Starting a Research Task — Background Mode
import asyncio
from google import genai
from google.genai import types
client = genai.Client(api_key="YOUR_API_KEY")
async def start_deep_research(topic: str) -> str:
interaction = await client.aio.interactions.create(
model="<Check the official docs for the latest Deep Research model ID>",
prompt=f"Please write an in-depth analysis report on the following topic: {topic}",
config=types.InteractionConfig(
background=True,
agent_config=types.AgentConfig(
thinking_summaries="auto",
grounding=types.GroundingConfig(
google_search=types.GoogleSearchConfig()
)
)
)
)
return interaction.interaction_idBe sure to persist the interaction_id to durable storage (Redis, DB, etc.) immediately. If the server restarts, any ID held only in memory will be lost.
2. Streaming Intermediate Thinking — FastAPI SSE Endpoint
Setting the thinking summary parameter to "auto" lets you receive in real time which questions the agent is exploring and which sources it is evaluating. Showing users "here's what we're researching right now" on the frontend dramatically reduces drop-off rates — I started with just a loading spinner and switched after getting user feedback.
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import json
app = FastAPI()
@app.get("/research/{interaction_id}/stream")
async def stream_research(interaction_id: str, request: Request):
last_event_id = request.headers.get("last-event-id")
async def event_generator():
stream = client.aio.interactions.stream(
interaction_id=interaction_id,
last_event_id=last_event_id,
)
async for event in stream:
# Event field names differ by SDK version. Inspect the actual
# object with dir() once to align your mapping correctly.
payload = _to_client_payload(event)
if payload is None:
continue
yield f"data: {json.dumps(payload)}\n\n"
if payload["type"] == "done":
break
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)_to_client_payload is a thin adapter that normalizes the event objects the SDK emits into a dict the frontend understands. Whatever field names the SDK uses (e.g., event.type/event.event_type, event.content/event.text), absorbing them in this one place keeps the code above it stable.
3. Dual Pipeline — Structured Report Extraction
Deep Research's default output is free-form text. Downstream pipeline stages often require JSON. The official Google documentation describes a flow for passing Deep Research results as context into a follow-up Gemini call to extract structured output. The previous_interaction_id field name used in the example below may differ in name or passing convention depending on the SDK version, so confirm the exact field name in the Interactions API documentation before using it.
from pydantic import BaseModel
from typing import List
class ResearchReport(BaseModel):
summary: str
key_findings: List[str]
sources: List[str]
confidence_score: float
recommended_actions: List[str]
async def extract_structured_report(research_interaction_id: str) -> ResearchReport:
response = await client.aio.models.generate_content(
model="gemini-2.5-pro",
contents="Based on the research results above, please write a structured summary report.",
config=types.GenerateContentConfig(
previous_interaction_id=research_interaction_id,
response_mime_type="application/json",
response_schema=ResearchReport,
),
)
# response.text can sometimes include markdown code fences,
# so prefer the parsed result the SDK provides (response.parsed, etc.).
parsed = getattr(response, "parsed", None)
if isinstance(parsed, ResearchReport):
return parsed
return ResearchReport.model_validate_json(response.text)If response.parsed is unavailable or you need to handle responses without a schema, it's safer to keep a separate path that iterates over candidates[0].content.parts and picks out only the JSON parts.
Private Research Targeting Internal Documents Only
The documentation also describes running the agent against an internal document corpus without web search. The approach uploads PDFs or CSVs via the Files API and disables Google Search grounding, but whether InteractionConfig accepts a files parameter directly or requires a separate context/tool field depends on the SDK version. The example below illustrates the conceptual flow.
async def internal_research(file_paths: list[str], query: str) -> str:
uploaded_files = []
for path in file_paths:
uploaded = await client.aio.files.upload(
file=path,
mime_type="application/pdf",
)
uploaded_files.append(uploaded)
interaction = await client.aio.interactions.create(
model="<Check the official docs for the latest Deep Research model ID>",
prompt=query,
config=types.InteractionConfig(
background=True,
files=uploaded_files, # Confirm actual field name in SDK docs
agent_config=types.AgentConfig(
thinking_summaries="auto"
),
),
)
return interaction.interaction_idAdvantages and Considerations to Weigh Before Adopting
Trade-offs are best judged by seeing both sides together, so I've organized them along two axes.
| Advantages | Considerations |
|---|---|
| Abstracts dozens of search and crawling loops into a single API call | Execution time is measured in minutes — synchronous endpoints are not viable |
| Handles a vast number of sources at once with a 1M-token context window | Default output is free-form text, requiring a post-processing pipeline |
| Supports mixed sources: web search, MCP, Files API, and URL context | Preview-stage fields and endpoints remain, so schema changes are possible |
Stream resumption after network interruption via Last-Event-ID |
Enterprise controls such as CMEK and VPC Service Controls are still partially supported |
| Stability secured with Interactions API GA transition | Not available on free API keys — paid tier required |
There are additional items worth addressing separately.
Context caching cost reduction: Gemini's separate Context Caching feature significantly lowers input token costs for cache hits. How much caching Deep Research leverages internally varies greatly by workload, so if you want to factor savings into your budget, check the Context Caching official documentation and the actual discount rates on the pricing page, then reproduce them with your own traffic.
Data retention policy: Prompts and generated outputs are stored on Google's servers for a period of time. For exact durations, check the Gemini API data usage policy and the Deep Research documentation for the latest values (they vary depending on whether grounding is enabled, and the policy is updated periodically). You must align with your legal and compliance teams before putting sensitive customer data or confidential information directly into a prompt. The same applies when feeding internal documents through the Files API.
Common mistakes in practice — things I've run into myself:
- Connecting directly to a synchronous endpoint: Wiring a minutes-long research task directly to
/api/researchwill trigger gateway timeouts. It must be offloaded to a task queue (Pub/Sub, Celery, etc.) or a background task. - Not disabling stream buffering: When proxying SSE through Nginx, events will arrive in batches without
X-Accel-Buffering: no. - Losing the interaction_id: An ID held only in memory is gone after a deployment restart or server failure. Store it immediately in Redis or a DB.
- Over-relying on MCP integration: When connecting internal APIs with network latency as agent tools, set generous timeouts. Otherwise the agent loop will fail silently mid-run.
How Far Can This Pattern Be Pushed
The Deep Research → reference previous interaction context → structured Gemini call dual pipeline is a form that can be reused across domains — just swap the domain — for competitive intelligence automation, financial and legal due diligence, and internal knowledge Q&A. That said, this pattern is not a silver bullet.
- It still doesn't fit ultra-low-latency UX. Flows that need instant chat-style responses require a separate short-response path.
- For organizations with air-gapped networks or strong data sovereignty requirements, the data retention policy and scope of enterprise control support are blockers. It should be compared against an on-premises search plus local LLM combination.
- The more a topic relies on domains the agent misses (private papers, paid databases, local internal wikis), the less benefit web grounding provides. How many custom sources you can attach via Files API and MCP is what ultimately determines real-world quality.
A few things worth trying next: first, revisiting the cost of keeping the event adapter thin while absorbing SDK updates; second, measuring how much previous interaction reuse and Context Caching actually overlap in real workloads; third, splitting the structured output schema by use case and treating it as a contract with downstream services (notifications, dashboards, workflow automation). Structuring things this way means the upper pipeline largely survives even when the API itself changes.
References
- Gemini Deep Research Agent Official Documentation — Google AI for Developers
- Interactions API Overview — Google AI for Developers
- Background Execution Official Documentation — Google AI for Developers
- Grounding with Google Search Official Documentation
- Structured Outputs Official Documentation — Google AI for Developers
- Context Caching Official Documentation — Google AI for Developers
- Gemini API Data Usage Policy
- Deep Research Max Announcement — Google Blog
- Interactions API GA Announcement — Google Blog
- How to use Deep Research with the Gemini API — philschmid.de
- Getting Started with Gemini Deep Research API — philschmid.de
- Gemini Enterprise Agent Platform — Deep Research Usage Guide