跳至主要內容
技術

Extended Thinking:複雜推理任務的殺手鐧

Extended Thinking:複雜推理任務的殺手鐧
Claude API & Agent SDK 完全指南 第 5 / 15 篇

本篇是「Claude API & Agent SDK 完全指南」系列的第 5 / 15 篇。你可以從系列總覽開始閱讀,也可以直接接著看本文。

讓我問你一個問題。

假設你要做一個很困難的決策——例如評估一個複雜的技術方案,或者分析一份合約的風險。你會怎麼做?

你不會在被問到的瞬間就立刻說出答案。你會先想一想:「這個方案的優點是什麼?缺點是什麼?有沒有遺漏什麼面向?」

這就是 Extended Thinking 給 Claude 的能力:在回答你之前,先花時間真正地思考

Extended Thinking 是什麼

一般情況下,Claude 生成回應是一個「前向傳遞」的過程——它一邊思考一邊輸出文字,無法「回頭修改」。這對大多數任務來說沒問題,但對於需要多步驟推理的困難問題,這種方式有根本的限制。

Extended Thinking 給 Claude 一個專用的「思考空間」(thinking tokens)。在這個空間裡,Claude 可以自由地探索問題、嘗試不同的推理路徑、驗證中間結果,而不需要立刻給出答案。思考完成後,它才把最終結論作為回答輸出。

你在 API 回應裡看到的是「thinking blocks」——Claude 的完整思考過程(Anthropic 選擇讓開發者可以看到這些思考過程,增加透明度)。

這個機制在 Anthropic 的研究中顯示,在困難的數學、邏輯、程式設計問題上,Extended Thinking 可以大幅提升準確率。在某些基準測試(例如 AIME 數學競賽題)上,提升幅度超過 20 個百分點。

適用場景

Extended Thinking 不是萬能藥。它最有價值的場景:

1. 數學與量化推理

"以下是一個投資組合,請計算夏普比率,並分析是否需要再平衡..."
"這個演算法的時間複雜度是多少?能優化到 O(n log n) 嗎?"
"解這個微分方程:dy/dx = 2xy,初始條件 y(0) = 1"

Claude 在沒有 thinking 的情況下做複雜計算容易出錯——它必須在「思考計算過程」和「輸出文字」之間切換。Thinking 讓它把計算做完再輸出。

2. 邏輯推理和謎題

"五個人各自住在不同顏色的房子裡,按照以下線索,誰養了魚?..."
"這段程式碼有個 bug,在並發場景下會出現 race condition,請找出來並修復"

3. 多步驟計畫和分析

"我想建立一個 SaaS 產品,目標市場是台灣中小企業的 HR 部門,請幫我做競爭分析,
 包括主要競爭對手、市場定位、定價策略,以及我應該優先實作的 MVP 功能"

4. 複雜的程式碼設計

"請設計一個分散式任務隊列系統,需要支援優先級、重試機制、dead letter queue,
 以及水平擴展。請給出完整的架構設計和主要元件的介面定義"

5. 需要仔細評估多個選項的決策

"我有三個 API 設計方案,各有優缺點,請幫我分析哪個最適合我們的使用場景..."

不適用場景

同樣重要的是知道什麼時候不要用 Extended Thinking:

簡單問答:「台灣的首都是哪裡?」「Python 的 list comprehension 語法怎麼寫?」——這些問題 Claude 早就知道答案,讓它思考 5000 tokens 是浪費錢。

創意寫作:寫詩、寫故事、頭腦風暴創意——這些任務的「好答案」不是靠邏輯推導出來的,thinking 對品質提升幫助有限。

速度敏感的場景:即時客服、使用者打字時的即時回應——thinking 需要額外時間,會增加等待時間。

翻譯和格式轉換:把英文翻成中文、把 CSV 轉 JSON——直接執行比先思考效率更高。

我的經驗法則:如果你自己解這個問題需要打草稿、分步驟計算、或者仔細想才能確認答案,那就值得用 Extended Thinking

API 使用方式

在 API 呼叫中啟用 Extended Thinking:

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-6",  # Extended Thinking 需要特定模型版本
    max_tokens=16000,
    thinking={
        "type": "enabled",
        "budget_tokens": 10000  # Claude 最多可以用多少 tokens 來思考
    },
    messages=[{
        "role": "user",
        "content": "在 1 到 100 之間有多少個質數?請一一列出並計算。"
    }]
)

# 回應會包含 thinking blocks 和 text blocks
for block in response.content:
    if block.type == "thinking":
        print("=== Claude 的思考過程 ===")
        print(block.thinking)
        print()
    elif block.type == "text":
        print("=== Claude 的回答 ===")
        print(block.text)

注意max_tokens 必須大於 budget_tokens,因為模型還需要 tokens 來生成最終的回答。

budget_tokens 的設定策略

budget_tokens 是 Extended Thinking 最難掌握的參數。

它的範圍是 1024(最小)到模型支援的最大值(視模型而定,通常是 32768 甚至更高)。你設定的是上限,Claude 可能用不完你給的預算。

幾個我測試出來的設定策略:

按任務難度分層

THINKING_BUDGETS = {
    "simple_math": 1024,        # 簡單計算
    "complex_analysis": 8000,   # 複雜分析
    "hard_reasoning": 16000,    # 困難推理
    "research_synthesis": 32000, # 大量資訊整合
}

def smart_thinking_budget(task_type: str) -> dict:
    budget = THINKING_BUDGETS.get(task_type, 4000)
    return {"type": "enabled", "budget_tokens": budget}

動態設定(根據問題長度)

def estimate_thinking_budget(user_message: str) -> int:
    """根據問題的長度和複雜度估算需要的 thinking budget"""
    word_count = len(user_message)

    # 關鍵字偵測
    complexity_keywords = [
        "分析", "比較", "設計", "架構", "優化", "評估",
        "計算", "證明", "推導", "解釋", "規劃"
    ]
    complexity_score = sum(1 for k in complexity_keywords if k in user_message)

    base_budget = 2048
    length_bonus = min(word_count * 2, 4096)
    complexity_bonus = complexity_score * 1000

    return min(base_budget + length_bonus + complexity_bonus, 16000)

我的實戰建議

  • 不確定時,從 4000-8000 開始。這個範圍對大多數「中等複雜」的問題足夠,而且不會太貴
  • 數學和程式碼問題,給 8000-16000。這些問題通常需要多次驗算
  • 不要設太低:我試過給 1024 做複雜問題,thinking 被切斷了,反而影響品質
  • 測試再決定:不同問題類型需要不同預算,最好用你的真實 use case 測試

回應格式:Thinking Blocks vs Text Blocks

啟用 Extended Thinking 後,response.content 會是一個混合陣列:

response.content = [
    ThinkingBlock(
        type="thinking",
        thinking="讓我系統性地思考這個問題...\n\n首先,我需要確認...\n...",
    ),
    TextBlock(
        type="text",
        text="根據分析,答案是..."
    )
]

Thinking blocks 的內容是 Claude 的原始思考流,包含:

  • 自我問答(「等等,這裡我算錯了…」)
  • 多種方案的比較
  • 中間計算步驟
  • 反思和修正

這些思考過程對你來說通常是有價值的透明度——你可以理解 Claude 是怎麼得出結論的。但在面向使用者的產品中,你可能不想把完整的思考過程顯示出來。

def get_final_answer(response) -> str:
    """只取最終的文字回答,不包含思考過程"""
    return " ".join(
        block.text
        for block in response.content
        if block.type == "text"
    )

def get_thinking_and_answer(response) -> tuple[str, str]:
    """分別取出思考過程和最終答案"""
    thinking = " ".join(
        block.thinking
        for block in response.content
        if block.type == "thinking"
    )
    answer = " ".join(
        block.text
        for block in response.content
        if block.type == "text"
    )
    return thinking, answer

Streaming + Extended Thinking

Streaming 和 Extended Thinking 可以同時使用,但有一點要注意:思考過程在串流時會以 thinking_delta 的形式出現:

with client.messages.stream(
    model="claude-sonnet-4-6",
    max_tokens=16000,
    thinking={"type": "enabled", "budget_tokens": 8000},
    messages=[{"role": "user", "content": "請分析..."}]
) as stream:
    in_thinking = False

    for event in stream:
        if event.type == "content_block_start":
            if event.content_block.type == "thinking":
                in_thinking = True
                print("\n[Claude 開始思考...]\n", end="", flush=True)
            elif event.content_block.type == "text":
                in_thinking = False
                print("\n[Claude 的回答:]\n", end="", flush=True)

        elif event.type == "content_block_delta":
            if event.delta.type == "thinking_delta":
                # 顯示思考過程(可選)
                print(event.delta.thinking, end="", flush=True)
            elif event.delta.type == "text_delta":
                # 顯示最終回答
                print(event.delta.text, end="", flush=True)

在面向使用者的產品中,一個好的 UX 做法是:

  1. 顯示「AI 正在思考中…」的動畫,同時接收 thinking blocks(不顯示詳細內容)
  2. 當 text blocks 開始時,顯示「AI 開始回答:」並串流文字

這讓使用者知道系統沒有當機,同時不被原始思考過程的混亂內容搞混。

成本計算

這是很多人忽略的部分:thinking tokens 是需要付費的,而且算在 output tokens 裡。

假設你設定 budget_tokens=8000,Claude 用了 6000 tokens 思考,輸出了 500 tokens 的回答:

Input tokens:  1000 (你的 prompt)
Output tokens: 6500 (6000 thinking + 500 answer)

使用 claude-sonnet-4-6:
Input cost:  1000 / 1,000,000 * $3  = $0.003
Output cost: 6500 / 1,000,000 * $15 = $0.0975
Total: 約 $0.10(台幣約 3 元)

一次呼叫 $0.10 聽起來不多,但如果你的應用有很多使用者,每天跑幾千次,成本就很可觀了。

成本優化建議:

  1. 只在真正需要的場景用 Extended Thinking,不要無差別地開啟
  2. 設定合理的 budget_tokens 上限,不要給 32000 tokens 的預算去解一個 8000 tokens 就夠的問題
  3. 監控實際使用的 thinking tokensresponse.usage 裡有 cache_creation_input_tokens 等欄位,你也可以從 content blocks 的長度估算
# 監控 thinking 使用量
def analyze_thinking_usage(response):
    thinking_tokens = 0
    text_tokens = 0

    for block in response.content:
        if block.type == "thinking":
            # 粗略估算:每個字符約 1 token
            thinking_tokens += len(block.thinking)
        elif block.type == "text":
            text_tokens += len(block.text)

    print(f"Thinking usage: ~{thinking_tokens} chars")
    print(f"Answer length: ~{text_tokens} chars")
    print(f"Total output tokens: {response.usage.output_tokens}")

實際效果對比

讓我用一個具體例子展示 with vs without Extended Thinking 的差異。

問題:找出以下程式碼的 Race Condition

# 測試問題:這段 Python 程式碼在並發環境有什麼問題?
"""
class Counter:
    def __init__(self):
        self.count = 0

    def increment(self):
        current = self.count
        time.sleep(0.001)  # 模擬處理時間
        self.count = current + 1

# 在 10 個線程同時呼叫 increment(),最終 count 應該是 10
# 但實際上通常是 1-3
"""

不使用 Extended Thinking 的回答(縮短版):

這段程式碼有 race condition 問題。increment() 方法不是原子操作:讀取 current、等待、然後寫入 self.count 這三步之間可能被其他線程介入…

(回答是對的,但直接說出結論,沒有深度分析)

使用 Extended Thinking 後(縮短的 thinking 過程):

[Thinking 開始] 讓我仔細分析這個問題。

首先,有個 time.sleep(0.001),這明顯是在模擬 I/O 操作或計算。在這個等待期間,GIL 會被釋放(因為 time.sleep 會讓出 GIL)…

等等,讓我想想 Python GIL 的行為。Python 的 GIL 確保一次只有一個線程執行 Python bytecode,但 time.sleep 會釋放 GIL,讓其他線程有機會執行。

所以時間線是: 線程 A: current = 0 線程 A: sleep(GIL 釋放) 線程 B: current = 0(因為 count 還是 0) 線程 B: sleep 線程 A: self.count = 0 + 1 = 1 線程 B: self.count = 0 + 1 = 1(覆蓋了!)

修復方案有幾種:

  1. threading.Lock()
  2. threading.Event()
  3. 用 queue.Queue 做工作隊列
  4. 改用 multiprocessing

哪個最合適?需要考慮使用場景… [Thinking 結束]

最終回答(帶 thinking 的版本):

這段程式碼存在一個典型的 Check-Then-Act(讀取-修改-寫回)Race Condition。根本原因是 increment() 方法不是原子操作,而 time.sleep() 會釋放 Python GIL,導致多個線程可能同時讀到舊的值,然後各自寫入,造成「更新丟失」… (接著給出三種修復方案和各自的適用場景)

差異很明顯:帶 thinking 的回答不只是說「有問題」,而是展示了完整的推理路徑,並且主動考慮了多種解決方案的取捨。

完整的 Python 範例

import anthropic
from typing import Optional

client = anthropic.Anthropic()


def analyze_with_thinking(
    question: str,
    budget_tokens: int = 8000,
    show_thinking: bool = False
) -> dict:
    """
    使用 Extended Thinking 分析複雜問題

    Args:
        question: 要分析的問題
        budget_tokens: 思考預算(tokens)
        show_thinking: 是否在回傳中包含思考過程

    Returns:
        包含 answer、thinking(可選)、usage 的字典
    """
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=budget_tokens + 4096,  # 思考 + 回答的總預算
        thinking={
            "type": "enabled",
            "budget_tokens": budget_tokens
        },
        messages=[
            {"role": "user", "content": question}
        ]
    )

    result = {
        "answer": "",
        "usage": {
            "input_tokens": response.usage.input_tokens,
            "output_tokens": response.usage.output_tokens,
        }
    }

    thinking_parts = []
    answer_parts = []

    for block in response.content:
        if block.type == "thinking":
            thinking_parts.append(block.thinking)
        elif block.type == "text":
            answer_parts.append(block.text)

    result["answer"] = "\n".join(answer_parts)

    if show_thinking:
        result["thinking"] = "\n".join(thinking_parts)

    return result


def compare_with_without_thinking(question: str) -> None:
    """比較有無 Extended Thinking 的差異"""
    print(f"問題:{question}\n")

    # 不使用 thinking
    print("=== 不使用 Extended Thinking ===")
    standard_response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2048,
        messages=[{"role": "user", "content": question}]
    )
    standard_answer = standard_response.content[0].text
    print(f"回答:{standard_answer[:300]}...")
    print(f"Output tokens: {standard_response.usage.output_tokens}\n")

    # 使用 thinking
    print("=== 使用 Extended Thinking (budget: 6000) ===")
    thinking_result = analyze_with_thinking(
        question,
        budget_tokens=6000,
        show_thinking=True
    )
    print(f"思考過程(前 200 字):{thinking_result.get('thinking', '')[:200]}...")
    print(f"回答:{thinking_result['answer'][:300]}...")
    print(f"Output tokens: {thinking_result['usage']['output_tokens']}")


# 實際使用範例
if __name__ == "__main__":
    # 範例 1:數學推理
    result = analyze_with_thinking(
        """
        一個公司有 3 個部門:A、B、C。
        - 部門 A 有 20 人,平均薪資 60000 元
        - 部門 B 有 15 人,平均薪資 80000 元
        - 部門 C 有 10 人,平均薪資 100000 元

        請計算:
        1. 整個公司的平均薪資
        2. 如果公司要全員加薪 10%,月薪資總支出增加多少?
        3. 如果只有薪資低於公司平均的員工加薪,使他們達到公司平均水準,
           月薪資總支出增加多少?
        """,
        budget_tokens=4000
    )
    print("薪資計算結果:")
    print(result["answer"])
    print(f"\n使用的 tokens - Input: {result['usage']['input_tokens']}, Output: {result['usage']['output_tokens']}")

TypeScript 完整範例

import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic();

interface ThinkingResult {
  thinking?: string;
  answer: string;
  inputTokens: number;
  outputTokens: number;
}

async function analyzeWithThinking(
  question: string,
  budgetTokens: number = 8000,
  includeThinking: boolean = false
): Promise<ThinkingResult> {
  const response = await client.messages.create({
    model: 'claude-sonnet-4-6',
    max_tokens: budgetTokens + 4096,
    thinking: {
      type: 'enabled',
      budget_tokens: budgetTokens,
    },
    messages: [{ role: 'user', content: question }],
  });

  const thinkingParts: string[] = [];
  const answerParts: string[] = [];

  for (const block of response.content) {
    if (block.type === 'thinking') {
      thinkingParts.push(block.thinking);
    } else if (block.type === 'text') {
      answerParts.push(block.text);
    }
  }

  return {
    thinking: includeThinking ? thinkingParts.join('\n') : undefined,
    answer: answerParts.join('\n'),
    inputTokens: response.usage.input_tokens,
    outputTokens: response.usage.output_tokens,
  };
}

// 使用範例
async function main() {
  const result = await analyzeWithThinking(
    `請分析以下技術決策:
    我們正在建立一個需要處理每秒 10,000 個請求的 API 服務。
    現在需要在以下三個方案中選擇一個:
    1. Node.js + Express + PostgreSQL
    2. Go + Gin + PostgreSQL
    3. Python + FastAPI + PostgreSQL

    考慮因素:團隊主要是 TypeScript 開發者,但有 3 位有 Python 經驗,
    無人有 Go 經驗。預算允許 6 個月的開發時間。`,
    12000, // 這個問題需要較多思考
    true // 顯示思考過程
  );

  if (result.thinking) {
    console.log('思考過程:');
    console.log(result.thinking.substring(0, 500) + '...\n');
  }

  console.log('最終建議:');
  console.log(result.answer);
  console.log(`\nToken 使用:Input ${result.inputTokens}, Output ${result.outputTokens}`);
}

main();

生產環境使用注意事項

1. 快取策略:Extended Thinking 的輸出(thinking blocks)不能快取,但你可以快取包含 thinking 的對話歷史,讓後續輪次的 Claude 看到之前的思考過程。

2. 多輪對話中的 Thinking:如果你要把包含 thinking blocks 的回應放進對話歷史,需要把完整的 response.content(包含 thinking blocks)加入 messages,而不只是 text blocks。

# 正確:保留 thinking blocks 在對話歷史
messages.append({
    "role": "assistant",
    "content": response.content  # 包含 thinking blocks
})

# 錯誤:只保留文字,下一輪 Claude 會失去上下文
messages.append({
    "role": "assistant",
    "content": final_text_only  # 這樣會失去 thinking blocks
})

3. 目前的限制:截至本書寫作時,Extended Thinking 不支援以下功能的組合使用:

  • Extended Thinking + streaming 的某些使用模式(確認最新文件)
  • 極長的 budget_tokens 需要對應更大的 max_tokens

永遠查閱 Anthropic 官方文件 確認最新的支援狀況,因為這個功能還在積極發展中。

小結

Extended Thinking 是 Claude API 裡最有差異化的功能之一。它不是讓所有事情都變得更好,而是讓特定一類問題的回答品質大幅提升:那些需要仔細推理、多步驟計算、或者評估多種可能性的困難問題。

我的建議是:建立一個問題難度分類器,自動決定是否啟用 Extended Thinking 以及給多少 budget_tokens。對於明顯簡單的問題跳過 thinking,對於困難問題才開啟,這樣你可以在品質和成本之間找到最佳平衡。


這五章涵蓋了 Claude API 的核心基礎:從第一個 API 呼叫,到多輪對話管理,到 Streaming 的即時體驗,到 Tool Use 的 Agent 能力,最後到 Extended Thinking 的深度推理。

接下來的章節,我們會繼續往上構建:Prompt Caching(節省費用)、Multimodal(圖片輸入)、Embeddings、以及最終的 Agent SDK——把這些能力組合起來,建立真正複雜的 AI 系統。

留言討論

esc
輸入關鍵字搜尋文章...
查看收藏 →