← Claude на русском
Открыть оригинал
Перевод
Перевёл Claude Opus 4.7 (ИИ). Это не официальный перевод Anthropic — при сомнениях сверяйся с оригиналом.

Building with extended thinking

Оригинал: Building with extended thinking · автор: Anthropic · сверено 2026-04-24

Extended thinking даёт Claude расширенные возможности рассуждения для сложных задач, обеспечивая разный уровень прозрачности пошагового мыслительного процесса перед тем, как модель выдаёт окончательный ответ.

Supported models

Ручное extended thinking (thinking: {type: "enabled", budget_tokens: N}) поддерживается на всех текущих моделях Claude, кроме Claude Opus 4.7 и более новых моделей, где оно больше не принимается и возвращает ошибку 400. У нескольких моделей поведение зависит от режима:

How extended thinking works

Когда extended thinking включён, Claude создаёт блоки контента thinking, в которых выводит свои внутренние рассуждения. Claude использует выводы этих рассуждений перед тем, как сформировать окончательный ответ.

Ответ API включает блоки контента thinking, за которыми следуют блоки контента text.

Вот пример формата ответа по умолчанию:

{
  "content": [
    {
      "type": "thinking",
      "thinking": "Let me analyze this step by step...",
      "signature": "WaUjzkypQ2mUEVM36O2TxuC06KN8xyfbJwyem2dw3URve/op91XWHOEBLLqIOMfFG/UvLEczmEsUjavL...."
    },
    {
      "type": "text",
      "text": "Based on my analysis..."
    }
  ]
}

Подробнее о формате ответа extended thinking см. в Messages API Reference.

How to use extended thinking

Вот пример использования extended thinking в Messages API:

curl https://api.anthropic.com/v1/messages \
     --header "x-api-key: $ANTHROPIC_API_KEY" \
     --header "anthropic-version: 2023-06-01" \
     --header "content-type: application/json" \
     --data \
'{
    "model": "claude-sonnet-4-6",
    "max_tokens": 16000,
    "thinking": {
        "type": "enabled",
        "budget_tokens": 10000
    },
    "messages": [
        {
            "role": "user",
            "content": "Are there an infinite number of prime numbers such that n mod 4 == 3?"
        }
    ]
}'
ant messages create \
  --transform content --format yaml \
    --model claude-sonnet-4-6 \
    --max-tokens 16000 \
    --thinking '{type: enabled, budget_tokens: 10000}' \
    --message '{role: user, content: Are there an infinite number of prime numbers such that n mod 4 == 3?}'
import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=16000,
    thinking={"type": "enabled", "budget_tokens": 10000},
    messages=[
        {
            "role": "user",
            "content": "Are there an infinite number of prime numbers such that n mod 4 == 3?",
        }
    ],
)

# Ответ содержит summarized thinking-блоки и text-блоки
for block in response.content:
    if block.type == "thinking":
        print(f"\nThinking summary: {block.thinking}")
    elif block.type == "text":
        print(f"\nResponse: {block.text}")
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 16000,
  thinking: {
    type: "enabled",
    budget_tokens: 10000
  },
  messages: [
    {
      role: "user",
      content: "Are there an infinite number of prime numbers such that n mod 4 == 3?"
    }
  ]
});

// Ответ содержит summarized thinking-блоки и text-блоки
for (const block of response.content) {
  if (block.type === "thinking") {
    console.log(`\nThinking summary: ${block.thinking}`);
  } else if (block.type === "text") {
    console.log(`\nResponse: ${block.text}`);
  }
}
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;

class Program
{
    static async Task Main(string[] args)
    {
        AnthropicClient client = new();

        var parameters = new MessageCreateParams
        {
            Model = Model.ClaudeSonnet4_6,
            MaxTokens = 16000,
            Thinking = new ThinkingConfigEnabled(budgetTokens: 10000),
            Messages = [
                new() {
                    Role = Role.User,
                    Content = "Are there an infinite number of prime numbers such that n mod 4 == 3?"
                }
            ]
        };

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

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

import (
	"context"
	"fmt"
	"log"

	"github.com/anthropics/anthropic-sdk-go"
)

func main() {
	client := anthropic.NewClient()

	response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
		Model:     anthropic.Model("claude-sonnet-4-6"),
		MaxTokens: 16000,
		Thinking:  anthropic.ThinkingConfigParamOfEnabled(10000),
		Messages: []anthropic.MessageParam{
			anthropic.NewUserMessage(anthropic.NewTextBlock("Are there an infinite number of prime numbers such that n mod 4 == 3?")),
		},
	})
	if err != nil {
		log.Fatal(err)
	}

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

public class ExtendedThinkingExample {
    public static void main(String[] args) {
        AnthropicClient client = AnthropicOkHttpClient.fromEnv();

        MessageCreateParams params = MessageCreateParams.builder()
            .model(Model.CLAUDE_SONNET_4_6)
            .maxTokens(16000L)
            .enabledThinking(10000L)
            .addUserMessage("Are there an infinite number of prime numbers such that n mod 4 == 3?")
            .build();

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

        response.content().forEach(block -> {
            block.thinking().ifPresent(thinkingBlock ->
                System.out.println("\nThinking summary: " + thinkingBlock.thinking())
            );
            block.text().ifPresent(textBlock ->
                System.out.println("\nResponse: " + textBlock.text())
            );
        });
    }
}
<?php

use Anthropic\Client;

$client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));

$message = $client->messages->create(
    maxTokens: 16000,
    messages: [
        [
            'role' => 'user',
            'content' => 'Are there an infinite number of prime numbers such that n mod 4 == 3?'
        ]
    ],
    model: 'claude-sonnet-4-6',
    thinking: ['type' => 'enabled', 'budget_tokens' => 10000],
);

foreach ($message->content as $block) {
    if ($block->type === 'thinking') {
        echo "\nThinking summary: " . $block->thinking;
    } elseif ($block->type === 'text') {
        echo "\nResponse: " . $block->text;
    }
}
require "anthropic"

client = Anthropic::Client.new

message = client.messages.create(
  model: "claude-sonnet-4-6",
  max_tokens: 16000,
  thinking: {
    type: "enabled",
    budget_tokens: 10000
  },
  messages: [
    {
      role: "user",
      content: "Are there an infinite number of prime numbers such that n mod 4 == 3?"
    }
  ]
)

message.content.each do |block|
  case block.type
  when :thinking
    puts "\nThinking summary: #{block.thinking}"
  when :text
    puts "\nResponse: #{block.text}"
  end
end

Чтобы включить extended thinking, добавьте объект thinking, установив параметру type значение enabled, а budget_tokens — указанный бюджет токенов для extended thinking. Для Claude Opus 4.6 и Claude Sonnet 4.6 используйте вместо этого type: "adaptive". Подробности — в Adaptive thinking. type: "enabled" с budget_tokens на этих моделях ещё работает, но устарел и будет удалён в будущем релизе.

Параметр budget_tokens определяет максимальное число токенов, которое Claude может использовать для внутренних рассуждений. В моделях Claude 4 и более новых этот лимит применяется к полным thinking-токенам, а не к суммаризированному выводу. Большие бюджеты могут улучшить качество ответа, обеспечивая более тщательный анализ сложных задач, хотя Claude может не использовать весь выделенный бюджет, особенно при значениях выше 32k.

budget_tokens должен быть меньше max_tokens. Однако при использовании interleaved thinking с инструментами вы можете превысить этот лимит, поскольку токеновый лимит становится размером всего вашего context window.

Summarized thinking

Когда extended thinking включён, Messages API для моделей Claude 4 возвращает summary полного thinking-процесса Claude. Summarized thinking даёт все интеллектуальные преимущества extended thinking, при этом предотвращая злоупотребления. Это поведение по умолчанию на моделях Claude 4, когда поле display в конфигурации thinking не задано или установлено в "summarized". На Claude Opus 4.7 и Claude Mythos Preview display по умолчанию имеет значение "omitted", поэтому, чтобы получить summarized thinking, нужно явно указать display: "summarized".

Несколько важных моментов про summarized thinking:

Controlling thinking display

Поле display в конфигурации thinking управляет тем, как thinking-контент возвращается в ответах API. Оно принимает два значения:

Установка display: "omitted" полезна, когда ваше приложение не показывает thinking-контент пользователям. Главное преимущество — более быстрое время до первого текстового токена при стриминге: сервер полностью пропускает стриминг thinking-токенов и доставляет только signature, поэтому финальный текстовый ответ начинает стримиться раньше.

Несколько важных моментов про omitted thinking:

Автоматизированные пайплайны, которые никогда не показывают thinking-контент конечным пользователям, могут пропустить накладные расходы на получение thinking-токенов по сети. Latency-чувствительные приложения получают то же качество рассуждений, не дожидаясь, пока поток thinking-текста завершится перед началом финального ответа.

curl https://api.anthropic.com/v1/messages \
     --header "x-api-key: $ANTHROPIC_API_KEY" \
     --header "anthropic-version: 2023-06-01" \
     --header "content-type: application/json" \
     --data \
'{
    "model": "claude-sonnet-4-6",
    "max_tokens": 16000,
    "thinking": {
        "type": "enabled",
        "budget_tokens": 10000,
        "display": "omitted"
    },
    "messages": [
        {
            "role": "user",
            "content": "What is 27 * 453?"
        }
    ]
}'
ant messages create \
  --model claude-sonnet-4-6 \
  --max-tokens 16000 \
  --transform content --format yaml \
    --thinking '{type: enabled, budget_tokens: 10000, display: omitted}' \
    --message '{role: user, content: "What is 27 * 453?"}'
import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=16000,
    thinking={
        "type": "enabled",
        "budget_tokens": 10000,
        "display": "omitted",
    },
    messages=[
        {"role": "user", "content": "What is 27 * 453?"},
    ],
)

for block in response.content:
    if block.type == "thinking":
        if block.thinking:
            print(f"Thinking: {block.thinking}")
        else:
            print("Thinking: [omitted]")
    elif block.type == "text":
        print(f"Response: {block.text}")
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 16000,
  thinking: {
    type: "enabled",
    budget_tokens: 10000,
    display: "omitted"
  },
  messages: [
    {
      role: "user",
      content: "What is 27 * 453?"
    }
  ]
} as unknown as Anthropic.MessageCreateParamsNonStreaming);

for (const block of response.content) {
  if (block.type === "thinking") {
    if (block.thinking) {
      console.log(`Thinking: ${block.thinking}`);
    } else {
      console.log("Thinking: [omitted]");
    }
  } else if (block.type === "text") {
    console.log(`Response: ${block.text}`);
  }
}
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var client = new HttpClient();
        client.DefaultRequestHeaders.Add("x-api-key", Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"));
        client.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");

        var body = """
        {
            "model": "claude-sonnet-4-6",
            "max_tokens": 16000,
            "thinking": {
                "type": "enabled",
                "budget_tokens": 10000,
                "display": "omitted"
            },
            "messages": [
                {"role": "user", "content": "What is 27 * 453?"}
            ]
        }
        """;

        var response = await client.PostAsync(
            "https://api.anthropic.com/v1/messages",
            new StringContent(body, Encoding.UTF8, "application/json"));

        var json = await response.Content.ReadAsStringAsync();
        var doc = JsonDocument.Parse(json);
        foreach (var block in doc.RootElement.GetProperty("content").EnumerateArray())
        {
            var type = block.GetProperty("type").GetString();
            if (type == "thinking")
            {
                var thinking = block.GetProperty("thinking").GetString();
                Console.WriteLine($"Thinking: {(string.IsNullOrEmpty(thinking) ? "[omitted]" : thinking)}");
            }
            else if (type == "text")
            {
                Console.WriteLine($"Response: {block.GetProperty("text").GetString()}");
            }
        }
    }
}
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	body := []byte(`{
		"model": "claude-sonnet-4-6",
		"max_tokens": 16000,
		"thinking": {
			"type": "enabled",
			"budget_tokens": 10000,
			"display": "omitted"
		},
		"messages": [
			{"role": "user", "content": "What is 27 * 453?"}
		]
	}`)

	req, _ := http.NewRequest("POST", "https://api.anthropic.com/v1/messages", bytes.NewBuffer(body))
	req.Header.Set("x-api-key", os.Getenv("ANTHROPIC_API_KEY"))
	req.Header.Set("anthropic-version", "2023-06-01")
	req.Header.Set("content-type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	respBody, _ := io.ReadAll(resp.Body)

	var result map[string]any
	json.Unmarshal(respBody, &result)
	for _, block := range result["content"].([]any) {
		b := block.(map[string]any)
		if b["type"] == "thinking" {
			thinking := b["thinking"].(string)
			if thinking == "" {
				fmt.Println("Thinking: [omitted]")
			} else {
				fmt.Println("Thinking:", thinking)
			}
		} else if b["type"] == "text" {
			fmt.Println("Response:", b["text"])
		}
	}
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.json.JSONArray;
import org.json.JSONObject;

public class ThinkingDisplay {
    public static void main(String[] args) throws Exception {
        String body = """
            {
                "model": "claude-sonnet-4-6",
                "max_tokens": 16000,
                "thinking": {
                    "type": "enabled",
                    "budget_tokens": 10000,
                    "display": "omitted"
                },
                "messages": [
                    {"role": "user", "content": "What is 27 * 453?"}
                ]
            }
            """;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.anthropic.com/v1/messages"))
            .header("x-api-key", System.getenv("ANTHROPIC_API_KEY"))
            .header("anthropic-version", "2023-06-01")
            .header("content-type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        JSONObject json = new JSONObject(response.body());
        JSONArray content = json.getJSONArray("content");
        for (int i = 0; i < content.length(); i++) {
            JSONObject block = content.getJSONObject(i);
            String type = block.getString("type");
            if (type.equals("thinking")) {
                String thinking = block.getString("thinking");
                System.out.println("Thinking: " + (thinking.isEmpty() ? "[omitted]" : thinking));
            } else if (type.equals("text")) {
                System.out.println("Response: " + block.getString("text"));
            }
        }
    }
}
<?php

$body = json_encode([
    "model" => "claude-sonnet-4-6",
    "max_tokens" => 16000,
    "thinking" => [
        "type" => "enabled",
        "budget_tokens" => 10000,
        "display" => "omitted",
    ],
    "messages" => [
        ["role" => "user", "content" => "What is 27 * 453?"],
    ],
]);

$ch = curl_init("https://api.anthropic.com/v1/messages");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "x-api-key: " . getenv("ANTHROPIC_API_KEY"),
    "anthropic-version: 2023-06-01",
    "content-type: application/json",
]);

$response = json_decode(curl_exec($ch), true);
curl_close($ch);

foreach ($response["content"] as $block) {
    if ($block["type"] === "thinking") {
        $thinking = $block["thinking"];
        echo "Thinking: " . ($thinking === "" ? "[omitted]" : $thinking) . "\n";
    } elseif ($block["type"] === "text") {
        echo "Response: " . $block["text"] . "\n";
    }
}
require "net/http"
require "json"
require "uri"

uri = URI("https://api.anthropic.com/v1/messages")
body = {
  model: "claude-sonnet-4-6",
  max_tokens: 16000,
  thinking: {
    type: "enabled",
    budget_tokens: 10000,
    display: "omitted"
  },
  messages: [
    { role: "user", content: "What is 27 * 453?" }
  ]
}

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request["x-api-key"] = ENV["ANTHROPIC_API_KEY"]
request["anthropic-version"] = "2023-06-01"
request["content-type"] = "application/json"
request.body = body.to_json

response = JSON.parse(http.request(request).body)
response["content"].each do |block|
  if block["type"] == "thinking"
    thinking = block["thinking"]
    puts "Thinking: #{thinking.empty? ? '[omitted]' : thinking}"
  elsif block["type"] == "text"
    puts "Response: #{block['text']}"
  end
end

Когда установлен display: "omitted", ответ содержит блоки thinking с пустым полем thinking:

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

При стриминге с display: "omitted" события thinking_delta не эмитятся; последовательность событий см. ниже в Streaming thinking.

Streaming thinking

Вы можете стримить ответы extended thinking через server-sent events (SSE).

Когда стриминг включён для extended thinking, вы получаете thinking-контент через события thinking_delta.

Когда установлен display: "omitted", события thinking_delta не эмитятся. См. Controlling thinking display.

Подробную документацию по стримингу через Messages API см. в Streaming Messages.

Вот как обрабатывать стриминг с thinking:

curl https://api.anthropic.com/v1/messages \
     --header "x-api-key: $ANTHROPIC_API_KEY" \
     --header "anthropic-version: 2023-06-01" \
     --header "content-type: application/json" \
     --data \
'{
    "model": "claude-sonnet-4-6",
    "max_tokens": 16000,
    "stream": true,
    "thinking": {
        "type": "enabled",
        "budget_tokens": 10000
    },
    "messages": [
        {
            "role": "user",
            "content": "What is the greatest common divisor of 1071 and 462?"
        }
    ]
}'
ant messages create --stream --format jsonl \
  --model claude-sonnet-4-6 \
  --max-tokens 16000 \
  --thinking '{type: enabled, budget_tokens: 10000}' \
  --message '{role: user, content: What is the greatest common divisor of 1071 and 462?}'
import anthropic

client = anthropic.Anthropic()

with client.messages.stream(
    model="claude-sonnet-4-6",
    max_tokens=16000,
    thinking={"type": "enabled", "budget_tokens": 10000},
    messages=[
        {
            "role": "user",
            "content": "What is the greatest common divisor of 1071 and 462?",
        }
    ],
) as stream:
    thinking_started = False
    response_started = False

    for event in stream:
        if event.type == "content_block_start":
            print(f"\nStarting {event.content_block.type} block...")
            # Сбрасываем флаги для каждого нового блока
            thinking_started = False
            response_started = False
        elif event.type == "content_block_delta":
            if event.delta.type == "thinking_delta":
                if not thinking_started:
                    print("Thinking: ", end="", flush=True)
                    thinking_started = True
                print(event.delta.thinking, end="", flush=True)
            elif event.delta.type == "text_delta":
                if not response_started:
                    print("Response: ", end="", flush=True)
                    response_started = True
                print(event.delta.text, end="", flush=True)
        elif event.type == "content_block_stop":
            print("\nBlock complete.")
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const stream = await client.messages.stream({
  model: "claude-sonnet-4-6",
  max_tokens: 16000,
  thinking: {
    type: "enabled",
    budget_tokens: 10000
  },
  messages: [
    {
      role: "user",
      content: "What is the greatest common divisor of 1071 and 462?"
    }
  ]
});

let thinkingStarted = false;
let responseStarted = false;

for await (const event of stream) {
  if (event.type === "content_block_start") {
    console.log(`\nStarting ${event.content_block.type} block...`);
    // Сбрасываем флаги для каждого нового блока
    thinkingStarted = false;
    responseStarted = false;
  } else if (event.type === "content_block_delta") {
    if (event.delta.type === "thinking_delta") {
      if (!thinkingStarted) {
        process.stdout.write("Thinking: ");
        thinkingStarted = true;
      }
      process.stdout.write(event.delta.thinking);
    } else if (event.delta.type === "text_delta") {
      if (!responseStarted) {
        process.stdout.write("Response: ");
        responseStarted = true;
      }
      process.stdout.write(event.delta.text);
    }
  } else if (event.type === "content_block_stop") {
    console.log("\nBlock complete.");
  }
}
using System;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;

public class Program
{
    public static async Task Main()
    {
        AnthropicClient client = new();

        var parameters = new MessageCreateParams
        {
            Model = Model.ClaudeSonnet4_6,
            MaxTokens = 16000,
            Thinking = new ThinkingConfigEnabled(budgetTokens: 10000),
            Messages = [new() { Role = Role.User, Content = "What is the greatest common divisor of 1071 and 462?" }]
        };

        bool thinkingStarted = false;
        bool responseStarted = false;

        await foreach (var streamEvent in client.Messages.CreateStreaming(parameters))
        {
            if (streamEvent.TryPickContentBlockStart(out var blockStart))
            {
                Console.WriteLine($"\nStarting {blockStart.ContentBlock.Type} block...");
                thinkingStarted = false;
                responseStarted = false;
            }
            else if (streamEvent.TryPickContentBlockDelta(out var blockDelta))
            {
                if (blockDelta.Delta.TryPickThinking(out var thinkingDelta))
                {
                    if (!thinkingStarted)
                    {
                        Console.Write("Thinking: ");
                        thinkingStarted = true;
                    }
                    Console.Write(thinkingDelta.Thinking);
                }
                else if (blockDelta.Delta.TryPickText(out var textDelta))
                {
                    if (!responseStarted)
                    {
                        Console.Write("Response: ");
                        responseStarted = true;
                    }
                    Console.Write(textDelta.Text);
                }
            }
            else if (streamEvent.TryPickContentBlockStop(out _))
            {
                Console.WriteLine("\nBlock complete.");
            }
        }
    }
}
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/anthropics/anthropic-sdk-go"
)

func main() {
	client := anthropic.NewClient()

	stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
		Model:     anthropic.Model("claude-sonnet-4-6"),
		MaxTokens: 16000,
		Thinking:  anthropic.ThinkingConfigParamOfEnabled(10000),
		Messages: []anthropic.MessageParam{
			anthropic.NewUserMessage(anthropic.NewTextBlock("What is the greatest common divisor of 1071 and 462?")),
		},
	})

	thinkingStarted := false
	responseStarted := false

	for stream.Next() {
		event := stream.Current()
		switch eventVariant := event.AsAny().(type) {
		case anthropic.ContentBlockStartEvent:
			fmt.Printf("\nStarting %s block...\n", eventVariant.ContentBlock.Type)
			thinkingStarted = false
			responseStarted = false
		case anthropic.ContentBlockDeltaEvent:
			switch deltaVariant := eventVariant.Delta.AsAny().(type) {
			case anthropic.ThinkingDelta:
				if !thinkingStarted {
					fmt.Print("Thinking: ")
					thinkingStarted = true
				}
				fmt.Print(deltaVariant.Thinking)
			case anthropic.TextDelta:
				if !responseStarted {
					fmt.Print("Response: ")
					responseStarted = true
				}
				fmt.Print(deltaVariant.Text)
			}
		case anthropic.ContentBlockStopEvent:
			fmt.Println("\nBlock complete.")
		}
	}

	if err := stream.Err(); err != nil {
		log.Fatal(err)
	}
}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;

public class ExtendedThinkingStreaming {
    public static void main(String[] args) {
        AnthropicClient client = AnthropicOkHttpClient.fromEnv();

        MessageCreateParams params = MessageCreateParams.builder()
            .model(Model.CLAUDE_SONNET_4_6)
            .maxTokens(16000L)
            .enabledThinking(10000L)
            .addUserMessage("What is the greatest common divisor of 1071 and 462?")
            .build();

        try (var streamResponse = client.messages().createStreaming(params)) {
            streamResponse.stream().forEach(event -> {
                event.contentBlockStart().ifPresent(startEvent ->
                    System.out.println("\nStarting block...")
                );
                event.contentBlockDelta().ifPresent(deltaEvent -> {
                    deltaEvent.delta().thinking().ifPresent(td ->
                        System.out.print(td.thinking())
                    );
                    deltaEvent.delta().text().ifPresent(td ->
                        System.out.print(td.text())
                    );
                });
                event.contentBlockStop().ifPresent(stopEvent ->
                    System.out.println("\nBlock complete.")
                );
            });
        }
    }
}
<?php

use Anthropic\Client;

$client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));

$thinkingStarted = false;
$responseStarted = false;

$stream = $client->messages->createStream(
    maxTokens: 16000,
    messages: [
        ['role' => 'user', 'content' => 'What is the greatest common divisor of 1071 and 462?']
    ],
    model: 'claude-sonnet-4-6',
    thinking: ['type' => 'enabled', 'budget_tokens' => 10000],
);

foreach ($stream as $event) {
    if ($event->type === 'content_block_start') {
        echo "\nStarting {$event->contentBlock->type} block...\n";
        $thinkingStarted = false;
        $responseStarted = false;
    } elseif ($event->type === 'content_block_delta') {
        if ($event->delta->type === 'thinking_delta') {
            if (!$thinkingStarted) {
                echo "Thinking: ";
                $thinkingStarted = true;
            }
            echo $event->delta->thinking;
        } elseif ($event->delta->type === 'text_delta') {
            if (!$responseStarted) {
                echo "Response: ";
                $responseStarted = true;
            }
            echo $event->delta->text;
        }
    } elseif ($event->type === 'content_block_stop') {
        echo "\nBlock complete.\n";
    }
}
require "anthropic"

client = Anthropic::Client.new

thinking_started = false
response_started = false

stream = client.messages.stream(
  model: "claude-sonnet-4-6",
  max_tokens: 16000,
  thinking: {
    type: "enabled",
    budget_tokens: 10000
  },
  messages: [
    { role: "user", content: "What is the greatest common divisor of 1071 and 462?" }
  ]
)

stream.each do |event|
  case event.type
  when :content_block_start
    puts "\nStarting #{event.content_block.type} block..."
    thinking_started = false
    response_started = false
  when :content_block_delta
    if event.delta.type == :thinking_delta
      unless thinking_started
        print "Thinking: "
        thinking_started = true
      end
      print event.delta.thinking
    elsif event.delta.type == :text_delta
      unless response_started
        print "Response: "
        response_started = true
      end
      print event.delta.text
    end
  when :content_block_stop
    puts "\nBlock complete."
  end
end

Пример вывода стриминга:

event: message_start
data: {"type": "message_start", "message": {"id": "msg_01...", "type": "message", "role": "assistant", "content": [], "model": "claude-sonnet-4-6", "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": "EqQBCgIYAhIM1gbcDa9GJwZA2b3hGgxBdjrkzLoky3dl1pkiMOYds..."}}

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"}

Когда установлен display: "omitted", thinking-блок открывается, приходит один signature_delta, и блок закрывается без каких-либо событий thinking_delta. Стриминг текста начинается сразу после:

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":"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":""}}

Extended thinking with tool use

Extended thinking можно использовать вместе с tool use, что позволяет Claude рассуждать в процессе выбора инструментов и обработки результатов.

При использовании extended thinking с tool use учитывайте следующие ограничения:

  1. Ограничение tool choice: tool use с thinking поддерживает только tool_choice: {"type": "auto"} (по умолчанию) или tool_choice: {"type": "none"}. Использование tool_choice: {"type": "any"} или tool_choice: {"type": "tool", "name": "..."} приведёт к ошибке, потому что эти опции принудительно включают tool use, что несовместимо с extended thinking.
  2. Сохранение thinking-блоков: при tool use вы должны передавать обратно в API блоки thinking для последнего сообщения ассистента. Включайте полный неизменённый блок обратно в API, чтобы поддерживать непрерывность рассуждений.

Toggling thinking modes in conversations

Нельзя переключать thinking в середине хода ассистента, в том числе во время циклов tool use. Весь ход ассистента должен работать в одном thinking-режиме:

С точки зрения модели циклы tool use — часть хода ассистента. Ход ассистента не завершён, пока 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"]

Несмотря на то что в API получается несколько сообщений, цикл tool use концептуально — часть одного непрерывного ответа ассистента.

Graceful thinking degradation

Когда возникает mid-turn-конфликт thinking (например, переключение thinking on/off во время цикла tool use), API автоматически отключает thinking для этого запроса. Чтобы сохранить качество модели и оставаться on-distribution, API может:

Это значит, что попытка переключить thinking в середине хода не приведёт к ошибке, но thinking будет тихо отключён для этого запроса. Чтобы убедиться, что thinking был активен, проверьте наличие блоков thinking в ответе.

Practical guidance

Best practice: планируйте свою стратегию 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-блоков с результатами инструментов

Вот практический пример, как сохранять thinking-блоки при предоставлении результатов инструментов:

ant messages create --transform content <<'YAML'
model: claude-sonnet-4-6
max_tokens: 16000
thinking:
  type: enabled
  budget_tokens: 10000
tools:
  - name: get_weather
    description: Get current weather for a location
    input_schema:
      type: object
      properties:
        location:
          type: string
      required:
        - location
messages:
  - role: user
    content: "What's the weather in Paris?"
YAML
weather_tool = {
    "name": "get_weather",
    "description": "Get current weather for a location",
    "input_schema": {
        "type": "object",
        "properties": {"location": {"type": "string"}},
        "required": ["location"],
    },
}

# Первый запрос — Claude отвечает thinking-блоком и tool-запросом
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=16000,
    thinking={"type": "enabled", "budget_tokens": 10000},
    tools=[weather_tool],
    messages=[{"role": "user", "content": "What's the weather in Paris?"}],
)
const weatherTool: Anthropic.Tool = {
  name: "get_weather",
  description: "Get current weather for a location",
  input_schema: {
    type: "object",
    properties: {
      location: { type: "string" }
    },
    required: ["location"]
  }
};

// Первый запрос — Claude отвечает thinking-блоком и tool-запросом
const response = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 16000,
  thinking: {
    type: "enabled",
    budget_tokens: 10000
  },
  tools: [weatherTool],
  messages: [{ role: "user", content: "What's the weather in Paris?" }]
});
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;

class Program
{
    static async Task Main(string[] args)
    {
        AnthropicClient client = new();

        var weatherTool = new ToolUnion(new Tool()
        {
            Name = "get_weather",
            Description = "Get current weather for a location",
            InputSchema = new InputSchema()
            {
                Properties = new Dictionary<string, JsonElement>
                {
                    ["location"] = JsonSerializer.SerializeToElement(new { type = "string" }),
                },
                Required = ["location"],
            },
        });

        var parameters = new MessageCreateParams
        {
            Model = Model.ClaudeSonnet4_6,
            MaxTokens = 16000,
            Thinking = new ThinkingConfigEnabled(budgetTokens: 10000),
            Tools = [weatherTool],
            Messages = [new() { Role = Role.User, Content = "What's the weather in Paris?" }]
        };

        var message = await client.Messages.Create(parameters);
        Console.WriteLine(message);
    }
}
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/anthropics/anthropic-sdk-go"
)

func main() {
	client := anthropic.NewClient()

	weatherTool := anthropic.ToolUnionParam{
		OfTool: &anthropic.ToolParam{
			Name:        "get_weather",
			Description: anthropic.String("Get current weather for a location"),
			InputSchema: anthropic.ToolInputSchemaParam{
				Properties: map[string]any{
					"location": map[string]any{
						"type": "string",
					},
				},
				Required: []string{"location"},
			},
		},
	}

	response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
		Model:     anthropic.Model("claude-sonnet-4-6"),
		MaxTokens: 16000,
		Thinking:  anthropic.ThinkingConfigParamOfEnabled(10000),
		Tools:     []anthropic.ToolUnionParam{weatherTool},
		Messages: []anthropic.MessageParam{
			anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather in Paris?")),
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(response)
}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.Tool;
import com.anthropic.core.JsonValue;
import java.util.List;
import java.util.Map;

public class ExtendedThinkingWithTools {
    public static void main(String[] args) {
        AnthropicClient client = AnthropicOkHttpClient.fromEnv();

        MessageCreateParams params = MessageCreateParams.builder()
            .model(Model.CLAUDE_SONNET_4_6)
            .maxTokens(16000L)
            .enabledThinking(10000L)
            .addTool(Tool.builder()
                .name("get_weather")
                .description("Get current weather for a location")
                .inputSchema(Tool.InputSchema.builder()
                    .properties(JsonValue.from(Map.of(
                        "location", Map.of("type", "string")
                    )))
                    .putAdditionalProperty("required", JsonValue.from(List.of("location")))
                    .build())
                .build())
            .addUserMessage("What's the weather in Paris?")
            .build();

        Message response = client.messages().create(params);
        System.out.println(response);
    }
}
<?php

use Anthropic\Client;

$client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));

$weatherTool = [
    'name' => 'get_weather',
    'description' => 'Get current weather for a location',
    'input_schema' => [
        'type' => 'object',
        'properties' => [
            'location' => ['type' => 'string']
        ],
        'required' => ['location']
    ]
];

$message = $client->messages->create(
    maxTokens: 16000,
    messages: [
        ['role' => 'user', 'content' => "What's the weather in Paris?"]
    ],
    model: 'claude-sonnet-4-6',
    thinking: ['type' => 'enabled', 'budget_tokens' => 10000],
    tools: [$weatherTool],
);
echo $message;
require "anthropic"

client = Anthropic::Client.new

weather_tool = {
  name: "get_weather",
  description: "Get current weather for a location",
  input_schema: {
    type: "object",
    properties: {
      location: { type: "string" }
    },
    required: ["location"]
  }
}

message = client.messages.create(
  model: "claude-sonnet-4-6",
  max_tokens: 16000,
  thinking: {
    type: "enabled",
    budget_tokens: 10000
  },
  tools: [weather_tool],
  messages: [
    { role: "user", content: "What's the weather in Paris?" }
  ]
)
puts message

Ответ API включает блоки thinking, text и tool_use:

{
  "content": [
    {
      "type": "thinking",
      "thinking": "The user wants to know the current weather in Paris. I have access to a function `get_weather`...",
      "signature": "BDaL4VrbR2Oj0hO4XpJxT28J5TILnCrrUXoKiiNBZW9P+nr8XSj1zuZzAl4egiCCpQNvfyUuFFJP5CncdYZEQPPmLxYsNrcs...."
    },
    {
      "type": "text",
      "text": "I can help you get the current weather information for Paris. Let me check that for you"
    },
    {
      "type": "tool_use",
      "id": "toolu_01CswdEQBMshySk6Y9DFKrfq",
      "name": "get_weather",
      "input": {
        "location": "Paris"
      }
    }
  ]
}

Теперь продолжим разговор и используем инструмент

# Первый ход: фиксируем массив content от ассистента (thinking + tool_use,
# с сохранёнными signatures) как компактный JSON.
ASSISTANT_CONTENT=$(ant messages create \
  --transform content <<'YAML'
model: claude-sonnet-4-6
max_tokens: 16000
thinking:
  type: enabled
  budget_tokens: 10000
tools:
  - name: get_weather
    description: Get the current weather in a given location
    input_schema:
      type: object
      properties:
        location:
          type: string
          description: The city and state
      required: [location]
messages:
  - role: user
    content: What's the weather in Paris?
YAML
)

TOOL_USE_ID=$(printf '%s' "$ASSISTANT_CONTENT" \
  | grep -o 'toolu_[A-Za-z0-9]*')

# Второй ход: передаём захваченные блоки обратно как сообщение ассистента.
# thinking-блок ОБЯЗАН сопровождать tool_use-блок.
ant messages create <<YAML
model: claude-sonnet-4-6
max_tokens: 16000
thinking:
  type: enabled
  budget_tokens: 10000
tools:
  - name: get_weather
    description: Get the current weather in a given location
    input_schema:
      type: object
      properties:
        location:
          type: string
          description: The city and state
      required: [location]
messages:
  - role: user
    content: What's the weather in Paris?
  - role: assistant
    content: $ASSISTANT_CONTENT
  - role: user
    content:
      - type: tool_result
        tool_use_id: $TOOL_USE_ID
        content: "Current temperature: 88°F"
YAML
import anthropic
from typing import Any

client = anthropic.Anthropic()
weather_tool = {
    "name": "get_weather",
    "description": "Get the current weather in a given location",
    "input_schema": {
        "type": "object",
        "properties": {
            "location": {"type": "string", "description": "The city and state"}
        },
        "required": ["location"],
    },
}
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=16000,
    thinking={"type": "enabled", "budget_tokens": 10000},
    tools=[weather_tool],
    messages=[{"role": "user", "content": "What's the weather in Paris?"}],
)
# Извлекаем thinking-блок и tool-use-блок
thinking_block = next(
    (block for block in response.content if block.type == "thinking"), None
)
tool_use_block = next(
    (block for block in response.content if block.type == "tool_use"), None
)

# Здесь был бы вызов вашего настоящего weather API
# Представим, что вот что мы получили в ответ
weather_data = {"temperature": 88}

# Второй запрос — включаем thinking-блок и результат инструмента
# В ответе новые thinking-блоки не генерируются
continuation = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=16000,
    thinking={"type": "enabled", "budget_tokens": 10000},
    tools=[weather_tool],
    messages=[
        {"role": "user", "content": "What's the weather in Paris?"},
        # обратите внимание, что передаётся и thinking_block, и tool_use_block
        # если этого не сделать, будет ошибка
        {"role": "assistant", "content": [thinking_block, tool_use_block]},
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": tool_use_block.id,
                    "content": f"Current temperature: {weather_data['temperature']}°F",
                }
            ],
        },
    ],
)
// Извлекаем thinking-блок и tool-use-блок
const thinkingBlock = response.content.find(
  (block): block is Anthropic.ThinkingBlock => block.type === "thinking"
);
const toolUseBlock = response.content.find(
  (block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
);

// Здесь был бы вызов вашего настоящего weather API
// Представим, что вот что мы получили в ответ
const weatherData = { temperature: 88 };

if (thinkingBlock && toolUseBlock) {
  // Второй запрос — включаем thinking-блок и результат инструмента
  // В ответе новые thinking-блоки не генерируются
  const continuation = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 16000,
    thinking: {
      type: "enabled",
      budget_tokens: 10000
    },
    tools: [weatherTool],
    messages: [
      { role: "user", content: "What's the weather in Paris?" },
      // обратите внимание, что передаётся и thinkingBlock, и toolUseBlock
      // если этого не сделать, будет ошибка
      { role: "assistant", content: [thinkingBlock, toolUseBlock] },
      {
        role: "user",
        content: [
          {
            type: "tool_result" as const,
            tool_use_id: toolUseBlock.id,
            content: `Current temperature: ${weatherData.temperature}°F`
          }
        ]
      }
    ]
  });
}
using System;
using System.Text.Json;
using System.Linq;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;

public class Program
{
    public static async Task Main(string[] args)
    {
        AnthropicClient client = new();

        var weatherTool = new ToolUnion(new Tool()
        {
            Name = "get_weather",
            Description = "Get current weather for a location",
            InputSchema = new InputSchema()
            {
                Properties = new Dictionary<string, JsonElement>
                {
                    ["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "City name" }),
                },
                Required = ["location"],
            },
        });

        var parameters = new MessageCreateParams
        {
            Model = Model.ClaudeSonnet4_6,
            MaxTokens = 16000,
            Thinking = new ThinkingConfigEnabled(budgetTokens: 10000),
            Tools = [weatherTool],
            Messages = [
                new() { Role = Role.User, Content = "What is the weather in Paris?" }
            ]
        };

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

        // Извлекаем thinking- и tool_use-блоки из ответа
        var thinkingBlock = response.Content.FirstOrDefault(b => b.TryPickThinking(out _));
        var toolUseBlock = response.Content.FirstOrDefault(b => b.TryPickToolUse(out _));

        var weatherData = new { temperature = 88 };

        // Собираем продолжение с результатом инструмента
        var continuationParams = new MessageCreateParams
        {
            Model = Model.ClaudeSonnet4_6,
            MaxTokens = 16000,
            Thinking = new ThinkingConfigEnabled(budgetTokens: 10000),
            Tools = [weatherTool],
            Messages = [
                new() { Role = Role.User, Content = "What is the weather in Paris?" },
                new() { Role = Role.Assistant, Content = response.Content },
                new() { Role = Role.User, Content = new MessageParamContent(new List<ContentBlockParam>
                {
                    new ContentBlockParam(new ToolResultBlockParam()
                    {
                        ToolUseID = toolUseBlock?.Id ?? "",
                        Content = $"Current temperature: {weatherData.temperature}°F"
                    })
                })}
            ]
        };

        var continuation = await client.Messages.Create(continuationParams);
        Console.WriteLine(continuation);
    }
}
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/anthropics/anthropic-sdk-go"
)

func main() {
	client := anthropic.NewClient()

	weatherTool := anthropic.ToolUnionParam{
		OfTool: &anthropic.ToolParam{
			Name:        "get_weather",
			Description: anthropic.String("Get current weather for a location"),
			InputSchema: anthropic.ToolInputSchemaParam{
				Properties: map[string]any{
					"location": map[string]any{
						"type":        "string",
						"description": "City name",
					},
				},
				Required: []string{"location"},
			},
		},
	}

	response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
		Model:     anthropic.Model("claude-sonnet-4-6"),
		MaxTokens: 16000,
		Thinking:  anthropic.ThinkingConfigParamOfEnabled(10000),
		Tools:     []anthropic.ToolUnionParam{weatherTool},
		Messages: []anthropic.MessageParam{
			anthropic.NewUserMessage(anthropic.NewTextBlock("What is the weather in Paris?")),
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	var toolUseBlock anthropic.ToolUseBlock
	for _, block := range response.Content {
		switch v := block.AsAny().(type) {
		case anthropic.ToolUseBlock:
			toolUseBlock = v
		}
	}

	weatherData := map[string]int{"temperature": 88}

	continuation, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
		Model:     anthropic.Model("claude-sonnet-4-6"),
		MaxTokens: 16000,
		Thinking:  anthropic.ThinkingConfigParamOfEnabled(10000),
		Tools:     []anthropic.ToolUnionParam{weatherTool},
		Messages: []anthropic.MessageParam{
			anthropic.NewUserMessage(anthropic.NewTextBlock("What is the weather in Paris?")),
			response.ToParam(),
			anthropic.NewUserMessage(
				anthropic.NewToolResultBlock(toolUseBlock.ID, fmt.Sprintf("Current temperature: %d°F", weatherData["temperature"]), false),
			),
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(continuation)
}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.ToolResultBlockParam;
import com.anthropic.models.messages.ToolUseBlock;
import com.anthropic.models.messages.ToolUseBlockParam;
import com.anthropic.models.messages.ThinkingBlock;
import com.anthropic.models.messages.ThinkingBlockParam;
import com.anthropic.core.JsonValue;
import java.util.List;
import java.util.Map;

public class ExtendedThinkingToolUse {
    public static void main(String[] args) {
        AnthropicClient client = AnthropicOkHttpClient.fromEnv();

        Tool weatherTool = Tool.builder()
            .name("get_weather")
            .description("Get current weather for a location")
            .inputSchema(Tool.InputSchema.builder()
                .properties(JsonValue.from(Map.of(
                    "location", Map.of("type", "string", "description", "City name")
                )))
                .putAdditionalProperty("required", JsonValue.from(List.of("location")))
                .build())
            .build();

        MessageCreateParams initialParams = MessageCreateParams.builder()
            .model(Model.CLAUDE_SONNET_4_6)
            .maxTokens(16000L)
            .enabledThinking(10000L)
            .addTool(weatherTool)
            .addUserMessage("What is the weather in Paris?")
            .build();

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

        ThinkingBlock thinkingBlock = null;
        ToolUseBlock toolUseBlock = null;
        for (var block : response.content()) {
            if (block.thinking().isPresent()) {
                thinkingBlock = block.thinking().get();
            }
            if (block.toolUse().isPresent()) {
                toolUseBlock = block.toolUse().get();
            }
        }

        int temperature = 88;

        // Второй запрос: передаём обратно thinking-блок и результат инструмента
        MessageCreateParams continuationParams = MessageCreateParams.builder()
            .model(Model.CLAUDE_SONNET_4_6)
            .maxTokens(16000L)
            .enabledThinking(10000L)
            .addTool(weatherTool)
            .addUserMessage("What is the weather in Paris?")
            .addAssistantMessageOfBlockParams(List.of(
                ContentBlockParam.ofThinking(ThinkingBlockParam.builder()
                    .thinking(thinkingBlock.thinking())
                    .signature(thinkingBlock.signature())
                    .build()),
                ContentBlockParam.ofToolUse(ToolUseBlockParam.builder()
                    .id(toolUseBlock.id())
                    .name(toolUseBlock.name())
                    .input(toolUseBlock._input())
                    .build())
            ))
            .addUserMessageOfBlockParams(List.of(
                ContentBlockParam.ofToolResult(
                    ToolResultBlockParam.builder()
                        .toolUseId(toolUseBlock.id())
                        .content("Current temperature: " + temperature + "°F")
                        .build()
                )
            ))
            .build();

        Message continuation = client.messages().create(continuationParams);
        System.out.println(continuation);
    }
}
<?php

use Anthropic\Client;

$client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));

$weatherTool = [
    'name' => 'get_weather',
    'description' => 'Get current weather for a location',
    'input_schema' => [
        'type' => 'object',
        'properties' => [
            'location' => [
                'type' => 'string',
                'description' => 'City name'
            ]
        ],
        'required' => ['location']
    ]
];

$response = $client->messages->create(
    maxTokens: 16000,
    messages: [
        ['role' => 'user', 'content' => 'What is the weather in Paris?']
    ],
    model: 'claude-sonnet-4-6',
    thinking: ['type' => 'enabled', 'budget_tokens' => 10000],
    tools: [$weatherTool],
);

$thinkingBlock = null;
$toolUseBlock = null;
foreach ($response->content as $block) {
    if ($block->type === 'thinking') {
        $thinkingBlock = $block;
    }
    if ($block->type === 'tool_use') {
        $toolUseBlock = $block;
    }
}

$weatherData = ['temperature' => 88];

$continuation = $client->messages->create(
    maxTokens: 16000,
    messages: [
        ['role' => 'user', 'content' => 'What is the weather in Paris?'],
        ['role' => 'assistant', 'content' => [$thinkingBlock, $toolUseBlock]],
        ['role' => 'user', 'content' => [
            [
                'type' => 'tool_result',
                'tool_use_id' => $toolUseBlock->id,
                'content' => "Current temperature: {$weatherData['temperature']}°F"
            ]
        ]]
    ],
    model: 'claude-sonnet-4-6',
    thinking: ['type' => 'enabled', 'budget_tokens' => 10000],
    tools: [$weatherTool],
);

echo $continuation;
require "anthropic"

client = Anthropic::Client.new

weather_tool = {
  name: "get_weather",
  description: "Get current weather for a location",
  input_schema: {
    type: "object",
    properties: {
      location: { type: "string", description: "City name" }
    },
    required: ["location"]
  }
}

response = client.messages.create(
  model: "claude-sonnet-4-6",
  max_tokens: 16000,
  thinking: {
    type: "enabled",
    budget_tokens: 10000
  },
  tools: [weather_tool],
  messages: [
    { role: "user", content: "What is the weather in Paris?" }
  ]
)

thinking_block = response.content.find { |block| block.type == :thinking }
tool_use_block = response.content.find { |block| block.type == :tool_use }

raise "No tool_use block found" unless tool_use_block

weather_data = { temperature: 88 }

continuation = client.messages.create(
  model: "claude-sonnet-4-6",
  max_tokens: 16000,
  thinking: {
    type: "enabled",
    budget_tokens: 10000
  },
  tools: [weather_tool],
  messages: [
    { role: "user", content: "What is the weather in Paris?" },
    { role: "assistant", content: [thinking_block, tool_use_block] },
    { role: "user", content: [
      {
        type: "tool_result",
        tool_use_id: tool_use_block.id,
        content: "Current temperature: #{weather_data[:temperature]}°F"
      }
    ] }
  ]
)

puts continuation

Ответ API теперь включает только текст

{
  "content": [
    {
      "type": "text",
      "text": "Currently in Paris, the temperature is 88°F (31°C)"
    }
  ]
}

Preserving thinking blocks

При tool use вы должны передавать блоки thinking обратно в API, причём передавать полный неизменённый блок. Это критично для сохранения потока рассуждений модели и целостности разговора.

Когда Claude вызывает инструменты, он приостанавливает построение ответа, ожидая внешней информации. Когда возвращаются результаты инструментов, Claude продолжает строить тот же существующий ответ. Это требует сохранения thinking-блоков во время tool use по нескольким причинам:

  1. Непрерывность рассуждений: thinking-блоки фиксируют пошаговые рассуждения Claude, которые привели к запросам инструментов. Когда вы отправляете результаты инструментов, включение оригинального thinking гарантирует, что Claude может продолжить рассуждения с того места, где остановился.
  2. Поддержание контекста: хотя результаты инструментов появляются как user-сообщения в структуре API, они — часть непрерывного потока рассуждений. Сохранение thinking-блоков поддерживает этот концептуальный поток между несколькими вызовами API. Подробнее об управлении контекстом — в guide on context windows.

Важно: при предоставлении блоков thinking вся последовательность последовательных блоков thinking должна совпадать с выводом, сгенерированным моделью при оригинальном запросе; нельзя переупорядочивать или модифицировать последовательность этих блоков.

Interleaved thinking

Extended thinking с tool use в моделях Claude 4 поддерживает interleaved thinking, что позволяет Claude думать между вызовами инструментов и принимать более сложные решения после получения результатов инструментов.

При interleaved thinking Claude может:

Поддержка моделей:

Несколько важных моментов про interleaved thinking:

Tool use without interleaved thinking

Без interleaved thinking Claude думает один раз в начале хода ассистента. Последующие ответы после результатов инструментов продолжаются без новых thinking-блоков.

User: "What's the total revenue if we sold 150 units at $50 each,
       and how does this compare to our average monthly revenue?"

Turn 1: [thinking] "I need to calculate 150 * $50, then check the database..."
        [tool_use: calculator] { "expression": "150 * 50" }
  ↓ tool result: "7500"

Turn 2: [tool_use: database_query] { "query": "SELECT AVG(revenue)..." }
        ↑ no thinking block
  ↓ tool result: "5200"

Turn 3: [text] "The total revenue is $7,500, which is 44% above your
        average monthly revenue of $5,200."
        ↑ no thinking block
Tool use with interleaved thinking

Когда interleaved thinking включён, Claude может думать после получения каждого результата инструмента, что позволяет ему рассуждать о промежуточных результатах перед продолжением.

User: "What's the total revenue if we sold 150 units at $50 each,
       and how does this compare to our average monthly revenue?"

Turn 1: [thinking] "I need to calculate 150 * $50 first..."
        [tool_use: calculator] { "expression": "150 * 50" }
  ↓ tool result: "7500"

Turn 2: [thinking] "Got $7,500. Now I should query the database to compare..."
        [tool_use: database_query] { "query": "SELECT AVG(revenue)..." }
        ↑ thinking after receiving calculator result
  ↓ tool result: "5200"

Turn 3: [thinking] "$7,500 vs $5,200 average - that's a 44% increase..."
        [text] "The total revenue is $7,500, which is 44% above your
        average monthly revenue of $5,200."
        ↑ thinking before final answer

Extended thinking with prompt caching

Prompt caching с thinking имеет несколько важных моментов:

Удаление контекста thinking-блоков

Паттерны инвалидации кеша

Understanding thinking block caching behavior

При использовании extended thinking с tool use thinking-блоки демонстрируют специфическое поведение кеширования, которое влияет на подсчёт токенов:

Как это работает:

  1. Кеширование происходит только тогда, когда вы делаете последующий запрос, включающий результаты инструментов
  2. Когда делается последующий запрос, предыдущая история разговора (включая thinking-блоки) может быть закеширована
  3. Эти закешированные thinking-блоки считаются как input-токены в ваших метриках использования при чтении из кеша
  4. Когда включён user-блок, не являющийся tool-result, все предыдущие thinking-блоки игнорируются и удаляются из контекста

Подробный пример потока:

Запрос 1:

User: "What's the weather in Paris?"

Ответ 1:

[thinking_block_1] + [tool_use block 1]

Запрос 2:

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

Ответ 2:

[thinking_block_2] + [text block 2]

Запрос 2 пишет кеш контента запроса (не ответа). Кеш включает оригинальное user-сообщение, первый thinking-блок, tool use-блок и tool result.

Запрос 3:

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]

Для Claude Opus 4.5 и более новых (включая Claude Opus 4.6) все предыдущие thinking-блоки сохраняются по умолчанию. Для более старых моделей, поскольку был включён user-блок, не являющийся tool-result, все предыдущие 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]

Ключевые моменты:

System prompt caching (preserved when thinking changes)
# Скачиваем ~10 КБ «Гордости и предубеждения» для кешируемого system-блока
curl -s https://www.gutenberg.org/cache/epub/1342/pg1342.txt \
  | head -c 10000 > pride.txt

# Формируем тело запроса для заданного thinking-бюджета. Когда CONTENT1
# заполнен (после первого хода), к телу добавляются ответ ассистента и
# следующее сообщение пользователя — диалог растёт.
build_body() {
  cat <<YAML
model: claude-sonnet-4-6
max_tokens: 20000
thinking:
  type: enabled
  budget_tokens: $1
system:
  - type: text
    text: >-
      You are an AI assistant that is tasked with literary analysis.
      Analyze the following text carefully.
  - type: text
    text: "@./pride.txt"
    cache_control:
      type: ephemeral
messages:
  - role: user
    content: Analyze the tone of this passage.
YAML
  if [[ -n "${CONTENT1:-}" ]]; then
    printf '  - role: assistant\n    content: %s\n' "$CONTENT1"
    printf '  - role: user\n'
    printf '    content: Analyze the characters in this passage.\n'
  fi
}

# Первый запрос (бюджет 4000): создаёт кеш. Сохраняем usage и content
# двумя строками jsonl, чтобы ответ можно было передать дальше.
printf 'First request - establishing cache\n'
{
  read -r USAGE1
  read -r CONTENT1
} < <(build_body 4000 \
  | ant messages create --transform '[usage,content]' --format jsonl)
printf 'First response usage: %s\n' "$USAGE1"

# Второй запрос: тот же бюджет, ожидается попадание в кеш system prompt.
printf '\nSecond request - same thinking parameters (cache hit expected)\n'
USAGE2=$(build_body 4000 \
  | ant messages create --transform usage --format jsonl)
printf 'Second response usage: %s\n' "$USAGE2"

# Третий запрос: бюджет изменён на 8000. Кешированный system prompt всё
# равно даёт hit; инвалидируется только кеширование message-блоков.
printf '\nThird request - different thinking parameters (cache miss for messages)\n'
USAGE3=$(build_body 8000 \
  | ant messages create --transform usage --format jsonl)
printf 'Third response usage: %s\n' "$USAGE3"
from anthropic import Anthropic
import requests
from bs4 import BeautifulSoup

client = Anthropic()


def fetch_article_content(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.content, "html.parser")

    # Удаляем элементы script и style
    for script in soup(["script", "style"]):
        script.decompose()

    # Получаем текст
    text = soup.get_text()

    # Разбиваем на строки и убираем пробелы по краям
    lines = (line.strip() for line in text.splitlines())
    # Разносим многострочные заголовки построчно
    chunks = (phrase.strip() for line in lines for phrase in line.split("  "))
    # Убираем пустые строки
    text = "\n".join(chunk for chunk in chunks if chunk)

    return text


# Скачиваем содержимое статьи
book_url = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt"
book_content = fetch_article_content(book_url)
# Берём ровно столько текста, сколько нужно для кеширования (первые главы)
LARGE_TEXT = book_content[:10000]

SYSTEM_PROMPT = [
    {
        "type": "text",
        "text": "You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully.",
    },
    {"type": "text", "text": LARGE_TEXT, "cache_control": {"type": "ephemeral"}},
]

MESSAGES = [{"role": "user", "content": "Analyze the tone of this passage."}]

# Первый запрос — создаём кеш
print("First request - establishing cache")
response1 = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=20000,
    thinking={"type": "enabled", "budget_tokens": 4000},
    system=SYSTEM_PROMPT,
    messages=MESSAGES,
)

print(f"First response usage: {response1.usage}")

MESSAGES.append({"role": "assistant", "content": response1.content})
MESSAGES.append({"role": "user", "content": "Analyze the characters in this passage."})
# Второй запрос — те же thinking-параметры (ожидается попадание в кеш)
print("\nSecond request - same thinking parameters (cache hit expected)")
response2 = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=20000,
    thinking={"type": "enabled", "budget_tokens": 4000},
    system=SYSTEM_PROMPT,
    messages=MESSAGES,
)

print(f"Second response usage: {response2.usage}")

# Третий запрос — другие thinking-параметры (ожидается промах кеша для messages)
print("\nThird request - different thinking parameters (cache miss for messages)")
response3 = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=20000,
    thinking={
        "type": "enabled",
        "budget_tokens": 8000,  # Изменили thinking-бюджет
    },
    system=SYSTEM_PROMPT,  # System prompt остаётся в кеше
    messages=MESSAGES,  # Кеш messages инвалидирован
)

print(f"Third response usage: {response3.usage}")
import Anthropic from "@anthropic-ai/sdk";
import axios from "axios";
import * as cheerio from "cheerio";

const client = new Anthropic();

async function fetchArticleContent(url: string): Promise<string> {
  const response = await axios.get(url);
  const $ = cheerio.load(response.data);
  $("script, style").remove();
  let text = $.text();
  const lines = text.split("\n").map((line) => line.trim());
  text = lines.filter((line) => line.length > 0).join("\n");
  return text;
}

async function main(): Promise<void> {
  const bookUrl = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt";
  const bookContent = await fetchArticleContent(bookUrl);
  const LARGE_TEXT = bookContent.slice(0, 10000);

  const SYSTEM_PROMPT: Anthropic.TextBlockParam[] = [
    {
      type: "text",
      text: "You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully."
    },
    {
      type: "text",
      text: LARGE_TEXT,
      cache_control: { type: "ephemeral" }
    }
  ];

  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: "Analyze the tone of this passage." }
  ];

  // Первый запрос — создаём кеш
  console.log("First request - establishing cache");
  const response1 = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 20000,
    thinking: { type: "enabled", budget_tokens: 4000 },
    system: SYSTEM_PROMPT,
    messages
  });

  console.log(`First response usage: ${JSON.stringify(response1.usage)}`);

  messages.push({
    role: "assistant",
    content: response1.content as Anthropic.ContentBlockParam[]
  });
  messages.push({
    role: "user",
    content: "Analyze the characters in this passage."
  });

  // Второй запрос — те же thinking-параметры (ожидается попадание в кеш)
  console.log("\nSecond request - same thinking parameters (cache hit expected)");
  const response2 = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 20000,
    thinking: { type: "enabled", budget_tokens: 4000 },
    system: SYSTEM_PROMPT,
    messages
  });

  console.log(`Second response usage: ${JSON.stringify(response2.usage)}`);

  // Третий запрос — другие thinking-параметры (ожидается промах кеша для messages)
  console.log("\nThird request - different thinking parameters (cache miss for messages)");
  const response3 = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 20000,
    thinking: { type: "enabled", budget_tokens: 8000 },
    system: SYSTEM_PROMPT,
    messages
  });

  console.log(`Third response usage: ${JSON.stringify(response3.usage)}`);
}

main();
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Collections.Generic;
using Anthropic;
using Anthropic.Models.Messages;

public class Program
{
    static async Task Main(string[] args)
    {
        AnthropicClient client = new();

        // Скачиваем содержимое книги
        using var httpClient = new HttpClient();
        var bookContent = await httpClient.GetStringAsync("https://www.gutenberg.org/cache/epub/1342/pg1342.txt");
        var largeText = bookContent.Substring(0, Math.Min(10000, bookContent.Length));

        var systemPrompt = new MessageCreateParamsSystem(new List<TextBlockParam>
        {
            new TextBlockParam()
            {
                Text = "You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully."
            },
            new TextBlockParam()
            {
                Text = largeText,
                CacheControl = new CacheControlEphemeral(),
            },
        });

        var messages = new List<MessageParam>
        {
            new() { Role = Role.User, Content = "Analyze the tone of this passage." }
        };

        // Первый запрос — создаём кеш
        Console.WriteLine("First request - establishing cache");
        var parameters1 = new MessageCreateParams
        {
            Model = Model.ClaudeSonnet4_6,
            MaxTokens = 20000,
            Thinking = new ThinkingConfigEnabled(budgetTokens: 4000),
            System = systemPrompt,
            Messages = messages
        };

        var response1 = await client.Messages.Create(parameters1);
        Console.WriteLine($"First response usage: {response1.Usage}");

        messages.Add(new() { Role = Role.Assistant, Content = response1.Content });
        messages.Add(new() { Role = Role.User, Content = "Analyze the characters in this passage." });

        // Второй запрос — те же thinking-параметры (ожидается попадание в кеш)
        Console.WriteLine("\nSecond request - same thinking parameters (cache hit expected)");
        var parameters2 = new MessageCreateParams
        {
            Model = Model.ClaudeSonnet4_6,
            MaxTokens = 20000,
            Thinking = new ThinkingConfigEnabled(budgetTokens: 4000),
            System = systemPrompt,
            Messages = messages
        };

        var response2 = await client.Messages.Create(parameters2);
        Console.WriteLine($"Second response usage: {response2.Usage}");

        // Третий запрос — другие thinking-параметры (ожидается промах кеша для messages)
        Console.WriteLine("\nThird request - different thinking parameters (cache miss for messages)");
        var parameters3 = new MessageCreateParams
        {
            Model = Model.ClaudeSonnet4_6,
            MaxTokens = 20000,
            Thinking = new ThinkingConfigEnabled(budgetTokens: 8000),
            System = systemPrompt,
            Messages = messages
        };

        var response3 = await client.Messages.Create(parameters3);
        Console.WriteLine($"Third response usage: {response3.Usage}");
    }
}
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net/http"

	"github.com/anthropics/anthropic-sdk-go"
)

func main() {
	client := anthropic.NewClient()

	// Скачиваем содержимое книги
	resp, err := http.Get("https://www.gutenberg.org/cache/epub/1342/pg1342.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	largeText := string(body)
	if len(largeText) > 10000 {
		largeText = largeText[:10000]
	}

	systemPrompt := []anthropic.TextBlockParam{
		{Text: "You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully."},
		{
			Text:         largeText,
			CacheControl: anthropic.NewCacheControlEphemeralParam(),
		},
	}

	messages := []anthropic.MessageParam{
		anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the tone of this passage.")),
	}

	// Первый запрос — создаём кеш
	fmt.Println("First request - establishing cache")
	response1, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
		Model:     anthropic.Model("claude-sonnet-4-6"),
		MaxTokens: 20000,
		Thinking:  anthropic.ThinkingConfigParamOfEnabled(4000),
		System:    systemPrompt,
		Messages:  messages,
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("First response usage: %+v\n", response1.Usage)

	messages = append(messages, response1.ToParam())
	messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the characters in this passage.")))

	// Второй запрос — те же thinking-параметры (ожидается попадание в кеш)
	fmt.Println("\nSecond request - same thinking parameters (cache hit expected)")
	response2, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
		Model:     anthropic.Model("claude-sonnet-4-6"),
		MaxTokens: 20000,
		Thinking:  anthropic.ThinkingConfigParamOfEnabled(4000),
		System:    systemPrompt,
		Messages:  messages,
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Second response usage: %+v\n", response2.Usage)

	// Третий запрос — другие thinking-параметры (ожидается промах кеша для messages)
	fmt.Println("\nThird request - different thinking parameters (cache miss for messages)")
	response3, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
		Model:     anthropic.Model("claude-sonnet-4-6"),
		MaxTokens: 20000,
		Thinking:  anthropic.ThinkingConfigParamOfEnabled(8000),
		System:    systemPrompt,
		Messages:  messages,
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Third response usage: %+v\n", response3.Usage)
}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;

public class ThinkingCacheExample {
    public static void main(String[] args) throws Exception {
        AnthropicClient client = AnthropicOkHttpClient.fromEnv();

        // Скачиваем содержимое книги
        HttpClient httpClient = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://www.gutenberg.org/cache/epub/1342/pg1342.txt"))
            .build();
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        String bookContent = response.body();
        String largeText = bookContent.substring(0, Math.min(10000, bookContent.length()));

        List<TextBlockParam> systemPrompt = List.of(
            TextBlockParam.builder()
                .text("You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully.")
                .build(),
            TextBlockParam.builder()
                .text(largeText)
                .cacheControl(CacheControlEphemeral.builder().build())
                .build()
        );

        // Первый запрос — создаём кеш
        System.out.println("First request - establishing cache");
        MessageCreateParams params1 = MessageCreateParams.builder()
            .model(Model.CLAUDE_SONNET_4_6)
            .maxTokens(20000L)
            .enabledThinking(4000L)
            .systemOfTextBlockParams(systemPrompt)
            .addUserMessage("Analyze the tone of this passage.")
            .build();

        Message response1 = client.messages().create(params1);
        System.out.println("First response usage: " + response1.usage());

        // Второй запрос — те же thinking-параметры (ожидается попадание в кеш)
        System.out.println("\nSecond request - same thinking parameters (cache hit expected)");
        MessageCreateParams params2 = MessageCreateParams.builder()
            .model(Model.CLAUDE_SONNET_4_6)
            .maxTokens(20000L)
            .enabledThinking(4000L)
            .systemOfTextBlockParams(systemPrompt)
            .addUserMessage("Analyze the tone of this passage.")
            .addAssistantMessageOfBlockParams(response1.content().stream()
                .map(block -> block.toParam())
                .collect(java.util.stream.Collectors.toList()))
            .addUserMessage("Analyze the characters in this passage.")
            .build();

        Message response2 = client.messages().create(params2);
        System.out.println("Second response usage: " + response2.usage());

        // Третий запрос — другие thinking-параметры (ожидается промах кеша для messages)
        System.out.println("\nThird request - different thinking parameters (cache miss for messages)");
        MessageCreateParams params3 = MessageCreateParams.builder()
            .model(Model.CLAUDE_SONNET_4_6)
            .maxTokens(20000L)
            .enabledThinking(8000L)
            .systemOfTextBlockParams(systemPrompt)
            .addUserMessage("Analyze the tone of this passage.")
            .addAssistantMessageOfBlockParams(response1.content().stream()
                .map(block -> block.toParam())
                .collect(java.util.stream.Collectors.toList()))
            .addUserMessage("Analyze the characters in this passage.")
            .build();

        Message response3 = client.messages().create(params3);
        System.out.println("Third response usage: " + response3.usage());
    }
}
<?php


use Anthropic\Client;

$client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));

// Скачиваем содержимое книги
$bookContent = file_get_contents("https://www.gutenberg.org/cache/epub/1342/pg1342.txt");
$largeText = substr($bookContent, 0, 10000);

$systemPrompt = [
    [
        'type' => 'text',
        'text' => 'You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully.'
    ],
    [
        'type' => 'text',
        'text' => $largeText,
        'cache_control' => ['type' => 'ephemeral']
    ]
];

$messages = [
    ['role' => 'user', 'content' => 'Analyze the tone of this passage.']
];

// Первый запрос — создаём кеш
echo "First request - establishing cache\n";
$response1 = $client->messages->create(
    maxTokens: 20000,
    messages: $messages,
    model: 'claude-sonnet-4-6',
    system: $systemPrompt,
    thinking: ['type' => 'enabled', 'budget_tokens' => 4000],
);

echo "First response usage: " . json_encode($response1->usage) . "\n";

$messages[] = ['role' => 'assistant', 'content' => $response1->content];
$messages[] = ['role' => 'user', 'content' => 'Analyze the characters in this passage.'];

// Второй запрос — те же thinking-параметры (ожидается попадание в кеш)
echo "\nSecond request - same thinking parameters (cache hit expected)\n";
$response2 = $client->messages->create(
    maxTokens: 20000,
    messages: $messages,
    model: 'claude-sonnet-4-6',
    system: $systemPrompt,
    thinking: ['type' => 'enabled', 'budget_tokens' => 4000],
);

echo "Second response usage: " . json_encode($response2->usage) . "\n";

// Третий запрос — другие thinking-параметры (ожидается промах кеша для messages)
echo "\nThird request - different thinking parameters (cache miss for messages)\n";
$response3 = $client->messages->create(
    maxTokens: 20000,
    messages: $messages,
    model: 'claude-sonnet-4-6',
    system: $systemPrompt,
    thinking: ['type' => 'enabled', 'budget_tokens' => 8000],
);

echo "Third response usage: " . json_encode($response3->usage) . "\n";
require "anthropic"
require "net/http"
require "uri"

client = Anthropic::Client.new

# Скачиваем содержимое книги
uri = URI("https://www.gutenberg.org/cache/epub/1342/pg1342.txt")
response = Net::HTTP.get_response(uri)
book_content = response.body
large_text = book_content[0...10000]

system_prompt = [
  {
    type: "text",
    text: "You are an AI assistant that is tasked with literary analysis. Analyze the following text carefully."
  },
  {
    type: "text",
    text: large_text,
    cache_control: { type: "ephemeral" }
  }
]

messages = [
  { role: "user", content: "Analyze the tone of this passage." }
]

# Первый запрос — создаём кеш
puts "First request - establishing cache"
response1 = client.messages.create(
  model: "claude-sonnet-4-6",
  max_tokens: 20000,
  thinking: {
    type: "enabled",
    budget_tokens: 4000
  },
  system: system_prompt,
  messages: messages
)

puts "First response usage: #{response1.usage}"

messages << { role: "assistant", content: response1.content }
messages << { role: "user", content: "Analyze the characters in this passage." }

# Второй запрос — те же thinking-параметры (ожидается попадание в кеш)
puts "\nSecond request - same thinking parameters (cache hit expected)"
response2 = client.messages.create(
  model: "claude-sonnet-4-6",
  max_tokens: 20000,
  thinking: {
    type: "enabled",
    budget_tokens: 4000
  },
  system: system_prompt,
  messages: messages
)

puts "Second response usage: #{response2.usage}"

# Третий запрос — другие thinking-параметры (ожидается промах кеша для messages)
puts "\nThird request - different thinking parameters (cache miss for messages)"
response3 = client.messages.create(
  model: "claude-sonnet-4-6",
  max_tokens: 20000,
  thinking: {
    type: "enabled",
    budget_tokens: 8000
  },
  system: system_prompt,
  messages: messages
)

puts "Third response usage: #{response3.usage}"
Messages caching (invalidated when thinking changes)
# Скачиваем первые ~10 КБ «Гордости и предубеждения» для кешируемого префикса
curl -sL 'https://www.gutenberg.org/cache/epub/1342/pg1342.txt' \
  | head -c 10000 > book.txt

# Вызов 1: thinking-бюджет 4000, записываем кеш
USAGE=$(ant messages create \
  --model claude-sonnet-4-6 --max-tokens 20000 \
  --transform usage <<'YAML'
thinking:
  type: enabled
  budget_tokens: 4000
messages:
  - role: user
    content:
      - type: text
        text: "@./book.txt"
        cache_control:
          type: ephemeral
      - type: text
        text: "Give a one-sentence summary of this passage."
YAML
)
printf 'Call 1 (budget 4000):\n%s\n\n' "$USAGE"

# Вызов 2: тот же бюджет, диалог расширен; ожидается попадание в кеш
USAGE=$(ant messages create \
  --model claude-sonnet-4-6 --max-tokens 20000 \
  --transform usage <<'YAML'
thinking:
  type: enabled
  budget_tokens: 4000
messages:
  - role: user
    content:
      - type: text
        text: "@./book.txt"
        cache_control:
          type: ephemeral
      - type: text
        text: "Give a one-sentence summary of this passage."
  - role: assistant
    content: "It opens Pride and Prejudice with the Bennet family."
  - role: user
    content: "Who is the protagonist?"
YAML
)
printf 'Call 2 (budget 4000):\n%s\n\n' "$USAGE"

# Вызов 3: бюджет изменён на 8000; промах кеша несмотря на тот же префикс
USAGE=$(ant messages create \
  --model claude-sonnet-4-6 --max-tokens 20000 \
  --transform usage <<'YAML'
thinking:
  type: enabled
  budget_tokens: 8000
messages:
  - role: user
    content:
      - type: text
        text: "@./book.txt"
        cache_control:
          type: ephemeral
      - type: text
        text: "Give a one-sentence summary of this passage."
  - role: assistant
    content: "It opens Pride and Prejudice with the Bennet family."
  - role: user
    content: "Who is the protagonist?"
  - role: assistant
    content: "Elizabeth Bennet is the protagonist."
  - role: user
    content: "What era is the story set in?"
YAML
)
printf 'Call 3 (budget 8000):\n%s\n' "$USAGE"
from anthropic import Anthropic
import requests
from bs4 import BeautifulSoup

client = Anthropic()


def fetch_article_content(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.content, "html.parser")

    # Удаляем элементы script и style
    for script in soup(["script", "style"]):
        script.decompose()

    # Получаем текст
    text = soup.get_text()

    # Разбиваем на строки и убираем пробелы по краям
    lines = (line.strip() for line in text.splitlines())
    # Разносим многострочные заголовки построчно
    chunks = (phrase.strip() for line in lines for phrase in line.split("  "))
    # Убираем пустые строки
    text = "\n".join(chunk for chunk in chunks if chunk)

    return text


# Скачиваем содержимое статьи
book_url = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt"
book_content = fetch_article_content(book_url)
# Берём ровно столько текста, сколько нужно для кеширования (первые главы)
LARGE_TEXT = book_content[:10000]

# Без system prompt — кеширование внутри messages
MESSAGES = [
    {
        "role": "user",
        "content": [
            {
                "type": "text",
                "text": LARGE_TEXT,
                "cache_control": {"type": "ephemeral"},
            },
            {"type": "text", "text": "Analyze the tone of this passage."},
        ],
    }
]

# Первый запрос — создаём кеш
print("First request - establishing cache")
response1 = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=20000,
    thinking={"type": "enabled", "budget_tokens": 4000},
    messages=MESSAGES,
)

print(f"First response usage: {response1.usage}")

MESSAGES.append({"role": "assistant", "content": response1.content})
MESSAGES.append({"role": "user", "content": "Analyze the characters in this passage."})
# Второй запрос — те же thinking-параметры (ожидается попадание в кеш)
print("\nSecond request - same thinking parameters (cache hit expected)")
response2 = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=20000,
    thinking={
        "type": "enabled",
        "budget_tokens": 4000,  # Тот же thinking-бюджет
    },
    messages=MESSAGES,
)

print(f"Second response usage: {response2.usage}")

MESSAGES.append({"role": "assistant", "content": response2.content})
MESSAGES.append({"role": "user", "content": "Analyze the setting in this passage."})

# Третий запрос — другой thinking-бюджет (ожидается промах кеша)
print("\nThird request - different thinking budget (cache miss expected)")
response3 = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=20000,
    thinking={
        "type": "enabled",
        "budget_tokens": 8000,  # Другой thinking-бюджет ломает кеш
    },
    messages=MESSAGES,
)

print(f"Third response usage: {response3.usage}")
import Anthropic from "@anthropic-ai/sdk";
import axios from "axios";
import * as cheerio from "cheerio";

const client = new Anthropic();

async function fetchArticleContent(url: string): Promise<string> {
  const response = await axios.get(url);
  const $ = cheerio.load(response.data);

  // Удаляем элементы script и style
  $("script, style").remove();

  // Получаем текст
  let text = $.text();

  // Чистим текст (разбиваем на строки, убираем пробелы)
  const lines = text.split("\n").map((line) => line.trim());
  const chunks = lines.flatMap((line) => line.split("  ").map((phrase) => phrase.trim()));
  text = chunks.filter((chunk) => chunk).join("\n");

  return text;
}

async function main(): Promise<void> {
  const bookUrl = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt";
  const bookContent = await fetchArticleContent(bookUrl);
  const LARGE_TEXT = bookContent.substring(0, 10000);

  // Без system prompt — кеширование внутри messages
  const messages: Anthropic.MessageParam[] = [
    {
      role: "user",
      content: [
        {
          type: "text",
          text: LARGE_TEXT,
          cache_control: { type: "ephemeral" }
        },
        {
          type: "text",
          text: "Analyze the tone of this passage."
        }
      ]
    }
  ];

  // Первый запрос — создаём кеш
  console.log("First request - establishing cache");
  const response1 = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 20000,
    thinking: { type: "enabled", budget_tokens: 4000 },
    messages
  });

  console.log("First response usage: ", response1.usage);

  messages.push(
    { role: "assistant", content: response1.content as Anthropic.ContentBlockParam[] },
    { role: "user", content: "Analyze the characters in this passage." }
  );

  // Второй запрос — те же thinking-параметры (ожидается попадание в кеш)
  console.log("\nSecond request - same thinking parameters (cache hit expected)");
  const response2 = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 20000,
    thinking: { type: "enabled", budget_tokens: 4000 },
    messages
  });

  console.log("Second response usage: ", response2.usage);

  messages.push(
    { role: "assistant", content: response2.content as Anthropic.ContentBlockParam[] },
    { role: "user", content: "Analyze the setting in this passage." }
  );

  // Третий запрос — другой thinking-бюджет (ожидается промах кеша)
  console.log("\nThird request - different thinking budget (cache miss expected)");
  const response3 = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 20000,
    thinking: { type: "enabled", budget_tokens: 8000 },
    messages
  });

  console.log("Third response usage: ", response3.usage);
}

main().catch(console.error);
using System;
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Models.Messages;

public class Program
{
    static async Task Main(string[] args)
    {
        AnthropicClient client = new();

        string bookUrl = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt";
        string bookContent = await FetchArticleContent(bookUrl);
        string largeText = bookContent.Substring(0, Math.Min(10000, bookContent.Length));

        Console.WriteLine("First request - establishing cache");
        var parameters1 = new MessageCreateParams
        {
            Model = Model.ClaudeSonnet4_6,
            MaxTokens = 20000,
            Thinking = new ThinkingConfigEnabled(budgetTokens: 4000),
            Messages =
            [
                new()
                {
                    Role = Role.User,
                    Content = new MessageParamContent(new List<ContentBlockParam>
                    {
                        new ContentBlockParam(new TextBlockParam()
                        {
                            Text = largeText,
                            CacheControl = new CacheControlEphemeral(),
                        }),
                        new ContentBlockParam(new TextBlockParam()
                        {
                            Text = "Analyze the tone of this passage."
                        }),
                    })
                }
            ]
        };

        var response1 = await client.Messages.Create(parameters1);
        Console.WriteLine($"First response usage: {response1.Usage}");

        Console.WriteLine("\nSecond request - same thinking parameters (cache hit expected)");
        var parameters2 = new MessageCreateParams
        {
            Model = Model.ClaudeSonnet4_6,
            MaxTokens = 20000,
            Thinking = new ThinkingConfigEnabled(budgetTokens: 4000),
            Messages =
            [
                new()
                {
                    Role = Role.User,
                    Content = new MessageParamContent(new List<ContentBlockParam>
                    {
                        new ContentBlockParam(new TextBlockParam()
                        {
                            Text = largeText,
                            CacheControl = new CacheControlEphemeral(),
                        }),
                        new ContentBlockParam(new TextBlockParam()
                        {
                            Text = "Analyze the tone of this passage."
                        }),
                    })
                },
                new()
                {
                    Role = Role.Assistant,
                    Content = response1.Content
                },
                new()
                {
                    Role = Role.User,
                    Content = "Analyze the characters in this passage."
                }
            ]
        };

        var response2 = await client.Messages.Create(parameters2);
        Console.WriteLine($"Second response usage: {response2.Usage}");

        Console.WriteLine("\nThird request - different thinking budget (cache miss expected)");
        var parameters3 = new MessageCreateParams
        {
            Model = Model.ClaudeSonnet4_6,
            MaxTokens = 20000,
            Thinking = new ThinkingConfigEnabled(budgetTokens: 8000),
            Messages =
            [
                new()
                {
                    Role = Role.User,
                    Content = new MessageParamContent(new List<ContentBlockParam>
                    {
                        new ContentBlockParam(new TextBlockParam()
                        {
                            Text = largeText,
                            CacheControl = new CacheControlEphemeral(),
                        }),
                        new ContentBlockParam(new TextBlockParam()
                        {
                            Text = "Analyze the tone of this passage."
                        }),
                    })
                },
                new()
                {
                    Role = Role.Assistant,
                    Content = response1.Content
                },
                new()
                {
                    Role = Role.User,
                    Content = "Analyze the characters in this passage."
                },
                new()
                {
                    Role = Role.Assistant,
                    Content = response2.Content
                },
                new()
                {
                    Role = Role.User,
                    Content = "Analyze the setting in this passage."
                }
            ]
        };

        var response3 = await client.Messages.Create(parameters3);
        Console.WriteLine($"Third response usage: {response3.Usage}");
    }

    static async Task<string> FetchArticleContent(string url)
    {
        using HttpClient httpClient = new();
        string content = await httpClient.GetStringAsync(url);
        return content;
    }
}
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"

	"github.com/anthropics/anthropic-sdk-go"
)

func fetchArticleContent(url string) (string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	text := string(body)
	lines := strings.Split(text, "\n")
	var cleanedLines []string
	for _, line := range lines {
		trimmed := strings.TrimSpace(line)
		if trimmed != "" {
			cleanedLines = append(cleanedLines, trimmed)
		}
	}

	return strings.Join(cleanedLines, "\n"), nil
}

func main() {
	client := anthropic.NewClient()

	bookURL := "https://www.gutenberg.org/cache/epub/1342/pg1342.txt"
	bookContent, err := fetchArticleContent(bookURL)
	if err != nil {
		log.Fatal(err)
	}

	largeText := bookContent
	if len(largeText) > 10000 {
		largeText = largeText[:10000]
	}

	// Без system prompt — кеширование внутри messages
	messages := []anthropic.MessageParam{
		anthropic.NewUserMessage(
			anthropic.ContentBlockParamUnion{OfText: &anthropic.TextBlockParam{
				Text:         largeText,
				CacheControl: anthropic.NewCacheControlEphemeralParam(),
			}},
			anthropic.NewTextBlock("Analyze the tone of this passage."),
		),
	}

	// Первый запрос — создаём кеш
	fmt.Println("First request - establishing cache")
	response1, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
		Model:     anthropic.Model("claude-sonnet-4-6"),
		MaxTokens: 20000,
		Thinking:  anthropic.ThinkingConfigParamOfEnabled(4000),
		Messages:  messages,
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("First response usage: %+v\n", response1.Usage)

	messages = append(messages, response1.ToParam())
	messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the characters in this passage.")))

	// Второй запрос — те же thinking-параметры (ожидается попадание в кеш)
	fmt.Println("\nSecond request - same thinking parameters (cache hit expected)")
	response2, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
		Model:     anthropic.Model("claude-sonnet-4-6"),
		MaxTokens: 20000,
		Thinking:  anthropic.ThinkingConfigParamOfEnabled(4000),
		Messages:  messages,
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Second response usage: %+v\n", response2.Usage)

	messages = append(messages, response2.ToParam())
	messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the setting in this passage.")))

	// Третий запрос — другой thinking-бюджет (ожидается промах кеша)
	fmt.Println("\nThird request - different thinking budget (cache miss expected)")
	response3, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
		Model:     anthropic.Model("claude-sonnet-4-6"),
		MaxTokens: 20000,
		Thinking:  anthropic.ThinkingConfigParamOfEnabled(8000),
		Messages:  messages,
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Third response usage: %+v\n", response3.Usage)
}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.Model;
import com.anthropic.models.messages.TextBlockParam;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;

public class CachingThinkingExample {
    public static void main(String[] args) throws Exception {
        AnthropicClient client = AnthropicOkHttpClient.fromEnv();

        String bookUrl = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt";
        String bookContent = fetchArticleContent(bookUrl);
        String largeText = bookContent.substring(0, Math.min(10000, bookContent.length()));

        // Первый запрос — создаём кеш
        System.out.println("First request - establishing cache");
        MessageCreateParams params1 = MessageCreateParams.builder()
            .model(Model.CLAUDE_SONNET_4_6)
            .maxTokens(20000L)
            .enabledThinking(4000L)
            .addUserMessageOfBlockParams(List.of(
                ContentBlockParam.ofText(TextBlockParam.builder()
                    .text(largeText)
                    .cacheControl(CacheControlEphemeral.builder().build())
                    .build()),
                ContentBlockParam.ofText(TextBlockParam.builder()
                    .text("Analyze the tone of this passage.")
                    .build())
            ))
            .build();

        Message response1 = client.messages().create(params1);
        System.out.println("First response usage: " + response1.usage());

        // Второй запрос — те же thinking-параметры (ожидается попадание в кеш)
        System.out.println("\nSecond request - same thinking parameters (cache hit expected)");
        MessageCreateParams params2 = MessageCreateParams.builder()
            .model(Model.CLAUDE_SONNET_4_6)
            .maxTokens(20000L)
            .enabledThinking(4000L)
            .addUserMessageOfBlockParams(List.of(
                ContentBlockParam.ofText(TextBlockParam.builder()
                    .text(largeText)
                    .cacheControl(CacheControlEphemeral.builder().build())
                    .build()),
                ContentBlockParam.ofText(TextBlockParam.builder()
                    .text("Analyze the tone of this passage.")
                    .build())
            ))
            .addAssistantMessageOfBlockParams(response1.content().stream()
                .map(block -> block.toParam())
                .collect(java.util.stream.Collectors.toList()))
            .addUserMessage("Analyze the characters in this passage.")
            .build();

        Message response2 = client.messages().create(params2);
        System.out.println("Second response usage: " + response2.usage());

        // Третий запрос — другой thinking-бюджет (ожидается промах кеша)
        System.out.println("\nThird request - different thinking budget (cache miss expected)");
        MessageCreateParams params3 = MessageCreateParams.builder()
            .model(Model.CLAUDE_SONNET_4_6)
            .maxTokens(20000L)
            .enabledThinking(8000L)
            .addUserMessageOfBlockParams(List.of(
                ContentBlockParam.ofText(TextBlockParam.builder()
                    .text(largeText)
                    .cacheControl(CacheControlEphemeral.builder().build())
                    .build()),
                ContentBlockParam.ofText(TextBlockParam.builder()
                    .text("Analyze the tone of this passage.")
                    .build())
            ))
            .addAssistantMessageOfBlockParams(response1.content().stream()
                .map(block -> block.toParam())
                .collect(java.util.stream.Collectors.toList()))
            .addUserMessage("Analyze the characters in this passage.")
            .addAssistantMessageOfBlockParams(response2.content().stream()
                .map(block -> block.toParam())
                .collect(java.util.stream.Collectors.toList()))
            .addUserMessage("Analyze the setting in this passage.")
            .build();

        Message response3 = client.messages().create(params3);
        System.out.println("Third response usage: " + response3.usage());
    }

    private static String fetchArticleContent(String url) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .build();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        return response.body();
    }
}
<?php


use Anthropic\Client;


function fetchArticleContent($url) {
    $content = file_get_contents($url);
    $lines = explode("\n", $content);
    $cleanedLines = array_filter(array_map('trim', $lines));
    return implode("\n", $cleanedLines);
}

$client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));

$bookUrl = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt";
$bookContent = fetchArticleContent($bookUrl);
$largeText = substr($bookContent, 0, 10000);

echo "First request - establishing cache\n";
$response1 = $client->messages->create(
    maxTokens: 20000,
    messages: [[
        'role' => 'user',
        'content' => [
            [
                'type' => 'text',
                'text' => $largeText,
                'cache_control' => ['type' => 'ephemeral']
            ],
            [
                'type' => 'text',
                'text' => 'Analyze the tone of this passage.'
            ]
        ]
    ]],
    model: 'claude-sonnet-4-6',
    thinking: ['type' => 'enabled', 'budget_tokens' => 4000],
);

echo "First response usage: " . json_encode($response1->usage) . "\n";

echo "\nSecond request - same thinking parameters (cache hit expected)\n";
$response2 = $client->messages->create(
    maxTokens: 20000,
    messages: [
        [
            'role' => 'user',
            'content' => [
                [
                    'type' => 'text',
                    'text' => $largeText,
                    'cache_control' => ['type' => 'ephemeral']
                ],
                [
                    'type' => 'text',
                    'text' => 'Analyze the tone of this passage.'
                ]
            ]
        ],
        [
            'role' => 'assistant',
            'content' => $response1->content
        ],
        [
            'role' => 'user',
            'content' => 'Analyze the characters in this passage.'
        ]
    ],
    model: 'claude-sonnet-4-6',
    thinking: ['type' => 'enabled', 'budget_tokens' => 4000],
);

echo "Second response usage: " . json_encode($response2->usage) . "\n";

echo "\nThird request - different thinking budget (cache miss expected)\n";
$response3 = $client->messages->create(
    maxTokens: 20000,
    messages: [
        [
            'role' => 'user',
            'content' => [
                [
                    'type' => 'text',
                    'text' => $largeText,
                    'cache_control' => ['type' => 'ephemeral']
                ],
                [
                    'type' => 'text',
                    'text' => 'Analyze the tone of this passage.'
                ]
            ]
        ],
        [
            'role' => 'assistant',
            'content' => $response1->content
        ],
        [
            'role' => 'user',
            'content' => 'Analyze the characters in this passage.'
        ],
        [
            'role' => 'assistant',
            'content' => $response2->content
        ],
        [
            'role' => 'user',
            'content' => 'Analyze the setting in this passage.'
        ]
    ],
    model: 'claude-sonnet-4-6',
    thinking: ['type' => 'enabled', 'budget_tokens' => 8000],
);

echo "Third response usage: " . json_encode($response3->usage) . "\n";
require "anthropic"
require "net/http"
require "uri"

def fetch_article_content(url)
  uri = URI.parse(url)
  response = Net::HTTP.get_response(uri)
  text = response.body

  lines = text.split("\n").map(&:strip)
  lines.reject(&:empty?).join("\n")
end

client = Anthropic::Client.new

book_url = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt"
book_content = fetch_article_content(book_url)
large_text = book_content[0...10000]

puts "First request - establishing cache"
response1 = client.messages.create(
  model: "claude-sonnet-4-6",
  max_tokens: 20000,
  thinking: {
    type: "enabled",
    budget_tokens: 4000
  },
  messages: [{
    role: "user",
    content: [
      {
        type: "text",
        text: large_text,
        cache_control: { type: "ephemeral" }
      },
      {
        type: "text",
        text: "Analyze the tone of this passage."
      }
    ]
  }]
)

puts "First response usage: #{response1.usage}"

puts "\nSecond request - same thinking parameters (cache hit expected)"
response2 = client.messages.create(
  model: "claude-sonnet-4-6",
  max_tokens: 20000,
  thinking: {
    type: "enabled",
    budget_tokens: 4000
  },
  messages: [
    {
      role: "user",
      content: [
        {
          type: "text",
          text: large_text,
          cache_control: { type: "ephemeral" }
        },
        {
          type: "text",
          text: "Analyze the tone of this passage."
        }
      ]
    },
    {
      role: "assistant",
      content: response1.content
    },
    {
      role: "user",
      content: "Analyze the characters in this passage."
    }
  ]
)

puts "Second response usage: #{response2.usage}"

puts "\nThird request - different thinking budget (cache miss expected)"
response3 = client.messages.create(
  model: "claude-sonnet-4-6",
  max_tokens: 20000,
  thinking: {
    type: "enabled",
    budget_tokens: 8000
  },
  messages: [
    {
      role: "user",
      content: [
        {
          type: "text",
          text: large_text,
          cache_control: { type: "ephemeral" }
        },
        {
          type: "text",
          text: "Analyze the tone of this passage."
        }
      ]
    },
    {
      role: "assistant",
      content: response1.content
    },
    {
      role: "user",
      content: "Analyze the characters in this passage."
    },
    {
      role: "assistant",
      content: response2.content
    },
    {
      role: "user",
      content: "Analyze the setting in this passage."
    }
  ]
)

puts "Third response usage: #{response3.usage}"

Вот вывод скрипта (числа могут немного отличаться)

First request - establishing cache
First response usage: { cache_creation_input_tokens: 1370, cache_read_input_tokens: 0, input_tokens: 17, output_tokens: 700 }

Second request - same thinking parameters (cache hit expected)

Second response usage: { cache_creation_input_tokens: 0, cache_read_input_tokens: 1370, input_tokens: 303, output_tokens: 874 }

Third request - different thinking budget (cache miss expected)
Third response usage: { cache_creation_input_tokens: 1370, cache_read_input_tokens: 0, input_tokens: 747, output_tokens: 619 }

Этот пример показывает, что когда кеширование настроено в массиве messages, изменение параметров thinking (budget_tokens увеличен с 4000 до 8000) инвалидирует кеш. Третий запрос показывает отсутствие cache hit с cache_creation_input_tokens=1370 и cache_read_input_tokens=0, что доказывает: кеширование на уровне messages инвалидируется при изменении параметров thinking.

Max tokens and context window size with extended thinking

В более старых моделях Claude (до Claude Sonnet 3.7), если сумма prompt-токенов и max_tokens превышала context window модели, система автоматически корректировала max_tokens, чтобы уложиться в лимит контекста. Это значило, что вы могли установить большое значение max_tokens, и система тихо уменьшала его при необходимости.

В моделях Claude 3.7 и 4 max_tokens (который включает ваш thinking-бюджет, когда thinking включён) применяется как строгий лимит. Теперь система возвращает ошибку валидации, если prompt-токены + max_tokens превышают размер context window.

The context window with extended thinking

При расчёте использования context window с включённым thinking есть несколько моментов, о которых стоит знать:

Диаграмма ниже демонстрирует специализированное управление токенами при включённом extended thinking:

Context window diagram with extended thinking

Эффективный context window рассчитывается так:

context window =
  (current input tokens - previous thinking tokens) +
  (thinking tokens + encrypted thinking tokens + text output tokens)

Используйте token counting API, чтобы получать точные подсчёты токенов для вашего конкретного use case, особенно при работе с multi-turn-разговорами, включающими thinking.

The context window with extended thinking and tool use

При использовании extended thinking с tool use thinking-блоки должны явно сохраняться и возвращаться вместе с результатами инструментов.

Расчёт эффективного context window для extended thinking с tool use становится:

context window =
  (current input tokens + previous thinking tokens + tool use tokens) +
  (thinking tokens + encrypted thinking tokens + text output tokens)

Диаграмма ниже иллюстрирует управление токенами для extended thinking с tool use:

Context window diagram with extended thinking and tool use

Managing tokens with extended thinking

С учётом поведения context window и max_tokens при extended thinking в моделях Claude 3.7 и 4, вам может потребоваться:

Это изменение сделано для того, чтобы обеспечить более предсказуемое и прозрачное поведение, особенно по мере значительного увеличения максимальных лимитов токенов.

Thinking encryption

Полный thinking-контент шифруется и возвращается в поле signature. Это поле используется для верификации того, что thinking-блоки были сгенерированы Claude, когда передаются обратно в API.

Несколько важных моментов о шифровании thinking:

Redacted thinking blocks

Помимо обычных блоков thinking API может возвращать блоки redacted_thinking. Блок redacted_thinking содержит зашифрованный thinking-контент в поле data без читаемого summary:

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

Поле data непрозрачное и зашифрованное. Как и поле signature на обычных thinking-блоках, вы должны передавать блоки redacted_thinking обратно в API без изменений при продолжении multi-turn-разговора с инструментами.

Differences in thinking across model versions

Messages API обрабатывает thinking по-разному в Claude Sonnet 3.7 и моделях Claude 4, прежде всего в поведении суммаризации.

Сжатое сравнение — в таблице ниже:

Feature Claude Sonnet 3.7 Claude 4 Models (pre-Opus 4.5) Claude Opus 4.5 Claude Sonnet 4.6 Claude Opus 4.6 (adaptive thinking) Claude Mythos Preview (adaptive thinking)
Вывод размышлений Возвращает полный вывод размышлений Возвращает суммаризированные размышления Возвращает суммаризированные размышления Возвращает суммаризированные размышления Возвращает суммаризированные размышления Omitted by default; set display: "summarized" to receive summarized thinking. Raw thinking tokens are never returned.
Interleaved Thinking Не поддерживается Поддерживается с beta-заголовком interleaved-thinking-2025-05-14 Поддерживается с beta-заголовком interleaved-thinking-2025-05-14 Поддерживается с beta-заголовком interleaved-thinking-2025-05-14 или автоматически с adaptive thinking Автоматически с adaptive thinking (beta-заголовок не поддерживается) Автоматически с adaptive thinking (beta-заголовок не поддерживается). На этой модели рассуждения между инструментами перемещаются в thinking-блоки.
Thinking Block Preservation Не сохраняются между ходами Не сохраняются между ходами Сохраняются по умолчанию Сохраняются по умолчанию Сохраняются по умолчанию Сохраняются по умолчанию. Блоки удаляются при продолжении разговора на модели, которая не поддерживает thinking-формат Mythos.

Thinking block preservation in Claude Opus 4.5 and later

Начиная с Claude Opus 4.5 (и продолжая в Claude Opus 4.6) thinking-блоки из предыдущих ходов ассистента сохраняются в контексте модели по умолчанию. Это отличается от более ранних моделей, которые удаляют thinking-блоки из предыдущих ходов.

Преимущества сохранения thinking-блоков:

Важные моменты:

Pricing

Полную информацию о ценах, включая базовые тарифы, cache writes, cache hits и output-токены, см. на странице цен.

Thinking-процесс тарифицируется за:

При использовании summarized thinking:

При использовании display: "omitted":

Best practices and considerations for extended thinking

Working with thinking budgets

Performance considerations

Feature compatibility

Usage guidelines

Next steps

Try the extended thinking cookbook

Изучите практические примеры thinking в cookbook.

Extended thinking prompting tips

Изучите best practices prompt engineering для extended thinking.