Thought Preservation은 Gemini 3.5 Flash 출시와 함께 기본값으로 켜졌습니다. API 설정 변경도 없고, 공지도 조용했습니다. 그리고 수많은 팀의 청구서가 예상보다 50~80% 높아졌습니다.
핵심 요약 → Thought Preservation: 이전 턴의 추론 토큰(Thought Tokens)을 다음 턴 입력에 자동 포함 → Gemini 3.5 Flash 출시와 함께 기본값 ON — API 변경 없이 자동 적용 → 효과: 멀티턴 에이전트 루프에서 맥락 일관성·품질 향상 → 비용: 10턴 에이전트 루프 입력 토큰 50~80% 증가 → Antigravity 쿼터 사태의 숨겨진 주범 — 단가 3배 × 토큰 소비 증가 = 5.6배 청구서 → GenerateContent API: thoughts 포함 전체 히스토리 전달 필요 → Interactions API: 서버가 자동 처리 (개발자 개입 불필요) → 방어 전략 4가지: thinking_level 낮추기, 세션 분리, 명시적 삭제, Interactions API 전환
Thought Preservation이란 무엇인가
# 기존 모델의 추론 방식 (3 Flash Preview까지)
턴 1: 사용자 "이 코드 버그 찾아줘"
→ 모델이 내부적으로 생각함 (Thought Tokens)
→ 답변 반환
→ 추론 과정 폐기 ← 다음 턴에서 모름
턴 2: 사용자 "고쳐줘"
→ 모델이 다시 처음부터 생각함
→ 이전에 어떻게 분석했는지 기억 없음
→ 일관성 낮음
# Gemini 3.5 Flash Thought Preservation
턴 1: 사용자 "이 코드 버그 찾아줘"
→ 모델이 추론 (Thought Tokens 생성)
→ 답변 반환
→ 추론 과정 보존 ← thought signature로 히스토리에 포함
턴 2: 사용자 "고쳐줘"
→ 이전 턴의 추론 컨텍스트 포함해서 생각
→ "아, 이전에 off-by-one 에러라고 분석했지"
→ 더 일관되고 정확한 수정
Gemini 3.5 Flash는 멀티턴 대화에서 중간 추론을 자동으로 유지합니다. API 변경 없이 작동하며, 모델이 이전에 어떻게 추론했는지를 참조할 수 있어 긴 에이전트 루프에서 일관성이 향상됩니다. 단, 보존된 추론 토큰은 이후 턴마다 입력으로 카운트됩니다.
1. 내부 동작 메커니즘 — Thought Signature란
# Thought Signature: 추론 토큰을 히스토리에 포함시키는 마커
import google.generativeai as genai
client = genai.Client()
# GenerateContent API 사용 시
# SDK가 자동으로 thought_signature를 히스토리에 포함
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="복잡한 에이전트 태스크를 분석해줘",
config={"thinking_level": "medium"}
)
# 응답에 포함된 것들
print(response.usage_metadata)
# CachedContentTokenCount: 3,072
# PromptTokenCount: 12,450
# CandidatesTokenCount: 891
# ThoughtsTokenCount: 4,203 ← 추론 토큰
# TotalTokenCount: 17,544
# thought_signature가 응답 파트에 포함됨
for part in response.candidates[0].content.parts:
if hasattr(part, 'thought') and part.thought:
# 이게 thought signature
# 다음 턴 히스토리에 그대로 포함해야 함
thought_content = part.text
2. 비용이 어떻게 누적되는가 — 실제 계산
10턴 에이전트 대화에서 구 API 기준 턴당 50,000 입력 토큰을 소비했다면 Thought Preservation 활성화 시 턴당 75,000~90,000 입력 토큰을 소비할 수 있습니다. thinking_level에 따라 50~80% 인플레이션이 발생합니다.
# Thought Preservation 비용 누적 시뮬레이션
def simulate_thought_preservation_cost(
turns: int = 10,
base_input_tokens: int = 2000, # 턴당 사용자 입력
base_output_tokens: int = 500, # 턴당 모델 출력
thinking_level: str = "medium", # low/medium/high
input_price: float = 1.50, # $1.50/1M tokens
output_price: float = 9.00 # $9.00/1M tokens
) -> dict:
# thinking_level별 턴당 thought 토큰 추정
thought_tokens_per_turn = {
"low": 800,
"medium": 2500,
"high": 6000
}[thinking_level]
total_cost_old = 0 # 3 Flash Preview 방식 (Thought 폐기)
total_cost_new = 0 # 3.5 Flash (Thought Preservation)
accumulated_thoughts = 0 # 누적 thought 토큰
for turn in range(1, turns + 1):
# 이번 턴 thought 토큰
this_turn_thoughts = thought_tokens_per_turn
# 구 방식: 매 턴 기본 입력만
old_input = base_input_tokens
old_cost = (
old_input / 1_000_000 * input_price +
base_output_tokens / 1_000_000 * output_price
)
total_cost_old += old_cost
# 신규 방식: 누적된 thought 토큰 포함
new_input = base_input_tokens + accumulated_thoughts
new_cost = (
new_input / 1_000_000 * input_price +
base_output_tokens / 1_000_000 * output_price
)
total_cost_new += new_cost
# thought 토큰 누적
accumulated_thoughts += this_turn_thoughts
inflation = (total_cost_new - total_cost_old) / total_cost_old * 100
return {
"turns": turns,
"thinking_level": thinking_level,
"old_cost": round(total_cost_old * 1000, 4), # 밀리달러
"new_cost": round(total_cost_new * 1000, 4),
"inflation_pct": round(inflation, 1),
"accumulated_thoughts_at_end": accumulated_thoughts
}
# 시뮬레이션 결과
results = [
simulate_thought_preservation_cost(thinking_level="low"),
simulate_thought_preservation_cost(thinking_level="medium"),
simulate_thought_preservation_cost(thinking_level="high"),
simulate_thought_preservation_cost(turns=20, thinking_level="medium"),
]
# 결과 출력
# turns=10, low: old=$0.33 → new=$0.45 (+36%)
# turns=10, medium: old=$0.33 → new=$0.63 (+91%)
# turns=10, high: old=$0.33 → new=$1.23 (+273%)
# turns=20, medium: old=$0.66 → new=$2.01 (+205%)
# ⚠ 핵심:
# medium 10턴: 91% 비용 증가
# high 10턴: 273% 비용 증가
# medium 20턴: 205% 비용 증가
# → 대화가 길어질수록 비용이 선형이 아닌 2차 함수로 증가
3. Antigravity 쿼터 사태의 진짜 구조
출시 당시 "3배 가격 인상"만 논란이 됐는데, 실제 청구서가 5.6배가 된 이유가 여기에 있습니다.
# 비용 폭발의 3중 구조
1. 토큰 단가 인상:
3 Flash Preview: $0.50/1M input
3.5 Flash: $1.50/1M input
→ 3배 인상
2. Thought Preservation 토큰 누적:
에이전트 루프 10턴 기준 입력 토큰 +91%
→ 동일 작업에 약 2배 입력 토큰 소비
3. thinking_level 기본값:
출시 당시: high (가장 많은 thought 토큰)
현재: medium (조정됨)
3 × 2 ≈ 5.6배 (Artificial Analysis 실측치와 일치)
Antigravity 유료 사용자 입장:
전날까지: 에이전트 루프 100회 → X 크레딧 소비
출시 당일: 에이전트 루프 100회 → 5.6X 크레딧 소비
→ 1시간 만에 주간 쿼터 소진
4. API별 처리 방식 차이
# ── GenerateContent API: 수동 관리 필요 ──
import google.generativeai as genai
client = genai.Client()
conversation_history = []
def chat_with_thought_preservation(user_message: str) -> str:
conversation_history.append({
"role": "user",
"parts": [{"text": user_message}]
})
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=conversation_history,
config={"thinking_level": "medium"}
)
# ⚠ 중요: thought_signature 포함해서 히스토리에 추가
# SDK가 자동 처리하지만 수동 구현 시 반드시 포함
model_parts = []
for part in response.candidates[0].content.parts:
model_parts.append(part) # thought signature 포함 전체 파트
conversation_history.append({
"role": "model",
"parts": model_parts # ← thought signature 포함
# 이걸 빠트리면 다음 턴에서 Thought Preservation 안 됨
})
# 텍스트 추출
return next(
p.text for p in response.candidates[0].content.parts
if not (hasattr(p, 'thought') and p.thought)
)
# ── Interactions API: 자동 처리 ──
def chat_interactions_api(user_message: str, previous_id: str | None) -> tuple[str, str]:
"""
Interactions API에서는 thought signature 관리 불필요
서버가 전담 처리
"""
interaction = client.interactions.create(
model="gemini-3.5-flash",
input=user_message,
previous_interaction_id=previous_id, # 이것만 전달
config={"thinking_level": "medium"}
)
for step in interaction.steps:
if step.type == "model_output":
return step.content[0].text, interaction.id
return "", interaction.id
5. 방어 전략 4가지
# ── 전략 1: thinking_level 낮추기 (가장 간단) ──
# 에이전트 루프 기본값 = low
# medium 대비 thought 토큰 ~68% 절감
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=history,
config={"thinking_level": "low"} # medium → low 전환
)
# ── 전략 2: 세션 적극 분리 ──
class SessionManager:
"""
자연스러운 단계 경계에서 히스토리 초기화
thought 토큰 누적 리셋
"""
MAX_TURNS = 8 # 8턴 이후 세션 교체
def __init__(self):
self.history = []
self.turn_count = 0
def should_reset(self) -> bool:
return self.turn_count >= self.MAX_TURNS
def add_turn(self, user_msg, model_response):
self.history.append(...)
self.turn_count += 1
if self.should_reset():
# 핵심 컨텍스트만 요약해서 새 세션 시작
summary = self._summarize_context()
self.history = [{"role": "user", "parts": [{"text": f"이전 컨텍스트 요약: {summary}"}]}]
self.turn_count = 0
def _summarize_context(self) -> str:
# 현재 히스토리 요약 생성 (thought 토큰 없는 단순 요약)
summary_response = client.models.generate_content(
model="gemini-3.5-flash",
contents=[{
"role": "user",
"parts": [{"text": f"다음 대화를 3문장으로 요약해줘: {self.history}"}]
}],
config={"thinking_level": "minimal"} # 요약은 minimal
)
return summary_response.text
# ── 전략 3: thought 토큰 명시적 삭제 ──
def strip_thoughts_from_history(history: list) -> list:
"""
단순 쿼리에서 thought signature를 히스토리에서 제거
Thought Preservation 비활성화 효과
"""
cleaned = []
for turn in history:
if turn["role"] == "model":
# thought 파트 필터링
clean_parts = [
p for p in turn["parts"]
if not (hasattr(p, 'thought') and p.thought)
]
cleaned.append({"role": "model", "parts": clean_parts})
else:
cleaned.append(turn)
return cleaned
# 사용: 단순 질의응답에서만 적용
# 복잡한 에이전트 루프에서는 유지 권장
simple_history = strip_thoughts_from_history(full_history)
# ── 전략 4: Interactions API 전환 (장기적 최선) ──
# Interactions API는 서버가 Thought Preservation 최적화 처리
# 개발자가 thought 토큰을 직접 다룰 필요 없음
# 서버사이드 히스토리 → 전체 입력 토큰 절감과 시너지
# 단, 6월 8일 스키마 마이그레이션 필요 (별도 가이드 참조)
6. 모니터링 — Thought Token을 추적하는 방법
import logging
logger = logging.getLogger(__name__)
def monitored_generate(contents, thinking_level="medium"):
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=contents,
config={"thinking_level": thinking_level}
)
usage = response.usage_metadata
thought_tokens = getattr(usage, 'thoughts_token_count', 0)
total_input = usage.prompt_token_count
# 경고: thought 비율이 입력의 30% 초과 시
thought_ratio = thought_tokens / total_input if total_input > 0 else 0
if thought_ratio > 0.30:
logger.warning(
f"Thought 토큰 비율 높음: {thought_ratio:.1%} "
f"(thought={thought_tokens}, total_input={total_input})\n"
f"→ thinking_level 낮추거나 세션 분리 고려"
)
logger.info(
f"level={thinking_level} | "
f"input={total_input} | "
f"thought={thought_tokens} ({thought_ratio:.1%}) | "
f"output={usage.candidates_token_count}"
)
return response
언제 Thought Preservation을 활용하고 언제 억제하나
# Thought Preservation 활성화 권장
✅ 복잡한 멀티스텝 디버깅 (이전 분석 맥락 유지가 핵심)
✅ 장기 코드 리팩토링 루프 (파일 간 연관성 추적)
✅ 멀티턴 수학/추론 문제 풀기
✅ 연구 에이전트 (긴 탐색 과정의 일관성)
→ 이런 케이스는 품질 향상이 비용 증가를 정당화
# Thought Preservation 억제 권장
❌ 독립적인 단건 쿼리 반복 (각 턴이 무관한 작업)
❌ 대량 배치 처리 (비용이 핵심 변수)
❌ 단순 분류·추출 태스크
❌ 에이전트 루프 > 15턴 (누적 비용이 기하급수적)
→ strip_thoughts_from_history() 또는 세션 분리 적용
결론
✅ Thought Preservation이 진짜로 좋은 것
- 멀티턴 에이전트에서 "이전에 어떻게 분석했는지" 자동 기억
- 복잡한 디버깅·리팩토링에서 일관성 대폭 향상
- API 변경 없이 자동 적용 — 기존 코드도 혜택
❌ 모르면 당하는 것
- medium 10턴: 비용 91% 증가 / high 10턴: 273% 증가
- Antigravity 쿼터 5.6배 소진의 숨겨진 원인
- GenerateContent API: thought signature 포함 히스토리 전달 필수
- 단순 쿼리 배치에 그냥 쓰면 의미 없는 비용만 증가
✅ 지금 해야 할 것
- usage_metadata.thoughts_token_count 모니터링 추가
- 에이전트 루프 8~10턴 기준 세션 분리 설계
- 단순 태스크는 thinking_level: "low" 또는 "minimal" 명시
- 장기적으로 Interactions API 전환 (서버가 최적화 처리)
관련 글
- Gemini Interactions API 완전가이드 — 서버사이드 히스토리와 브레이킹 체인지
- Gemini 3.5 Flash 비용 최적화 완전가이드 — Low/Medium/High + 캐싱 전략
- Gemini 3.5 Flash 출시 9일 사용자 반응 총정리
'Gemini' 카테고리의 다른 글
| Gemini Omni vs Veo 3.1 — Google이 비디오 모델을 두 개 운영하는 이유 (0) | 2026.05.28 |
|---|---|
| Gemini 3.5 Flash + Interactions API로 MCP 에이전트 만들기 — 완전 실전 가이드 (0) | 2026.05.28 |
| Gemini Interactions API 완전분석 — OpenAI Responses API의 대항마, 서버사이드 히스토리 관리의 실체 (0) | 2026.05.28 |
| Gemini 3.5 Flash 출시 9일 — 실제 사용자들은 뭐라고 했나 (0) | 2026.05.28 |
| Gemini 3.5 Flash 가격 3배 인상의 전략적 의미 — Google이 Flash를 프리미엄으로 올린 이유 (0) | 2026.05.28 |