Gemini Deep Research API: 검색 쿼리 자동 생성부터 구조화 보고서까지, 비동기 백엔드 파이프라인에서 다루는 법
처음 Deep Research API 문서를 봤을 때 솔직히 "그냥 Gemini에 검색 기능 붙인 거 아닌가?" 싶었습니다. 그런데 직접 연결해보고 생각이 바뀌었습니다. 이건 단발 추론 모델이 아니라 멀티스텝 검색 파이프라인을 내장한 에이전트입니다. 프롬프트 하나를 던지면 하위 질문을 분해하고, 각 질문마다 검색 쿼리를 생성·실행하고, 결과에 빈 곳이 보이면 후속 쿼리를 추가하고, 마지막에 출처가 붙은 보고서를 합성해서 내놓습니다. Google이 미리 짜둔 루프 위에 올라탄 구조라 완전한 자율성이라기보다는 "관리된 다단계 리서치 오케스트레이션"에 가깝습니다.
서버 개발자 입장에서 이게 의미하는 건 하나입니다. 실행 시간이 수 분 단위라는 것. 기존 generateContent 방식처럼 동기 HTTP 엔드포인트에 바로 붙이면 타임아웃이 납니다. 비동기 아키텍처 없이는 프로덕션에서 쓸 수 없습니다.
이 글에서는 Interactions API의 작동 방식, 비동기 백엔드에서 interaction_id를 관리하는 패턴, 중간 사고 과정을 SSE로 스트리밍하는 개념적 코드, 그리고 최종 보고서를 구조화된 JSON으로 뽑아내는 이중 파이프라인까지 다룹니다. 2026년 기준 Interactions API가 GA로 전환되어 프로덕션 환경에서 안정적으로 사용 가능한 상태입니다. 아래 코드 예시는 공식 문서의 개념적 흐름을 재구성한 것이며, 실제 SDK 시그니처·클래스명·이벤트 필드는 버전마다 다를 수 있으니 공식 Deep Research 문서에서 최종 확인해 주세요.
Deep Research 내부에서 실제로 무슨 일이 벌어지는가
generateContent는 단발성 추론입니다. 요청 → 응답, 끝. Deep Research는 다릅니다. 에이전트가 하나의 프롬프트를 받아 내부적으로 멀티스텝 루프를 돌립니다.
이 루프를 구동하는 인터페이스가 Interactions API입니다. 기존 generateContent와 달리 장시간 실행 태스크를 비동기로 처리하도록 설계되어 있습니다. SDK 최소 버전은 시점마다 다르니 프로젝트에 도입할 때 공식 릴리스 노트에서 확인하는 편이 안전합니다. 참고로 구형 google-generativeai 패키지는 이미 deprecated로 처리되었으므로 google-genai / @google/genai 계열로 옮겨야 합니다.
모델 ID 선택
2026년 기준 Deep Research 계열은 크게 표준 프리뷰와 최고 정밀도 프리뷰(Deep Research Max)로 나뉩니다. 실제 API에 넣는 모델 ID 문자열은 릴리스 시점마다 갱신되므로 공식 문서에서 최신 문자열을 그대로 복사해 사용하세요.
| 등급 | 특징 |
|---|---|
| 표준 프리뷰 | 빠른 인터랙티브 리서치. 응답성이 중요할 때 |
| Max 프리뷰 | 탐색 깊이와 반복 횟수가 대폭 늘어난 최고 정밀도 버전. 금융·법률 실사처럼 철저함이 중요한 경우 |
Background Execution의 의미
background=True를 설정하면 API가 즉시 interaction_id를 반환합니다. 에이전트는 서버 측에서 계속 돌고, 클라이언트는 이 ID로 폴링하거나 스트림에 재접속합니다. 이 하나의 파라미터가 전체 아키텍처를 결정합니다.
비동기 아키텍처가 강제되는 이유
복잡한 쿼리는 수 분 이상 걸릴 수 있습니다. 동기 HTTP 요청으로 연결하면 타임아웃이 불가피합니다. 실제 프로덕션에서 쓸 만한 패턴은 대략 이렇게 생겼습니다.
클라이언트는 interaction_id를 받자마자 응답이 오고, 이후 SSE 커넥션으로 에이전트 진행 상황을 실시간으로 받습니다. 네트워크가 끊겨도 Last-Event-ID로 스트림을 재개할 수 있어서 장시간 실행 태스크에 적합합니다.
코드로 보는 세 가지 시나리오
아래 세 스니펫은 모두 개념적 예시입니다. 실제 SDK의 클래스명(InteractionConfig, AgentConfig, UploadFileConfig 등), 이벤트 필드명, 파라미터 명세는 SDK 버전에 따라 달라집니다. 그대로 복사해서 실행하기보다는 흐름을 참고하고 최종 시그니처는 SDK 소스와 공식 문서로 확인해 주세요.
1. 리서치 태스크 시작 — background 모드
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="<공식 문서에서 최신 Deep Research 모델 ID 확인>",
prompt=f"다음 주제에 대한 심층 분석 보고서를 작성해 주세요: {topic}",
config=types.InteractionConfig(
background=True,
agent_config=types.AgentConfig(
thinking_summaries="auto",
grounding=types.GroundingConfig(
google_search=types.GoogleSearchConfig()
)
)
)
)
return interaction.interaction_idinteraction_id는 반드시 영속 스토리지(Redis, DB 등)에 즉시 저장해 두세요. 서버가 재시작되면 in-memory에만 있던 ID가 날아갑니다.
2. 중간 사고 과정 스트리밍 — FastAPI SSE 엔드포인트
사고 요약 파라미터 값을 "auto"로 설정하면 에이전트가 어떤 질문을 탐색 중인지, 어떤 출처를 평가 중인지 실시간으로 받을 수 있습니다. 프론트엔드에서 "지금 이런 거 조사 중입니다"를 보여주면 사용자 이탈률이 확 줄어듭니다 — 저도 처음엔 그냥 로딩 스피너만 보여줬다가 사용자 피드백 받고 바꿨습니다.
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:
# 이벤트 필드명은 SDK 버전에 따라 상이. 실제 객체를
# 한 번 dir()로 확인한 뒤 매핑을 맞춰야 합니다.
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는 SDK가 뱉는 이벤트 객체를 프론트가 이해할 dict로 정규화하는 얇은 어댑터입니다. SDK가 어떤 필드명을 쓰든(예: event.type/event.event_type, event.content/event.text 등) 이 한 곳에서 흡수하면 상위 코드가 흔들리지 않습니다.
3. 이중 파이프라인 — 구조화 보고서 추출
Deep Research의 기본 출력은 자유 형식 텍스트입니다. 파이프라인 다음 단계에서 가공하려면 JSON이 필요한 경우가 많습니다. Google 공식 문서에는 Deep Research 결과를 후속 Gemini 호출의 컨텍스트로 이어 붙여 구조화 출력으로 뽑는 흐름이 소개되어 있습니다. 아래 예시에서 사용한 previous_interaction_id 필드명은 SDK에 따라 파라미터명이나 전달 방식이 다를 수 있으니, 실제 사용 전에 Interactions API 문서에서 정확한 필드명을 확인해 주세요.
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="위 리서치 결과를 바탕으로 구조화된 요약 보고서를 작성해 주세요.",
config=types.GenerateContentConfig(
previous_interaction_id=research_interaction_id,
response_mime_type="application/json",
response_schema=ResearchReport,
),
)
# response.text는 마크다운 코드펜스가 섞여 오는 경우가 있어
# SDK가 제공하는 파싱 결과(response.parsed 등)를 우선 사용.
parsed = getattr(response, "parsed", None)
if isinstance(parsed, ResearchReport):
return parsed
return ResearchReport.model_validate_json(response.text)response.parsed가 없거나 스키마를 지정하지 않은 응답을 처리해야 한다면, candidates[0].content.parts를 순회해 JSON 파트만 골라내는 경로를 별도로 두는 편이 안전합니다.
내부 문서만 대상으로 하는 프라이빗 리서치
웹 검색 없이 사내 문서 코퍼스만 대상으로 에이전트를 실행하는 흐름도 소개되어 있습니다. Files API로 PDF나 CSV를 업로드하고 Google Search 그라운딩을 비활성화하는 방식인데, InteractionConfig가 files 파라미터를 직접 받는지, 아니면 별도 컨텍스트/도구 필드로 붙여야 하는지는 SDK 버전에 따라 다릅니다. 아래는 개념 흐름을 보여주기 위한 예시입니다.
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="<공식 문서에서 최신 Deep Research 모델 ID 확인>",
prompt=query,
config=types.InteractionConfig(
background=True,
files=uploaded_files, # 실제 필드명은 SDK 문서에서 확인
agent_config=types.AgentConfig(
thinking_summaries="auto"
),
),
)
return interaction.interaction_id도입 전 따져봐야 할 장점과 고려사항
트레이드오프는 장단을 나란히 봐야 판단이 서기 때문에 두 축으로 나눠 정리했습니다.
| 장점 | 고려사항 |
|---|---|
| 수십 번의 검색·크롤링 루프를 단일 API 호출로 추상화 | 실행 시간이 수 분 단위라 동기 엔드포인트 불가 |
| 1M 토큰 컨텍스트 창으로 방대한 소스를 한 번에 처리 | 기본 출력이 자유 형식 텍스트라 후처리 파이프라인 필요 |
| 웹 검색·MCP·Files API·URL 컨텍스트를 혼합 소스로 사용 | Preview 성격의 필드·엔드포인트가 남아 있어 스키마 변경 가능성 |
Last-Event-ID로 네트워크 중단 후 스트림 재개 |
CMEK와 VPC Service Controls 등 엔터프라이즈 통제는 아직 부분 지원 |
| Interactions API GA 전환으로 안정성 확보 | 무료 API 키에서는 사용 불가, 유료 티어 필수 |
여기에 별도로 다뤄야 할 항목이 있습니다.
컨텍스트 캐싱 절감: Gemini는 Context Caching이라는 별도 기능으로 캐시 히트분에 대해 입력 토큰 요금을 크게 낮춰 줍니다. Deep Research가 내부적으로 캐시를 얼마나 활용하는지는 워크로드에 따라 크게 갈리니, 절감폭을 예산에 반영하려면 Context Caching 공식 문서와 요금 페이지의 실제 할인율을 확인한 뒤 자기 트래픽으로 재현해 보는 편이 안전합니다.
데이터 보존 정책: 프롬프트와 생성 결과가 일정 기간 Google 서버에 저장됩니다. 정확한 기간은 Gemini API 데이터 사용 정책 및 Deep Research 문서에서 최신 값을 확인하세요(그라운딩 활성화 여부에 따라 다르며, 정책은 시점마다 갱신됩니다). 민감 고객 데이터나 기밀 정보를 프롬프트에 직접 담기 전 반드시 법무팀과 컴플라이언스 관점을 정렬해 두어야 합니다. 내부 문서를 Files API로 넣는 경우도 마찬가지입니다.
실무에서 흔한 실수 — 저도 겪었던 것들입니다.
- 동기 엔드포인트에 직접 연결: 수 분 걸리는 리서치를
/api/research에 그대로 물리면 게이트웨이 타임아웃이 납니다. 태스크 큐(Pub/Sub, Celery 등)나 백그라운드 태스크로 분리해야 합니다. - 스트리밍 버퍼링 미설정: Nginx 앞단에서 SSE를 프록시할 때
X-Accel-Buffering: no가 없으면 이벤트가 뭉쳐서 옵니다. - interaction_id 유실: 배포 재시작이나 서버 장애 시 in-memory에만 보관한 ID는 사라집니다. Redis나 DB에 즉시 저장하는 게 맞습니다.
- MCP 통합 과신: 네트워크 지연이 있는 내부 API를 에이전트 도구로 붙일 때는 타임아웃 여유를 넉넉히 잡아야 합니다. 안 그러면 에이전트 루프 도중 조용히 실패합니다.
이 패턴을 어디까지 밀어붙일 수 있을까
Deep Research → 이전 인터랙션 컨텍스트 참조 → 구조화 Gemini 호출 이중 파이프라인은 경쟁 인텔리전스 자동화, 금융·법률 실사, 내부 지식 Q&A까지 도메인만 바꿔 재활용할 수 있는 형태입니다. 다만 이 패턴이 만능은 아닙니다.
- 초저지연 UX에는 여전히 맞지 않습니다. 챗봇 톤의 즉답이 필요한 흐름이라면 별도의 짧은 응답 경로를 유지해야 합니다.
- 폐쇄망·강한 데이터 주권 요구가 있는 조직에서는 데이터 보존 정책과 엔터프라이즈 통제 지원 범위가 걸림돌이 됩니다. 온프렘 검색 + 로컬 LLM 조합과 비교해 봐야 합니다.
- 에이전트가 놓치는 도메인(비공개 논문, 유료 데이터베이스, 로컬 사내 위키)이 많은 주제일수록 웹 그라운딩의 이득이 줄어듭니다. Files API + MCP로 커스텀 소스를 얼마나 얹을 수 있는지가 실제 품질을 좌우합니다.
다음으로 시도해볼 만한 것들도 정리해 두면 좋습니다. 첫째, 이벤트 어댑터를 얇게 유지하면서 SDK 업데이트 대응 비용을 재보기. 둘째, 이전 인터랙션 재사용과 Context Caching이 실제 워크로드에서 얼마나 겹치는지 계측해 보기. 셋째, 구조화 출력 스키마를 유즈케이스별로 분화해 후속 서비스(알림, 대시보드, 워크플로 자동화)와의 계약으로 삼기. 이렇게 정리해 두면 API 자체가 바뀌더라도 상위 파이프라인은 대체로 그대로 살아남습니다.
참고 자료
- Gemini Deep Research Agent 공식 문서 — Google AI for Developers
- Interactions API 개요 — Google AI for Developers
- Background Execution 공식 문서 — Google AI for Developers
- Grounding with Google Search 공식 문서
- Structured Outputs 공식 문서 — Google AI for Developers
- Context Caching 공식 문서 — Google AI for Developers
- Gemini API 데이터 사용 정책
- Deep Research Max 발표 — Google Blog
- Interactions API GA 발표 — 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 사용 가이드