OpenAI互換インターフェースのアップグレード:Claudeへの深いサポート

2026年7月31日 · AIHubMix · 8 min read · ニュース

OpenAI互換インターフェースのアップグレード:Claudeへの深いサポート

私たちはClaudeシリーズモデル向けに特化した深い最適化を施したOpenAI互換インターフェースをアップグレードしました。これにより、思考とキャッシングをより正確かつ便利に制御できるようになりました。マルチターン会話におけるインタリーブ思考は、追加のパラメータなしでシームレスな統合を可能にし、よりユーザーフレンドリーになりました。また、Anthropicが提供するベータ機能の有効化もサポートしています。

1. モデル思考(拡張思考)

1.1 インタリーブ思考の利点

インタリーブ思考が有効でない場合、モデルはアシスタントターンの最初にのみ思考を行い、その後の応答はツール結果を受け取った後に直接生成され、新しい思考ブロックは生成されません:

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

インタリーブ思考が有効な場合、モデルはツール結果を受け取るたびに新しい思考ブロックを挿入し、推論の連鎖を形成します:

User → [Thinking] → Tool Call → Tool Result → [Thinking] → Response
                                                ↑ インタリーブ思考

これにより、モデルは以下を実現できます:

  • ツール結果に基づく二次的な推論を行う、単に出力を連結するのではなく。
  • 複数のツールコール間での推論を連鎖させる、各決定が前のステップの分析に基づく。
参考文献: Anthropicインタリーブ思考

1.2 思考の有効化

思考を有効にする方法は4つあり、その中から1つを選択できます:

方法 説明
reasoning_effort "reasoning_effort": "low" OpenAIの標準パラメータで、リクエストボディの最上位に配置されます
reasoning.effort "reasoning": {"effort": "low"} 前の方法と同等で、推論オブジェクト内に配置されます
reasoning.max_tokens "reasoning": {"max_tokens": 1024} 思考の最大トークン数を正確に制御します
モデル名に-think "model": "claude-sonnet-4-5-think" 最も簡単な方法で、追加のパラメータは不要です
優先順位(複数の方法が使用される場合): reasoning_effort > reasoning.max_tokens > reasoning.effort > -thinkサフィックス

努力の可能な値: minimal / low / medium / high / xhigh

1.3 思考の返却

応答メッセージには2つの新しいフィールドが含まれます:

  • reasoning_content: 思考内容(文字列)、表示が容易です。
  • reasoning_details: 思考に関する完全な構造化情報、マルチターン会話ではそのまま返却する必要があります;内部構造はプロバイダーによって異なる場合があります。

非ストリーミングの例(無関係なフィールドを省略):

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "こんにちは!今日はどのようにお手伝いできますか?",
      "reasoning_content": "ユーザーはただ挨拶をしています...",
      "reasoning_details": {
        "type": "thinking",
        "thinking": "ユーザーはただ挨拶をしています...",
        "signature": "Er8CCkYI..."
      }
    }
  }]
}

ストリーミング応答では、思考内容はdelta.reasoning_contentおよびdelta.reasoning_detailsを介してチャンクで送信されます。完全なストリーミング連結ロジックについては、以下の完全な例を参照してください。

1.4 マルチターン会話における思考の保持 (インタリーブ思考は組み込まれており、追加のパラメータは不要です)

モデルがマルチターン会話で推論能力を継続できるようにするには、前回返却されたreasoning_detailsそのまま次のラウンドのアシスタントメッセージに配置します:

messages = [
    {"role": "user", "content": "ボストンの天気はどうですか?次に何を着るべきかを勧めてください。"},
    {
        "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 完全な例

以下の2つの例は、完全なマルチターンツールコール + インタリーブ思考プロセスを示しています:ユーザーの問い合わせ → モデルが思考しツールを呼び出す → ツール結果を注入(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-***"),
)

# ── ツール定義 ───────────────────────────────────────────
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "特定の場所の現在の天気を取得",
        "parameters": {
            "type": "object",
            "properties": {"location": {"type": "string", "description": "都市名"}},
            "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 "{}"

# ── マルチターン会話ループ ─────────────────────────────
messages = [
    {"role": "user", "content": "ボストンの天気はどうですか?次に何を着るべきかを勧めてください。"}
]

turn = 0
while True:
    turn += 1
    print(f"\n── ターン {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

    # 思考プロセスを表示
    if msg.reasoning_content:
        label = "インタリーブ思考" if turn > 1 else "思考"
        print(f"[{label}] {msg.reasoning_content}")

    # 応答内容を表示
    if msg.content:
        print(f"[応答] {msg.content}")

    # ツールコールを表示
    if msg.tool_calls:
        for tc in msg.tool_calls:
            print(f"[ツールコール: {tc.function.name}] {tc.function.arguments}")

    # アシスタントメッセージを構築し、reasoning_detailsを保持(重要!)
    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  # 変更せずに返す
    messages.append(assistant_msg)

    # ツールコールがない場合、会話は終了
    if not msg.tool_calls:
        break

    # ツールを実行し、結果をメッセージに追加
    for tc in msg.tool_calls:
        args = json.loads(tc.function.arguments)
        result = execute_tool(tc.function.name, args)
        print(f"[ツール結果: {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-***"),
)

# ── ツール定義 & モック実行 ─────────────────────────
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "特定の場所の現在の天気を取得",
        "parameters": {
            "type": "object",
            "properties": {"location": {"type": "string", "description": "都市名"}},
            "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 "{}"

# ── ストリーム応答コレクター ─────────────────────────────
def stream_and_collect(turn: int, **kwargs):
    """ストリーム応答を取得し、思考/内容をリアルタイムで表示し、reasoning_details/tool_callsを蓄積します。"""
    rd = {}            # 蓄積されたreasoning_details
    content = ""       # 蓄積された応答テキスト
    tc_map = {}        # 蓄積されたtool_calls(インデックス別)
    cur = "none"       # 現在の出力セクション: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

        # ── 思考の処理 ──
        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
            # リアルタイムで思考チャンクを表示
            thinking_chunk = rd_delta.get("thinking", "")
            if thinking_chunk:
                if cur != "thinking":
                    cur = "thinking"
                    label = "インタリーブ思考" if turn > 1 else "思考"
                    sys.stdout.write(f"\n[{label}] ")
                sys.stdout.write(thinking_chunk)
                sys.stdout.flush()

        # ── 内容の処理 ──
        if delta.content:
            if cur != "content":
                if cur == "thinking":
                    sys.stdout.write("\n")
                cur = "content"
                sys.stdout.write("\n[応答] ")
            sys.stdout.write(delta.content)
            sys.stdout.flush()
            content += delta.content

        # ── ツールコールの処理 ──
        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 ""

    # 現在の出力セクションを終了
    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,
    }

# ── マルチターン会話ループ ─────────────────────────────
messages = [
    {"role": "user", "content": "ボストンの天気はどうですか?次に何を着るべきかを勧めてください。"}
]

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

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

    # ツールコールを表示
    if result["tool_calls"]:
        for tc in result["tool_calls"]:
            print(f"[ツールコール: {tc['function']['name']}] {tc['function']['arguments']}")

    # アシスタントメッセージを構築し、reasoning_detailsを保持(重要!)
    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"]  # 変更せずに返す
    messages.append(assistant_msg)

    # ツールコールがない場合、会話は終了
    if not result["tool_calls"]:
        break

    # ツールを実行し、結果をメッセージに追加
    for tc in result["tool_calls"]:
        args = json.loads(tc["function"]["arguments"])
        tool_result = execute_tool(tc["function"]["name"], args)
        print(f"[ツール結果: {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のネイティブ適応思考努力レベルにマッピングされます。
  • その他のモデル: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

適応思考努力マッピング:

受信努力 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+は適応思考を使用します(努力=medium);他のモデルはbudget_tokens = min(10240, max_tokens - 1)を設定し、デフォルトのmax_tokensは4096です。


2. プロンプトキャッシング

チャットインターフェースを介してClaudeモデルにリクエストを行う際にプロンプトキャッシングを使用できます。メッセージ内にcache_controlブレークポイントを設定することで、役割カード、RAGデータ、本の章などの大きなテキストブロックをキャッシュして再利用できるようになり、後続のリクエストがキャッシュに直接ヒットし、コストを大幅に削減できます。

Claude公式ドキュメント: プロンプトキャッシング

2.1 キャッシングコスト

操作 価格倍率(元の入力価格に対して)
キャッシュ書き込み(5分TTL) 1.25x
キャッシュ書き込み(1時間TTL) 2x
キャッシュ読み取り 0.1x

2.2 対応モデルと最小キャッシュ長

モデル 最小キャッシュトークン数
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
ブレークポイント数量制限: リクエストごとに最大4cache_controlブレークポイント。

2.3 キャッシュTTL

TTL 構文 適用シナリオ
5分(デフォルト) "cache_control": {"type": "ephemeral"} 短いセッション、ルーチンリクエスト
1時間 "cache_control": {"type": "ephemeral", "ttl": "1h"} 長いセッション、繰り返しキャッシュ書き込みを避けるため

1時間TTLの書き込みコストは高くなりますが、長いセッションでの繰り返し書き込みを減らすことで総費用を節約できます。Claude 4.5以降のすべてのモデルは、すべてのプロバイダー(Anthropic、Amazon Bedrock、Google Vertex AIを含む)から1時間TTLをサポートしています。

2.4 使用法

systemuser(画像を含む)、およびtools内でcache_controlフィールドを使用してキャッシュブレークポイントを設定できます。以下の例は、テキストブロックを省略してキー構造のみを示しています。

システムメッセージキャッシング(デフォルト5分TTL):

{
  "model": "claude-opus-4-5",
  "messages": [
    {
      "role": "system",
      "content": [
        {"type": "text", "text": "あなたはAIアシスタントです"},
        {
          "type": "text",
          "text": "(長いコンテキスト)",
          "cache_control": {"type": "ephemeral"}
        }
      ]
    },
    {
      "role": "user",
      "content": [{"type": "text", "text": "こんにちは"}]
    }
  ]
}

ユーザーメッセージキャッシング(1時間TTL):

{
  "model": "claude-opus-4-5",
  "messages": [
    {
      "role": "system",
      "content": [{"type": "text", "text": "あなたはAIアシスタントです"}]
    },
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "(長いコンテキスト)",
          "cache_control": {"type": "ephemeral", "ttl": "1h"}
        },
        {"type": "text", "text": "こんにちは"}
      ]
    }
  ]
}

画像メッセージキャッシング:

{
  "role": "user",
  "content": [
    {
      "type": "image_url",
      "image_url": {"detail": "auto", "url": "data:image/jpeg;base64,/9j/4AAQ..."},
      "cache_control": {"type": "ephemeral"}
    },
    {"type": "text", "text": "これは何ですか?"}
  ]
}

ツール定義キャッシング:

cache_controlはツールオブジェクトの最上位に配置されます(typeおよびfunctionと並んで):

{
  "tools": [{
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "特定の場所の現在の天気を取得",
      "parameters": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"]
      }
    },
    "cache_control": {"type": "ephemeral", "ttl": "1h"}
  }]
}

2.5 キャッシュ状態の表示

応答のusageclaude_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時間_input_tokens": 0
    }
  }
}
フィールド 意味
cache_creation_input_tokens このリクエストでキャッシュに書き込まれたトークン数
cache_read_input_tokens このリクエストでキャッシュから読み取られたトークン数
cache_write_5_minutes_input_tokens 5分TTLキャッシュに書き込まれたトークン数
cache_write_1_hour_input_tokens 1時間TTLキャッシュに書き込まれたトークン数
prompt_tokens_details.cached_tokens キャッシュがヒットしたときのキャッシュトークン数、OpenAI形式と互換性があります

3. anthropic-beta用リクエストヘッダー

HTTPヘッダーanthropic-betaを介してClaudeモデルのベータ機能を有効にできます。AIHubMixはこれをAnthropic APIに渡します。

使用法

リクエストヘッダーにanthropic-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": "あなたはAIアシスタントです"},
        {
          "type": "text",
          "text": "(長いコンテキスト)",
          "cache_control": {"type": "ephemeral"}
        }
      ]
    },
    {"role": "user", "content": [{"type": "text", "text": "こんにちは"}]}
  ]
}'
特定の利用可能なベータ識別子については、Anthropic APIドキュメントを参照してください。

最終更新日:2026-06-01

More from the blog