"LLM 추론 모드 (2/6) — Claude의 Thinking: 고정 예산에서 adaptive로"
1편이 "사고에는 비용이 든다"는 전제를 세웠다면, 2편은 Claude가 그 사고를 어떻게 제어하는지를 본다. 핵심은 고정 토큰 예산(
budget_tokens)에서 모델이 스스로 정하는 adaptive thinking으로의 전환이다.
Claude의 추론 제어는 두 층으로 나뉜다. Thinking(사고를 켤지, 얼마나 깊게 할지)과 effort(응답 전체 토큰 예산). 이 둘은 함께 동작하지만 별개의 손잡이다. 2편은 앞쪽 — Thinking — 을 다루고, effort는 3편에서 본다.
한 문단 요약
예전 Claude는
thinking: {type: "enabled", budget_tokens: N}으로 사고에 고정 토큰 예산을 줬다. 현행 모델은 이를 버리고 adaptive thinking(thinking: {type: "adaptive"})으로 갔다 — 모델이 요청마다 언제, 얼마나 생각할지 스스로 정한다.budget_tokens는 Opus 4.6/Sonnet 4.6에서 deprecated(아직 동작), Opus 4.7·4.8·Fable 5에서는 제거(400 에러)됐다. 사고 깊이는 이제 effort로 조절한다. 사고 원문은 반환되지 않으며display로 요약만 켤 수 있다.
1. extended thinking: 고정 예산 시대
Claude가 처음 사고를 노출한 방식은 명시적 토큰 예산이었다.
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=16000,
thinking={"type": "enabled", "budget_tokens": 8000},
messages=[...],
)
규칙은 단순했다.
budget_tokens는max_tokens보다 작아야 한다.- 최소 1024.
- 개발자가 "이 호출은 사고에 최대 N 토큰까지"를 직접 못 박는다.
문제는 이 방식이 경직돼 있다는 점이다. 쉬운 질문에도 예산을 잡아두면 낭비고, 어려운 질문에 예산이 모자라면 사고가 잘린다. 호출마다 난이도를 사람이 가늠해 숫자를 정해야 했다.
2. 왜 고정 예산을 버렸나 — adaptive thinking
현행 Claude는 adaptive thinking을 쓴다.
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=16000,
thinking={"type": "adaptive"},
output_config={"effort": "high"},
messages=[...],
)
핵심 차이는 결정 주체다. 고정 예산은 개발자가 사고량을 못 박지만, adaptive는 모델이 요청을 보고 언제·얼마나 생각할지 스스로 정한다. 쉬운 질문엔 사고를 짧게 하거나 건너뛰고, 어려운 질문엔 더 길게 한다.
부수 효과도 있다. adaptive thinking은 interleaved thinking(도구 호출 사이사이의 사고)을 자동으로 켠다 — 별도 베타 헤더가 필요 없다. 에이전트 루프에서 "도구 결과를 보고 다음을 계획"하는 사고가 자연스럽게 끼어든다.
그렇다면 사고 깊이는 무엇으로 조절하나? effort다(3편). adaptive thinking이 "생각할지 말지·언제"를 모델에 맡기고, effort가 "얼마나 깊게"의 기조를 정한다. 둘의 조합이 고정 예산 하나를 대체한다.
3. 모델별 현황 — 무엇이 동작하고 무엇이 막혔나
이 전환은 모델마다 단계가 다르다. 마이그레이션 시 가장 자주 부딪히는 표다.
| 모델 | budget_tokens (고정 예산) |
adaptive thinking | 비고 |
|---|---|---|---|
| Opus 4.5 | 사용(수동 thinking) | — | effort가 사고 예산과 함께 동작 |
| Opus 4.6 | deprecated(아직 허용) | 권장 | 전환기 escape hatch로만 |
| Sonnet 4.6 | deprecated(아직 허용) | 권장 | 기본 effort high |
| Opus 4.7 | 제거 → 400 | 사용 | thinking 미지정 시 사고 꺼짐 |
| Opus 4.8 | 제거 → 400 | 사용 | 요청 표면은 4.7과 동일 |
| Fable 5 | 제거 → 400 | 항상 켜짐 | thinking 자체를 생략, disabled도 400 |
읽는 법:
- Opus 4.7 / 4.8:
thinking: {type: "adaptive"}를 명시해야 사고가 켜진다. 필드를 생략하면 사고 없이 동작한다.budget_tokens를 보내면 400. - Fable 5: 사고가 항상 켜져 있다.
thinking필드를 아예 생략하고,{type: "disabled"}를 보내면 400. - Opus 4.5: 유일하게 수동 thinking(
budget_tokens)과 effort를 함께 쓴다.
새 코드라면 답은 하나다 — budget_tokens를 쓰지 말고 thinking: {type: "adaptive"} + effort로 가라. "고정 사고 예산"이라는 개념 자체가 폐기 수순이다.
4. 사고 블록과 display — 보이는 것과 가려진 것
사고는 일어나고 과금되지만, 원문은 절대 반환되지 않는다. 응답 스트림에는 thinking 타입 블록이 들어오는데, 그 안에 무엇이 담기는지는 thinking.display가 정한다.
display: "summarized"→ 읽을 수 있는 요약이 담긴다.display: "omitted"→ 사고 블록은 오지만 텍스트가 빈 문자열이다.
중요한 함정: 기본값이 omitted다(Fable 5·Mythos 5·Opus 4.8·4.7 기준). 이는 Opus 4.6에서 summarized였던 것이 조용히 바뀐 것이다. 그 결과 진행 상황을 사용자에게 보여주려던 UI가 갑자기 "긴 침묵 후 답"으로 보일 수 있다. 요약을 보이려면 명시해야 한다.
thinking={"type": "adaptive", "display": "summarized"}
display는 표시 여부만 정한다. 어떤 설정이든 사고는 동일하게 일어나고 동일하게 과금된다. 그리고 어떤 설정으로도 사고 원문(raw chain-of-thought)은 노출되지 않는다 — 요약이 최대치다.
5. 멀티턴 규칙 — 사고 블록을 그대로 돌려보내라
에이전트·멀티턴에서 한 가지 규칙을 지켜야 한다. 같은 모델로 대화를 이어갈 때는 받은 사고 블록을 그대로(수정 없이) 다시 보낸다. 텍스트가 빈 블록이라도 그대로 넣는다 — API는 읽은 블록이 아니라 변형된 블록을 거부한다.
반대로 다른 모델로 넘길 때는 그 사고 블록이 프롬프트에서 드롭된다(대개 조용히, 에러 아님). 드롭은 과금 전에 일어나므로 비용도 들지 않고 따로 떼어낼 것도 없다. 일반 사고 블록은 모델 간 자유롭게 재생되지만, 이 드롭은 정상 동작으로 알아두면 된다.
6. Thinking과 effort의 관계 (다음 편 예고)
정리하면, 현행 Claude의 사고 제어는 이렇게 읽힌다.
- adaptive thinking = "생각할지·언제 생각할지"를 모델이 결정.
- effort = "얼마나 깊게 생각하고, 응답 전체에 토큰을 얼마나 쓸지"의 기조.
high·xhigh·max effort에서는 모델이 거의 항상 깊게 생각하고, 낮은 effort에서는 쉬운 문제의 사고를 건너뛸 수 있다. 즉 effort가 adaptive thinking의 적극성을 끌어올리거나 낮춘다.
다음 편(3/6)은 그 effort를 정면으로 해부한다 — low / medium / high / xhigh / max가 각각 무엇을, 사고뿐 아니라 도구 호출과 프리앰블까지 어떻게 바꾸는지.
파라미터 동작·모델별 지원은 Anthropic 공식 문서(extended thinking, adaptive thinking, effort) 및 모델 마이그레이션 가이드를 1차 자료로 정리했다.
시리즈 전체 안내: 시리즈 목차
댓글
댓글 쓰기