Gemini

Gemini Interactions API 완전분석 — OpenAI Responses API의 대항마, 서버사이드 히스토리 관리의 실체

cell-devlog 2026. 5. 28. 15:41
반응형

Gemini API에 조용히 새로운 표준이 생겼습니다. generateContent는 이제 구식입니다. Google은 모든 새 프로젝트에 Interactions API를 권장하고, 6월 8일엔 기존 스키마를 완전 제거합니다.


 

핵심 요약 → Interactions API: Gemini 3.5 Flash와 함께 베타 출시, 모든 신규 프로젝트 기본 권장 → 핵심 기능: previous_interaction_id — 대화 히스토리를 클라이언트가 아닌 Google 서버가 관리 → OpenAI Responses API의 서버사이드 히스토리 패턴과 동일한 개념 → 브레이킹 체인지: outputs 배열 → steps 배열 (5월 26일 기본값, 6월 8일 레거시 완전 제거) → 상태 저장: 유료 플랜 55일, 무료 플랜 1일 보관 → background=true 지원 — 비동기 장시간 에이전트 실행 → 현재 미지원: Computer Use, 비디오 메타데이터, 명시적 캐싱, Remote MCP → generateContent는 계속 지원되지만 신규 기능은 Interactions API에만 추가


generateContent vs Interactions API — 무엇이 다른가

# 두 API의 근본적 차이

generateContent (기존):
  클라이언트 → 매 요청마다 전체 대화 히스토리 전송
  → 개발자가 히스토리 배열 직접 관리
  → 대화가 길어질수록 입력 토큰 폭발
  → 서버는 무상태(stateless) — 이전 요청을 모름

Interactions API (신규):
  클라이언트 → 첫 요청 → 서버가 interaction_id 반환
  → 이후 요청에 previous_interaction_id만 전달
  → 서버가 히스토리 저장·관리·추론 컨텍스트 유지
  → 개발자 코드에서 히스토리 배열 사라짐

OpenAI Responses API와의 비교:
  OpenAI: previous_response_id → 이전 응답 참조
  Google: previous_interaction_id → 이전 인터랙션 참조
  → 개념 동일, API 형태만 다름

1. 핵심 개념 — Interaction이란 무엇인가

# Interaction: 하나의 완전한 대화 턴 (세션 레코드)
# 각 Interaction은 다음을 포함:
# - 사용자 입력
# - 모델의 추론 과정 (Thought)
# - 도구 호출 및 결과
# - 최종 응답

# 모든 이것이 "steps" 배열로 구조화됨

# 예시: 검색 도구를 사용하는 Interaction의 steps

steps = [
    {"type": "user_input",         "content": "최신 Gemini 가격은?"},
    {"type": "thought",            "content": "검색이 필요한 질문..."},  # 추론 과정
    {"type": "google_search_call", "query": "Gemini API pricing 2026"},
    {"type": "google_search_result","content": "검색 결과..."},
    {"type": "model_output",       "content": "Gemini 3.5 Flash는 $1.50/1M..."},
]

# ← 이게 6월 8일 이후 유일한 응답 형태
# 이전: response.outputs[0].text (flat 배열)
# 이후: interaction.steps[-1].content[0].text (typed 배열)

2. 실전 코드 — generateContent에서 Interactions API로

import google.generativeai as genai
# SDK 버전 확인 필수: google-genai >= 2.0.0
# pip install -U google-genai

client = genai.Client(api_key="YOUR_API_KEY")

# ══════════════════════════════════════
# [기존] generateContent — 히스토리 직접 관리
# ══════════════════════════════════════

history = []  # 개발자가 직접 관리

def chat_old(user_message: str) -> str:
    history.append({"role": "user", "content": user_message})

    response = client.models.generate_content(
        model="gemini-3.5-flash",
        contents=history,  # 매번 전체 히스토리 전송
        # → 대화 10턴 = 10턴치 토큰 매번 재전송
    )

    history.append({
        "role": "model",
        "content": response.text
    })
    return response.text

# 문제:
# - 히스토리 관리 코드 직접 작성
# - Thought Preservation 수동 처리 필요
# - 대화 길어질수록 입력 토큰 선형 증가


# ══════════════════════════════════════
# [신규] Interactions API — 서버사이드 히스토리
# ══════════════════════════════════════

previous_id = None  # 이게 전부

def chat_new(user_message: str) -> str:
    global previous_id

    interaction = client.interactions.create(
        model="gemini-3.5-flash",
        contents=user_message,          # 현재 메시지만 전송
        previous_interaction_id=previous_id,  # 이전 컨텍스트 참조
        config={
            "thinking_level": "medium",
            "tools": [{"google_search": {}}]
        }
        # store=True (기본값) → 서버가 55일 보관
    )

    previous_id = interaction.id  # 다음 턴을 위해 저장

    # 응답 읽기 (새 steps 스키마)
    for step in interaction.steps:
        if step.type == "model_output":
            return step.content[0].text

# 장점:
# - 히스토리 관리 코드 = 0줄
# - Thought Preservation 자동 처리
# - 서버가 추론 컨텍스트 유지 → 더 일관된 멀티턴 응답

3. 브레이킹 체인지 — 6월 8일 데드라인

Gemini Interactions API를 사용 중이라면 6월 8일에 코드가 깨집니다. Google은 응답 객체의 outputs 배열을 폐기하고 타입이 있는 steps 배열로 교체하며, 레거시 스키마는 그 날짜에 영구 제거됩니다. 연장 공지는 없습니다.

# ══════════════════════════════════════════════
# 브레이킹 체인지 1: outputs → steps 스키마 변경
# ══════════════════════════════════════════════

# ❌ 레거시 (6월 8일 완전 삭제)
text = interaction.outputs[0].text
# → June 8 이후 AttributeError

# ✅ 신규 스키마
for step in interaction.steps:
    if step.type == "model_output":
        text = step.content[0].text
        break

# steps type 목록:
# "user_input"          - 사용자 입력
# "thought"             - 모델 추론 과정 (Thought Preservation)
# "model_output"        - 최종 응답
# "function_call"       - 커스텀 함수 호출
# "function_result"     - 함수 실행 결과
# "google_search_call"  - 검색 도구 호출
# "google_search_result"- 검색 결과


# ══════════════════════════════════════════════
# 브레이킹 체인지 2: response_format 변경
# ══════════════════════════════════════════════

# ❌ 레거시
interaction = client.interactions.create(
    model="gemini-3.5-flash",
    contents="JSON으로 분석해줘",
    response_mime_type="application/json",  # 삭제됨
)

# ✅ 신규 (polymorphic response_format)
interaction = client.interactions.create(
    model="gemini-3.5-flash",
    contents="JSON으로 분석해줘",
    config={
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "analysis_result",
                "schema": {
                    "type": "object",
                    "properties": {
                        "summary": {"type": "string"},
                        "score": {"type": "number"}
                    }
                }
            }
        }
    }
)


# ══════════════════════════════════════════════
# 마이그레이션 헤더로 전환 시점 제어
# ══════════════════════════════════════════════

import httpx

# 5월 26일 이전: 옵트인으로 새 스키마 테스트
headers = {"Api-Revision": "2026-05-20"}   # 신규 스키마
# headers = {"Api-Revision": "2026-05-07"}  # 레거시 (6월 8일까지만)

# 5월 26일 이후: 기본값이 신규 스키마로 전환
# 6월 8일: 레거시 완전 제거, 헤더 옵트아웃 불가

4. 함수 호출 — steps 기반 새 패턴

# 커스텀 함수 호출 (Function Calling) — 새 스키마

def get_weather(city: str) -> dict:
    # 실제 날씨 API 호출
    return {"city": city, "temperature": 22, "condition": "맑음"}

interaction = client.interactions.create(
    model="gemini-3.5-flash",
    contents="서울 날씨 알려줘",
    config={
        "tools": [{
            "function_declarations": [{
                "name": "get_weather",
                "description": "도시의 현재 날씨를 가져옵니다",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "city": {"type": "string", "description": "도시명"}
                    },
                    "required": ["city"]
                }
            }]
        }]
    }
)

# 함수 호출이 필요한 경우 steps에서 탐지
for step in interaction.steps:
    if step.type == "function_call":
        func_name = step.name          # "get_weather"
        func_args = step.args          # {"city": "서울"}

        # 함수 실행
        result = get_weather(**func_args)

        # 결과를 다음 Interaction으로 전달
        interaction = client.interactions.create(
            model="gemini-3.5-flash",
            contents={
                "function_response": {
                    "name": func_name,
                    "response": result
                }
            },
            previous_interaction_id=interaction.id
        )
        break

5. background=true — 비동기 장시간 에이전트 패턴

# 장시간 실행 에이전트 — background 모드

# 시나리오: 대용량 코드베이스 분석 (수 분 소요)

interaction = client.interactions.create(
    model="gemini-3.5-flash",
    contents="이 저장소의 보안 취약점 전체 분석해줘",
    config={
        "thinking_level": "high",
        "background": True,    # 비동기 실행
        # background=True는 store=True 필수 (기본값)
    },
    files=["gs://my-bucket/entire-codebase.zip"]
)

# 즉시 반환 — 아직 완료 안 됨
print(interaction.status)  # "running"
print(interaction.id)      # "int_xxxxxxxxx" — 나중에 결과 조회용

# 폴링 대신 Webhook 사용 (2026.05 신규 출시)
# → 완료 시 설정한 엔드포인트로 POST 콜백
# → 수 분 걸리는 에이전트도 서버리스 환경에서 처리 가능

# 결과 조회
completed = client.interactions.get(interaction.id)
if completed.status == "completed":
    for step in completed.steps:
        if step.type == "model_output":
            print(step.content[0].text)

6. 서버사이드 히스토리의 실제 비용 이점

# 10턴 대화 기준 토큰 비용 비교

# 가정: 턴당 평균 2,000 입력 토큰 + 500 출력 토큰

# [generateContent — 클라이언트 히스토리]
# 1턴: 2,000 입력
# 2턴: 4,000 입력 (1턴 히스토리 포함)
# 3턴: 6,000 입력 ...
# 10턴: 20,000 입력
# 총 입력 토큰: 2000+4000+...+20000 = 110,000 토큰

# [Interactions API — 서버사이드 히스토리]
# 1턴: 2,000 입력
# 2턴: 2,000 입력 (previous_interaction_id만 참조)
# ...
# 10턴: 2,000 입력
# 총 입력 토큰: 20,000 토큰 (단, Thought Preservation 토큰 별도)

# 순수 입력 토큰 절감: 최대 82%
# 실제 절감: Thought Preservation 토큰 누적 감안 시 40~60% 절감

# 비용 계산 ($1.50/1M input)
traditional_cost = 110_000 / 1_000_000 * 1.50   # $0.165
interactions_cost = 20_000 / 1_000_000 * 1.50    # $0.030
# → 10턴 기준 82% 절감 (Thought 토큰 제외 순수 계산)

7. 현재 Interactions API 미지원 기능 — 전환 전 확인 필수

# generateContent에서 지원, Interactions API 미지원 (2026.05 기준)

❌ Computer Use
   → gemini-3-flash-preview 유지 필요

❌ 비디오 메타데이터 (video_metadata)
   → 클리핑 구간, 커스텀 프레임레이트 설정 불가

❌ 명시적 캐싱 (Explicit Caching)
   → previous_interaction_id를 통한 암시적 캐싱은 지원

❌ Remote MCP
   → "Coming Soon" 공식 예고

✅ 지원 기능
   → Google Search 그라운딩
   → URL Context
   → 코드 실행 (Code Execution)
   → 커스텀 함수 호출 (Function Calling)
   → 구조화 출력 (Structured Outputs)
   → 파일 검색 (File Search — 멀티모달 포함)
   → 스트리밍
   → 백그라운드 실행 + Webhook

마이그레이션 체크리스트 — 6월 8일 전 완료

# [BLOCKS] 미처리 시 코드 깨짐 항목

checklist = {
    "1. SDK 업데이트": {
        "action": "pip install -U google-genai  # >= 2.0.0",
        "why": "구 SDK는 Interactions API 미지원"
    },

    "2. 메서드 교체": {
        "before": "client.models.generate_content()",
        "after":  "client.interactions.create()",
    },

    "3. 응답 읽기 수정": {
        "before": "response.text",
        "after":  "interaction.steps[-1].content[0].text",
    },

    "4. 히스토리 관리 교체": {
        "before": "client.chats.create() / 수동 history 배열",
        "after":  "previous_interaction_id=interaction.id",
    },

    "5. response_format 수정": {
        "before": "response_mime_type='application/json'",
        "after":  "config={'response_format': {'type': 'json_schema', ...}}",
    },

    "6. 함수 호출 로직 수정": {
        "before": "response.candidates[0].content.parts 탐색",
        "after":  "step.type == 'function_call' 탐색",
    },
}

# [TUNE] 성능·비용 최적화 항목

tuning = {
    "thinking_level 명시 설정": "기본값 medium — 워크로드에 맞게 low/medium/high 선택",
    "store=False 검토": "히스토리 불필요한 단건 요청은 store=False로 비용 절감",
    "background=True 활용": "수 분 걸리는 태스크는 비동기로 전환 + Webhook 연동",
}

# 자동 마이그레이션 도구 (Gemini CLI 사용 시)
# /gemini-interactions-api migrate my-app
# → 기계적 코드 변환 자동 처리

결론

Interactions API로 전환해야 하는 이유

  • 서버사이드 히스토리 = 멀티턴 입력 토큰 최대 82% 절감
  • Thought Preservation 자동 처리 — 직접 구현 불필요
  • 신규 기능(Webhook, 비동기 실행, mid-flight steering)은 Interactions API에만 추가
  • 6월 8일 이후 레거시 스키마 완전 제거 — 미리 마이그레이션이 유일한 선택

OpenAI Responses API와의 비교

  • 개념 동일: 서버사이드 히스토리 → 클라이언트 상태 관리 불필요
  • Google 차별점: background=true 비동기 실행, Webhook 네이티브 지원
  • 아직 베타 — GA 전까지는 프로덕션 크리티컬 경로에 단독 의존 주의

아직 주의해야 할 것

  • Computer Use 미지원 — gemini-3-flash-preview 병행 유지 필요
  • 베타 상태 — 추가 브레이킹 체인지 가능성 존재 ("actively developing")
  • store=True 기본값 — 민감 데이터 저장 정책 검토 필요 (55일 보관)
  • Remote MCP 미지원 — 내부망 MCP 연결은 별도 방법 필요

관련 글

반응형