Nâng cấp giao diện tương thích OpenAI: Hỗ trợ sâu cho Claude

AIHubMix đã nâng cấp API tương thích OpenAI cho Claude: suy nghĩ xen kẽ (interleaved thinking) không cần thêm tham số, prompt caching và hỗ trợ các tính năng beta của Anthropic.

AIHubMixĐọc 8 phút
Nâng cấp giao diện tương thích OpenAI: Hỗ trợ sâu cho Claude

Chúng tôi đã nâng cấp giao diện tương thích OpenAI với các tối ưu hóa sâu hơn, dành riêng cho các model thuộc dòng Claude. Giờ đây bạn có thể kiểm soát suy nghĩ và caching chính xác và thuận tiện hơn. Suy nghĩ xen kẽ trong hội thoại nhiều lượt cũng thân thiện với người dùng hơn, cho phép tích hợp liền mạch mà không cần tham số bổ sung. Ngoài ra, còn hỗ trợ bật các tính năng beta do Anthropic cung cấp.

1. Suy nghĩ của model (Extended Thinking)

1.1 Ưu điểm của suy nghĩ xen kẽ

Khi không bật suy nghĩ xen kẽ, model chỉ thực hiện suy nghĩ một lần ở đầu lượt của assistant; các phản hồi tiếp theo được tạo trực tiếp sau khi nhận kết quả công cụ, mà không tạo khối suy nghĩ mới:

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

Khi bật suy nghĩ xen kẽ, model sẽ chèn một khối suy nghĩ mới mỗi khi nhận kết quả công cụ, tạo thành một chuỗi lập luận:

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

Điều này cho phép model:

  • Thực hiện suy luận thứ cấp dựa trên kết quả công cụ, thay vì chỉ ghép nối các đầu ra.
  • Liên kết suy luận giữa nhiều lần gọi công cụ, trong đó mỗi quyết định dựa trên phân tích của bước trước.
Tham khảo: Anthropic Interleaved Thinking

1.2 Bật chế độ suy nghĩ

Bạn có thể bật suy nghĩ theo bốn cách, chọn bất kỳ cách nào:

Phương thức Ví dụ Mô tả
reasoning_effort "reasoning_effort": "low" Tham số chuẩn của OpenAI, đặt ở cấp cao nhất trong phần thân request
reasoning.effort "reasoning": {"effort": "low"} Tương đương phương thức trước, đặt trong đối tượng reasoning
reasoning.max_tokens "reasoning": {"max_tokens": 1024} Kiểm soát chính xác số token tối đa dùng cho quá trình suy nghĩ
Tên model kèm -think "model": "claude-sonnet-4-5-think" Cách đơn giản nhất, không cần tham số bổ sung
Thứ tự ưu tiên (khi dùng nhiều phương thức): reasoning_effort > reasoning.max_tokens > reasoning.effort > hậu tố -think

Các giá trị có thể của effort: minimal / low / medium / high / xhigh

1.3 Trả về nội dung suy nghĩ

Tin nhắn phản hồi sẽ bao gồm hai trường mới:

  • reasoning_content: Nội dung suy nghĩ (chuỗi), để hiển thị dễ dàng.
  • reasoning_details: Thông tin cấu trúc đầy đủ về suy nghĩ, cần được trả lại nguyên vẹn trong hội thoại nhiều lượt; cấu trúc nội bộ có thể khác nhau giữa các nhà cung cấp.

Ví dụ phản hồi không streaming (bỏ qua các trường không liên quan):

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

Trong phản hồi streaming, nội dung suy nghĩ sẽ được gửi theo từng phần qua delta.reasoning_content và delta.reasoning_details. Để xem logic nối chuỗi streaming đầy đủ, hãy tham khảo ví dụ đầy đủ bên dưới.

1.4 Giữ lại suy nghĩ trong hội thoại nhiều lượt (Suy nghĩ xen kẽ được tích hợp sẵn, không cần tham số bổ sung)

Để model tiếp tục khả năng suy luận trong hội thoại nhiều lượt, chỉ cần đưa reasoning_details đã trả về trước đó nguyên vẹn vào tin nhắn assistant của lượt tiếp theo:

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 sẽ tự động bật suy nghĩ xen kẽ khi phát hiện thông tin suy nghĩ lịch sử trong request, cho phép model tiếp tục suy luận sâu sau khi nhận kết quả gọi công cụ mà không cần tham số bổ sung.

1.5 Ví dụ hoàn chỉnh

Hai ví dụ sau minh họa quy trình hoàn chỉnh của Tool Call + suy nghĩ xen kẽ qua nhiều lượt: câu hỏi của người dùng → model suy nghĩ và gọi công cụ → đưa kết quả công cụ vào (giữ nguyên reasoning_details) → model suy luận xen kẽ và đưa ra phản hồi cuối cùng.

Không streaming · Suy nghĩ xen kẽ

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

Streaming · Suy nghĩ xen kẽ

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 Quy tắc ánh xạ cường độ suy nghĩ

Chế độ Effort:

  • Opus 4.6 / Sonnet 4.6 trở lên: ánh xạ sang mức effort Adaptive Thinking gốc của Anthropic.
  • Các model khác: tính theo công thức của 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

Ánh xạ Effort của Adaptive Thinking:

Effort đầu vào Opus 4.6 Sonnet 4.6
xhigh max high
high high high
medium medium medium
low low low
minimal low low

Chế độ max_tokens: Gán trực tiếp làm budget_tokens của Anthropic.

-think hậu tố: Opus/Sonnet 4.6+ dùng adaptive thinking (effort=medium); các model khác đặt budget_tokens = min(10240, max_tokens - 1), với max_tokens mặc định là 4096.


2. Prompt Caching

Bạn có thể sử dụng Prompt Caching khi gửi request đến model Claude qua giao diện Chat. Bằng cách đặt các breakpoint cache_control trong tin nhắn, các khối văn bản lớn (như thẻ vai trò, dữ liệu RAG, chương sách, v.v.) có thể được lưu vào cache để tái sử dụng, giúp các request sau truy cập trực tiếp vào cache và giảm đáng kể chi phí.

Tài liệu chính thức của Claude: Prompt Caching

2.1 Chi phí cache

Thao tác Hệ số giá (so với giá đầu vào gốc)
Ghi cache (TTL 5 phút) 1.25x
Ghi cache (TTL 1 giờ) 2x
Đọc cache 0.1x

2.2 Các model được hỗ trợ và độ dài cache tối thiểu

Model Số token cache tối thiểu
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 (đã ngừng hỗ trợ) 1024
Claude Haiku 4.5 4096
Claude Haiku 3.5 (đã ngừng hỗ trợ) / Haiku 3 2048
Giới hạn số lượng breakpoint: Tối đa 4 breakpoint cache_control cho mỗi request.

2.3 TTL của cache

TTL Cú pháp Tình huống áp dụng
5 phút (mặc định) "cache_control": {"type": "ephemeral"} Phiên ngắn, request thông thường
1 giờ "cache_control": {"type": "ephemeral", "ttl": "1h"} Phiên dài, để tránh ghi cache lặp lại

Chi phí ghi cho TTL 1 giờ cao hơn, nhưng có thể tiết kiệm tổng chi phí nhờ giảm số lần ghi lặp lại trong các phiên dài. Tất cả các model từ Claude 4.5 trở lên, từ mọi nhà cung cấp (bao gồm Anthropic, Amazon Bedrock, Google Vertex AI), đều hỗ trợ TTL 1 giờ.

2.4 Cách sử dụng

Bạn có thể đặt breakpoint cache bằng trường cache_control trong system, user (bao gồm hình ảnh) và tools. Các ví dụ dưới đây chỉ thể hiện cấu trúc chính, bỏ qua các khối văn bản lớn.

Cache tin nhắn hệ thống (mặc định TTL 5 phút):

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

Cache tin nhắn người dùng (TTL 1 giờ):

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

Cache tin nhắn hình ảnh:

{
  "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 định nghĩa công cụ:

cache_control được đặt ở cấp cao nhất của đối tượng tool (cùng với type và 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 Xem trạng thái cache

usage trong phản hồi sẽ trả về claude_cache_tokens_details, ghi lại thông tin cache chi tiết:

Request đầu tiên (tạo cache):

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

Các request tiếp theo (trúng cache):

{
  "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
    }
  }
}
Trường Ý nghĩa
cache_creation_input_tokens Số token được ghi vào cache trong request này
cache_read_input_tokens Số token được đọc từ cache trong request này
cache_write_5_minutes_input_tokens Số token được ghi vào cache TTL 5 phút
cache_write_1_hour_input_tokens Số token được ghi vào cache TTL 1 giờ
prompt_tokens_details.cached_tokens Số token cache khi trúng cache, tương thích với định dạng OpenAI

3. Request Header cho anthropic-beta

Bạn có thể bật các tính năng beta của model Claude qua HTTP Header anthropic-beta, AIHubMix sẽ chuyển tiếp header này đến API của Anthropic.

Cách sử dụng

Thêm anthropic-beta vào header của request, với giá trị là mã định danh của tính năng beta tương ứng:

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"}]}
  ]
}'
Để xem các mã định danh beta hiện có, vui lòng tham khảo Tài liệu API của Anthropic.

Cập nhật lần cuối: 2026-06-01

Một cổng duy nhất tới nhiều nhà cung cấp AI. Sẵn sàng cho môi trường production.

Truy cập AIHubMix