---
title: 사고 조정하기
url: https://platform.claude.com/docs/ko/build-with-claude/thinking-steering-and-cost
description: effort 수준, 시스템 프롬프트 지침, 메시지별 조정을 통해 Claude가 얼마나 자주, 얼마나 깊이 사고하는지 조정하고, 사고의 비용과 가격 책정을 이해합니다.
---

<Note>
  "zero data retention"(제로 데이터 보존), 즉 ZDR이 이 기능에 어떻게 적용되는지 알아보려면 [API 및 데이터 보존](https://platform.claude.com/docs/ko/manage-claude/api-and-data-retention)을 참조하세요.
</Note>

Claude의 사고는 "adaptive"(적응형)입니다. 모델은 각 요청을 평가하여 사고할지 여부와 얼마나 사고할지를 결정합니다. 사용자는 의도를 설정하고, 선택적으로 effort를 지정하며, 모델은 추론이 도움이 될 것이라고 판단하는 곳에 추론을 할당합니다.

이 덕분에 사고는 사소한 요청과 복잡한 요청이 섞여 있는 워크로드, 그리고 단계마다 적절한 추론의 양이 달라지는 장기 에이전트 워크플로에 매우 적합합니다.

사고를 켜는 방법, 사고 출력을 읽는 방법, 그리고 [Claude Fable 5 및 Claude Mythos 5의 사고 출력](https://platform.claude.com/docs/ko/build-with-claude/thinking#thinking-output-on-claude-fable-5-and-claude-mythos-5)에 대해 알아보려면 [사고](https://platform.claude.com/docs/ko/build-with-claude/thinking) 개요를 참조하세요. 이 페이지에서는 Claude가 언제 사고할지 결정하는 방식, 사고를 조정하는 방법, 그리고 이에 따른 캐싱, 비용 및 가격 책정 메커니즘을 다룹니다.

## Claude가 사고 여부를 결정하는 방식

사고는 모델에게 선택 사항입니다. 각 요청에서 Claude는 입력의 복잡성을 따져보고 더 깊은 추론이 답변을 개선할지 판단합니다. 간단한 사실 질문에는 사고 블록 없이 바로 응답할 수 있으며, 여러 단계로 이루어진 수학 문제나 까다로운 디버깅 작업은 더 깊은 추론을 유발합니다.

이 결정은 요청마다 이루어집니다. 같은 대화 안에 사고가 있는 턴과 없는 턴이 함께 있을 수 있으며, Claude가 사고하지 않기로 선택한 턴에는 사고 블록이 없습니다. 모든 어시스턴트 턴이 사고 블록으로 시작한다고 가정하는 애플리케이션 로직을 만들지 마세요.

이 결정에 대한 주요 제어 수단은 [effort](https://platform.claude.com/docs/ko/build-with-claude/effort)(노력 수준) 매개변수로, Claude가 얼마나 기꺼이 사고해야 하는지, 얼마나 깊이 사고해야 하는지에 대한 부드러운 지침 역할을 합니다. 각 수준이 무엇을 하는지는 이 페이지의 [Effort 수준](https://platform.claude.com/docs/ko/build-with-claude/thinking-steering-and-cost#effort-levels)을 참조하세요.

Claude가 덜 자주 사고하기를 원한다면, 프롬프트 기반 조정을 사용하기 전에 먼저 effort 수준을 낮추세요.

사고는 도구 사용과도 자동으로 교차됩니다. Claude는 도구 호출 사이에 사고하면서 각 도구 결과를 검토한 후 다음에 무엇을 할지 결정할 수 있습니다([interleaved thinking](https://platform.claude.com/docs/ko/build-with-claude/thinking#interleaved-thinking)(인터리브 사고)). 이를 위해 베타 헤더나 추가 구성이 필요하지 않습니다.

사고 구성과 effort 매개변수가 어떻게 상호작용하는지에 대한 전체 그림은 [사고와 effort](https://platform.claude.com/docs/ko/build-with-claude/thinking#thinking-and-effort)를 참조하세요.

## Claude가 얼마나 자주 사고하는지 조정하기

Claude가 특정 턴에서 사고할지 여부는 프롬프트로 조정할 수 있습니다. Effort가 전반적인 태도를 설정하지만, 시스템 프롬프트에서 전역적으로 또는 사용자 턴에서 메시지별로 자연어 지침을 통해 그 결정을 직접 형성할 수도 있습니다.

두 가지 수단을 다음 순서로 함께 사용하세요:

1. 워크로드의 기본적인 품질과 지연 시간 균형에 맞는 effort 수준을 설정합니다.
2. 해당 수준에서 Claude의 사고 유발이 여전히 요구 사항에 맞지 않는 경우에만 프롬프트 지침을 추가합니다.

<Note>
  Claude Sonnet 5.5와 Claude Haiku 5.5에서 사고를 줄이려면 effort 수준을 낮추세요. 시스템 프롬프트에서 Claude Sonnet 5.5에게 덜 사고하라고 요청해도 사고가 안정적으로 줄어들지 않으며, Anthropic의 테스트에서 프롬프트로 Claude Haiku 5.5에게 바로 답변하라고 지시해도 사고를 멈추지 않았습니다. [Claude Sonnet 5.5 프롬프팅](https://platform.claude.com/docs/ko/build-with-claude/prompt-engineering/prompting-claude-sonnet-5-5#calibrate-effort) 및 [Claude Haiku 5.5 프롬프팅](https://platform.claude.com/docs/ko/build-with-claude/prompt-engineering/prompting-claude-haiku-5-5#use-effort-to-control-thinking)을 참조하세요.
</Note>

사고와 관련된 더 폭넓은 프롬프팅 지침은 [사고 및 인터리브 사고 기능 활용하기](https://platform.claude.com/docs/ko/build-with-claude/prompt-engineering/claude-prompting-best-practices#leverage-thinking-and-interleaved-thinking-capabilities)를 참조하세요.

### Effort 수준

Effort는 사고를 조정하는 주된 수단입니다. 수준마다 Claude가 사고하는 빈도와 깊이의 기본값이 다릅니다.

| Effort 수준                                           | 사고 동작                                                     |
| --------------------------------------------------- | --------------------------------------------------------- |
| `max`                                               | Claude가 가장 적극적으로, 가장 깊이 사고하며, 사고 길이에 제약이 없습니다.            |
| `xhigh`                                             | Claude가 `high`보다 더 적극적으로, 더 깊이 사고하며, 광범위한 탐색에 적합합니다.      |
| `high` (대부분의 모델에서 기본값)                              | 사고가 도움이 되는 대부분의 요청에서 Claude가 사고합니다. 복잡한 작업에 깊은 추론을 제공합니다. |
| `medium` (Claude Opus 5.5 및 Claude Haiku 5.5에서 기본값) | Claude가 적당한 수준으로 사고합니다. 간단한 쿼리에서는 사고를 건너뛸 수 있습니다.         |
| `low`                                               | Claude가 사고를 최소화합니다. 속도가 가장 중요한 간단한 작업에서는 사고를 건너뜁니다.       |

모든 수준에서 Claude는 요청마다 사고할지 여부를 결정합니다. 도구 사용 루프에서는 새로운 사용자 입력 이후의 첫 번째 요청이 일반적으로 대부분의 추론을 담당하며, 도구 결과만 처리하는 후속 요청은 `xhigh`와 `max`에서도 사고를 건너뛸 수 있습니다. 또한 대화가 길어질수록 요청당 사고량이 줄어드는 경향이 있습니다. 어떤 수준도 모든 요청에서 사고 블록을 보장하지 않습니다.

이 표는 각 수준이 사고 동작을 어떻게 바꾸는지 설명합니다. 워크로드별로 어떤 수준을 선택할지에 대한 지침과 모델별 권장 사항은 effort 페이지의 [effort 매개변수를 조정해야 하는 경우](https://platform.claude.com/docs/ko/build-with-claude/effort#when-to-adjust-the-effort-parameter)를 참조하세요.

Effort는 `thinking` 객체 안이 아니라 `output_config.effort`에서 설정합니다. 언어별 전체 예제는 [Effort](https://platform.claude.com/docs/ko/build-with-claude/effort#basic-usage)를 참조하세요.

```json
{
  "model": "claude-opus-5-5",
  "max_tokens": 4096,
  "output_config": { "effort": "medium" },
  "messages": [{ "role": "user", "content": "..." }]
}
```

사용 가능한 수준은 모델마다 다릅니다. 각 모델이 지원하는 수준은 effort 페이지의 [effort 가용성 표](https://platform.claude.com/docs/ko/build-with-claude/effort#effort-levels)를 기준으로 확인하세요.

### 시스템 프롬프트 지침

시스템 프롬프트 지침은 대화의 모든 요청에 대해 Claude의 사고 임계값을 이동시킵니다. Claude가 워크로드에 필요한 것보다 더 자주 사고한다면, 시스템 프롬프트에 다음과 같은 지침을 추가하세요:

```text wrap
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.
```

반대로 사고를 장려하려면 다음과 같은 문구를 사용하세요:

```text wrap
This task involves multistep reasoning. Think carefully before responding.
```

조정 효과는 정확한 문구에 민감할 수 있습니다. 한 가지 표현이 원하는 동작을 만들어내지 않는다면, 더 직접적인 변형을 시도해 보세요.

### 메시지별 조정

시스템 프롬프트와 별개로 사용자 턴에서 메시지별로 사고를 조정할 수도 있습니다. 사용자 메시지에 `"Please think hard before responding."`을 덧붙이면 Claude가 해당 턴에서 사고하도록 장려하고, `"Answer directly without deliberating."`을 덧붙이면 사고를 억제합니다.

메시지별 조정은 대화의 일부 요청에만 확장된 추론이 필요할 때 유용합니다. 예를 들어 에이전트 하네스는 시스템 프롬프트를 건드리거나 턴 사이에 요청 매개변수를 변경하지 않고도, 계획 단계에는 장려 문구를, 일상적인 확인에는 억제 문구를 덧붙일 수 있습니다.

Claude Haiku 5.5에서는 억제 문구가 사고를 안정적으로 줄이지 못합니다. Claude API와 Google Cloud에서는 대신 [메시지별 effort 변경](https://platform.claude.com/docs/ko/build-with-claude/effort#change-effort-mid-conversation-beta)(베타)을 사용하여 해당 모델의 일상적인 턴을 더 적은 사고로 실행하세요. 이 방법은 프롬프트 캐시도 유지합니다.

### 워크로드에서 조정 효과 검증하기

프롬프트 기반 조정은 모델 동작을 바꾸므로, 다른 프롬프트 변경과 마찬가지로 취급하세요. 즉, 배포하기 전에 측정하세요. 지침이 있는 경우와 없는 경우로 트래픽의 대표 샘플을 실행하고, 사고가 얼마나 자주 유발되는지(응답에 사고 블록이 있는지), 출력 토큰 사용량, 지연 시간, 그리고 중요한 사례에서의 답변 품질을 비교하세요.

<Warning>
  Claude가 덜 자주 사고하도록 조정하면 추론의 이점을 얻는 작업에서 품질이 떨어질 수 있습니다. [effort](https://platform.claude.com/docs/ko/build-with-claude/effort) 수준을 낮추는 것이 보통 더 나은 첫 번째 수단입니다. 문구에 민감한 지시가 아니라 보정된 제어 수단이기 때문입니다. 프롬프트 기반 튜닝을 프로덕션에 배포하기 전에 특정 워크로드에 미치는 영향을 측정하세요.
</Warning>

## 메커니즘

Claude가 자신의 사고를 스스로 관리한다는 점에서 세 가지 메커니즘이 따라옵니다: 턴 검증, 프롬프트 캐싱, 그리고 비용을 제한하는 방법입니다.

### 턴 검증

어시스턴트 턴은 사고 블록으로 시작할 필요가 없습니다. (레거시 수동 사고 예산을 사용하는 모델은 사고가 활성화된 요청의 마지막 어시스턴트 턴이 사고 블록으로 시작하도록 강제합니다. [수동 모드의 턴 구조](https://platform.claude.com/docs/ko/build-with-claude/extended-thinking#turn-structure-in-manual-mode)를 참조하세요.)

멀티턴 애플리케이션의 경우, 이는 대화 기록을 가지고 있는 형태 그대로 다시 전달할 수 있다는 뜻입니다:

* Claude가 사고하지 않기로 선택한 어시스턴트 턴은 그대로 유효한 기록입니다.
* 사고 없이 시작했거나 다른 사고 구성을 사용한 대화를 기록을 다시 작성하지 않고 재개할 수 있습니다.
* 여러 출처에서 조합된 기록은 검증을 통과하기 위해 각 어시스턴트 턴의 시작 부분에 사고 블록을 다시 삽입할 필요가 없습니다.

이 완화는 검증에 관한 것이지, 무엇을 보내야 하는지에 관한 것이 아닙니다. 사고 블록이 있다면 수정하지 않고 그대로 다시 전달하세요. 특히 도구 사용 중에는 사고 블록이 Claude의 도구 호출 뒤에 있는 추론을 담고 있으므로 더욱 그렇습니다. 전체 규칙은 [사고](https://platform.claude.com/docs/ko/build-with-claude/thinking) 개요를 참조하세요.

### 프롬프트 캐싱

연속된 요청에서 사고 구성과 effort 수준을 동일하게 유지하면 프롬프트 캐싱이 유지됩니다. 전체 규칙은 [사고와 프롬프트 캐싱](https://platform.claude.com/docs/ko/build-with-claude/thinking#thinking-and-prompt-caching)을 참조하세요. 확정된 effort 값은 프롬프트에 포함됩니다. 따라서 요청 사이에 이 값을 바꾸면 "cache breakpoint"(캐시 중단점)가 무효화됩니다. 레거시 [`budget_tokens`](https://platform.claude.com/docs/ko/build-with-claude/extended-thinking#extended-thinking-with-prompt-caching) 매개변수를 사용하는 모델에서 그 값을 바꿀 때와 같습니다. `effort`를 모델의 기본값으로 명시적으로 설정하는 것은 생략하는 것과 같으므로 캐시가 깨지지 않습니다.

실질적인 결론은 다음과 같습니다. 대화마다 사고 구성과 최상위 effort 수준을 정하고 유지하세요. 일부 턴에서 더 많거나 적은 사고가 필요하다면, 이를 지원하는 모델에서 [메시지별 effort 변경](https://platform.claude.com/docs/ko/build-with-claude/effort#change-effort-mid-conversation-beta)(베타)을 사용하거나 [메시지별 프롬프팅](https://platform.claude.com/docs/ko/build-with-claude/thinking-steering-and-cost#tuning-thinking-behavior)으로 조정하세요. 두 방법 모두 이전 캐시 중단점을 그대로 유지하지만, 사고 구성이나 최상위 effort 수준을 변경하면 그렇지 않습니다.

다음 예제는 직접 실행할 수 있는 멀티턴 스크립트로 캐시 무효화를 보여 줍니다.

<Accordion title="Effort 변경 시 프롬프트 캐싱이 무효화됩니다">
  <Tabs>
    <Tab title="cURL">
      <Note>
        이 워크플로는 일회성 셸 명령으로 표현하기에 적합하지 않습니다. 멀티턴 패턴은 SDK 탭을 참조하세요. 턴별 HTTP 요청은 [프롬프트 캐싱](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching) 페이지의 예제를 따릅니다.
      </Note>
    </Tab>

    <Tab title="CLI">
      <Note>
        이 워크플로는 일회성 셸 명령으로 표현하기에 적합하지 않습니다. 멀티턴 패턴은 SDK 탭을 참조하세요. 턴별 CLI 호출은 [프롬프트 캐싱](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching) 페이지의 예제를 따릅니다.
      </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)


      # 기사 내용을 가져옵니다
      book_url = "https://www.gutenberg.org/cache/epub/1342/pg1342.txt"
      book_content = fetch_article_content(book_url)
      # 캐싱에 충분한 만큼의 텍스트만 사용합니다(처음 몇 장)
      LARGE_TEXT = book_content[:10000]

      # 시스템 프롬프트 없음 - 대신 메시지에서 캐싱합니다
      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-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."})

      # 두 번째 요청 - 동일한 구성(캐시 적중 예상)
      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."})

      # 세 번째 요청 - 다른 effort 수준(캐시 미스 예상)
      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);

      // 시스템 프롬프트 없음 - 대신 메시지에서 캐싱합니다
      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-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." }
      );

      // 두 번째 요청 - 동일한 구성(캐시 적중 예상)
      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." }
      );

      // 세 번째 요청 - 다른 effort 수준(캐시 미스 예상)
      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]
      }

      // 시스템 프롬프트 없음 - 대신 메시지에서 캐싱합니다
      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.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.")))

      // 두 번째 요청 - 동일한 구성(캐시 적중 예상)
      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.")))

      // 세 번째 요청 - 다른 effort 수준(캐시 미스 예상)
      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()));

          // 첫 번째 요청 - 캐시 생성
          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());

          // 두 번째 요청 - 동일한 구성(캐시 적중 예상)
          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());

          // 세 번째 요청 - 다른 effort 수준(캐시 미스 예상)
          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>
  </Tabs>

  다음은 스크립트의 출력입니다(숫자는 약간 다를 수 있습니다).

  ```text Output wrap
  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 배열에 있으므로, effort를 Claude Opus 5.5의 기본값인 `medium`에서 `low`로 바꾸면 캐시 중단점이 무효화됩니다. 두 번째 요청에서는 캐시 전체를 읽었지만, 세 번째 요청에서는 `cache_creation_input_tokens=3546`과 `cache_read_input_tokens=0`이 표시됩니다.
</Accordion>

### 비용 제어

사고 토큰 예산은 설정하지 않습니다. 두 가지 제어 수단이 비용을 제한합니다:

* `max_tokens`는 사고와 응답 텍스트를 합친 요청의 총 출력에 대한 하드 캡입니다. Claude는 절대 이를 넘어서 생성하지 않습니다. 도구 사용 루프에서는 턴 내의 각 요청이 자체 `max_tokens`를 가지므로, 전체 턴의 지출을 제한하지는 않습니다.
* `effort`는 Claude가 그 출력 중 얼마를 사고에 할당할지에 대한 부드러운 지침입니다. 동작을 형성하지만 토큰 수를 보장하지는 않습니다.

사고는 `max_tokens`에 포함되므로, 추론과 답변 모두를 위한 여유가 남도록 충분히 높게 설정하세요. 사고가 없는 응답에 맞춰 크기를 정한 `max_tokens`는 Claude가 어려운 요청에서 사고하기 시작하면 너무 작은 경우가 많습니다.

`high` effort 이상에서는 Claude가 광범위하게 사고할 수 있으며 예산을 소진할 가능성이 더 높습니다. 응답에서 [`stop_reason: "max_tokens"`](https://platform.claude.com/docs/ko/build-with-claude/thinking-troubleshooting#stopped-at-max-tokens)가 보인다면 두 가지 해결책이 있습니다:

* `max_tokens`를 높여 모델에 사고와 답변을 위한 더 많은 여유를 줍니다.
* effort 수준을 낮춰 Claude가 덜 사고하고 예산의 더 많은 부분을 응답 텍스트에 남기도록 합니다.

어느 쪽이 맞는지는 잘린 응답에 추론이 필요했는지에 달려 있습니다. 해당 요청의 품질이 중요하다면 캡을 높이고, 과도하게 사고한 것이었다면 effort를 낮추세요.

## 가격 책정

사고에는 다음에 대한 요금이 발생합니다:

* Claude가 사고하는 동안 사용하는 토큰(출력 토큰으로 청구)
* [보존 기본값](https://platform.claude.com/docs/ko/build-with-claude/thinking#thinking-block-preservation-by-model)에 따라 컨텍스트에 남아 있는 이전 어시스턴트 턴의 사고 블록: keep-all 모델에서는 기본적으로 모든 턴, 그 외에는 마지막 턴만(입력 토큰으로 청구)
* 표준 텍스트 출력 토큰

<Note>
  사고가 활성화되면 이 기능을 지원하기 위해 특수한 시스템 프롬프트가 자동으로 포함됩니다.
</Note>

청구되는 내용은 `display` 설정과 관계없이 동일하며, 보이는 것만 달라집니다:

|               | `display: "summarized"`    | `display: "omitted"`           |
| ------------- | -------------------------- | ------------------------------ |
| **입력 토큰**     | 원래 요청의 토큰                  | summarized와 동일                 |
| **출력 토큰(청구)** | Claude가 내부적으로 생성한 전체 사고 토큰 | summarized와 동일                 |
| **출력 토큰(표시)** | 요약된 사고 텍스트                 | 사고 토큰 0개(`thinking` 필드가 비어 있음) |
| **요약 생성**     | 요금 없음                      | 해당 없음                          |

<Warning>
  청구되는 출력 토큰 수는 응답에 표시되는 토큰 수와 일치하지 **않습니다**. 응답에 표시되는 사고 내용이 아니라 전체 사고 과정에 대해 청구됩니다.
</Warning>

내부 추론에 청구된 출력 토큰이 얼마나 사용되었는지 확인하려면 응답의 `usage.output_tokens_details.thinking_tokens`를 읽으세요. 이 값은 모델이 생성한 원시 추론을 반영하며(본문에 반환된 요약 텍스트가 아님), 항상 `output_tokens`보다 작거나 같습니다. 이를 `output_tokens`에서 빼면 출력의 비추론 부분을 근사할 수 있습니다. 스트리밍 시 이 세부 내역은 마지막 `message_delta` 이벤트에만 나타납니다.

```json
{
  "usage": {
    "input_tokens": 25,
    "output_tokens": 348,
    "output_tokens_details": {
      "thinking_tokens": 312
    }
  }
}
```

`output_tokens`는 여전히 청구에 사용되는 포괄적이고 권위 있는 총계입니다. `output_tokens_details`는 관측 가능성을 위한 읽기 전용 세부 내역입니다. 기본 요금, 캐시 쓰기, 캐시 히트, 출력 토큰을 포함한 전체 가격 정보는 [가격 책정](https://platform.claude.com/docs/ko/about-claude/pricing)을 참조하세요.

## 다음 단계

<CardGroup cols={3}>
  <Card title="사고" icon="brain" href="https://platform.claude.com/docs/ko/build-with-claude/thinking">
    사고를 켜고, 사고 출력을 읽고, 모델별 지원을 확인하세요.
  </Card>

  <Card title="도구 및 멀티턴 워크플로에서의 사고" icon="wrench" href="https://platform.claude.com/docs/ko/build-with-claude/thinking-tool-workflows">
    도구 호출 전반에 걸쳐 사고 블록을 보존하고 멀티턴 대화에서 사고를 관리하세요.
  </Card>

  <Card title="Effort" icon="sliders" href="https://platform.claude.com/docs/ko/build-with-claude/effort">
    Claude가 요청당 할당하는 사고와 출력의 양을 제어하세요.
  </Card>
</CardGroup>
