OpenAI 相容介面升級:深度支援 Claude

AIHubMix 升級了 Claude 的 OpenAI 相容 API:無須額外參數即可使用交錯思考、提示詞快取,以及 Anthropic Beta 功能支援。

推理時代閱讀約 8 分鐘
OpenAI 相容介面升級:深度支援 Claude

我們已針對 Claude 系列模型,對 OpenAI 相容介面進行更深入的最佳化。現在,你可以更精準、更方便地控制思考與快取。多輪對話中的交錯思考也變得更容易使用,無須額外參數即可無縫整合。此外,介面也支援啟用 Anthropic 提供的 Beta 功能。

1. 模型思考(延伸思考)

1.1 交錯思考的優勢

未啟用交錯思考時,模型只會在助理回合開始時思考一次;收到工具結果後,便會直接產生後續回覆,不會再產生新的思考區塊:

User → [Thinking] → Tool Call → Tool Result → Response

啟用交錯思考後,模型每次收到工具結果時都會插入新的思考區塊,形成推理鏈:

User → [Thinking] → Tool Call → Tool Result → [Thinking] → Response
                                                ↑ Interleaved Thinking

這讓模型能夠:

  • 根據工具結果進行二次推理,而不是單純串接輸出。
  • 在多次工具呼叫之間串接推理,每個決策都以先前步驟的分析為依據。
參考資料:Anthropic 交錯式思考

1.2 啟用思考

你可以透過以下四種方式啟用思考,任選一種即可:

方式 範例 說明
reasoning_effort "reasoning_effort": "low" OpenAI 標準參數,放在請求本文的頂層
reasoning.effort "reasoning": {"effort": "low"} 與前一種方式等效,放在 reasoning 物件內
reasoning.max_tokens "reasoning": {"max_tokens": 1024} 精確控制思考的 token 數上限
模型名稱加上 -think "model": "claude-sonnet-4-5-think" 最簡單的方式,無須額外參數
優先順序(同時使用多種方式時):reasoning_effort > reasoning.max_tokens > reasoning.effort > -think 後綴

可用的推理強度值:minimal / low / medium / high / xhigh

1.3 思考內容回傳

回應訊息會包含兩個新欄位:

  • reasoning_content:思考內容(字串),方便顯示。
  • reasoning_details:思考內容的完整結構化資訊;在多輪對話中必須原樣回傳,不同供應商的內部結構可能有所不同。

非串流範例(省略無關欄位):

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "Hello! How can I help you today?",
      "reasoning_content": "The user is just saying hello...",
      "reasoning_details": {
        "type": "thinking",
        "thinking": "The user is just saying hello...",
        "signature": "Er8CCkYI..."
      }
    }
  }]
}

串流回應中,思考內容會透過 delta.reasoning_content 和 delta.reasoning_details 分段傳送。完整的串流串接邏輯請參閱下方完整範例。

1.4 在多輪對話中保留思考內容(交錯思考為內建功能,無須額外參數)

若要讓模型在多輪對話中延續推理能力,只需將先前回傳的 reasoning_details原樣放入下一輪的 assistant 訊息中:

messages = [
    {"role": "user", "content": "What's the weather like in Boston?"},
    {
        "role": "assistant",
        "content": response.choices[0].message.content,
        "tool_calls": response.choices[0].message.tool_calls,
        "reasoning_details": response.choices[0].message.reasoning_details,
    },
    {
        "role": "tool",
        "tool_call_id": "toolu_xxx",
        "content": '{"temperature": 45, "condition": "rainy"}',
    }
]

AIHubMix 偵測到請求中包含歷史思考資訊時,會自動啟用交錯思考,讓模型收到工具呼叫結果後無須額外參數即可繼續深度推理。

1.5 完整範例

以下兩個範例展示完整的多輪 Tool Call + 交錯思考流程:使用者提問 → 模型思考並呼叫工具 → 注入工具結果(保留 reasoning_details)→ 模型透過交錯思考給出最終回覆。

非串流 · 交錯思考

import os
import json
from openai import OpenAI

client = OpenAI(
    base_url="https://aihubmix.com/v1",
    api_key=os.environ.get("AIHUBMIX_API_KEY", "sk-***"),
)

# ── Tool definition ───────────────────────────────────────────
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get current weather for a location",
        "parameters": {
            "type": "object",
            "properties": {"location": {"type": "string", "description": "City name"}},
            "required": ["location"]
        }
    }
}]

# ── Mock tool execution ───────────────────────────────────────
WEATHER_DB = {
    "boston": {"temperature": "45°F (7°C)", "condition": "rainy", "humidity": "85%", "wind": "15 mph NE"},
    "tokyo":  {"temperature": "72°F (22°C)", "condition": "sunny", "humidity": "45%", "wind": "5 mph S"},
}

def execute_tool(name: str, args: dict) -> str:
    if name == "get_weather":
        key = next((k for k in WEATHER_DB if k in args.get("location", "").lower()), None)
        return json.dumps(WEATHER_DB.get(key, {"temperature": "65°F", "condition": "clear"}))
    return "{}"

# ── Multi-turn conversation loop ─────────────────────────────
messages = [
    {"role": "user", "content": "What's the weather like in Boston? Then recommend what to wear."}
]

turn = 0
while True:
    turn += 1
    print(f"\n── Turn {turn} ──")

    response = client.chat.completions.create(
        model="claude-sonnet-4-5",
        messages=messages,
        tools=tools,
        extra_body={"reasoning": {"max_tokens": 2000}},
    )
    msg = response.choices[0].message

    # Print thinking process
    if msg.reasoning_content:
        label = "Interleaved Thinking" if turn > 1 else "Thinking"
        print(f"[{label}] {msg.reasoning_content}")

    # Print response content
    if msg.content:
        print(f"[Response] {msg.content}")

    # Print tool calls
    if msg.tool_calls:
        for tc in msg.tool_calls:
            print(f"[Tool Call: {tc.function.name}] {tc.function.arguments}")

    # Build assistant message, preserve reasoning_details (critical!)
    assistant_msg = {"role": "assistant", "content": msg.content}
    if msg.tool_calls:
        assistant_msg["tool_calls"] = msg.tool_calls
    if msg.reasoning_details:
        assistant_msg["reasoning_details"] = msg.reasoning_details  # pass back unmodified
    messages.append(assistant_msg)

    # No tool_calls means conversation is done
    if not msg.tool_calls:
        break

    # Execute tools and append results to messages
    for tc in msg.tool_calls:
        args = json.loads(tc.function.arguments)
        result = execute_tool(tc.function.name, args)
        print(f"[Tool Result: {tc.function.name}] {result}")
        messages.append({"role": "tool", "tool_call_id": tc.id, "content": result})

串流 · 交錯思考

import os
import sys
import json
from openai import OpenAI

client = OpenAI(
    base_url="https://aihubmix.com/v1",
    api_key=os.environ.get("AIHUBMIX_API_KEY", "sk-***"),
)

# ── Tool definition & mock execution ─────────────────────────
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get current weather for a location",
        "parameters": {
            "type": "object",
            "properties": {"location": {"type": "string", "description": "City name"}},
            "required": ["location"]
        }
    }
}]

WEATHER_DB = {
    "boston": {"temperature": "45°F (7°C)", "condition": "rainy", "humidity": "85%", "wind": "15 mph NE"},
    "tokyo":  {"temperature": "72°F (22°C)", "condition": "sunny", "humidity": "45%", "wind": "5 mph S"},
}

def execute_tool(name: str, args: dict) -> str:
    if name == "get_weather":
        key = next((k for k in WEATHER_DB if k in args.get("location", "").lower()), None)
        return json.dumps(WEATHER_DB.get(key, {"temperature": "65°F", "condition": "clear"}))
    return "{}"

# ── Stream response collector ────────────────────────────────
def stream_and_collect(turn: int, **kwargs):
    """Stream response, print thinking/content in real-time, accumulate reasoning_details/tool_calls."""
    rd = {}            # accumulated reasoning_details
    content = ""       # accumulated response text
    tc_map = {}        # accumulated tool_calls (by index)
    cur = "none"       # current output section: none / thinking / content

    stream = client.chat.completions.create(stream=True, **kwargs)
    for chunk in stream:
        if not chunk.choices:
            continue
        delta = chunk.choices[0].delta

        # ── Handle thinking ──
        rd_delta = getattr(delta, "reasoning_details", None)
        if rd_delta and isinstance(rd_delta, dict):
            for k, v in rd_delta.items():
                if k == "type":
                    rd[k] = v
                elif isinstance(v, str):
                    rd[k] = rd.get(k, "") + v
                elif v is not None:
                    rd[k] = v
            # Print thinking chunks in real-time
            thinking_chunk = rd_delta.get("thinking", "")
            if thinking_chunk:
                if cur != "thinking":
                    cur = "thinking"
                    label = "Interleaved Thinking" if turn > 1 else "Thinking"
                    sys.stdout.write(f"\n[{label}] ")
                sys.stdout.write(thinking_chunk)
                sys.stdout.flush()

        # ── Handle content ──
        if delta.content:
            if cur != "content":
                if cur == "thinking":
                    sys.stdout.write("\n")
                cur = "content"
                sys.stdout.write("\n[Response] ")
            sys.stdout.write(delta.content)
            sys.stdout.flush()
            content += delta.content

        # ── Handle tool_calls ──
        for tc in delta.tool_calls or []:
            i = tc.index
            if i not in tc_map:
                tc_map[i] = {"id": "", "type": "function",
                             "function": {"name": "", "arguments": ""}}
            if tc.id:
                tc_map[i]["id"] = tc.id
            if tc.function:
                tc_map[i]["function"]["name"] += tc.function.name or ""
                tc_map[i]["function"]["arguments"] += tc.function.arguments or ""

    # End current output section
    if cur in ("thinking", "content"):
        sys.stdout.write("\n")

    tool_calls = [tc_map[i] for i in sorted(tc_map)] if tc_map else None
    return {
        "content": content or None,
        "reasoning_details": rd or None,
        "tool_calls": tool_calls,
    }

# ── Multi-turn conversation loop ─────────────────────────────
messages = [
    {"role": "user", "content": "What's the weather like in Boston? Then recommend what to wear."}
]

turn = 0
while True:
    turn += 1
    print(f"\n── Turn {turn} ──")

    result = stream_and_collect(
        turn,
        model="claude-sonnet-4-5",
        messages=messages,
        tools=tools,
        extra_body={"reasoning": {"max_tokens": 2000}},
    )

    # Print tool calls
    if result["tool_calls"]:
        for tc in result["tool_calls"]:
            print(f"[Tool Call: {tc['function']['name']}] {tc['function']['arguments']}")

    # Build assistant message, preserve reasoning_details (critical!)
    assistant_msg = {"role": "assistant", "content": result["content"]}
    if result["tool_calls"]:
        assistant_msg["tool_calls"] = result["tool_calls"]
    if result["reasoning_details"]:
        assistant_msg["reasoning_details"] = result["reasoning_details"]  # pass back unmodified
    messages.append(assistant_msg)

    # No tool_calls means conversation is done
    if not result["tool_calls"]:
        break

    # Execute tools and append results to messages
    for tc in result["tool_calls"]:
        args = json.loads(tc["function"]["arguments"])
        tool_result = execute_tool(tc["function"]["name"], args)
        print(f"[Tool Result: {tc['function']['name']}] {tool_result}")
        messages.append({"role": "tool", "tool_call_id": tc["id"], "content": tool_result})

1.6 思考強度對應規則

推理強度模式:

  • Opus 4.6 / Sonnet 4.6 及以上版本:對應至 Anthropic 原生的 Adaptive Thinking 推理強度。
  • 其他模型:使用 budget_tokens 公式計算:
budget_tokens = max(min(max_tokens × effort_ratio, 128000), 1024)
推理強度 推理強度比例
xhigh 0.95
high 0.80
medium 0.50
low 0.20
minimal 0.10

Adaptive Thinking 推理強度對應:

傳入的推理強度 Opus 4.6 Sonnet 4.6
xhigh max high
high high high
medium medium medium
low low low
minimal low low

max_tokens 模式:直接指定為 Anthropic 的 budget_tokens。

-think 後綴:Opus/Sonnet 4.6+ 使用 adaptive thinking(effort=medium);其他模型則設定 budget_tokens = min(10240, max_tokens - 1),max_tokens 預設為 4096。


2. 提示詞快取

透過 Chat 介面向 Claude 模型發出請求時,可以使用提示詞快取。在訊息中設定 cache_control 快取斷點,即可快取大型文字區塊(例如角色卡、RAG 資料、書籍章節等)以供重複使用,讓後續請求直接命中快取,大幅降低成本。

Claude 官方文件:提示詞快取

2.1 快取費用

操作 價格倍率(相對於原始輸入價格)
寫入快取(5 分鐘 TTL) 1.25x
寫入快取(1 小時 TTL) 2x
讀取快取 0.1x

2.2 支援的模型與快取長度下限

模型 快取 token 數下限
Claude Opus 4.8 1024
Claude Opus 4.7 2048
Claude Opus 4.6 / Opus 4.5 4096
Claude Sonnet 4.6 / Sonnet 4.5 / Opus 4.1 / Opus 4 / Sonnet 4 / Sonnet 3.7(已棄用) 1024
Claude Haiku 4.5 4096
Claude Haiku 3.5(已棄用)/ Haiku 3 2048
斷點數量限制:每個請求最多可設定 4 個 cache_control 斷點。

2.3 快取 TTL

TTL 語法 適用情境
5 分鐘(預設) "cache_control": {"type": "ephemeral"} 短時間工作階段、一般請求
1 小時 "cache_control": {"type": "ephemeral", "ttl": "1h"} 長時間工作階段,避免重複寫入快取

1 小時 TTL 的寫入成本較高,但在長時間工作階段中,減少重複寫入即可節省整體費用。所有供應商(包括 Anthropic、Amazon Bedrock、Google Vertex AI)提供的 Claude 4.5 及後續版本模型,皆支援 1 小時 TTL。

2.4 使用方式

你可以在 system、user(包括圖片)和 tools 中使用 cache_control 欄位設定快取斷點。以下範例僅展示關鍵結構,省略大型文字區塊。

System 訊息快取(預設 5 分鐘 TTL):

{
  "model": "claude-opus-4-5",
  "messages": [
    {
      "role": "system",
      "content": [
        {"type": "text", "text": "You are an AI assistant"},
        {
          "type": "text",
          "text": "(long context)",
          "cache_control": {"type": "ephemeral"}
        }
      ]
    },
    {
      "role": "user",
      "content": [{"type": "text", "text": "Hello"}]
    }
  ]
}

User 訊息快取(1 小時 TTL):

{
  "model": "claude-opus-4-5",
  "messages": [
    {
      "role": "system",
      "content": [{"type": "text", "text": "You are an AI assistant"}]
    },
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "(long context)",
          "cache_control": {"type": "ephemeral", "ttl": "1h"}
        },
        {"type": "text", "text": "Hello"}
      ]
    }
  ]
}

圖片訊息快取:

{
  "role": "user",
  "content": [
    {
      "type": "image_url",
      "image_url": {"detail": "auto", "url": "data:image/jpeg;base64,/9j/4AAQ..."},
      "cache_control": {"type": "ephemeral"}
    },
    {"type": "text", "text": "What's this?"}
  ]
}

工具定義快取:

cache_control 放在工具物件的頂層(與 type 和 function 同一層):

{
  "tools": [{
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "Get current weather for a location",
      "parameters": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"]
      }
    },
    "cache_control": {"type": "ephemeral", "ttl": "1h"}
  }]
}

2.5 查看快取狀態

回應中的 usage 會回傳 claude_cache_tokens_details,記錄快取的詳細資訊:

首次請求(建立快取):

{
  "usage": {
    "prompt_tokens": 22,
    "completion_tokens": 890,
    "total_tokens": 912,
    "claude_cache_tokens_details": {
      "cache_creation_input_tokens": 6266,
      "cache_read_input_tokens": 0,
      "cache_write_5_minutes_input_tokens": 6266,
      "cache_write_1_hour_input_tokens": 0
    }
  }
}

後續請求(快取命中):

{
  "usage": {
    "prompt_tokens": 22,
    "completion_tokens": 810,
    "total_tokens": 832,
    "prompt_tokens_details": {
      "cached_tokens": 6266
    },
    "claude_cache_tokens_details": {
      "cache_creation_input_tokens": 0,
      "cache_read_input_tokens": 6266,
      "cache_write_5_minutes_input_tokens": 0,
      "cache_write_1_hour_input_tokens": 0
    }
  }
}
欄位 意義
cache_creation_input_tokens 此請求寫入快取的 token 數
cache_read_input_tokens 此請求從快取讀取的 token 數
cache_write_5_minutes_input_tokens 寫入 5 分鐘 TTL 快取的 token 數
cache_write_1_hour_input_tokens 寫入 1 小時 TTL 快取的 token 數
prompt_tokens_details.cached_tokens 快取命中時的快取 token 數,與 OpenAI 格式相容

3. anthropic-beta 請求標頭

你可以透過 HTTP 標頭 anthropic-beta 啟用 Claude 模型的 Beta 功能,AIHubMix 會將此標頭轉送至 Anthropic API。

使用方式

在請求標頭中加入 anthropic-beta,並將值設為對應的 Beta 功能識別碼:

curl "https://aihubmix.com/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
  -H "anthropic-beta: context-1m-2025-08-07" \
  -d '{
  "model": "claude-opus-4-5",
  "messages": [
    {
      "role": "system",
      "content": [
        {"type": "text", "text": "You are an AI assistant"},
        {
          "type": "text",
          "text": "(long context)",
          "cache_control": {"type": "ephemeral"}
        }
      ]
    },
    {"role": "user", "content": [{"type": "text", "text": "hello"}]}
  ]
}'
如需查詢可用的 Beta 識別碼,請參閱Anthropic API 文件。

最後更新:2026-06-01

一個閘道接入多家 AI 服務商, 為正式環境而生。

前往推理時代官網