Thinking

Thinking (사고)

Thinking은 Claude가 답변하기 전에 자기 말로 문제를 풀어보게 하는 기능이에요. 수학, 코딩, 분석처럼 복잡한 과제에서 첫 번째 접근이 최선이 아닐 때가 많은데, thinking을 켜면 Claude가 문제를 다시 진술하고, 여러 방법을 시도하고, 중간 결과를 점검하며 성과가 없는 경로를 버릴 수 있어요. 이 페이지에서는 thinking을 켜는 방법, 출력을 읽는 방법, 그리고 도구·스트리밍·캐싱·컨텍스트 창과의 상호작용을 다룰게요.

출처: 문서

본문

이 기능에 zero data retention(ZDR)이 어떻게 적용되는지 배우려면 [API 및 데이터 보존](https://platform.claude.com/docs/en/manage-claude/api-and-data-retention)을 보세요.

한 번에 답하는 모델은 첫 시도에서 모든 걸 맞혀야 해요: 낙서(scratch work), 점검, 중간에 방향 바꾸기가 없죠. 증명, 까다로운 버그, 긴 에이전트 과제에서는 첫 번째 접근이 종종 최선이 아니에요.

Thinking은 그 제약을 없애요. thinking이 활성화되면 Claude는 답하기 전에 자기 말로 문제를 풀어봐요: 무엇을 묻는지 다시 진술하고, 방법을 시도하고, 중간 결과를 점검하고, 성립하지 않는 경로를 버려요. 그 추론은 응답보다 앞서 thinking 콘텐츠 블록으로 도착하고, Claude는 이를 바탕으로 최종 답을 만들어요. 그래서 thinking은 수학, 코딩, 분석, 오래 걸리는 에이전트 작업 같은 복잡한 과제에서 성능을 높여요. 그런 과제에서는 답의 질이, 그렇지 않으면 응답 자체에 압축되거나 건너뛰어질 중간 작업에 달려 있거든요.

Thinking은 비용이 들어요: Claude가 추론에 쓰는 토큰은 thinking 텍스트가 돌려받지 않더라도 출력 토큰으로 청구되고, 응답 텍스트와 함께 max_tokens에 포함돼요. 이 페이지는 API 전면에서 thinking이 어떻게 동작하는지 다뤄요: 켜는 방법, 출력을 읽는 방법, 그리고 도구·스트리밍·캐싱·컨텍스트 창과의 상호작용을 관리하는 법이에요.

Thinking이 동작하는 방식

Thinking이 동작하는 방식 다이어그램: Claude가 요청을 평가하고 생각할지 결정합니다. 도구 사용 시 thinking은 도구 호출 사이에 반복될 수 있고, 하나의 응답은 thinking 블록들 다음에 text 블록들을 반환합니다

Claude가 주어진 요청에 대해 생각할지, 얼마나 깊이 생각할지는 thinking 구성과 요청의 복잡성에 달려 있어요.

응답에서 thinking은 이렇게 보여요: text 블록보다 먼저 하나 이상의 thinking 콘텐츠 블록이 도착해요. thinking 블록은 뒤따르는 text 블록처럼 생성된 콘텐츠이지만, 표준 응답에서 분리돼 있어요. 또한 각 thinking 블록은 signature 필드, 즉 멀티턴 및 도구 사용 대화에서 그대로 다시 전달하는 전체 추론의 암호화된 사본을 지녀요 (Thinking 암호화 참고):

{
  "content": [
    {
      "type": "thinking",
      "thinking": "Let me break this down. The question has two parts, so I'll start with the simpler one and use its result to constrain the second...",
      "signature": "WaUjzkypQ2mUEVM36O2Txu...."
    },
    {
      "type": "text",
      "text": "Based on my analysis..."
    }
  ]
}

항상 이 텍스트를 보는 건 아니고, 보는 것도 결코 원시 사고 흐름(raw chain of thought)은 아니에요: thinking 블록의 텍스트는 Claude 추론의 요약이에요. thinking 구성의 display 필드는 그 요약을 아예 반환할지 제어해요: "summarized"는 반환하고, 많은 모델의 기본인 "omitted"는 빈 thinking 필드를 가진 thinking 블록을 반환해요. 어느 쪽이든 블록은 똑같이 청구되고 멀티턴 대화에서 똑같이 다시 전달돼요. 모델별 기본값과 세부 사항은 Thinking 표시 제어를 보세요.

Claude가 도구를 쓰면 thinking이 도구 호출 사이에도 나타날 수 있어요. 도구 사용과 thinking을 보세요. 전체 응답 형식은 Messages API 참조를 보세요.

Thinking 구성하기

대부분의 모델에서 thinking은 기본으로 켜져 있거나 파라미터 하나만 바꾸면 돼요. 각 모델이 어떤 구성을 받고, 기본값이 무엇인지는 Troubleshooting 페이지의 모델별 구성 표에 나열돼 있어요.

Claude Opus 5.5, Claude Opus 5, Claude Sonnet 5, Claude Fable 5.1, Claude Mythos 5.1, Claude Fable 5, Claude Mythos 5, Claude Mythos Preview에서는 thinking이 이미 켜져 있고 구성이 필요 없어요. 이 모델들에서 display은 기본적으로 "omitted"라서, 옵트인하기 전까지 thinking 텍스트가 숨겨져요. thinking: {"type": "adaptive", "display": "summarized"}로 옵트인하는데, 이는 정확히 아래 요청에서 모델 문자열만 바꾼 것과 같아요.

Claude Opus 4.8, Claude Opus 4.7, Claude Opus 4.6, Claude Sonnet 4.6에서는 thinking: {type: "adaptive"}를 설정하기 전까지 thinking이 꺼져 있어요. 이 설정으로 Claude가 요청에 따라 언제, 얼마나 깊이 생각할지 스스로 결정해요. 아래 예시들은 그렇게 하고, display: "summarized"를 설정해 thinking 텍스트를 보이게 하며, 여유 있는 max_tokens를 사용해요:

```bash cURL curl https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-opus-4-8", "max_tokens": 16000, "thinking": { "type": "adaptive", "display": "summarized" }, "messages": [ { "role": "user", "content": "What is the greatest common divisor of 1071 and 462?" } ] }' ```
ant messages create \
  --model claude-opus-4-8 \
  --max-tokens 16000 \
  --thinking '{type: adaptive, display: summarized}' \
  --message '{role: user, content: "What is the greatest common divisor of 1071 and 462?"}' \
  --transform content \
  --format yaml
client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=16000,
    thinking={"type": "adaptive", "display": "summarized"},
    messages=[
        {
            "role": "user",
            "content": "What is the greatest common divisor of 1071 and 462?",
        }
    ],
)

for block in response.content:
    match block.type:
        case "thinking":
            print(f"\nThinking: {block.thinking}")
        case "text":
            print(f"\nResponse: {block.text}")
const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-opus-4-8",
  max_tokens: 16000,
  thinking: {
    type: "adaptive",
    display: "summarized"
  },
  messages: [
    {
      role: "user",
      content: "What is the greatest common divisor of 1071 and 462?"
    }
  ]
});

for (const block of response.content) {
  switch (block.type) {
    case "thinking":
      console.log(`\nThinking: ${block.thinking}`);
      break;
    case "text":
      console.log(`\nResponse: ${block.text}`);
      break;
  }
}
AnthropicClient client = new();

var parameters = new MessageCreateParams
{
    Model = Model.ClaudeOpus4_8,
    MaxTokens = 16000,
    Thinking = new ThinkingConfigAdaptive { Display = Display.Summarized },
    Messages = [
        new() {
            Role = Role.User,
            Content = "What is the greatest common divisor of 1071 and 462?"
        }
    ]
};

var message = await client.Messages.Create(parameters);

foreach (var block in message.Content)
{
    if (block.TryPickThinking(out ThinkingBlock? thinking))
    {
        Console.WriteLine($"\nThinking: {thinking.Thinking}");
    }
    else if (block.TryPickText(out TextBlock? text))
    {
        Console.WriteLine($"\nResponse: {text.Text}");
    }
}
client := anthropic.NewClient()

response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
	Model:     anthropic.ModelClaudeOpus4_8,
	MaxTokens: 16000,
	Thinking: anthropic.ThinkingConfigParamUnion{
		OfAdaptive: &anthropic.ThinkingConfigAdaptiveParam{
			Display: anthropic.ThinkingConfigAdaptiveDisplaySummarized,
		},
	},
	Messages: []anthropic.MessageParam{
		anthropic.NewUserMessage(anthropic.NewTextBlock("What is the greatest common divisor of 1071 and 462?")),
	},
})
if err != nil {
	log.Fatal(err)
}

for _, block := range response.Content {
	switch v := block.AsAny().(type) {
	case anthropic.ThinkingBlock:
		fmt.Printf("\nThinking: %s", v.Thinking)
	case anthropic.TextBlock:
		fmt.Printf("\nResponse: %s", v.Text)
	}
}
import com.anthropic.models.messages.ThinkingConfigAdaptive;

void main() {
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();

    MessageCreateParams params = MessageCreateParams.builder()
        .model(Model.CLAUDE_OPUS_4_8)
        .maxTokens(16000L)
        .thinking(ThinkingConfigAdaptive.builder()
            .display(ThinkingConfigAdaptive.Display.SUMMARIZED)
            .build())
        .addUserMessage("What is the greatest common divisor of 1071 and 462?")
        .build();

    Message response = client.messages().create(params);

    response.content().forEach(block -> {
        block.thinking().ifPresent(thinkingBlock ->
            IO.println("\nThinking: " + thinkingBlock.thinking())
        );
        block.text().ifPresent(textBlock ->
            IO.println("\nResponse: " + textBlock.text())
        );
    });
}
use Anthropic\Messages\TextBlock;
use Anthropic\Messages\ThinkingBlock;

$client = new Client();

$message = $client->messages->create(
    maxTokens: 16000,
    messages: [
        [
            'role' => 'user',
            'content' => 'What is the greatest common divisor of 1071 and 462?'
        ]
    ],
    model: 'claude-opus-4-8',
    thinking: ['type' => 'adaptive', 'display' => 'summarized'],
);

foreach ($message->content as $block) {
    switch (true) {
        case $block instanceof ThinkingBlock:
            echo "\nThinking: " . $block->thinking;
            break;
        case $block instanceof TextBlock:
            echo "\nResponse: " . $block->text;
            break;
    }
}
client = Anthropic::Client.new

message = client.messages.create(
  model: "claude-opus-4-8",
  max_tokens: 16000,
  thinking: {
    type: "adaptive",
    display: "summarized"
  },
  messages: [
    {
      role: "user",
      content: "What is the greatest common divisor of 1071 and 462?"
    }
  ]
)

message.content.each do |block|
  case block
  when Anthropic::Models::ThinkingBlock
    puts "\nThinking: #{block.thinking}"
  when Anthropic::Models::TextBlock
    puts "\nResponse: #{block.text}"
  end
end

예시를 실행하면 요약된 thinking이 먼저, 그다음 답이 출력돼요:

Thinking: Use Euclidean algorithm.
1071 = 2*462 + 147
462 = 3*147 + 21
147 = 7*21 + 0
GCD = 21

Response: ## Finding GCD of 1071 and 462

I'll use the **Euclidean algorithm**, repeatedly dividing and taking remainders...

Thinking 토큰은 max_tokens에 포함되므로, thinking과 응답 텍스트 둘 다 담을 만큼 충분히 높게 설정하세요. 스티어링 페이지의 비용 제어Thinking과 컨텍스트 창을 보세요.

Thinking 끄기

thinking이 기본으로 켜진 Claude Sonnet 5에서는 끌 수 있어요:

```bash cURL curl https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-sonnet-5", "max_tokens": 4096, "thinking": {"type": "disabled"}, "messages": [ { "role": "user", "content": "Summarize this article in one sentence." } ] }' ```
ant messages create \
  --model claude-sonnet-5 \
  --max-tokens 4096 \
  --thinking '{type: disabled}' \
  --message '{role: user, content: "Summarize this article in one sentence."}'
client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=4096,
    thinking={"type": "disabled"},
    messages=[{"role": "user", "content": "Summarize this article in one sentence."}],
)
const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 4096,
  thinking: { type: "disabled" },
  messages: [{ role: "user", content: "Summarize this article in one sentence." }]
});
AnthropicClient client = new();

var parameters = new MessageCreateParams
{
    Model = Model.ClaudeSonnet5,
    MaxTokens = 4096,
    Thinking = new ThinkingConfigDisabled(),
    Messages = [
        new() {
            Role = Role.User,
            Content = "Summarize this article in one sentence."
        }
    ]
};

var message = await client.Messages.Create(parameters);
client := anthropic.NewClient()

response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
	Model:     anthropic.ModelClaudeSonnet5,
	MaxTokens: 4096,
	Thinking: anthropic.ThinkingConfigParamUnion{
		OfDisabled: &anthropic.ThinkingConfigDisabledParam{},
	},
	Messages: []anthropic.MessageParam{
		anthropic.NewUserMessage(anthropic.NewTextBlock("Summarize this article in one sentence.")),
	},
})
if err != nil {
	log.Fatal(err)
}
import com.anthropic.models.messages.ThinkingConfigDisabled;

void main() {
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();

    MessageCreateParams params = MessageCreateParams.builder()
        .model(Model.CLAUDE_SONNET_5)
        .maxTokens(4096L)
        .thinking(ThinkingConfigDisabled.builder().build())
        .addUserMessage("Summarize this article in one sentence.")
        .build();

    Message response = client.messages().create(params);
}
$client = new Client();

$message = $client->messages->create(
    maxTokens: 4096,
    messages: [
        [
            'role' => 'user',
            'content' => 'Summarize this article in one sentence.'
        ]
    ],
    model: 'claude-sonnet-5',
    thinking: ['type' => 'disabled'],
);
client = Anthropic::Client.new

message = client.messages.create(
  model: "claude-sonnet-5",
  max_tokens: 4096,
  thinking: { type: "disabled" },
  messages: [
    {
      role: "user",
      content: "Summarize this article in one sentence."
    }
  ]
)

Claude Opus 5도 thinking이 기본으로 켜져 있고, efforthigh 이하일 때 thinking: {type: "disabled"}를 받아요. xhighmax effort에서는 thinking을 끌 수 없어요: 그 effort 수준과 thinking: {type: "disabled"}를 결합한 요청은 400 오류를 반환해요. 이 제한은 요청마다 적용돼요. thinking이 꺼지면 Claude Opus 5는 도구 호출을 평문 텍스트로 내보내거나 눈에 보이는 출력에 내부 XML 태그를 넣을 수 있어요. 프롬프팅 완화책은 Thinking 비활성화로 실행하기를 보세요.

Claude Fable 5.1, Claude Mythos 5.1, Claude Fable 5, Claude Mythos 5, Claude Opus 5.5, Claude Mythos Preview는 thinking: {type: "disabled"}를 거부해요. 이 모델들에서는 thinking을 끌 수 없어요.

모델이 확장 thinking(extended thinking)만 지원한다면(모델별 구성 표 참고), 대신 type: "enabled"budget_tokens 값으로 구성하세요. 확장 thinking 페이지가 그 구성을 다뤄요. 그리고 thinking 구성이 400 오류로 돌아오면 Thinking 문제 해결이 각 오류 메시지를 그에 맞는 해결책과 짝지어 줘요.

Thinking 출력 읽기

Thinking 표시 제어

thinking 구성의 display 필드는 API 응답에서 thinking 콘텐츠가 어떻게 반환될지 제어해요. display는 두 모드에서 모두 작동해요: type: "adaptive"type: "enabled" 옆에 설정하세요. 다음 값을 받아요:

  • "summarized": thinking 블록이 요약된 thinking 텍스트, 즉 Claude 추론의 읽을 수 있는 요약을 담아요. Claude Opus 4.6, Claude Sonnet 4.6 이하 모델에서 기본이에요.
  • "omitted": thinking 블록이 빈 thinking 필드로 반환돼요. signature 필드는 여전히 멀티턴 연속을 위한 암호화된 전체 thinking을 담아요(Thinking 암호화 참고). Claude Fable 5.1, Claude Mythos 5.1, Claude Fable 5, Claude Mythos 5, Claude Opus 5.5, Claude Opus 5, Claude Sonnet 5, Claude Opus 4.8, Claude Opus 4.7, Claude Mythos Preview에서 기본이에요.
  • "updates" (베타): reasoning 블록이 "omitted"처럼 빈 thinking 필드로 반환되고, 일부 모델이 도구 호출 사이에 쓰는 짧은 진행 업데이트가 읽을 수 있는 텍스트로 돌아와요. 베타 헤더 thinking-display-updates-2026-08-18이 필요해요.

애플리케이션이 thinking 콘텐츠를 사용자에게 보여주지 않는다면 display: "omitted"를 설정하세요. 주된 이점은 스트리밍 시 첫 텍스트 토큰까지 더 빨라지는 거예요: 서버가 thinking 토큰을 아예 스트리밍하지 않고 서명만 전달하므로 최종 텍스트 응답이 더 일찍 스트리밍되기 시작해요.

display: "omitted"를 쓰면 응답에 빈 thinking 필드를 가진 thinking 블록이 포함돼요:

{
  "content": [
    {
      "type": "thinking",
      "thinking": "",
      "signature": "EosnCkYICxIMMb3LzNrMu..."
    },
    {
      "type": "text",
      "text": "The answer is 12,231."
    }
  ]
}

생략된 thinking으로 작업할 때 다음을 명심하세요:

  • 여전히 전체 thinking 토큰에 대한 요금이 부과돼요. 생략은 지연 시간을 줄일 뿐 비용을 줄이지 않아요.
  • 멀티턴 대화에서 thinking 블록을 다시 전달한다면 그대로 전달하세요. 서버가 signature를 복호화해 프롬프트 구성을 위해 원래 thinking을 재구성해요(Thinking 블록 보존하기 참고). 왕복된 생략 블록의 thinking 필드에 넣은 텍스트는 무시돼요.
  • displaythinking.type: "disabled"와 함께 쓰면 무효예요(표시할 게 없으니까요).
  • thinking.type: "adaptive"를 쓰고 모델이 간단한 요청에 생각을 건너뛰면, display와 무관하게 thinking 블록이 생성되지 않아요.
  • display: "omitted"로 스트리밍하면 thinking 텍스트가 스트리밍되지 않아요. 각 thinking 블록은 빈 thinking 문자열을 가진 thinking_delta를 스트리밍한 다음 signature_delta를 스트리밍해요. display: "updates"에서는 진행 업데이트 블록만 텍스트를 담은 thinking_delta 이벤트를 스트리밍해요. 이벤트 순서는 Streaming thinking을 보세요.
`signature` 필드는 어떤 `display` 값을 설정하든 동일해요. 대화에서 턴 사이에 `display` 값을 바꾸는 것은 지원돼요.

Ruby SDK에서는 예시처럼 보통 해시가 display:를 받아요. 타입이 있는 ThinkingConfigAdaptive 클래스는 파라미터를 display_(Ruby의 Kernel#display를 가리지 않도록 끝에 밑줄)로 부르지만, 와이어 필드는 여전히 display예요.

요약된 thinking (Summarized thinking)

display"summarized"일 때 받는 thinking 텍스트는 Claude 전체 thinking 과정의 요약이지, 원시 사고 흐름이 아니에요. 요약된 thinking은 오용을 막으면서도 thinking의 완전한 지능적 이점을 제공해요. 어떤 display 설정도 원시 사고 흐름을 반환하지 않아요.

요약된 thinking으로 작업할 때 다음을 명심하세요:

  • 원래 요청이 생성한 전체 thinking 토큰에 대한 요금이 부과되지, 요약 토큰에 대한 게 아니에요. 청구되는 출력 토큰 수는 응답에서 보이는 토큰 수와 일치하지 않아요.
  • Claude Opus 4.6, Claude Sonnet 4.6 이하 모델에서는 thinking 출력의 처음 몇 줄이 더 장황해서, 프롬프트 엔지니어링 목적에 특히 유용한 상세 추론을 제공해요. Claude Mythos Preview는 첫 토큰부터 요약하므로 그 thinking 블록에는 이 장황한 머리말이 없어요.
  • 요약은 최소한의 지연 시간 추가로 Claude thinking 과정의 핵심 아이디어를 보존하므로, 요약은 도착하는 대로 스트리밍될 수 있어요.
  • 요약은 요청에서 대상으로 하는 모델과 다른 모델이 처리해요. thinking 모델은 요약된 출력을 보지 않아요.
  • Anthropic이 thinking 기능을 개선하려 하므로, 요약 동작은 바뀔 수 있어요.
전체 thinking 출력에 접근해야 하는 드문 경우에는 [Anthropic 영업팀에 문의](mailto:[email protected])하세요.

모델의 추론을 보려면 응답 텍스트에서 추론을 요청하는 대신 thinking 블록을 읽으세요. Claude Fable 5.1, Claude Opus 5.5, Claude Fable 5에서 응답 텍스트의 일부로 모델의 내부 추론을 이끌어내려는 요청은 stop_details.category: "reasoning_extraction"으로 거부될 수 있어요. 필드 참조와 처리 지침은 거부 범주를 보세요.

Streaming thinking

Thinking은 스트리밍과 함께 작동해요. thinking 블록은 content_block_delta 이벤트 안의 thinking_delta 이벤트로 스트리밍되고, 블록의 content_block_stop 직전에 단일 signature_delta 이벤트가 뒤따라요. 텍스트 블록은 그다음 평소처럼 스트리밍돼요.

thinking과 함께하는 스트리밍 이벤트 순서 다이어그램: thinking 블록이 열리고, thinking 델타는 display 설정이 텍스트를 반환할 때만 텍스트를 담으며(summarized, 혹은 진행 업데이트 블록용 updates), 단일 서명 델타가 블록을 닫고, 그다음 텍스트 델타가 스트리밍됩니다

다음 예시들은 적응형 thinking으로 응답을 스트리밍하며, thinking과 텍스트 델타가 도착하는 대로 출력해요:

```bash cURL curl https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-opus-4-8", "max_tokens": 16000, "stream": true, "thinking": { "type": "adaptive", "display": "summarized" }, "messages": [ { "role": "user", "content": "What is the greatest common divisor of 1071 and 462?" } ] }' ```
ant messages create \
  --model claude-opus-4-8 \
  --max-tokens 16000 \
  --thinking '{type: adaptive, display: summarized}' \
  --message '{role: user, content: "What is the greatest common divisor of 1071 and 462?"}' \
  --stream \
  --format jsonl
client = anthropic.Anthropic()

with client.messages.stream(
    model="claude-opus-4-8",
    max_tokens=16000,
    thinking={"type": "adaptive", "display": "summarized"},
    messages=[
        {
            "role": "user",
            "content": "What is the greatest common divisor of 1071 and 462?",
        }
    ],
) as stream:
    for event in stream:
        match event.type:
            case "content_block_start":
                print(f"\nStarting {event.content_block.type} block...")
            case "content_block_delta":
                delta = event.delta
                match delta.type:
                    case "thinking_delta":
                        print(delta.thinking, end="", flush=True)
                    case "text_delta":
                        print(delta.text, end="", flush=True)
const client = new Anthropic();

const stream = client.messages.stream({
  model: "claude-opus-4-8",
  max_tokens: 16000,
  thinking: { type: "adaptive", display: "summarized" },
  messages: [{ role: "user", content: "What is the greatest common divisor of 1071 and 462?" }]
});

for await (const event of stream) {
  switch (event.type) {
    case "content_block_start":
      console.log(`\nStarting ${event.content_block.type} block...`);
      break;
    case "content_block_delta":
      switch (event.delta.type) {
        case "thinking_delta":
          process.stdout.write(event.delta.thinking);
          break;
        case "text_delta":
          process.stdout.write(event.delta.text);
          break;
      }
      break;
  }
}
AnthropicClient client = new();

var parameters = new MessageCreateParams
{
    Model = Model.ClaudeOpus4_8,
    MaxTokens = 16000,
    Thinking = new ThinkingConfigAdaptive { Display = Display.Summarized },
    Messages = [new() { Role = Role.User, Content = "What is the greatest common divisor of 1071 and 462?" }]
};

await foreach (var rawEvent in client.Messages.CreateStreaming(parameters))
{
    if (rawEvent.TryPickContentBlockStart(out var start))
    {
        Console.WriteLine($"\nStarting {start.ContentBlock.Type} block...");
    }
    else if (rawEvent.TryPickContentBlockDelta(out var delta))
    {
        if (delta.Delta.TryPickThinking(out var thinkingDelta))
        {
            Console.Write(thinkingDelta.Thinking);
        }
        else if (delta.Delta.TryPickText(out var textDelta))
        {
            Console.Write(textDelta.Text);
        }
    }
}
client := anthropic.NewClient()

stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
	Model:     anthropic.ModelClaudeOpus4_8,
	MaxTokens: 16000,
	Thinking: anthropic.ThinkingConfigParamUnion{
		OfAdaptive: &anthropic.ThinkingConfigAdaptiveParam{
			Display: anthropic.ThinkingConfigAdaptiveDisplaySummarized,
		},
	},
	Messages: []anthropic.MessageParam{
		anthropic.NewUserMessage(anthropic.NewTextBlock("What is the greatest common divisor of 1071 and 462?")),
	},
})

for stream.Next() {
	event := stream.Current()
	switch eventVariant := event.AsAny().(type) {
	case anthropic.ContentBlockStartEvent:
		fmt.Printf("\nStarting %s block...\n", eventVariant.ContentBlock.Type)
	case anthropic.ContentBlockDeltaEvent:
		switch deltaVariant := eventVariant.Delta.AsAny().(type) {
		case anthropic.ThinkingDelta:
			fmt.Print(deltaVariant.Thinking)
		case anthropic.TextDelta:
			fmt.Print(deltaVariant.Text)
		}
	}
}
if err := stream.Err(); err != nil {
	log.Fatal(err)
}
import com.anthropic.models.messages.ThinkingConfigAdaptive;

void main() {
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();

    MessageCreateParams params = MessageCreateParams.builder()
        .model(Model.CLAUDE_OPUS_4_8)
        .maxTokens(16000L)
        .thinking(ThinkingConfigAdaptive.builder()
            .display(ThinkingConfigAdaptive.Display.SUMMARIZED)
            .build())
        .addUserMessage("What is the greatest common divisor of 1071 and 462?")
        .build();

    try (var streamResponse = client.messages().createStreaming(params)) {
        streamResponse.stream().forEach(event -> {
            switch (event.type().value()) {
                case CONTENT_BLOCK_START -> {
                    var startEvent = event.asContentBlockStart();
                    var block = startEvent.contentBlock();
                    switch (block.type().value()) {
                        case THINKING -> IO.println("\nStarting thinking block...");
                        case TEXT -> IO.println("\nStarting text block...");
                    }
                }
                case CONTENT_BLOCK_DELTA -> {
                    var deltaEvent = event.asContentBlockDelta();
                    deltaEvent.delta().thinking().ifPresent(td ->
                        IO.print(td.thinking())
                    );
                    deltaEvent.delta().text().ifPresent(td ->
                        IO.print(td.text())
                    );
                }
            }
        });
    }
}
use Anthropic\Messages\RawContentBlockDeltaEvent;
use Anthropic\Messages\RawContentBlockStartEvent;
use Anthropic\Messages\TextDelta;
use Anthropic\Messages\ThinkingDelta;

$client = new Client();

$stream = $client->messages->createStream(
    maxTokens: 16000,
    messages: [
        ['role' => 'user', 'content' => 'What is the greatest common divisor of 1071 and 462?']
    ],
    model: 'claude-opus-4-8',
    thinking: ['type' => 'adaptive', 'display' => 'summarized'],
);

foreach ($stream as $event) {
    switch (true) {
        case $event instanceof RawContentBlockStartEvent:
            echo "\nStarting {$event->contentBlock->type} block...\n";
            break;
        case $event instanceof RawContentBlockDeltaEvent:
            switch (true) {
                case $event->delta instanceof ThinkingDelta:
                    echo $event->delta->thinking;
                    break;
                case $event->delta instanceof TextDelta:
                    echo $event->delta->text;
                    break;
            }
            break;
    }
}
client = Anthropic::Client.new

stream = client.messages.stream(
  model: "claude-opus-4-8",
  max_tokens: 16000,
  thinking: { type: "adaptive", display: "summarized" },
  messages: [
    { role: "user", content: "What is the greatest common divisor of 1071 and 462?" }
  ]
)

stream.each do |event|
  case event
  when Anthropic::Streaming::ThinkingEvent
    print event.thinking
  when Anthropic::Streaming::TextEvent
    print event.text
  end
end

스트리밍 후 서명과 함께 완전한 thinking 블록을 재조립하려면, 직접 델타를 이어 붙이는 대신 SDK의 메시지 누적 헬퍼(예: Python의 stream.get_final_message(), TypeScript의 stream.finalMessage())를 사용하세요.

```sse Output event: message_start data: {"type": "message_start", "message": {"id": "msg_01...", "type": "message", "role": "assistant", "content": [], "model": "claude-opus-4-8", "stop_reason": null, "stop_sequence": null}}

event: content_block_start data: {"type": "content_block_start", "index": 0, "content_block": {"type": "thinking", "thinking": "", "signature": ""}}

event: content_block_delta data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "I need to find the GCD of 1071 and 462 using the Euclidean algorithm.\n\n1071 = 2 × 462 + 147"}}

event: content_block_delta data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "\n462 = 3 × 147 + 21\n147 = 7 × 21 + 0\n\nSo GCD(1071, 462) = 21"}}

// Additional thinking deltas...

event: content_block_delta data: {"type": "content_block_delta", "index": 0, "delta": {"type": "signature_delta", "signature": "EqQBCgIYAhIM1gbcDa9GJwZA2b..."}}

event: content_block_stop data: {"type": "content_block_stop", "index": 0}

event: content_block_start data: {"type": "content_block_start", "index": 1, "content_block": {"type": "text", "text": ""}}

event: content_block_delta data: {"type": "content_block_delta", "index": 1, "delta": {"type": "text_delta", "text": "The greatest common divisor of 1071 and 462 is 21."}}

// Additional text deltas...

event: content_block_stop data: {"type": "content_block_stop", "index": 1}

event: message_delta data: {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence": null}}

event: message_stop data: {"type": "message_stop"}

</Accordion>

`display: "omitted"`를 설정하면 thinking 블록이 열리고, 빈 `thinking` 문자열을 가진 `thinking_delta`가 도착하며, 단일 `signature_delta`가 뒤따르고 블록이 닫혀요. 텍스트 스트리밍은 그 직후 시작돼요:

```sse Output
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"EosnCkYICxIMMb3LzNrMu..."}}

event: content_block_stop
data: {"type":"content_block_stop","index":0}

event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}}

display: "updates"(베타)에서는 reasoning 블록이 "omitted" 아래처럼 스트리밍돼요. 각 진행 업데이트 블록은 그것이 소개하는 tool_use 블록 앞에서 thinking_delta 이벤트로 텍스트를 스트리밍해요. 진행 업데이트 블록이 열리기 전 몇 초의 멈춤은 정상이에요:

event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"thinking","thinking":"","signature":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"thinking_delta","thinking":"Confirmed the retry path never refreshes the expired token. Editing auth.py to add the refresh call."}}

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"signature_delta","signature":"Es8CCkYICxIM..."}}

event: content_block_stop
data: {"type":"content_block_stop","index":1}

event: content_block_start
data: {"type":"content_block_start","index":2,"content_block":{"type":"tool_use","id":"toolu_01D7FLrfh4GYq7yT1ULFeyMV","name":"edit_file","input":{}}}

"updates" 아래에서는 그 thinking_delta 이벤트 중 하나가 비어 있지 않은 텍스트를 담는 순간 블록을 진행 업데이트로 취급하세요.

thinking을 켜고 스트리밍하면 텍스트가 더 큰 덩어리로, 토큰 단위 배달과 번갈아 도착하는 경우가 있어요. 이는 특히 thinking 콘텐츠에서 기대되는 동작이에요.

스트리밍 시스템은 콘텐츠를 배치로 처리하므로, 스트리밍 이벤트를 지연시키고 이 "덩어리" 배달 패턴으로 묶을 수 있어요.

일반적인 스트리밍 메커니즘은 Streaming Messages를 보세요.

Thinking과 effort

thinking 파라미터는 Claude가 답하기 전에 thinking 블록으로 생각할지 제어하고, effort 파라미터는 Claude가 전체 응답에 얼마나 많은 작업을 들일지 제어해요. adaptive 모드에서는 여기엔 생각하는 빈도와 깊이도 포함돼요. effort 값으로 adaptive를 전달하지 마세요: adaptive는 thinking 모드이지 effort 수준이 아니에요.

각 effort 수준이 thinking 동작에 무엇을 하는지 배우려면 Steering thinking 페이지의 수준별 thinking 동작 표를 보세요. Effort 페이지는 그 파라미터 자체를 문서화하는데, 각 모델이 지원하는 수준도 포함해요. effort를 지원하는 유일한 확장 thinking 전용 모델인 Claude Opus 4.5에서는 effort가 budget_tokens와 결합돼요. 예산 규칙과 튜닝을 보세요.

두 제어가 이렇게 분리되어 있으니 목표에 맞는 것을 고르세요:

  • thinking 활성 워크로드에서 비용이나 지연 시간 낮추기: effort를 먼저 낮추세요. thinking을 포함한 전체 응답이 축소돼요.
  • Claude가 너무 드물게 혹은 너무 얕게 생각한다면: effort를 올리거나, 스티어링 페이지의 Claude가 생각하는 빈도 조절하기를 보세요.
  • thinking을 완전히 꺼야 한다면: 허용하는 모델에서 thinking: {type: "disabled"}를 쓰세요(모델별 구성 표 참고).
  • 지출의 확실한 상한이 필요하다면: max_tokens을 쓰세요. Effort는 부드러운 지침이에요. max_tokens은 엄격한 한계예요.

도구 사용과 thinking

Thinking은 도구 사용과 함께 작동해서, Claude가 도구 선택을 추론하고 도구 결과를 처리하게 해요. 두 가지 제약이 적용돼요:

  1. 도구 선택 제한(수동 모드): 수동 확장 thinking(thinking: {type: "enabled"})과 함께하는 도구 사용은 tool_choice: {"type": "auto"}(기본)나 tool_choice: {"type": "none"}만 지원해요. tool_choice: {"type": "any"}tool_choice: {"type": "tool", "name": "..."}를 쓰면 오류가 나는데, 이 옵션들은 도구 사용을 강제해서 수동 확장 thinking과 호환되지 않거든요. 적응형 thinking(기본으로 thinking이 켜진 모델 포함)은 강제 도구 사용을 지원하지만, Claude Opus 5.5, Claude Fable 5.1, Claude Mythos 5.1은 예외예요(응답 프리필과 강제 도구 사용 참고).
  2. Thinking 블록 보존하기: 도구 결과를 반환할 때 어시스턴트 메시지의 thinking 블록을 완전하고 수정 없이 API에 다시 전달해야 해요. Thinking 블록 보존하기를 보세요.

도구 사용 루프는 하나의 어시스턴트 턴이에요. 모델 관점에서 어시스턴트 턴은 Claude가 전체 응답(여러 도구 호출과 결과 포함)을 끝낼 때까지 완료되지 않아요. 이 전체 순서가 하나의 어시스턴트 턴이에요:

User: "What's the weather in Paris?"
Assistant: [thinking] + [tool_use: get_weather]
User: [tool_result: "20°C, sunny"]
Assistant: [text: "The weather in Paris is 20°C and sunny"]

전체 턴은 단일 thinking 모드로 실행돼요: 턴 중간(도구 사용 루프 포함)에는 thinking을 토글할 수 없어요. 확장(수동) 모드에서 API는 추가로 thinking 지원 요청의 마지막 어시스턴트 턴이 thinking 블록으로 시작하도록 강제해요. 적응형 모드는 이걸 완화해요: 어느 어시스턴트 턴도 thinking 블록으로 시작할 필요가 없어요.

턴 중간 충돌은 우아하게 저하돼요. 턴 중간에 thinking을 토글하면(예: 도구 호출을 보내고 결과를 반환하는 사이), API는 오류를 내지 않아요. 대신 그 요청에 대해 thinking을 조용히 비활성화해요. 모델 품질을 보존하기 위해 API는 잘못된 턴 구조를 만들 thinking 블록을 제거하거나, 대화 기록이 thinking 활성화와 호환되지 않을 때 thinking을 비활성화할 수 있어요. thinking이 활성화되었는지 확인하려면 응답에 thinking 블록이 있는지 보세요.

턴 사이가 아니라 턴 사이에 토글하세요. 각 턴 시작에 thinking 전략을 계획하세요. 어시스턴트 턴을 끝낸 뒤, 다음 턴의 thinking 구성을 바꾸세요:

User: "What's the weather?"
Assistant: [tool_use] (thinking disabled)
User: [tool_result]
Assistant: [text: "It's sunny"]
User: "What about tomorrow?"
Assistant: [thinking] + [text: "..."] (thinking enabled - new turn)

thinking 모드를 토글하면 프롬프트 캐싱도 무효화돼요. Thinking과 프롬프트 캐싱을 보세요.

Thinking 블록 보존하기

Claude가 도구를 호출하면 외부 정보를 기다리기 위해 응답 구성을 일시 중지해요. 도구 결과를 반환하면 Claude는 그 같은 응답을 계속 만드므로, 이전 추론이 여전히 존재해야 해요. 모든 thinking 블록을 그것이 동반했던 tool_use 블록과 함께 완전하고 수정 없이 API에 다시 전달하세요. 이는 두 가지 이유로 중요해요:

  1. 추론 연속성: thinking 블록은 도구 요청으로 이끈 단계별 추론을 담아요. 그것을 포함하면 Claude가 멈춘 지점부터 추론을 계속할 수 있어요.
  2. 컨텍스트 유지: 도구 결과는 API 구조에서 사용자 메시지로 나타나지만, 하나의 연속된 추론 흐름의 일부예요. thinking 블록을 보존하면 그 흐름을 API 호출 간에 유지해요.

요약하면:

  • 필수: 도구 사용 턴 안에서는 thinking 블록을 다시 전달하세요.
  • 권장: 턴을 넘어 모든 것을 다시 전달하세요.
  • 허용: 도구 사용 밖에서는 이전 턴의 thinking을 생략해도 돼요.

오래된 thinking을 직접 정리할 필요는 없어요. 멀티턴 대화에서 모든 thinking 블록을 다시 전달하면, API가 자동으로 그것을 걸러내고 모델의 추론을 보존하는 데 필요한 블록만 유지하며, Claude에게 실제로 보여지는 블록에 대해서만 입력 토큰을 청구해요. 어느 이전 턴 블록이 유지될지는 모델마다 달라요. 모델별 thinking 블록 보존을 보세요. 기본값을 덮어쓰려면 clear_thinking_20251015 컨텍스트 편집 전략을 쓰세요.

최신 어시스턴트 메시지 안에서 연속된 thinking 블록의 순서는 모델이 원래 요청에서 생성한 것과 일치해야 해요: 재배열하거나, 편집하거나, 부분적으로 버릴 수 없어요. 여기엔 redacted_thinking 블록도 포함돼요.

수정된 thinking 블록은 400 오류로 거부돼요. 정확한 메시지, 흔한 원인, 해결책은 [400 오류가 thinking 블록을 수정할 수 없다고 말합니다](https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting#error-thinking-blocks-modified)를 보세요. 예외가 하나 있어요: [생략된](https://platform.claude.com/docs/en/build-with-claude/thinking#controlling-thinking-display) 블록의 빈 `thinking` 필드에 넣은 텍스트는 거부되는 대신 무시돼요.

모든 SDK의 코드와 함께 완전한 2턴 워크스루는 도구 및 멀티턴 워크플로에서의 thinking을 보세요. 거기서 도구를 정의하고, thinking과 도구 사용 응답을 받고, 도구 결과와 함께 어시스턴트 턴을 그대로 돌려보내요.

인터리브된 thinking (Interleaved thinking)

인터리브된 thinking은 Claude가 도구 호출 사이에 생각하게 해서, 각 도구 결과에 대해 행동하기 전에 추론하게 해요. 인터리브된 thinking으로 Claude는:

  • 도구 호출 결과에 대해, 다음에 무엇을 할지 결정하기 전에 추론해요
  • 사이에 추론 단계와 함께 여러 도구 호출을 이어요
  • 중간 결과를 바탕으로 더 섬세한 결정을 내려요
연속적인 도구 호출에 인터리브된 thinking이 필요한 건 아니에요. Claude는 인터리브된 thinking 유무와 무관하게 도구 호출을 이을 수 있어요. 인터리빙은 thinking 블록이 도구 호출 사이 어디에 나타나는지를 바꿀 뿐, 도구 호출이 이어질 수 있는지는 바꾸지 않아요.

적응형 thinking을 쓰면 인터리브된 thinking은 적응형 thinking을 지원하는 모든 모델에서 자동이에요. 베타 헤더가 필요 없어요. Claude Fable 5.1, Claude Mythos 5.1, Claude Fable 5, Claude Mythos 5, Claude Mythos Preview, Claude Opus 5.5, Claude Opus 5, Claude Opus 4.8, Claude Opus 4.7에서는 도구 호출 사이의 추론이 항상 thinking 블록에 나타나요. Claude Haiku 4.5는 인터리브된 thinking을 지원하지 않아요. 수동 확장 thinking 모델에서는 인터리빙에 베타 헤더가 필요하고 thinking 예산 계산 방식이 바뀌어요. 수동 모드에서의 인터리브된 thinking이 모델별 규칙과 플랫폼별 헤더 동작을 다뤄요.

인터리브된 thinking을 쓰면 thinking 할당이 단일 응답이 아니라 전체 어시스턴트 턴에 걸칠 수 있어요. 인터리브된 thinking은 Messages API를 통해 사용되는 도구에서만 지원돼요.

2개 도구 워크플로에서 인터리브된 thinking이 무엇을 바꾸는지 비교해 보려면 인터리브된 thinking이 흐름을 바꾸는 방식을 보세요.

도구 호출 사이의 진행 업데이트

Claude Fable 5.1, Claude Mythos 5.1, Claude Opus 5.5, Claude Fable 5에서 모델은 도구 호출 사이에 진행 업데이트를 쓸 수 있어요. 진행 업데이트는 모델이 방금 찾은 것과 다음에 하려는 것에 대한 한두 문장으로, 에이전트를 지켜보는 사람을 위해 쓰지 추론으로 쓰지 않아요. 각각은 같은 지점의 reasoning 블록과 분리된, 자신만의 signature를 가진 자신만의 thinking 블록으로 돌아와요. 그것이 소개하는 tool_useserver_tool_use 블록 바로 앞에 놓여요. 각 도구 호출 앞에는 진행 업데이트가 최대 하나이고, 모델은 그중 아무거나 건너뛸 수 있어요. 진행 업데이트는 인터리브된 thinking이 아니에요: 도구 호출 사이에 reasoning 블록이 나타나는지와 무관하게 나타나고, 응답이 둘 다 담을 수 있어요.

진행 업데이트 블록이 담는 것은 display에 따라 달라져요:

display Reasoning 블록 진행 업데이트 블록
"omitted" (이 모델들의 기본) thinking 필드 thinking 필드
"updates" (베타) thinking 필드 요약 텍스트
"summarized" 요약 텍스트 요약 텍스트, reasoning 블록과 구분할 수 없음

에이전트 인터페이스에서 reasoning을 숨기고 각 단계에서 사용자에게 상태 줄을 보여주려면 display: "updates"를 쓰세요. 그것 아래에서 비어 있지 않은 텍스트를 가진 thinking 블록은 모두 진행 업데이트이므로, 그것들만 렌더링하세요. 베타이며 베타 헤더 thinking-display-updates-2026-08-18이 필요해요(Amazon Bedrock, Google Cloud, Microsoft Foundry에서는 Beta headers에 설명된 대로 베타 값을 전달하세요). 그것 없이는 알 수 없는 display 값과 같은 400 invalid_request_error로 거부돼요.

{
  "model": "claude-fable-5-1",
  "max_tokens": 16000,
  "thinking": { "type": "adaptive", "display": "updates" },
  "tools": [
    {
      "name": "edit_file",
      "description": "Replace the contents of a file in the repository.",
      "input_schema": {
        "type": "object",
        "properties": {
          "path": { "type": "string" },
          "content": { "type": "string" }
        },
        "required": ["path", "content"]
      }
    }
  ],
  "messages": [
    {
      "role": "user",
      "content": "The login test fails after an hour of uptime. Find out why and fix it."
    }
  ]
}

"updates" 아래에서 tool_result 뒤에 이어지는 응답의 시작은 이렇게 생겼어요. 첫 블록은 reasoning이고, "omitted" 아래처럼 비어 있어요. 둘째는 텍스트를 담으므로 진행 업데이트예요. "summarized" 아래에서는 두 블록 모두 텍스트를 담고, "omitted" 아래에서는 둘 다 비어 있어요.

{
  "content": [
    {
      "type": "thinking",
      "thinking": "",
      "signature": "EqMBCkYICxIM..."
    },
    {
      "type": "thinking",
      "thinking": "Confirmed the retry path never refreshes the expired token. Editing auth.py to add the refresh call.",
      "signature": "Es8CCkYICxIM..."
    },
    {
      "type": "tool_use",
      "id": "toolu_01D7FLrfh4GYq7yT1ULFeyMV",
      "name": "edit_file",
      "input": { "path": "auth.py", "content": "..." }
    }
  ]
}

진행 업데이트로 작업할 때 다음을 명심하세요:

  • 진행 업데이트 블록을 어시스턴트 턴의 나머지와 함께 변경 없이 다시 전달하세요. 다른 thinking 블록처럼요.
  • 받는 텍스트는 진행 업데이트의 요약인데, 보통 한두 문장이에요. 그 길이에 의존하지 마세요. 진행 업데이트는 요약 길이로가 아니라 전체 길이로 usage.output_tokens에 포함돼요.
  • 진행 업데이트 블록은 어떤 display 값에서도 빈 thinking 필드로 돌아올 수 있어요. 빈 블록에는 아무것도 렌더링하지 마세요. "updates" 아래에서는 빈 reasoning 블록과 똑같이 보여서 별도 처리가 필요 없어요.
  • 응답이 max_tokens, model_context_window_exceeded, stop_sequence로 도구 호출이나 도구 결과 직후 멈추면, 그 마지막 블록은 모델이 끝내지 못한 작업을 대신하는 진행 업데이트 블록일 수 있어요. "updates""summarized" 아래에서는 그 텍스트가 정확히 This part of the response was interrupted before it finished.이고 다른 업데이트처럼 보여주면 돼요. "omitted" 아래에서는 비어 있어요. 계속하려면 어시스턴트 턴을 그대로 다시 전달하고, 새 user 메시지(그 턴의 각 tool_use 블록에 대한 tool_result 포함)를 덧붙이세요.
  • 스트리밍할 때는 진행 업데이트 블록이 열리기 전 몇 초의 멈춤을 기대하세요. Streaming thinking"updates" 트레이스를 보세요.
  • 이 모델들은 더 높은 effort와 긴 도구 체인에서 더 적은 진행 업데이트를 써요. 인터페이스가 그것에 의존한다면 사용자용 진행 업데이트 요청하기나, Claude Opus 5.5의 경우 사용자용 진행 업데이트를 보세요.

모델별 thinking 블록 보존

이전 어시스턴트 턴의 thinking 블록이 컨텍스트에 남는지는 기본적으로 모델에 따라 달라요:

  • 모든 이전 턴 유지: Claude Opus 4.5 이후 Opus 모델, Claude Sonnet 4.6 이후 Sonnet 모델, Claude Fable 5.1, Claude Mythos 5.1, Claude Fable 5, Claude Mythos 5, Claude Mythos Preview.
  • 마지막 턴만 유지: 이전 Opus·Sonnet 모델과 Claude Haiku 4.5까지의 모든 Haiku 모델. 오래된 thinking 블록을 다시 전달하면 API가 자동으로 제거해요. 직접 없앨 필요가 없어요.

보존은 두 가지 이점을 가져와요:

  • 캐시 최적화: 보존된 thinking 블록은 도구 사용 중 캐시 적중을 가능하게 해요. 도구 결과와 함께 다시 전달되고 어시스턴트 턴 전체에 걸쳐 점진적으로 캐시되므로, 다단계 워크플로에서 토큰을 절약해요.
  • 지능 영향 없음: thinking 블록을 보존해도 모델 성능에 부정적 영향이 없어요.

절충은 컨텍스트 사용이에요: 유지-전부 모델에서 긴 대화는 더 많은 컨텍스트 공간을 소비하는데, 보존된 thinking 블록이 다른 대화 기록처럼 입력으로 세기 때문이에요(Thinking과 컨텍스트 창 참고). 두 체제 모두에서 동작은 자동이에요. 코드 변경이나 베타 헤더가 필요 없고, Thinking 블록 보존하기에 설명된 대로 완전하고 수정 없는 thinking 블록을 계속 다시 전달하면 돼요. 어느 방향이든 기본값을 덮어쓰려면 thinking 블록 지우기를 쓰세요.

대화 중간에 모델 바꾸기. 모델을 바꿀 때 thinking 블록을 그대로 다시 전달하세요. 예를 들어 분류기 거부 폴백 후처럼요. thinking 블록은 그것을 만든 모델과 특정 다른 모델만 읽을 수 있고, API는 대상 모델이 읽을 수 없는 블록을 무시하거나 버려요. Claude Fable 5.1과 Claude Mythos 5.1에서는 방향이 중요해요: 뒤로 전환하면 이전 턴들의 추론을 유지하고(이전 모델들이 읽을 수 없더라도), 아래로 전환하면(Claude Fable 5.1에서 Claude Opus 5.5로) 이전 턴들의 추론을 버려요. 대신 턴 기록을 정리하거나, 바꿀 때 기존 추론을 이어가고 싶다면 모델 전환에서 논의된 대로 처리하세요.

보존된 thinking (Preserved thinking)

보존된 thinking은 모델이 이전 턴에서 다시 전달한 thinking 블록을 사용할 수 있는지 결정해요. Claude Fable 5.1부터 API는 요청의 모든 thinking·redacted_thinking 블록의 signature를 두 가지에 대해 검사해요:

  • 그것을 만든 모델. 각 모델은 자신의 thinking 블록과 고정된 다른 모델 집합의 것만 읽어요. Claude Fable 5.1은 Claude Opus 5의 블록을 읽고, Claude API에서는 Claude Opus 5.5의 블록도 읽어요. 그러나 Claude Opus 5도 Claude Opus 5.5도 Claude Fable 5.1의 블록은 읽지 못해요. API는 현재 모델이 읽을 수 없는 블록을 오류 없이, 청구 없이 버려요. 대화 중간에 모델 바꾸기를 보세요.
  • 그 앞에 보낸 모든 것. 블록은 최상위 system 프롬프트, tools, 그리고 그 앞의 메시지들이 바뀌지 않은 동안에만 유효해요. 그중 하나라도 바뀌면 그 블록과 이후의 모든 thinking 블록이 무효가 되고, API는 요청을 400 오류로 거부하거나 무효 블록을 버려요(이 중 하나를 선택해요). 접두사 변경 없이 유지하기를 보세요.

모델 검사는 모든 계정에 적용돼요. API는 2026년 8월 31일 00:00 UTC 이후 생성된 계정에서 접두사 검사를 기본으로 시행해요. 더 오래된 계정에서는 thinking.block_binding.prefix_mismatch_behavior를 설정한 요청에만 검사를 시행해요. 계정 나이와 무관하게 통합을 append-only로 만들어, 같은 코드가 모든 계정(기본으로 시행되는 신규 계정 포함)에서 작동하게 하세요.

thinking을 유효하게 유지하려면 받은 어시스턴트 턴을 정확히 그대로 다시 보내고, messages 끝에만 새 메시지를 추가하세요. messages 배열을 직접 만드는 코드라면 Preserved thinking 페이지가 다음을 다뤄요:

Thinking과 프롬프트 캐싱

프롬프트 캐싱은 thinking과 몇 가지 특정 방식으로 상호작용해요. 다음 규칙은 두 thinking 모드 모두에 적용돼요.

구성 변경은 캐싱을 무효화해요. thinking 구성과 해석된 effort 수준은 프롬프트 자체로 렌더링되므로, 그중 하나를 바꾸면 새 캐시 접두사가 시작돼요. adaptive, enabled, disabled 사이를 바꾸고, budget_tokens를 바꾸고, effort 값을 바꾸는 것 모두 캐시 중단점을 무효화해요: 메시지 수준 중단점은 항상 miss하고, 도구·시스템 프롬프트 중단점은 모델이 구성을 렌더링하는 위치에 따라 miss할 수도 있어요. thinking이나 최상위 effort 변경을 캐시를 다시 시작하는 것으로 취급하세요. 메시지별 effort를 지원하는 모델에서는 messages 안의 role: "system" 메시지로 전달된 effort 변경이 캐시된 접두사를 그대로 유지해요. 같은 구성을 유지하는 연속 요청은 캐시를 보존하고, 파라미터를 명시적으로 기본값으로 설정하는 것은 생략하는 것과 동등해요. API가 두 보존된 thinking 조건 중 하나에서 버린 thinking 블록은 그 블록 위치부터 캐시된 접두사를 바꿔요. 그대로 다시 전달된 블록은 캐시를 유지해요. 사용량 출력과 함께하는 실제 데모는 Steering thinking 페이지에 있어요.

Thinking 블록은 도구 결과와 함께 캐시돼요. 도구 사용 루프에서 도구 결과를 포함한 후속 요청을 만들 때 캐싱이 일어나요. 그 시점에 thinking 블록을 포함한 이전 대화 기록이 캐시될 수 있고, 캐시에서 읽을 때 그 캐시된 thinking 블록이 사용량 지표에서 입력 토큰으로 계산돼요. 이는 명시적인 cache_control 마커 없이도 자동으로 일어나고, 일반·인터리브 thinking에서 똑같이 동작해요. 절충: 응답에서 다시는 보지 못할 thinking 블록도 캐시에서 읽을 때 입력 토큰 사용량에 기여해요.

이전 블록이 컨텍스트에 있는지 자체가 모델별이에요. 보존 기본값이 이것을 관장해요. 유지-전부 모델에서는 이전 턴들의 thinking 블록이 캐시되고 컨텍스트에 남아요. 마지막 턴만 유지 모델에서는 도구 결과가 아닌 사용자 메시지를 보내면 모든 이전 thinking 블록이 컨텍스트에서 제거돼요. 그 모델들에서 이 같은 대화는:

User: ["What's the weather in Paris?"],
Assistant: [thinking_block_1] + [tool_use block 1],
User: [tool_result_1, cache=True],
Assistant: [thinking_block_2] + [text block 2],
User: [Text response, cache=True]

thinking 블록이 처음부터 없었던 것처럼 처리돼요:

User: ["What's the weather in Paris?"],
Assistant: [tool_use block 1],
User: [tool_result_1, cache=True],
Assistant: [text block 2],
User: [Text response, cache=True]

유지-전부 모델에서는 같은 요청이 thinking_block_1thinking_block_2를 컨텍스트와 캐시에 유지해요.

저하(디그레이드)는 캐시 가능한 기록에서 thinking을 뺍니다. 턴 중간에 thinking이 비활성화되고 현재 도구 사용 턴에 thinking 콘텐츠를 전달하면, thinking 콘텐츠는 제거되고 그 요청에서 thinking은 비활성화된 채로 유지돼요(우아한 저하 참고). 인터리브된 thinking은 thinking 블록이 여러 도구 호출 사이에 나타날 수 있으므로 캐시 무효화 효과를 증폭해요.

thinking이 많은 과제는 기본 5분 캐시 수명보다 오래 걸리는 경우가 많아요. [1시간 캐시 기간](https://platform.claude.com/docs/en/build-with-claude/prompt-caching#1-hour-cache-duration)을 고려해 더 긴 thinking 세션과 다단계 워크플로에서 캐시 적중을 유지하세요.

Thinking과 컨텍스트 창

현재 턴에서 Claude가 생성하는 모든 thinking을 포함하는 max_tokens은 엄격한 한계로 시행돼요. Claude 4.5 이상 모델에서 입력 토큰과 max_tokens의 합이 컨텍스트 창 크기를 초과하면 API는 요청을 받아들여요. 생성이 컨텍스트 창 한계에 도달하면 오류를 반환하는 대신 stop_reason: "model_context_window_exceeded"로 멈춰요. 이전 모델에서는 API가 검증 오류를 반환해요. Stop reasons 처리를 보세요.

thinking이 창에 얼마나 세는지는 생성된 시점에 따라 달라져요:

  • 현재 턴 thinking은 항상 max_tokens에 세고, 출력 토큰으로 청구되며, 그것을 생성한 턴의 컨텍스트 창 공간을 차지해요.
  • 이전 턴 thinking보존 기본값에 달려요. 모든 이전 턴을 유지하는 모델에서는 이전 thinking 블록이 컨텍스트에 남고, 창에 세며, 다른 대화 기록처럼 입력 토큰으로 청구돼요. 마지막 턴만 유지하는 모델에서는 다시 전달하면 API가 오래된 thinking 블록을 자동으로 제거하므로, 창 공간이나 입력 토큰을 소비하지 않아요.

실무에서는:

  • 유지-전부 모델에서는 thinking을 보통 대화 기록처럼 컨텍스트 창을 예산 잡으세요. (실제로 그렇거든요.) 긴 에이전트 세션은 컨텍스트에 thinking을 쌓아요. 공간을 되찾으려면 thinking 블록 지우기를 쓰세요.
  • 마지막 턴만 유지 모델에서는 thinking이 턴당 비용일 뿐이에요: 각 턴의 thinking은 그 턴의 max_tokens에 세고 나서 창에서 빠져요.

다음 다이어그램은 마지막 턴만 유지(제거) 체제를 보여줘요. 첫 번째는 멀티턴 대화를 보여줘요: 각 턴의 thinking 블록은 출력에서 생성되지만 이후 턴들의 입력으로 이어지지 않아요.

이전 thinking 블록을 제거하는 모델에서 thinking 다이어그램: 각 턴의 thinking 블록은 출력에서 생성되고 이후 턴들의 입력으로 이어지지 않습니다

두 번째는 도구 사용과 함께 같은 체제를 보여줘요: thinking은 어시스턴트 턴 지속 동안 도구 결과와 함께 컨텍스트에 남다가, 다음 사용자 턴에 빠져요.

이전 thinking 블록을 제거하는 모델에서 도구 사용과 함께하는 thinking 다이어그램: thinking은 도구 결과와 함께 유지되다가 다음 사용자 턴에 버려집니다

토큰 계산 API를 사용해 특정 사용 사례에 대한 정확한 수치를 얻으세요. 특히 thinking을 포함한 멀티턴 대화에서요.

Thinking 암호화

전체 thinking 콘텐츠는 암호화되어 각 thinking 블록의 signature 필드에 반환돼요. API는 서명을 사용해 thinking 블록이 다시 전달될 때 Claude가 생성했음을 검증해요.

서명으로 작업할 때 다음을 명심하세요:

  • thinking 블록을 다시 보내는 것은 도구와 함께 thinking을 쓸 때만 엄밀히 필요해요. 그 외에는 이전 턴의 thinking 블록을 생략할 수 있어요. 다시 전달한다면 API가 유지할지 제거할지는 모델에 따라 달라요(모델별 thinking 블록 보존 참고). 이를 구성하려면 컨텍스트 편집을 쓰세요.
  • thinking 블록을 다시 보낼 때는 일관성을 위해, 그리고 잠재적 문제를 피하기 위해 받은 그대로 정확히 전달하세요.
  • 응답 스트리밍 시 서명은 content_block_stop 이벤트 직전에 content_block_delta 이벤트 안의 signature_delta로 도착해요.
  • signature 값은 Claude 4 이후 모델에서 이전 모델보다 훨씬 길어요.
  • signature 필드는 불투명해요: 해석하거나 파싱하지 마세요.
  • signature 값은 플랫폼 간(Claude API, Amazon Bedrock, Google Cloud) 호환돼요. 한 플랫폼에서 생성한 값이 다른 플랫폼에서도 작동해요.

Redacted thinking 블록

일반 thinking 블록 외에도, Claude의 추론 일부가 안전상 수정(redacted)되면 API가 redacted_thinking 블록을 반환할 수 있어요. redacted_thinking 블록은 읽을 수 있는 텍스트 없이 data 필드에 암호화된 thinking 콘텐츠를 담아요:

{
  "type": "redacted_thinking",
  "data": "..."
}

data 필드는 불투명하고 암호화되어 있어요. 일반 thinking 블록의 signature 필드처럼, 도구와 함께 멀티턴 대화를 계속할 때 redacted_thinking 블록을 그대로 API에 다시 전달하세요.

코드가 도구 사용과 함께 응답을 왕복할 때 콘텐츠 블록을 타입으로 걸러낸다면(예: `block.type == "thinking"`), `redacted_thinking` 블록도 포함하세요. `block.type == "thinking"`만으로 걸러내면 `redacted_thinking` 블록을 조용히 놓쳐서 [Thinking 블록 보존하기](https://platform.claude.com/docs/en/build-with-claude/thinking#preserving-thinking-blocks)에 설명된 멀티턴 프로토콜을 깨뜨려요. `redacted_thinking` 블록은 thinking이 안전상 수정될 때 반환되는 별개의 콘텐츠 블록 타입이에요. 이는 빈 `thinking` 필드를 가진 일반 `thinking` 블록을 반환하는 [`display: "omitted"`](https://platform.claude.com/docs/en/build-with-claude/thinking#controlling-thinking-display) 옵션과는 별개예요.

한계와 기능 호환성

샘플링 파라미터

Claude Fable 5.1, Claude Mythos 5.1, Claude Fable 5, Claude Mythos 5, Claude Mythos Preview, Claude Opus 5.5, Claude Opus 5, Claude Opus 4.8, Claude Opus 4.7, Claude Sonnet 5에서는 기본이 아닌 temperature, top_p, top_k 값이 thinking 사용 여부와 무관하게 모든 요청에서 400 오류를 반환해요. 더 오래된 모델에서는 제한이 thinking이 켜져 있을 때만 적용돼요: temperaturetop_k는 thinking과 호환되지 않고, top_p는 0.95~1 사이 값만 허용돼요.

응답 프리필과 강제 도구 사용

thinking이 켜져 있는 동안 어시스턴트 응답을 프리필할 수 없어요. 강제 도구 사용(tool_choice: {"type": "any"}{"type": "tool", ...})은 수동 확장 thinking과 호환되지 않지만 적응형 thinking과는 작동해요. 예외는 Claude Opus 5.5, Claude Fable 5.1, Claude Mythos 5.1으로, 강제 도구 사용을 모든 요청에서 400 오류로 거부해요. 그 모델들에서는 엄격한 도구 사용이나 구조화된 출력과 함께 tool_choice: {"type": "auto"}를 쓰세요. 도구 사용과 thinking을 보세요.

출력 한계

각 모델은 여기 나열된 상한까지 max_tokens을 받아요. Message Batches API에서 output-300k-2026-03-24 베타 헤더는 batches 상한이 나열된 모델에 대해 그 상한을 올려요.

Model Max output tokens Batches beta ceiling
Claude Fable 5.1 128K
Claude Mythos 5.1 128K
Claude Fable 5 128K
Claude Mythos 5 128K
Claude Mythos Preview 128K Not available
Claude Opus 5.5 128K 300K
Claude Opus 5 128K 300K
Claude Opus 4.8 128K 300K
Claude Opus 4.7 128K 300K
Claude Opus 4.6 128K 300K
Claude Opus 4.5 64K Not available
Claude Sonnet 5 128K 300K
Claude Sonnet 4.6 128K 300K
Claude Sonnet 4.5 64K Not available
Claude Haiku 4.5 64K Not available

레거시 모델의 한계는 모델 개요를 보세요.

긴 요청

SDK는 max_tokens이 21,333보다 클 때 스트리밍을 요구해요. 이는 오래 걸리는 요청에서 HTTP 타임아웃을 피하기 위해서예요. 이는 클라이언트 측 검증이지, API 제한이 아니에요. 이벤트를 증분 처리할 필요가 없다면 개별 이벤트를 다루지 않고 완전한 Message 객체를 얻으려고 .stream().get_final_message()(Python)나 .finalMessage()(TypeScript)를 쓰세요. Streaming Messages를 보세요. thinking이 활성화되면 thinking 블록 생성이 처리 시간을 더하므로 더 긴 응답 시간을 기대하세요. thinking이 요청당 약 32k 토큰을 넘는 워크로드에는 배치 처리를 써서 네트워킹 문제를 피하세요: 그런 요청은 시스템 타임아웃과 열린 연결 한계에 닿을 만큼 오래 돌 수 있어요.

다음 단계

Steer how often and how deeply Claude thinks with effort levels, system prompt guidance, and per-message steering, and understand thinking's cost and pricing. Walk through a complete two-turn tool-use round trip that preserves thinking blocks correctly, and see how interleaved thinking changes the flow. Find out whether your Messages API integration edits conversation history, and replace each edit with the API feature that keeps earlier thinking blocks valid. Diagnose and fix the most common thinking failures: configuration 400 errors, empty or missing thinking blocks, max\_tokens stops, and cache misses. Control how many tokens Claude uses when responding with the effort parameter, trading off between response thoroughness and token efficiency.

더 알아보기 (Learn more)