Actualización de la interfaz compatible con OpenAI: soporte profundo para Claude

AIHubMix actualizó su API compatible con OpenAI para Claude: pensamiento intercalado sin parámetros adicionales, caché de prompts y soporte para las funciones beta de Anthropic.

AIHubMix8 min de lectura
Actualización de la interfaz compatible con OpenAI: soporte profundo para Claude

Hemos actualizado la interfaz compatible con OpenAI con optimizaciones más profundas específicamente para los modelos de la serie Claude. Ahora puede controlar el pensamiento y la caché de forma más precisa y cómoda. El pensamiento intercalado en conversaciones de varios turnos es ahora más fácil de usar, lo que permite una integración fluida sin parámetros adicionales. También admite la activación de las funciones beta ofrecidas por Anthropic.

1. Pensamiento del modelo (Extended Thinking)

1.1 Ventajas del pensamiento intercalado

Cuando el pensamiento intercalado no está activado, el modelo realiza el pensamiento solo una vez al principio de un turno del asistente; las respuestas posteriores se generan directamente después de recibir los resultados de las herramientas, sin producir nuevos bloques de pensamiento:

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

Cuando el pensamiento intercalado está activado, el modelo inserta un nuevo bloque de pensamiento cada vez que recibe un resultado de herramienta, formando una cadena de razonamiento:

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

Esto permite que el modelo:

  • realice un razonamiento secundario basado en los resultados de las herramientas, en lugar de simplemente concatenar las salidas.
  • encadene el razonamiento entre varias llamadas a herramientas, donde cada decisión se basa en el análisis del paso anterior.
Referencia: Pensamiento intercalado de Anthropic

1.2 Activar el pensamiento

Puede activar el pensamiento de cuatro formas, eligiendo cualquiera de ellas:

Método Ejemplo Descripción
reasoning_effort "reasoning_effort": "low" Parámetro estándar de OpenAI, colocado en el nivel superior del cuerpo de la solicitud
reasoning.effort "reasoning": {"effort": "low"} Equivalente al método anterior, colocado dentro del objeto reasoning
reasoning.max_tokens "reasoning": {"max_tokens": 1024} Controla con precisión el número máximo de tokens para el pensamiento
Nombre del modelo con -think "model": "claude-sonnet-4-5-think" La forma más sencilla, no requiere parámetros adicionales
Prioridad (cuando se usan varios métodos): reasoning_effort > reasoning.max_tokens > reasoning.effort > sufijo -think

Valores posibles para effort: minimal / low / medium / high / xhigh

1.3 Retorno del pensamiento

El mensaje de respuesta incluirá dos campos nuevos:

  • reasoning_content: contenido del pensamiento (cadena), para mostrarlo fácilmente.
  • reasoning_details: información estructurada completa sobre el pensamiento, que debe devolverse tal cual en conversaciones de varios turnos; la estructura interna puede diferir entre proveedores.

Ejemplo de respuesta no streaming (omitiendo campos no relacionados):

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

En las respuestas en streaming, el contenido del pensamiento se envía por fragmentos mediante delta.reasoning_content y delta.reasoning_details. Para la lógica completa de concatenación en streaming, consulte el ejemplo completo a continuación.

1.4 Conservar el pensamiento en conversaciones de varios turnos (El pensamiento intercalado está integrado, no se necesitan parámetros adicionales)

Para que el modelo pueda seguir usando sus capacidades de razonamiento en conversaciones de varios turnos, basta con colocar los reasoning_details devueltos previamente tal cual en el mensaje del asistente de la siguiente ronda:

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 activará automáticamente el pensamiento intercalado cuando detecte información de pensamiento histórica en la solicitud, permitiendo que el modelo continúe con el razonamiento profundo tras recibir los resultados de las llamadas a herramientas, sin necesidad de parámetros adicionales.

1.5 Ejemplo completo

Los dos ejemplos siguientes muestran el proceso completo de llamadas a herramientas + pensamiento intercalado en varios turnos: consulta del usuario → el modelo piensa y llama a una herramienta → se inyectan los resultados de la herramienta (conservando reasoning_details) → el modelo, con pensamiento intercalado, da la respuesta final.

Sin streaming · Pensamiento intercalado

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

Con streaming · Pensamiento intercalado

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 Reglas de asignación de la intensidad del pensamiento

Modo effort:

  • Opus 4.6 / Sonnet 4.6 y posteriores: se asigna al nivel de esfuerzo nativo de Adaptive Thinking de Anthropic.
  • Otros modelos: se calcula con la fórmula de 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

Asignación del esfuerzo de Adaptive Thinking:

Effort entrante Opus 4.6 Sonnet 4.6
xhigh max high
high high high
medium medium medium
low low low
minimal low low

Modo max_tokens: se asigna directamente como budget_tokens de Anthropic.

-think sufijo: Opus/Sonnet 4.6+ utiliza el pensamiento adaptativo (effort=medium); los demás modelos establecen budget_tokens = min(10240, max_tokens - 1), con un valor predeterminado de max_tokens de 4096.


2. Caché de prompts

Puede usar la caché de prompts al realizar solicitudes al modelo Claude a través de la interfaz Chat. Al establecer puntos de interrupción cache_control en los mensajes, los bloques grandes de texto (como tarjetas de rol, datos de RAG, capítulos de libros, etc.) pueden guardarse en caché para su reutilización, de modo que las solicitudes posteriores accedan directamente a la caché y se reduzcan significativamente los costos.

Documentación oficial de Claude: Caché de prompts

2.1 Costos de la caché

Operación Multiplicador de precio (en relación con el precio de entrada original)
Escritura en caché (TTL de 5 minutos) 1.25x
Escritura en caché (TTL de 1 hora) 2x
Lectura de caché 0.1x

2.2 Modelos compatibles y longitud mínima de caché

Modelo Número mínimo de tokens para la caché
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 (obsoleto) 1024
Claude Haiku 4.5 4096
Claude Haiku 3.5 (obsoleto) / Haiku 3 2048
Límite de puntos de interrupción: un máximo de 4 puntos de interrupción cache_control por solicitud.

2.3 TTL de la caché

TTL Sintaxis Escenarios aplicables
5 minutos (predeterminado) "cache_control": {"type": "ephemeral"} Sesiones cortas, solicitudes rutinarias
1 hora "cache_control": {"type": "ephemeral", "ttl": "1h"} Sesiones largas, para evitar escrituras repetidas en la caché

Los costos de escritura con TTL de 1 hora son más altos, pero pueden ahorrar gastos totales al reducir las escrituras repetidas en sesiones largas. Todos los modelos de Claude 4.5 y posteriores de todos los proveedores (incluidos Anthropic, Amazon Bedrock y Google Vertex AI) admiten TTL de 1 hora.

2.4 Uso

Puede establecer puntos de interrupción de caché mediante el campo cache_control en system, user (incluidas las imágenes) y tools. Los ejemplos siguientes solo muestran la estructura clave, omitiendo bloques grandes de texto.

Caché de mensajes del sistema (TTL predeterminado de 5 minutos):

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

Caché de mensajes del usuario (TTL de 1 hora):

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

Caché de mensajes con imágenes:

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

Caché de definiciones de herramientas:

cache_control se coloca en el nivel superior del objeto de la herramienta (junto con type y 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 Consulta del estado de la caché

La respuesta usage devolverá claude_cache_tokens_details, que registra la información detallada de la caché:

Primera solicitud (creación de caché):

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

Solicitudes posteriores (acierto de caché):

{
  "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
    }
  }
}
Campo Significado
cache_creation_input_tokens Número de tokens escritos en la caché en esta solicitud
cache_read_input_tokens Número de tokens leídos de la caché en esta solicitud
cache_write_5_minutes_input_tokens Número de tokens escritos en la caché con TTL de 5 minutos
cache_write_1_hour_input_tokens Número de tokens escritos en la caché con TTL de 1 hora
prompt_tokens_details.cached_tokens Número de tokens en caché cuando se produce un acierto, compatible con el formato de OpenAI

3. Cabecera de solicitud para anthropic-beta

Puede activar las funciones beta del modelo Claude mediante la cabecera HTTP anthropic-beta, que AIHubMix pasará directamente a la API de Anthropic.

Uso

Añada anthropic-beta a la cabecera de la solicitud, con como valor el identificador de la función beta correspondiente:

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"}]}
  ]
}'
Para conocer los identificadores beta disponibles, consulte la documentación de la API de Anthropic.

Última actualización: 2026-06-01

Un solo gateway para varios proveedores de IA. Diseñado para producción.

Visitar AIHubMix