컴팩션과 보존 thinking

컴팩션과 보존 thinking (Compaction and preserved thinking)

보존 thinking이 있는 모델에 thinking 블록을 다시 보내고 컴팩션 블록 뒤에 턴들을 유지할 때만 이 페이지를 보세요. 유지된 턴은 블록 뒤에 오는 턴들이에요: 최근 턴을 유지하는 컴팩션처럼 컴팩션 요청에서 뺀 최근 턴들, 또는 백그라운드 컴팩션처럼 요약이 작성되는 동안 도착한 턴들이에요.

출처: 문서

본문

보존 thinking이 있는 모델에 thinking 블록을 다시 보내고 컴팩션 블록 뒤에 턴들을 유지할 때만 이 페이지를 보세요. 유지된 턴은 블록 뒤에 오는 턴들이에요: 최근 턴을 유지하는 컴팩션처럼 컴팩션 요청에서 뺀 최근 턴들, 또는 백그라운드 컴팩션처럼 요약이 작성되는 동안 도착한 턴들이에요.

보존 thinking이 있는 모델은 이전 thinking 블록을 그것을 만든 대화와 대조해요. 요약이 그 대화의 일부를 대체하지만, API가 요약을 썼을 때 검사는 그 교체를 수용해요. 그래서 유지된 턴의 thinking은 유효하게 유지될 수 있어요.

유지된 thinking이 유효하게 유지되는 조건

유지된 턴의 thinking 블록은 다음이 모두 성립하는 동안 유효해요:

  • 컴팩션 요청이 보존 thinking이 있는 모델에서 실행됨. 이 조건은 thinking 블록이 생성된 이후의 모든 컴팩션 요청을 다뤄요. 최근 것만이 아니라요. 충족하는 한 방법은 모든 컴팩션 요청을 대화가 사용하는 모델로 보내는 거예요.
  • 유지된 턴이 요약된 메시지 바로 뒤를 따르고, 그대로 보냄. 각 유지된 메시지를 히스토리에 있는 그대로 보내세요. 마지막 요약 메시지와 첫 유지 메시지 사이의 메시지를 건너뛰거나 추가하지 마세요. 첫 유지 메시지는 또한 마지막 요약 메시지와 역할이 달라야 하고, 대화 중간 role: "system" 메시지일 수 없어요. 그렇지 않으면 API가 그것을 마지막 요약 메시지에 병합해요. 첫 유지 메시지를 올바르게 얻는 한 방법은 이미 보낸 요청의 messages를 정확히 컴팩션하는 거예요. 그러면 유지된 턴이 그에 대한 Claude의 응답으로 시작돼요.
  • systemdefer_loading: true로 표시되지 않은 tools가 변하지 않음. 그것들은 컴팩션 요청에서 유지된 thinking을 만든 요청들과 같고, 이후 요청에서도 같아요. 시스템 프롬프트나 도구 바꾸기가 안전하게 바꾸는 방법을 다뤄요.

조건이 성립하지 않아도 컴팩션할 때 아무것도 실패하지 않고, API는 이후 요청에서 블록을 어느 쪽이든 수용해요. 실패는 유지된 thinking을 보내는 첫 이후 요청에서, API가 검사를 적용하는 곳에서 발생해요: 기본적으로 400 오류, 또는 요청이 thinking.block_binding.prefix_mismatch_behavior"drop_block"으로 설정하면 thinking 블록을 버려요. Message Batches API에서는 필드를 설정하지 않은 항목이 실패하지 않아요. 기본적으로 검사가 적용되는 곳에서는 API가 그 대신 블록을 버려요. API가 잘못된 블록으로 하는 일이 두 결과를 설명하고, API가 검사를 적용하는 때가 어떤 요청이 검사되는지 말해요.

오래된 thinking을 깨지 않고 다시 컴팩션하기

다시 컴팩션하고 턴을 유지할 수 있어요. 새 블록은 컴팩션 요청의 옛 요약과 그 뒤의 모든 메시지를 덮고, 그 요청에서 뺀 모든 턴은 새 블록의 유지된 턴이 돼요.

유지된 thinking의 조건 중 첫 번째는 thinking 블록이 생성된 이후의 모든 컴팩션을 세므로, 두 번의 컴팩션을 통해 유지하는 턴은 둘 다 보존 thinking이 있는 모델에서 실행되어야 해요.

thinking 블록이 생성되기 전의 컴팩션은 그것에 대해 계산되지 않아요. 블록이 자리한 뒤 생성된 thinking은 그 블록에 묶여 있고, 조건을 충족하는 이후 컴팩션을 통해 유효하게 유지돼요.

시스템 프롬프트나 도구 바꾸기

이후 요청은 컴팩션 요청과 다른 system, 다른 tools, 다른 모델을 사용할 수 있고 API는 여전히 블록을 수용해요. 그런 변경은 유지된 턴의 thinking을 무효화할 수 있지만 다른 효과는 없어요.

유지된 thinking을 무효화하지 않고 system이나 tools를 바꾸려면 먼저 전체 대화를 컴팩션해서 유지되는 턴이 없게 하세요. 그런 다음 다음 요청에서 바꾸세요.

system이나 tools를 건드리지 않고 지침을 추가하거나 사용 가능한 도구를 바꾸려면 그 변경을 messages에 추가하세요. 접두사를 편집하지 않고 변경하기에서 설명해요.

요약된 턴 안의 대화 중간 시스템 메시지도 요약되므로, 교체 후에는 그 텍스트 지침이 더 이상 적용되지 않아요. 하나를 계속 시행하려면 유지된 턴 뒤를 따르는 첫 새 user 턴 바로 뒤에 role: "system" 메시지로 다시 진술하세요. 그 턴 안의 도구 변경은 컴팩션 요청도 inline-tools-2026-09-15를 운반할 때 스스로 이어져요. 반환된 블록은 그 순 효과를 tool_changes 필드에 기록하므로, 블록을 수정 없이 다시 보내세요. 블록에 tool_changes 필드가 없으면 그 도구 변경을 같은 방식으로 다시 진술하세요. 블록과 유지된 턴 사이에 놓인 시스템 메시지는 그들의 thinking을 깨뜨려요.

유지된 thinking이 유지됐는지 확인하기

컴팩션 응답은 유지된 thinking이 유지되는지 말하지 않아요. 교체 후의 첫 요청이 말해요. 테스트에서 확인하려면:

  1. thinking을 켜고 짧은 대화를 하세요. API가 검사를 실행하는 모델(API가 검사를 적용하는 때 참고)을 사용하고, 모든 단계에 그것을 사용하세요. thinking 블록을 읽을 수 없는 모델은 오류 없이 그것을 버리기 때문이에요.
  2. 오래된 턴을 컴팩션하고, thinking 블록을 보유한 턴을 최소 하나 유지하세요.
  3. 다음 요청을 보내세요. 블록을 먼저, 그 다음 유지된 턴, 그 다음 새 user 메시지와 함께, 그리고 thinking.block_binding.prefix_mismatch_behavior"error"로 설정해서요.
  4. 결과를 읽으세요. input_transformations 배열이 비어 있는 200 응답은 어떤 thinking 블록도 검사에 실패하거나 버려지지 않았단 뜻이에요. 블록이 다른 대화에 묶여 있다고 하는 400 오류는 하나가 실패했단 뜻이에요. 그 메시지는 실패한 첫 블록의 경로로 시작하고, API가 잘못된 블록으로 하는 일이 그것을 전체로 보여줘요.

prefix_mismatch_behavior 필드는 컴팩션 베타 헤더 compact-2026-09-04에 더해 thinking-binding-controls-2026-08-01 베타 헤더가 필요해요. 이 필드를 설정하는 것은 또한 검사가 기본으로 켜져 있지 않은 계정에서 검사에 요청을 옵트인해요.

다음 프로그램이 네 단계를 실행해요. 유지된 턴이 보유한 thinking 블록 수와 input_transformations 항목 수를 인쇄해요. 항목이 없으면 유지된 thinking이 유지된 거예요:

```python Python from anthropic.types.beta import BetaMessageParam, BetaThinkingConfigParam

client = anthropic.Anthropic()

Claude Fable 5.1 is the first model that checks sent-back thinking against the conversation.

MODEL = "claude-fable-5-1" BETAS = ["compact-2026-09-04", "thinking-binding-controls-2026-08-01"] SYSTEM = "You help plan a recipe app's release. Keep answers short."

With "error", a thinking block that fails the check makes the request fail with a 400.

THINKING: BetaThinkingConfigParam = { "type": "adaptive", "block_binding": {"prefix_mismatch_behavior": "error"}, }

1. Have a short conversation with thinking on.

history: list[BetaMessageParam] = [ {"role": "user", "content": "What are the main entities in the app's data model?"} ] first = client.beta.messages.create( model=MODEL, max_tokens=8192, system=SYSTEM, betas=BETAS, thinking=THINKING, messages=history, ) history += [ {"role": "assistant", "content": first.content}, { "role": "user", "content": "Testing starts on Tuesday, March 3, 2026, takes 10 weekdays, and pauses on March 9 and March 16. On which date does it end?", }, ] second = client.beta.messages.create( model=MODEL, max_tokens=8192, system=SYSTEM, betas=BETAS, thinking=THINKING, messages=history, ) history.append({"role": "assistant", "content": second.content}) thinking_blocks = sum(block.type == "thinking" for block in second.content) print(f"Thinking blocks in the kept turn: {thinking_blocks}")

2. Summarize the first turn. The second turn stays out of the request.

summary = client.beta.messages.create( model=MODEL, max_tokens=4096, system=SYSTEM, betas=BETAS, thinking=THINKING, messages=history[:2], compaction={"type": "summarize"}, ) if summary.stop_reason != "compaction": raise SystemExit(f"No summary: {summary.stop_reason}")

3. Put the block in front of the kept turn and ask the next question.

history = [ {"role": "assistant", "content": summary.content}, *history[2:], {"role": "user", "content": "Which day should the release go out?"}, ] third = client.beta.messages.create( model=MODEL, max_tokens=8192, system=SYSTEM, betas=BETAS, thinking=THINKING, messages=history, )

4. A 200 with no dropped blocks means the kept thinking held.

print(f"Dropped thinking blocks: {len(third.input_transformations)}")


```typescript TypeScript
const client = new Anthropic();

// Claude Fable 5.1 is the first model that checks sent-back thinking against the conversation.
const model: Anthropic.Model = "claude-fable-5-1";
const betas: Anthropic.Beta.AnthropicBeta[] = [
  "compact-2026-09-04",
  "thinking-binding-controls-2026-08-01"
];
const systemPrompt = "You help plan a recipe app's release. Keep answers short.";
// With "error", a thinking block that fails the check makes the request fail with a 400.
const thinking: Anthropic.Beta.Messages.BetaThinkingConfigParam = {
  type: "adaptive",
  block_binding: { prefix_mismatch_behavior: "error" }
};

// 1. Have a short conversation with thinking on.
let history: Anthropic.Beta.Messages.BetaMessageParam[] = [
  { role: "user", content: "What are the main entities in the app's data model?" }
];
const first = await client.beta.messages.create({
  model,
  max_tokens: 8192,
  system: systemPrompt,
  betas,
  thinking,
  messages: history
});
history.push(
  { role: "assistant", content: first.content },
  {
    role: "user",
    content:
      "Testing starts on Tuesday, March 3, 2026, takes 10 weekdays, and pauses on March 9 and March 16. On which date does it end?"
  }
);
const second = await client.beta.messages.create({
  model,
  max_tokens: 8192,
  system: systemPrompt,
  betas,
  thinking,
  messages: history
});
history.push({ role: "assistant", content: second.content });
const thinkingBlocks = second.content.filter((block) => block.type === "thinking").length;
console.log(`Thinking blocks in the kept turn: ${thinkingBlocks}`);

// 2. Summarize the first turn. The second turn stays out of the request.
const summary = await client.beta.messages.create({
  model,
  max_tokens: 4096,
  system: systemPrompt,
  betas,
  thinking,
  messages: history.slice(0, 2),
  compaction: { type: "summarize" }
});
if (summary.stop_reason !== "compaction") {
  throw new Error(`No summary: ${summary.stop_reason}`);
}

// 3. Put the block in front of the kept turn and ask the next question.
history = [
  { role: "assistant", content: summary.content },
  ...history.slice(2),
  { role: "user", content: "Which day should the release go out?" }
];
const third = await client.beta.messages.create({
  model,
  max_tokens: 8192,
  system: systemPrompt,
  betas,
  thinking,
  messages: history
});

// 4. A 200 with no dropped blocks means the kept thinking held.
console.log(`Dropped thinking blocks: ${third.input_transformations?.length ?? 0}`);
using Anthropic.Models.Beta;
using Anthropic.Models.Beta.Messages;
using Model = Anthropic.Models.Messages.Model;

AnthropicClient client = new();

// Claude Fable 5.1 is the first model that checks sent-back thinking against the conversation.
const Model ModelId = Model.ClaudeFable5_1;
AnthropicBeta[] betas = [AnthropicBeta.Compact2026_09_04, AnthropicBeta.ThinkingBindingControls2026_08_01];
const string SystemPrompt = "You help plan a recipe app's release. Keep answers short.";
// With "error", a thinking block that fails the check makes the request fail with a 400.
BetaThinkingConfigAdaptive thinking = new()
{
    BlockBinding = new() { PrefixMismatchBehavior = BetaThinkingPrefixMismatchBehavior.Error },
};

// 1. Have a short conversation with thinking on.
List<BetaMessageParam> history =
[
    new() { Role = Role.User, Content = "What are the main entities in the app's data model?" },
];
var first = await client.Beta.Messages.Create(new MessageCreateParams
{
    Model = ModelId,
    MaxTokens = 8192,
    System = SystemPrompt,
    Betas = [.. betas],
    Thinking = thinking,
    Messages = history,
});
history.AddRange(
[
    new()
    {
        Role = Role.Assistant,
        Content = first.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
    },
    new()
    {
        Role = Role.User,
        Content = "Testing starts on Tuesday, March 3, 2026, takes 10 weekdays, and pauses on March 9 and March 16. On which date does it end?",
    },
]);
var second = await client.Beta.Messages.Create(new MessageCreateParams
{
    Model = ModelId,
    MaxTokens = 8192,
    System = SystemPrompt,
    Betas = [.. betas],
    Thinking = thinking,
    Messages = history,
});
history.Add(new()
{
    Role = Role.Assistant,
    Content = second.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
});
var thinkingBlocks = second.Content.Count(block => block.TryPickThinking(out _));
Console.WriteLine($"Thinking blocks in the kept turn: {thinkingBlocks}");

// 2. Summarize the first turn. The second turn stays out of the request.
var summary = await client.Beta.Messages.Create(new MessageCreateParams
{
    Model = ModelId,
    MaxTokens = 4096,
    System = SystemPrompt,
    Betas = [.. betas],
    Thinking = thinking,
    Messages = history[..2],
    Compaction = new BetaCompactionConfig(), // type defaults to "summarize"
});
if (summary.StopReason != BetaStopReason.Compaction)
{
    throw new InvalidOperationException($"No summary: {summary.StopReason?.Raw()}");
}

// 3. Put the block in front of the kept turn and ask the next question.
history =
[
    new()
    {
        Role = Role.Assistant,
        Content = summary.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
    },
    .. history[2..],
    new() { Role = Role.User, Content = "Which day should the release go out?" },
];
var third = await client.Beta.Messages.Create(new MessageCreateParams
{
    Model = ModelId,
    MaxTokens = 8192,
    System = SystemPrompt,
    Betas = [.. betas],
    Thinking = thinking,
    Messages = history,
});

// 4. A 200 with no dropped blocks means the kept thinking held.
Console.WriteLine($"Dropped thinking blocks: {third.InputTransformations?.Count ?? 0}");
ctx := context.Background()
client := anthropic.NewClient()

// Claude Fable 5.1 is the first model that checks sent-back thinking against the conversation.
const model = anthropic.ModelClaudeFable5_1
betas := []anthropic.AnthropicBeta{
	anthropic.AnthropicBetaCompact2026_09_04,
	anthropic.AnthropicBetaThinkingBindingControls2026_08_01,
}
system := []anthropic.BetaTextBlockParam{{Text: "You help plan a recipe app's release. Keep answers short."}}
// With "error", a thinking block that fails the check makes the request fail with a 400.
thinking := anthropic.BetaThinkingConfigParamUnion{
	OfAdaptive: &anthropic.BetaThinkingConfigAdaptiveParam{
		BlockBinding: anthropic.BetaThinkingBlockBindingParam{
			PrefixMismatchBehavior: anthropic.BetaThinkingPrefixMismatchBehaviorError,
		},
	},
}

// 1. Have a short conversation with thinking on.
history := []anthropic.BetaMessageParam{
	anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("What are the main entities in the app's data model?")),
}
first, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
	Model:     model,
	MaxTokens: 8192,
	System:    system,
	Betas:     betas,
	Thinking:  thinking,
	Messages:  history,
})
if err != nil {
	log.Fatal(err)
}
history = append(history,
	first.ToParam(),
	anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Testing starts on Tuesday, March 3, 2026, takes 10 weekdays, and pauses on March 9 and March 16. On which date does it end?")),
)
second, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
	Model:     model,
	MaxTokens: 8192,
	System:    system,
	Betas:     betas,
	Thinking:  thinking,
	Messages:  history,
})
if err != nil {
	log.Fatal(err)
}
history = append(history, second.ToParam())
thinkingBlocks := 0
for _, block := range second.Content {
	if _, ok := block.AsAny().(anthropic.BetaThinkingBlock); ok {
		thinkingBlocks++
	}
}
fmt.Printf("Thinking blocks in the kept turn: %d\n", thinkingBlocks)

// 2. Summarize the first turn. The second turn stays out of the request.
summary, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
	Model:     model,
	MaxTokens: 4096,
	System:    system,
	Betas:     betas,
	Thinking:  thinking,
	Messages:  history[:2],
	Compaction: anthropic.BetaCompactionConfigUnionParam{
		OfSummarize: &anthropic.BetaSummarizeCompactionParam{},
	},
})
if err != nil {
	log.Fatal(err)
}
if summary.StopReason != anthropic.BetaStopReasonCompaction {
	log.Fatalf("No summary: %s", summary.StopReason)
}

// 3. Put the block in front of the kept turn and ask the next question.
history = slices.Concat(
	[]anthropic.BetaMessageParam{summary.ToParam()},
	history[2:],
	[]anthropic.BetaMessageParam{anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Which day should the release go out?"))},
)
third, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
	Model:     model,
	MaxTokens: 8192,
	System:    system,
	Betas:     betas,
	Thinking:  thinking,
	Messages:  history,
})
if err != nil {
	log.Fatal(err)
}

// 4. A 200 with no dropped blocks means the kept thinking held.
fmt.Printf("Dropped thinking blocks: %d\n", len(third.InputTransformations))
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.beta.messages.BetaCompactionConfig;
import com.anthropic.models.beta.messages.BetaContentBlock;
import com.anthropic.models.beta.messages.BetaMessageParam;
import com.anthropic.models.beta.messages.BetaStopReason;
import com.anthropic.models.beta.messages.BetaThinkingBlockBinding;
import com.anthropic.models.beta.messages.BetaThinkingConfigAdaptive;
import com.anthropic.models.beta.messages.BetaThinkingPrefixMismatchBehavior;
import com.anthropic.models.beta.messages.MessageCreateParams;

// Claude Fable 5.1 is the first model that checks sent-back thinking against the conversation.
static final Model MODEL = Model.CLAUDE_FABLE_5_1;
static final List<AnthropicBeta> BETAS = List.of(
    AnthropicBeta.COMPACT_2026_09_04,
    AnthropicBeta.THINKING_BINDING_CONTROLS_2026_08_01
);
static final String SYSTEM = "You help plan a recipe app's release. Keep answers short.";
// With "error", a thinking block that fails the check makes the request fail with a 400.
static final BetaThinkingConfigAdaptive THINKING = BetaThinkingConfigAdaptive.builder()
    .blockBinding(BetaThinkingBlockBinding.builder()
        .prefixMismatchBehavior(BetaThinkingPrefixMismatchBehavior.ERROR)
        .build())
    .build();

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

    // 1. Have a short conversation with thinking on.
    var history = new ArrayList<BetaMessageParam>();
    history.add(BetaMessageParam.builder()
        .role(BetaMessageParam.Role.USER)
        .content("What are the main entities in the app's data model?")
        .build());
    var firstParams = MessageCreateParams.builder()
        .model(MODEL)
        .maxTokens(8192)
        .system(SYSTEM)
        .betas(BETAS)
        .thinking(THINKING)
        .messages(history)
        .build();
    var first = client.beta().messages().create(firstParams);
    history.add(first.toParam());
    history.add(BetaMessageParam.builder()
        .role(BetaMessageParam.Role.USER)
        .content("Testing starts on Tuesday, March 3, 2026, takes 10 weekdays, and pauses on March 9 and March 16. On which date does it end?")
        .build());
    var secondParams = MessageCreateParams.builder()
        .model(MODEL)
        .maxTokens(8192)
        .system(SYSTEM)
        .betas(BETAS)
        .thinking(THINKING)
        .messages(history)
        .build();
    var second = client.beta().messages().create(secondParams);
    history.add(second.toParam());
    long thinkingBlocks = second.content().stream().filter(BetaContentBlock::isThinking).count();
    IO.println("Thinking blocks in the kept turn: " + thinkingBlocks);

    // 2. Summarize the first turn. The second turn stays out of the request.
    var firstTurn = history.subList(0, 2);
    var summaryParams = MessageCreateParams.builder()
        .model(MODEL)
        .maxTokens(4096)
        .system(SYSTEM)
        .betas(BETAS)
        .thinking(THINKING)
        .messages(firstTurn)
        .compaction(BetaCompactionConfig.builder().build()) // type defaults to "summarize"
        .build();
    var summary = client.beta().messages().create(summaryParams);
    if (!summary.stopReason().map(BetaStopReason.COMPACTION::equals).orElse(false)) {
        throw new IllegalStateException("No summary: " + summary.stopReason().orElseThrow());
    }

    // 3. Put the block in front of the kept turn and ask the next question.
    firstTurn.clear();
    history.addFirst(summary.toParam());
    history.add(BetaMessageParam.builder()
        .role(BetaMessageParam.Role.USER)
        .content("Which day should the release go out?")
        .build());
    var thirdParams = MessageCreateParams.builder()
        .model(MODEL)
        .maxTokens(8192)
        .system(SYSTEM)
        .betas(BETAS)
        .thinking(THINKING)
        .messages(history)
        .build();
    var third = client.beta().messages().create(thirdParams);

    // 4. A 200 with no dropped blocks means the kept thinking held.
    IO.println("Dropped thinking blocks: " + third.inputTransformations().map(List::size).orElse(0));
}
use Anthropic\Beta\AnthropicBeta;
use Anthropic\Beta\Messages\BetaCompactionConfig;
use Anthropic\Beta\Messages\BetaMessageParam;
use Anthropic\Beta\Messages\BetaMessageParam\Role;
use Anthropic\Beta\Messages\BetaStopReason;
use Anthropic\Beta\Messages\BetaThinkingBlock;
use Anthropic\Beta\Messages\BetaThinkingBlockBinding;
use Anthropic\Beta\Messages\BetaThinkingConfigAdaptive;
use Anthropic\Beta\Messages\BetaThinkingPrefixMismatchBehavior;

$client = new Client();

// Claude Fable 5.1 is the first model that checks sent-back thinking against the conversation.
const MODEL = Model::CLAUDE_FABLE_5_1;
const BETAS = [AnthropicBeta::COMPACT_2026_09_04, AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01];
const SYSTEM = "You help plan a recipe app's release. Keep answers short.";
// With "error", a thinking block that fails the check makes the request fail with a 400.
$thinking = BetaThinkingConfigAdaptive::with(
    blockBinding: BetaThinkingBlockBinding::with(
        prefixMismatchBehavior: BetaThinkingPrefixMismatchBehavior::ERROR,
    ),
);

// 1. Have a short conversation with thinking on.
$history = [
    BetaMessageParam::with(role: Role::USER, content: "What are the main entities in the app's data model?"),
];
$first = $client->beta->messages->create(
    model: MODEL,
    maxTokens: 8192,
    system: SYSTEM,
    betas: BETAS,
    thinking: $thinking,
    messages: $history,
);
$history[] = BetaMessageParam::with(role: Role::ASSISTANT, content: $first->content);
$history[] = BetaMessageParam::with(
    role: Role::USER,
    content: 'Testing starts on Tuesday, March 3, 2026, takes 10 weekdays, and pauses on March 9 and March 16. On which date does it end?',
);
$second = $client->beta->messages->create(
    model: MODEL,
    maxTokens: 8192,
    system: SYSTEM,
    betas: BETAS,
    thinking: $thinking,
    messages: $history,
);
$history[] = BetaMessageParam::with(role: Role::ASSISTANT, content: $second->content);
$thinkingBlocks = count(array_filter($second->content, fn ($block) => $block instanceof BetaThinkingBlock));
printf("Thinking blocks in the kept turn: %d\n", $thinkingBlocks);

// 2. Summarize the first turn. The second turn stays out of the request.
$summary = $client->beta->messages->create(
    model: MODEL,
    maxTokens: 4096,
    system: SYSTEM,
    betas: BETAS,
    thinking: $thinking,
    messages: array_slice($history, 0, 2),
    compaction: new BetaCompactionConfig(), // type defaults to 'summarize'
);
if ($summary->stopReason !== BetaStopReason::COMPACTION->value) {
    throw new RuntimeException("No summary: {$summary->stopReason}");
}

// 3. Put the block in front of the kept turn and ask the next question.
$history = [
    BetaMessageParam::with(role: Role::ASSISTANT, content: $summary->content),
    ...array_slice($history, 2),
    BetaMessageParam::with(role: Role::USER, content: 'Which day should the release go out?'),
];
$third = $client->beta->messages->create(
    model: MODEL,
    maxTokens: 8192,
    system: SYSTEM,
    betas: BETAS,
    thinking: $thinking,
    messages: $history,
);

// 4. A 200 with no dropped blocks means the kept thinking held.
printf("Dropped thinking blocks: %d\n", count($third->inputTransformations));
client = Anthropic::Client.new

# Claude Fable 5.1 is the first model that checks sent-back thinking against the conversation.
MODEL = Anthropic::Model::CLAUDE_FABLE_5_1
BETAS = [
  Anthropic::AnthropicBeta::COMPACT_2026_09_04,
  Anthropic::AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01
]
SYSTEM = "You help plan a recipe app's release. Keep answers short."
# With "error", a thinking block that fails the check makes the request fail with a 400.
THINKING = Anthropic::Beta::BetaThinkingConfigAdaptive.new(
  block_binding: Anthropic::Beta::BetaThinkingBlockBinding.new(
    prefix_mismatch_behavior: Anthropic::Beta::BetaThinkingPrefixMismatchBehavior::ERROR
  )
)

# 1. Have a short conversation with thinking on.
history = [{ role: "user", content: "What are the main entities in the app's data model?" }]
first = client.beta.messages.create(
  model: MODEL,
  max_tokens: 8192,
  system_: SYSTEM,
  betas: BETAS,
  thinking: THINKING,
  messages: history
)
history += [
  { role: "assistant", content: first.content },
  {
    role: "user",
    content: "Testing starts on Tuesday, March 3, 2026, takes 10 weekdays, and pauses on March 9 and March 16. On which date does it end?"
  }
]
second = client.beta.messages.create(
  model: MODEL,
  max_tokens: 8192,
  system_: SYSTEM,
  betas: BETAS,
  thinking: THINKING,
  messages: history
)
history << { role: "assistant", content: second.content }
thinking_blocks = second.content.count { it.is_a?(Anthropic::Beta::BetaThinkingBlock) }
puts "Thinking blocks in the kept turn: #{thinking_blocks}"

# 2. Summarize the first turn. The second turn stays out of the request.
summary = client.beta.messages.create(
  model: MODEL,
  max_tokens: 4096,
  system_: SYSTEM,
  betas: BETAS,
  thinking: THINKING,
  messages: history.first(2),
  compaction: { type: "summarize" }
)
abort "No summary: #{summary.stop_reason}" unless summary.stop_reason == :compaction

# 3. Put the block in front of the kept turn and ask the next question.
history = [
  { role: "assistant", content: summary.content },
  *history.drop(2),
  { role: "user", content: "Which day should the release go out?" }
]
third = client.beta.messages.create(
  model: MODEL,
  max_tokens: 8192,
  system_: SYSTEM,
  betas: BETAS,
  thinking: THINKING,
  messages: history
)

# 4. A 200 with no dropped blocks means the kept thinking held.
puts "Dropped thinking blocks: #{third.input_transformations.length}"
Thinking blocks in the kept turn: 1
Dropped thinking blocks: 0

프로덕션에서 "drop_block"은 조건이 성립하지 않을 때 요청이 계속 성공하게 하고, 각 버려진 블록을 input_transformationsreason: "prefix_binding_mismatch"로 보고해요. path가 유지된 턴에 속하는 항목은 그 턴의 thinking이 유지되지 않았단 뜻이에요. API가 잘못된 블록으로 하는 일이 무엇이 버려지는지 설명하고 그것에 대해 경고하는 방법을 말해요.

더 알아보기 (Learn more)