Thinking 스티어링
Thinking 스티어링 (Steering thinking)
Claude의 thinking은 적응형이에요: 모델이 각 요청을 평가하고 스스로 생각할지, 얼마나 생각할지 결정해요. 여러분은 의도를 설정하고, 선택적으로 effort를 지정하며, 모델은 추론이 도움이 될 곳에 추론을 배분해요. 이 페이지에서는 Claude가 언제 생각할지 결정하는 방식, 그 결정을 스티어링하는 방법, 그리고 그에 따르는 캐싱·비용·가격 메커니즘을 다뤄요.
출처: 문서
본문
Claude의 thinking은 적응형이에요: 모델이 각 요청을 평가하고 스스로 생각할지, 얼마나 생각할지 결정해요. 여러분은 의도를 설정하고, 선택적으로 effort를 지정하며, 모델은 추론이 도움이 될 것이라 판단하는 곳에 추론을 배분해요.
이 덕분에 thinking은 사소한 요청과 복잡한 요청이 섞인 워크로드와, 단계마다 적절한 추론 양이 달라지는 장기 에이전트 워크플로에 잘 맞아요.
thinking을 켜는 방법, thinking 출력을 읽는 방법, Claude Fable 5 및 Claude Mythos 5에서의 thinking 출력에 대해 배우려면 Thinking 개요를 보세요. 이 페이지는 Claude가 언제 생각할지 결정하는 방식, 그 결정을 스티어링하는 방법, 그리고 그에 따르는 캐싱·비용·가격 메커니즘을 다뤄요.
Claude가 언제 생각할지 결정하는 방식
Thinking은 모델에게 선택적이에요. 각 요청에서 Claude는 입력의 복잡성을 저울질하고 더 깊은 추론이 답을 개선할지 결정해요. 단순한 사실 질문은 생각 블록 없이 직접 응답할 수 있고, 다단계 수학 문제나 까다로운 디버깅 과제는 더 깊은 추론을 촉발해요.
결정은 요청마다 일어나요. 같은 대화에 thinking이 있는 턴과 없는 턴이 섞일 수 있고, Claude가 생각하지 않기로 한 턴에는 thinking 블록이 없어요. 모든 어시스턴트 턴이 thinking 블록으로 시작한다고 가정하는 애플리케이션 로직을 만들지 마세요.
이 결정의 주요 제어는 effort 파라미터인데, 이는 Claude가 생각하려는 의지와 깊이에 대한 부드러운 지침 역할을 해요. 각 수준이 무엇을 하는지는 이 페이지의 Effort 수준을 보세요.
Claude가 덜 자주 생각하길 원하면 프롬프트 기반 스티어링에 손대기 전에 effort 수준을 낮추세요.
Thinking은 또한 도구 사용과 자동으로 인터리브돼요: Claude는 도구 호출 사이에 생각해서, 각 도구 결과에 대해 다음에 무엇을 할지 결정하기 전에 숙고할 수 있어요(인터리브된 thinking). 이를 위해 베타 헤더나 추가 구성이 필요 없어요.
thinking 구성과 effort 파라미터가 어떻게 상호작용하는지 전체 그림은 Thinking과 effort를 보세요.
Claude가 생각하는 빈도 스티어링하기
Claude가 주어진 턴에 생각할지 여부는 프롬프트 가능해요. Effort가 전체 자세를 정하지만, 자연어 지침으로 결정을 직접 다듬을 수도 있어요. 시스템 프롬프트에서 전역으로, 혹은 사용자 턴에서 메시지별로요.
두 레버를 이 순서로 함께 쓰세요:
- 워크로드의 기본 품질-지연 시간 균형에 맞는 effort 수준을 설정하세요.
- 그 수준에서도 Claude의 트리거링이 요구와 여전히 맞지 않을 때만 프롬프트 지침을 추가하세요.
thinking과 함께하는 넓은 프롬프팅 지침은 thinking 및 인터리브된 thinking 역량 활용을 보세요.
Effort 수준
Effort는 thinking의 주요 스티어링 레버예요. 각 수준은 Claude가 생각하는 빈도와 깊이에 대해 다른 기본값을 설정해요:
| Effort 수준 | Thinking 동작 |
|---|---|
max |
Claude가 가장 쉽게, 가장 깊이 생각하고 thinking 길이에 제약이 없어요. |
xhigh |
Claude가 high보다 더 쉽고 더 깊게 생각해요. 확장된 탐색에 적합해요. |
high (대부분 모델의 기본) |
Claude가 혜택을 받는 대부분의 요청에서 생각해요. 복잡한 과제에서 깊은 추론을 제공해요. |
medium (Claude Opus 5.5의 기본) |
Claude가 적당한 thinking을 써요. 단순한 쿼리에서는 생각을 건너뛸 수 있어요. |
low |
Claude가 thinking을 최소화해요. 속도가 가장 중요한 단순 과제에서는 생각을 건너뛰어요. |
모든 수준에서 Claude는 요청마다 생각할지 결정해요. 도구 사용 루프에서 새 사용자 입력 후의 첫 요청이 보통 추론 대부분을 담고, 도구 결과만 처리하는 후속 요청은 xhigh와 max에서도 생각을 건너뛸 수 있어요. 요청당 thinking은 대화가 길어질수록 줄어드는 경향도 있어요. 어떤 수준도 모든 요청에 thinking 블록을 보장하지 않아요.
이 표는 각 수준이 thinking 동작을 어떻게 바꾸는지 설명해요. 주어진 워크로드에 어떤 수준을 고를지에 대한 지침(모델별 권장 포함)은 effort 페이지의 effort 파라미터를 조정할 때를 보세요.
Effort는 thinking 객체 안이 아니라 output_config.effort에 설정돼요. 언어별 전체 예시는 Effort를 보세요.
{
"model": "claude-opus-5-5",
"max_tokens": 4096,
"output_config": { "effort": "medium" },
"messages": [{ "role": "user", "content": "..." }]
}
수준 가용성은 모델마다 달라요. effort 페이지의 effort 가용성 표가 각 모델이 지원하는 수준의 권위 있는 출처예요.
시스템 프롬프트 지침
시스템 프롬프트 지침은 대화의 모든 요청에 대해 Claude의 thinking 임계값을 이동시켜요. Claude가 워크로드가 필요로 하는 것보다 더 자주 생각한다면, 시스템 프롬프트에 이런 지침을 추가하세요:
Extended thinking adds latency and should only be used when it
will meaningfully improve answer quality, typically for problems
that require multistep reasoning. When in doubt, respond directly.
대신 생각을 장려하려면 이런 표현을 쓰세요:
This task involves multistep reasoning. Think carefully before responding.
스티어링 효과는 정확한 표현에 민감할 수 있어요. 한 표현이 원하는 동작을 만들지 못하면 더 직접적인 변형을 시도하세요.
메시지별 스티어링
시스템 프롬프트와 별개로, 사용자 턴에서도 메시지 단위로 thinking을 스티어링할 수 있어요. 사용자 메시지에 "Please think hard before responding."를 덧붙이면 그 턴에서 Claude가 생각하도록 장려하고, "Answer directly without deliberating."는 그것을 억제해요.
메시지별 스티어링은 대화에서 일부 요청만 확장 추론이 필요할 때 유용해요. 에이전트 하니스는 예를 들어 계획 단계에는 장려 표현을, 일상적인 확인에는 억제 표현을, 시스템 프롬프트를 건드리거나 턴 사이에 요청 파라미터를 바꾸지 않고도 붙일 수 있어요.
워크로드에서 스티어링 검증하기
프롬프트 기반 스티어링은 모델 동작을 바꾸므로, 다른 프롬프트 변경처럼 취급하세요: 출시 전에 측정하세요. 지침 유무로 대표적인 트래픽 샘플을 실행하고, thinking 트리거 빈도(응답에서 thinking 블록의 존재), 출력 토큰 사용량, 지연 시간, 그리고 당신에게 중요한 케이스에서의 답 품질을 비교하세요.
메커니즘
Claude가 자신의 thinking을 관리하는 데서 세 가지 메커니즘이 따라와요: 턴 검증, 프롬프트 캐싱, 비용을 제한하는 방법.
턴 검증
어시스턴트 턴은 thinking 블록으로 시작할 필요가 없어요. (레거시 수동 thinking 예산 모델은 thinking 지원 요청의 마지막 어시스턴트 턴이 thinking 블록으로 시작하도록 강제해요. 수동 모드에서의 턴 구조를 보세요.)
멀티턴 애플리케이션에서는 이 말이 곧 가진 형태 그대로 대화 기록을 다시 전달할 수 있다는 뜻이에요:
- Claude가 생각하지 않기로 한 어시스턴트 턴은 그대로 유효한 기록이에요.
- thinking 없이 시작했거나 다른 thinking 구성을 쓴 대화를, 기록을 다시 쓰지 않고 재개할 수 있어요.
- 섞인 출처에서 조립한 기록은 검증을 통과하려고 각 어시스턴트 턴 시작에 thinking 블록을 다시 넣을 필요가 없어요.
이 완화는 무엇을 보내야 하는지가 아니라 검증에 관한 거예요. thinking 블록이 있으면 수정 없이 다시 전달하세요. 특히 도구 사용 중에는요. 거기서 thinking 블록은 Claude 도구 호출 뒤의 추론을 담아요. 전체 규칙은 Thinking 개요를 보세요.
프롬프트 캐싱
같은 thinking 구성과 effort 수준을 유지하는 연속 요청은 프롬프트 캐싱을 보존해요. 전체 규칙은 Thinking과 프롬프트 캐싱을 보세요. 해석된 effort 값은 프롬프트로 렌더링되므로, 요청 사이에 바꾸면 캐시 중단점을 무효화해요. 마치 그것을 쓰는 모델에서 레거시 budget_tokens 파라미터를 바꾸는 것과 같아요. effort를 모델의 기본값으로 명시적으로 설정하는 것은 생략과 동등하고 캐시를 깨지 않아요.
실용적 결과: 대화당 thinking 구성과 effort 수준을 고르고 유지하세요. 어떤 턴이 더 많거나 적은 생각을 필요로 하면 메시지별 프롬프팅으로 스티어링하세요: 가장 최근 사용자 메시지에 덧붙인 지침은 이전 캐시 중단점을 그대로 두지만, 구성이나 effort 변경은 그렇지 않아요.
다음 예시는 직접 실행할 수 있는 멀티턴 스크립트로 무효화를 보여줘요:
<Tab title="CLI">
<Note>
This workflow doesn't translate well to a one-off shell command. See the SDK tabs for the multi-turn pattern; per-turn CLI invocations follow the examples on the [Prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) page.
</Note>
</Tab>
<Tab title="Python">
```python
import requests
client = Anthropic()
def fetch_article_content(url):
text = requests.get(url).text
lines = (line.strip() for line in text.splitlines())
return "\n".join(line for line in lines if line)
# Fetch the content of the article
book_url = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt"
book_content = fetch_article_content(book_url)
# Use just enough text for caching (first few chapters)
LARGE_TEXT = book_content[:10000]
# No system prompt - caching in messages instead
MESSAGES = [
{
"role": "user",
"content": [
{
"type": "text",
"text": LARGE_TEXT,
"cache_control": {"type": "ephemeral"},
},
{"type": "text", "text": "Analyze the tone of this passage."},
],
}
]
# First request - establish cache
print("First request - establishing cache")
response1 = client.messages.create(
model="claude-opus-5-5",
max_tokens=16000,
thinking={"type": "adaptive"},
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."})
# Second request - same configuration (cache hit expected)
print("\nSecond request - same configuration (cache hit expected)")
response2 = client.messages.create(
model="claude-opus-5-5",
max_tokens=16000,
thinking={"type": "adaptive"},
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."})
# Third request - different effort level (cache miss expected)
print("\nThird request - different effort level (cache miss expected)")
response3 = client.messages.create(
model="claude-opus-5-5",
max_tokens=16000,
thinking={"type": "adaptive"},
output_config={"effort": "low"},
messages=MESSAGES,
)
print(f"Third response usage: {response3.usage}")
```
</Tab>
<Tab title="TypeScript">
```typescript
const client = new Anthropic();
async function fetchArticleContent(url: string): Promise<string> {
const response = await fetch(url);
const text = await response.text();
const lines = text.split("\n").map((line) => line.trim());
return lines.filter((line) => line).join("\n");
}
const bookUrl = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt";
const bookContent = await fetchArticleContent(bookUrl);
const LARGE_TEXT = bookContent.substring(0, 10000);
// No system prompt - caching in messages instead
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."
}
]
}
];
// First request - establish cache
console.log("First request - establishing cache");
const response1 = await client.messages.create({
model: "claude-opus-5-5",
max_tokens: 16000,
thinking: { type: "adaptive" },
messages
});
console.log("First response usage: ", response1.usage);
messages.push(
{ role: "assistant", content: response1.content },
{ role: "user", content: "Analyze the characters in this passage." }
);
// Second request - same configuration (cache hit expected)
console.log("\nSecond request - same configuration (cache hit expected)");
const response2 = await client.messages.create({
model: "claude-opus-5-5",
max_tokens: 16000,
thinking: { type: "adaptive" },
messages
});
console.log("Second response usage: ", response2.usage);
messages.push(
{ role: "assistant", content: response2.content },
{ role: "user", content: "Analyze the setting in this passage." }
);
// Third request - different effort level (cache miss expected)
console.log("\nThird request - different effort level (cache miss expected)");
const response3 = await client.messages.create({
model: "claude-opus-5-5",
max_tokens: 16000,
thinking: { type: "adaptive" },
output_config: { effort: "low" },
messages
});
console.log("Third response usage: ", response3.usage);
```
</Tab>
<Tab title="C#">
```csharp
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.ClaudeOpus5_5,
MaxTokens = 16000,
Thinking = new ThinkingConfigAdaptive(),
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 configuration (cache hit expected)");
var parameters2 = new MessageCreateParams
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 16000,
Thinking = new ThinkingConfigAdaptive(),
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.Select(block => new ContentBlockParam(block.Json)).ToList()
},
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 effort level (cache miss expected)");
var parameters3 = new MessageCreateParams
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 16000,
Thinking = new ThinkingConfigAdaptive(),
OutputConfig = new OutputConfig
{
Effort = Effort.Low
},
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.Select(block => new ContentBlockParam(block.Json)).ToList()
},
new()
{
Role = Role.User,
Content = "Analyze the characters in this passage."
},
new()
{
Role = Role.Assistant,
Content = response2.Content.Select(block => new ContentBlockParam(block.Json)).ToList()
},
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;
}
```
</Tab>
<Tab title="Go">
```go
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]
}
// No system prompt - caching in messages instead
messages := []anthropic.MessageParam{
anthropic.NewUserMessage(
anthropic.ContentBlockParamUnion{OfText: &anthropic.TextBlockParam{
Text: largeText,
CacheControl: anthropic.NewCacheControlEphemeralParam(),
}},
anthropic.NewTextBlock("Analyze the tone of this passage."),
),
}
// First request - establish cache
fmt.Println("First request - establishing cache")
response1, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamUnion{
OfAdaptive: &anthropic.ThinkingConfigAdaptiveParam{},
},
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("First response usage: %s\n", response1.Usage.RawJSON())
messages = append(messages, response1.ToParam())
messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the characters in this passage.")))
// Second request - same configuration (cache hit expected)
fmt.Println("\nSecond request - same configuration (cache hit expected)")
response2, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamUnion{
OfAdaptive: &anthropic.ThinkingConfigAdaptiveParam{},
},
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Second response usage: %s\n", response2.Usage.RawJSON())
messages = append(messages, response2.ToParam())
messages = append(messages, anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze the setting in this passage.")))
// Third request - different effort level (cache miss expected)
fmt.Println("\nThird request - different effort level (cache miss expected)")
response3, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 16000,
Thinking: anthropic.ThinkingConfigParamUnion{
OfAdaptive: &anthropic.ThinkingConfigAdaptiveParam{},
},
OutputConfig: anthropic.OutputConfigParam{
Effort: anthropic.OutputConfigEffortLow,
},
Messages: messages,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Third response usage: %s\n", response3.Usage.RawJSON())
```
</Tab>
<Tab title="Java">
```java
import com.anthropic.models.messages.CacheControlEphemeral;
// ...
void main() 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()));
// First request - establishing cache
IO.println("First request - establishing cache");
MessageCreateParams params1 = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(16000L)
.thinking(ThinkingConfigAdaptive.builder().build())
.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);
IO.println("First response usage: " + response1.usage());
// Second request - same configuration (cache hit expected)
IO.println("\nSecond request - same configuration (cache hit expected)");
MessageCreateParams params2 = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(16000L)
.thinking(ThinkingConfigAdaptive.builder().build())
.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);
IO.println("Second response usage: " + response2.usage());
// Third request - different effort level (cache miss expected)
IO.println("\nThird request - different effort level (cache miss expected)");
MessageCreateParams params3 = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(16000L)
.thinking(ThinkingConfigAdaptive.builder().build())
.outputConfig(OutputConfig.builder()
.effort(OutputConfig.Effort.LOW)
.build())
.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);
IO.println("Third response usage: " + response3.usage());
}
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();
}
```
</Tab>
<Tab title="PHP">
```php
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();
$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: 16000,
messages: [[
'role' => 'user',
'content' => [
[
'type' => 'text',
'text' => $largeText,
'cache_control' => ['type' => 'ephemeral']
],
[
'type' => 'text',
'text' => 'Analyze the tone of this passage.'
]
]
]],
model: 'claude-opus-5-5',
thinking: ['type' => 'adaptive'],
);
echo "First response usage: " . json_encode($response1->usage) . "\n";
echo "\nSecond request - same configuration (cache hit expected)\n";
$response2 = $client->messages->create(
maxTokens: 16000,
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-opus-5-5',
thinking: ['type' => 'adaptive'],
);
echo "Second response usage: " . json_encode($response2->usage) . "\n";
echo "\nThird request - different effort level (cache miss expected)\n";
$response3 = $client->messages->create(
maxTokens: 16000,
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-opus-5-5',
thinking: ['type' => 'adaptive'],
outputConfig: ['effort' => 'low'],
);
echo "Third response usage: " . json_encode($response3->usage) . "\n";
```
</Tab>
<Tab title="Ruby">
```ruby
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-opus-5-5",
max_tokens: 16000,
thinking: {
type: "adaptive"
},
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 configuration (cache hit expected)"
response2 = client.messages.create(
model: "claude-opus-5-5",
max_tokens: 16000,
thinking: {
type: "adaptive"
},
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 effort level (cache miss expected)"
response3 = client.messages.create(
model: "claude-opus-5-5",
max_tokens: 16000,
thinking: {
type: "adaptive"
},
output_config: {
effort: "low"
},
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}"
```
</Tab>
다음은 스크립트의 출력이에요(숫자가 약간 다를 수 있어요):
First request - establishing cache
First response usage: { cache_creation_input_tokens: 3546, cache_read_input_tokens: 0, input_tokens: 15, output_tokens: 1033 }
Second request - same configuration (cache hit expected)
Second response usage: { cache_creation_input_tokens: 0, cache_read_input_tokens: 3546, input_tokens: 1062, output_tokens: 1630 }
Third request - different effort level (cache miss expected)
Third response usage: { cache_creation_input_tokens: 3546, cache_read_input_tokens: 0, input_tokens: 2706, output_tokens: 1468 }
캐시 중단점이 messages 배열에 있으므로, Claude Opus 5.5의 기본인 medium에서 low로 effort를 바꾸면 그것이 무효화돼요: 세 번째 요청은 두 번째가 전체 캐시 읽기를 보여준 데 반해 cache_creation_input_tokens=3546과 cache_read_input_tokens=0을 보여줘요.
비용 제어
thinking 토큰 예산을 설정하지 않아요. 두 가지 제어가 비용을 제한해요:
max_tokens은 요청의 총 출력(thinking과 응답 텍스트 합산)에 대한 하드 상한이에요. Claude는 절대 그 이상을 생성하지 않아요. 도구 사용 루프에서는 턴의 각 요청이 자신의max_tokens을 가지므로, 전체 턴의 지출을 제한하지 않아요.effort는 Claude가 그 출력 중 얼마를 thinking에 배분할지에 대한 부드러운 지침이에요. 행동을 만들지만 토큰 수를 보장하지는 않아요.
Thinking이 max_tokens에 포함되므로, 추론과 답 둘 다 담을 만큼 충분히 높게 설정하세요. thinking이 없는 응답에 맞춘 max_tokens은 Claude가 어려운 요청에서 생각하기 시작하면 종종 너무 작아요.
high effort 이상에서는 Claude가 광범위하게 생각할 수 있고 예산을 소진할 가능성이 더 커요. 응답에서 stop_reason: "max_tokens"이 보이면 두 가지 해결책이 있어요:
max_tokens을 올려 모델에 thinking과 답 둘 다 더 많은 공간을 주세요.- effort 수준을 낮춰 Claude가 덜 생각하고 응답 텍스트에 예산을 더 남기게 하세요.
어느 쪽이 옳은지는 잘린 응답이 추론을 필요로 했는지에 달려 있어요. 그 요청들에서 품질이 중요하면 상한을 올리고, 과하게 생각한 것이면 effort를 낮추세요.
가격
Thinking은 다음에 대해 요금을 부과해요:
- Claude가 생각하는 동안 쓰는 토큰(출력 토큰으로 청구)
- 보존 기본값에 따라 컨텍스트에 남는 이전 어시스턴트 턴의 thinking 블록: 유지-전부 모델에서는 기본적으로 모든 턴, 그 외에는 마지막 턴만(입력 토큰으로 청구)
- 표준 텍스트 출력 토큰
청구되는 것은 display 설정과 무관하게 같아요. 보이는 것만 바뀌죠:
display: "summarized" |
display: "omitted" |
|
|---|---|---|
| 입력 토큰 | 원래 요청의 토큰 | summarized와 동일 |
| 출력 토큰 (청구) | Claude가 내부적으로 생성한 전체 thinking 토큰 | summarized와 동일 |
| 출력 토큰 (보임) | 요약된 thinking 텍스트 | 0 thinking 토큰 (thinking 필드가 비어 있음) |
| 요약 생성 | 무료 | 해당 없음 |
내부 추론에 얼마나 많은 청구 출력 토큰이 쓰였는지 보려면 응답에서 usage.output_tokens_details.thinking_tokens를 읽으세요. 이 값은 모델이 생성한 원시 추론(본문에 반환된 요약 텍스트가 아니라)을 반영하고 항상 output_tokens보다 작거나 같아요. 그것을 output_tokens에서 빼면 출력의 비추론 부분을 근사할 수 있어요. 스트리밍 시 이 세부 내역은 마지막 message_delta 이벤트에서만 나타나요.
{
"usage": {
"input_tokens": 25,
"output_tokens": 348,
"output_tokens_details": {
"thinking_tokens": 312
}
}
}
output_tokens은 청구에 사용되는 포괄적이고 권위 있는 총계로 유지돼요. output_tokens_details는 관측 가능성을 위한 읽기 전용 세부 내역이에요. 기본 요금, 캐시 쓰기, 캐시 적중, 출력 토큰을 포함한 완전한 가격 정보는 가격을 보세요.