Ulepszenie interfejsu kompatybilnego z OpenAI: pełne wsparcie dla Claude

AIHubMix ulepszył swoje API kompatybilne z OpenAI dla Claude: przeplatane myślenie bez dodatkowych parametrów, buforowanie promptów oraz obsługę funkcji beta Anthropic.

AIHubMix8 min czytania
Ulepszenie interfejsu kompatybilnego z OpenAI: pełne wsparcie dla Claude

Ulepszyliśmy interfejs kompatybilny z OpenAI, wprowadzając głębsze optymalizacje dedykowane modelom z serii Claude. Teraz możesz precyzyjniej i wygodniej kontrolować myślenie oraz buforowanie. Przeplatane myślenie w rozmowach wieloetapowych jest teraz bardziej przyjazne dla użytkownika i pozwala na bezproblemową integrację bez dodatkowych parametrów. Obsługiwane jest także włączanie funkcji beta oferowanych przez Anthropic.

1. Myślenie modelu (Extended Thinking)

1.1 Zalety przeplatanego myślenia

Gdy przeplatane myślenie nie jest włączone, model wykonuje myślenie tylko raz, na początku tury asystenta; kolejne odpowiedzi są generowane bezpośrednio po otrzymaniu wyników narzędzi, bez tworzenia nowych bloków myślenia:

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

Gdy przeplatane myślenie jest włączone, model wstawia nowy blok myślenia za każdym razem, gdy otrzymuje wynik narzędzia, tworząc łańcuch rozumowania:

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

Dzięki temu model potrafi:

  • Przeprowadzić wtórne rozumowanie na podstawie wyników narzędzi, zamiast po prostu łączyć wyniki.
  • Łączyć rozumowanie między wieloma wywołaniami narzędzi, gdzie każda decyzja opiera się na analizie poprzedniego kroku.
Źródło: Anthropic Interleaved Thinking

1.2 Włączanie myślenia

Myślenie możesz włączyć na cztery sposoby, wybierając dowolny z nich:

Metoda Przykład Opis
reasoning_effort "reasoning_effort": "low" Standardowy parametr OpenAI, umieszczany na najwyższym poziomie treści żądania
reasoning.effort "reasoning": {"effort": "low"} Odpowiednik poprzedniej metody, umieszczany w obiekcie reasoning
reasoning.max_tokens "reasoning": {"max_tokens": 1024} Precyzyjnie kontroluje maksymalną liczbę tokenów przeznaczonych na myślenie
Nazwa modelu z sufiksem -think "model": "claude-sonnet-4-5-think" Najprostszy sposób, nie wymaga dodatkowych parametrów
Priorytet (gdy używanych jest wiele metod): reasoning_effort > reasoning.max_tokens > reasoning.effort > sufiks -think

Możliwe wartości effort: minimal / low / medium / high / xhigh

1.3 Zwracanie myślenia

Wiadomość odpowiedzi zawiera dwa nowe pola:

  • reasoning_content: treść myślenia (ciąg znaków), ułatwiająca wyświetlanie.
  • reasoning_details: kompletne, ustrukturyzowane informacje o myśleniu, które należy zwracać bez zmian w rozmowach wieloetapowych; wewnętrzna struktura może różnić się między dostawcami.

Przykład odpowiedzi bez strumieniowania (pominięto niezwiązane pola):

{
  "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..."
      }
    }
  }]
}

W odpowiedziach strumieniowanych treść myślenia wysyłana jest fragmentami przez delta.reasoning_content i delta.reasoning_details. Pełną logikę łączenia fragmentów w strumieniu znajdziesz w poniższym kompletnym przykładzie.

1.4 Zachowywanie myślenia w rozmowach wieloetapowych (przeplatane myślenie jest wbudowane, nie wymaga dodatkowych parametrów)

Aby model mógł kontynuować rozumowanie w rozmowach wieloetapowych, wystarczy umieścić wcześniej zwrócone reasoning_details bez zmian w wiadomości asystenta z następnej rundy:

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 automatycznie włączy przeplatane myślenie, gdy wykryje w żądaniu historyczne informacje o myśleniu, dzięki czemu model będzie mógł kontynuować głębokie rozumowanie po otrzymaniu wyników wywołań narzędzi, bez dodatkowych parametrów.

1.5 Kompletny przykład

Poniższe dwa przykłady pokazują pełny proces wieloetapowego wywołania narzędzi z przeplatanym myśleniem: zapytanie użytkownika → model myśli i wywołuje narzędzie → wstrzyknięcie wyników narzędzia (z zachowaniem reasoning_details) → model, dzięki przeplatanemu myśleniu, udziela końcowej odpowiedzi.

Bez strumieniowania · przeplatane myślenie

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})

Strumieniowanie · przeplatane myślenie

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 Zasady mapowania intensywności myślenia

Tryb effort:

  • Opus 4.6 / Sonnet 4.6 i nowsze: mapowane na natywny poziom effort Adaptive Thinking firmy Anthropic.
  • Inne modele: obliczane według wzoru dla budget_tokens:
budget_tokens = max(min(max_tokens × effort_ratio, 128000), 1024)
effort effort_ratio
xhigh 0.95
high 0.80
medium 0.50
low 0.20
minimal 0.10

Mapowanie effort dla Adaptive Thinking:

Przychodzący effort Opus 4.6 Sonnet 4.6
xhigh max high
high high high
medium medium medium
low low low
minimal low low

Tryb max_tokens: wartość jest przekazywana bezpośrednio jako budget_tokens Anthropic.

-think sufiks: Opus/Sonnet 4.6+ korzysta z adaptacyjnego myślenia (effort=medium); pozostałe modele ustawiają budget_tokens = min(10240, max_tokens - 1), przy domyślnym max_tokens wynoszącym 4096.


2. Buforowanie promptów

Buforowanie promptów możesz wykorzystać przy wysyłaniu żądań do modelu Claude przez interfejs Chat. Ustawiając punkty kontrolne cache_control w wiadomościach, duże bloki tekstu (jak karty ról, dane RAG, rozdziały książek itp.) mogą zostać zbuforowane do ponownego użycia, dzięki czemu kolejne żądania trafiają bezpośrednio do bufora, co znacząco obniża koszty.

Oficjalna dokumentacja Claude: Buforowanie promptów

2.1 Koszty buforowania

Operacja Mnożnik ceny (w stosunku do oryginalnej ceny wejścia)
Zapis do bufora (TTL 5 minut) 1.25x
Zapis do bufora (TTL 1 godzina) 2x
Odczyt z bufora 0.1x

2.2 Obsługiwane modele i minimalna długość bufora

Model Minimalna liczba tokenów do buforowania
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 (wycofany) 1024
Claude Haiku 4.5 4096
Claude Haiku 3.5 (wycofany) / Haiku 3 2048
Limit punktów kontrolnych: maksymalnie 4 punkty cache_control na żądanie.

2.3 TTL bufora

TTL Składnia Zastosowania
5 minut (domyślnie) "cache_control": {"type": "ephemeral"} Krótkie sesje, rutynowe żądania
1 godzina "cache_control": {"type": "ephemeral", "ttl": "1h"} Długie sesje, aby uniknąć powtarzających się zapisów do bufora

Koszty zapisu dla TTL 1 godziny są wyższe, ale mogą obniżyć łączne wydatki dzięki ograniczeniu powtarzających się zapisów w długich sesjach. Wszystkie modele Claude 4.5 i nowsze, od wszystkich dostawców (w tym Anthropic, Amazon Bedrock, Google Vertex AI), obsługują TTL 1 godzina.

2.4 Użycie

Punkty buforowania ustawiasz za pomocą pola cache_control w system, user (w tym w obrazach) oraz tools. Poniższe przykłady pokazują tylko strukturę kluczy, bez dużych bloków tekstu.

Buforowanie wiadomości systemowej (domyślnie TTL 5 minut):

{
  "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"}]
    }
  ]
}

Buforowanie wiadomości użytkownika (TTL 1 godzina):

{
  "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"}
      ]
    }
  ]
}

Buforowanie wiadomości z obrazem:

{
  "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?"}
  ]
}

Buforowanie definicji narzędzi:

cache_control umieszczane jest na najwyższym poziomie obiektu narzędzia (obok type i 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 Sprawdzanie stanu bufora

Pole usage w odpowiedzi zwróci claude_cache_tokens_details, zawierające szczegółowe informacje o buforze:

Pierwsze żądanie (tworzenie bufora):

{
  "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
    }
  }
}

Kolejne żądania (trafienie w bufor):

{
  "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
    }
  }
}
Pole Znaczenie
cache_creation_input_tokens Liczba tokenów zapisanych do bufora w tym żądaniu
cache_read_input_tokens Liczba tokenów odczytanych z bufora w tym żądaniu
cache_write_5_minutes_input_tokens Liczba tokenów zapisanych do bufora o TTL 5 minut
cache_write_1_hour_input_tokens Liczba tokenów zapisanych do bufora o TTL 1 godzina
prompt_tokens_details.cached_tokens Liczba tokenów z bufora w przypadku trafienia, zgodna z formatem OpenAI

3. Nagłówek żądania anthropic-beta

Funkcje beta modelu Claude możesz włączyć za pomocą nagłówka HTTP anthropic-beta, który AIHubMix przekaże bez zmian do API Anthropic.

Użycie

Dodaj anthropic-beta do nagłówka żądania, z wartością będącą odpowiednim identyfikatorem funkcji 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"}]}
  ]
}'
Aktualne identyfikatory funkcji beta znajdziesz w dokumentacji API Anthropic.

Ostatnia aktualizacja: 2026-06-01

Jedna brama do wielu dostawców AI. Stworzona z myślą o produkcji.

Odwiedź AIHubMix