Extended thinking даёт Claude расширенные возможности рассуждения для сложных задач, обеспечивая разный уровень прозрачности пошагового мыслительного процесса перед тем, как модель выдаёт окончательный ответ.
Ручное extended thinking (thinking: {type: "enabled", budget_tokens: N}) поддерживается на всех текущих моделях Claude, кроме Claude Opus 4.7 и более новых моделей, где оно больше не принимается и возвращает ошибку 400. У нескольких моделей поведение зависит от режима:
claude-opus-4-7) и более новые модели: ручное extended thinking больше не поддерживается. Используйте вместо этого adaptive thinking (thinking: {type: "adaptive"}) с параметром effort.thinking: {type: "enabled", budget_tokens: N} также принимается. thinking: {type: "disabled"} не поддерживается, а display по умолчанию имеет значение "omitted", а не возвращает thinking-контент. Передайте display: "summarized", чтобы получить summary.claude-opus-4-6): рекомендуется adaptive thinking; ручной режим (type: "enabled") устарел, но ещё работает.claude-sonnet-4-6): рекомендуется adaptive thinking; ручной режим (type: "enabled") с interleaved-режимом устарел, но ещё работает.Когда 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.
Вот пример использования 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.
Когда 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:
Поле display в конфигурации thinking управляет тем, как thinking-контент возвращается в ответах API. Оно принимает два значения:
"summarized": thinking-блоки содержат суммаризированный thinking-текст. Подробнее — в Summarized thinking. Это значение по умолчанию на Claude Opus 4.6, Claude Sonnet 4.6 и более ранних моделях Claude 4."omitted": thinking-блоки возвращаются с пустым полем thinking. Поле signature по-прежнему несёт зашифрованный полный thinking для multi-turn-консистентности (см. Thinking encryption). Это значение по умолчанию на Claude Opus 4.7 и Claude Mythos Preview.Установка display: "omitted" полезна, когда ваше приложение не показывает thinking-контент пользователям. Главное преимущество — более быстрое время до первого текстового токена при стриминге: сервер полностью пропускает стриминг thinking-токенов и доставляет только signature, поэтому финальный текстовый ответ начинает стримиться раньше.
Несколько важных моментов про omitted thinking:
signature, чтобы реконструировать оригинальный thinking для построения промпта (см. Preserving thinking blocks). Любой текст, который вы помещаете в поле thinking вернувшегося omitted-блока, игнорируется.display недопустим с thinking.type: "disabled" (нечего показывать).thinking.type: "adaptive", если модель пропускает thinking для простого запроса, thinking-блок не создаётся, независимо от display.Автоматизированные пайплайны, которые никогда не показывают 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.
Вы можете стримить ответы 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 можно использовать вместе с tool use, что позволяет Claude рассуждать в процессе выбора инструментов и обработки результатов.
При использовании extended thinking с tool use учитывайте следующие ограничения:
tool_choice: {"type": "auto"} (по умолчанию) или tool_choice: {"type": "none"}. Использование tool_choice: {"type": "any"} или tool_choice: {"type": "tool", "name": "..."} приведёт к ошибке, потому что эти опции принудительно включают tool use, что несовместимо с extended thinking.thinking для последнего сообщения ассистента. Включайте полный неизменённый блок обратно в API, чтобы поддерживать непрерывность рассуждений.Нельзя переключать 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 концептуально — часть одного непрерывного ответа ассистента.
Когда возникает mid-turn-конфликт thinking (например, переключение thinking on/off во время цикла tool use), API автоматически отключает thinking для этого запроса. Чтобы сохранить качество модели и оставаться on-distribution, API может:
Это значит, что попытка переключить thinking в середине хода не приведёт к ошибке, но thinking будет тихо отключён для этого запроса. Чтобы убедиться, что thinking был активен, проверьте наличие блоков thinking в ответе.
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-блоки при предоставлении результатов инструментов:
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)"
}
]
}
При tool use вы должны передавать блоки thinking обратно в API, причём передавать полный неизменённый блок. Это критично для сохранения потока рассуждений модели и целостности разговора.
Когда Claude вызывает инструменты, он приостанавливает построение ответа, ожидая внешней информации. Когда возвращаются результаты инструментов, Claude продолжает строить тот же существующий ответ. Это требует сохранения thinking-блоков во время tool use по нескольким причинам:
Важно: при предоставлении блоков thinking вся последовательность последовательных блоков thinking должна совпадать с выводом, сгенерированным моделью при оригинальном запросе; нельзя переупорядочивать или модифицировать последовательность этих блоков.
Extended thinking с tool use в моделях Claude 4 поддерживает interleaved thinking, что позволяет Claude думать между вызовами инструментов и принимать более сложные решения после получения результатов инструментов.
При interleaved thinking Claude может:
Поддержка моделей:
interleaved-thinking-2025-05-14 на Opus 4.6 устарел и безопасно игнорируется, если включён.interleaved-thinking-2025-05-14 с ручным extended thinking (thinking: {type: "enabled"}) ещё работает, но устарел.interleaved-thinking-2025-05-14 в свой запрос API, чтобы включить interleaved thinking.Несколько важных моментов про interleaved thinking:
budget_tokens может превышать параметр max_tokens, поскольку он представляет общий бюджет всех thinking-блоков в одном ходе ассистента.interleaved-thinking-2025-05-14 в запросах к любой модели, без эффекта (кроме Opus 4.7 и Opus 4.6, где он устарел и безопасно игнорируется).interleaved-thinking-2025-05-14 любой модели, кроме Claude Opus 4.7, Claude Opus 4.6, Claude Sonnet 4.6, Claude Opus 4.5, Claude Opus 4.1, Opus 4 (deprecated), Sonnet 4.5 или Sonnet 4 (deprecated), ваш запрос упадёт.Без 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
Когда 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
Prompt caching с thinking имеет несколько важных моментов:
Удаление контекста thinking-блоков
Паттерны инвалидации кеша
При использовании extended thinking с tool use 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]
Ключевые моменты:
cache_control# Скачиваем ~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}"
# Скачиваем первые ~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.
В более старых моделях 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.
При расчёте использования context window с включённым thinking есть несколько моментов, о которых стоит знать:
max_tokens для этого ходаДиаграмма ниже демонстрирует специализированное управление токенами при включённом 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.
При использовании 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 и max_tokens при extended thinking в моделях Claude 3.7 и 4, вам может потребоваться:
max_tokens по мере изменения длины промптаЭто изменение сделано для того, чтобы обеспечить более предсказуемое и прозрачное поведение, особенно по мере значительного увеличения максимальных лимитов токенов.
Полный thinking-контент шифруется и возвращается в поле signature. Это поле используется для верификации того, что thinking-блоки были сгенерированы Claude, когда передаются обратно в API.
Несколько важных моментов о шифровании thinking:
signature_delta внутри события content_block_delta прямо перед событием content_block_stop.signature в моделях Claude 4 значительно длиннее, чем в предыдущих моделях.signature — непрозрачное, и его не следует интерпретировать или парсить.signature совместимы между платформами (Claude APIs, Amazon Bedrock и Vertex AI). Значения, сгенерированные на одной платформе, совместимы с другой.Помимо обычных блоков thinking API может возвращать блоки redacted_thinking. Блок redacted_thinking содержит зашифрованный thinking-контент в поле data без читаемого summary:
{
"type": "redacted_thinking",
"data": "..."
}
Поле data непрозрачное и зашифрованное. Как и поле signature на обычных thinking-блоках, вы должны передавать блоки redacted_thinking обратно в API без изменений при продолжении multi-turn-разговора с инструментами.
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. |
Начиная с Claude Opus 4.5 (и продолжая в Claude Opus 4.6) thinking-блоки из предыдущих ходов ассистента сохраняются в контексте модели по умолчанию. Это отличается от более ранних моделей, которые удаляют thinking-блоки из предыдущих ходов.
Преимущества сохранения thinking-блоков:
Важные моменты:
Полную информацию о ценах, включая базовые тарифы, cache writes, cache hits и output-токены, см. на странице цен.
Thinking-процесс тарифицируется за:
При использовании summarized thinking:
При использовании display: "omitted":
thinking пустое)max_tokens больше 21 333, чтобы избежать HTTP-таймаутов на длительных запросах. Это валидация на стороне клиента, не ограничение API. Если вам не нужно обрабатывать события инкрементально, используйте .stream() с .get_final_message() (Python) или .finalMessage() (TypeScript), чтобы получить полный объект Message без обработки отдельных событий. Подробнее — в Streaming Messages. При стриминге будьте готовы обрабатывать как thinking-, так и text-блоки контента по мере их прихода.display: "omitted" в конфигурации thinking, чтобы уменьшить time-to-first-text-token. См. Controlling thinking display.temperature или top_k, а также с принудительным tool use.top_p в значения от 1 до 0,95.