La API de Doubao ofrece una interfaz compatible con OpenAI, pero «compatible» no significa «copiar y pegar». Los tres puntos donde más gente se bloquea son: escribir la Base URL con /v1, poner en model el nombre comercial en lugar del ID de modelo o del ID de endpoint, y activar el razonamiento profundo en la primera llamada hasta truncar la salida. Este artículo recorre en orden curl y Python, y después explica streaming, llamadas a herramientas, errores habituales y una lista de verificación para migraciones.

Nota: Doubao, Volcano Ark, Volcengine y ByteDance son marcas o nombres de productos de ByteDance y sus empresas vinculadas; OpenAI es una marca de su titular. Este artículo es solo una explicación técnica de integración y no implica afiliación, representación ni respaldo de esas compañías. La interfaz, los modelos y las cuotas pueden cambiar; consulta la documentación oficial y los precios de modelos del sitio.

1. Confirma tres cosas antes de integrar

Elemento Conexión directa a Volcano Ark Gateway unificado
Base URL https://ark.cn-beijing.volces.com/api/v3 https://ai.zhongshangqihang.com/v1
Autenticación Authorization: Bearer <ARK_API_KEY> Authorization: Bearer <clave del gateway>
model ID de modelo o ID de endpoint ep- Según la lista de modelos del sitio
Adecuado para Solo Doubao, verificación y recarga propias Varios modelos, facturación y cuota unificadas

La clave y la Base URL deben usarse en pareja. Una clave oficial con la dirección del gateway, o una clave de gateway con la dirección oficial, suele devolver un 401 directo. Al cambiar de entrada, cambia las dos cosas a la vez.

2. Base URL y ruta de la solicitud: /api/v3, no /v1

La dirección oficial del plano de datos es https://ark.cn-beijing.volces.com/api/v3, y la ruta de chat completions es /chat/completions; la dirección completa es https://ark.cn-beijing.volces.com/api/v3/chat/completions. No lleva el prefijo /v1 habitual de OpenAI, y añadir otro /v1 produce un 404.

La región va dentro del dominio (cn-beijing). Si los recursos están en otra región, usa la dirección que indiquen la consola y la documentación. La documentación oficial también advierte que casos como Coding Plan usan otra Base URL y que una dirección incorrecta puede generar cargos adicionales; revisa antes de cambiar de plan.

3. Qué poner en model: ID de modelo o ID de endpoint

La documentación oficial distingue dos formas de llamada:

  • Inferencia predefinida: se indica directamente el ID de modelo, por ejemplo doubao-seed-2-1-pro-260915, sin crear un endpoint antes.
  • Endpoint de inferencia personalizado: se indica el ID del endpoint, normalmente con prefijo ep-, adecuado para despliegues fijos, gestión detallada o modelos ajustados con LoRA.

El ID de modelo incluye la fecha de versión; no escribas nombres comerciales como «Doubao 2.1 Pro» ni completes sufijos de memoria. Al firmar con Access Key (AK/SK), model debe ser el ID de endpoint; con API Key se puede usar el ID de modelo o el de endpoint según la documentación. Si aparece InvalidEndpointOrModel.ModelIDAccessDisabled, la cuenta no admite llamadas directas por ID de modelo y hay que crear un endpoint.

4. Primera solicitud con curl

Desactiva primero el razonamiento profundo para reducir variables. Ejemplo con la entrada oficial:

export ARK_API_KEY="tu API Key de Volcano Ark"
export ARK_MODEL_ID="doubao-seed-2-1-pro-260915"

curl "https://ark.cn-beijing.volces.com/api/v3/chat/completions" \
  -H "Authorization: Bearer $ARK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "'"$ARK_MODEL_ID"'",
    "messages": [{"role": "user", "content": "Responde solo OK"}],
    "thinking": {"type": "disabled"},
    "max_tokens": 32
  }'

Si devuelve 200 y choices[0].message.content trae contenido, la clave, la dirección y el modelo están bien. Ante un 404, revisa primero si el model lleva el sufijo de versión y si hace falta usar un endpoint ep-; ante un 401, revisa la clave y la cabecera antes de sospechar del saldo.

5. Llamada desde Python con el SDK de OpenAI

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["ARK_API_KEY"],
    base_url="https://ark.cn-beijing.volces.com/api/v3",
)

resp = client.chat.completions.create(
    model=os.environ.get("ARK_MODEL_ID", "doubao-seed-2-1-pro-260915"),
    messages=[
        {"role": "system", "content": "Eres un asistente técnico. Responde de forma breve."},
        {"role": "user", "content": "Explica en tres frases qué es un endpoint de inferencia."},
    ],
    max_tokens=512,
    extra_body={"thinking": {"type": "disabled"}},
)

print(resp.choices[0].message.content)
print(resp.usage)

El SDK de OpenAI no reconoce el campo thinking, así que se pasa mediante extra_body; si el gateway o el modelo no lo admiten, basta con quitar ese campo. Cuando la llamada funcione, revisa los tokens de entrada y salida en usage y después decide tiempos de espera, concurrencia y presupuesto.

6. Streaming: delta, reasoning_content y [DONE]

Con stream=True, el servidor devuelve bloques por SSE, el objeto de respuesta es chat.completion.chunk, el texto va en choices[0].delta.content, el contenido de razonamiento en delta.reasoning_content, finish_reason aparece solo en el último chunk y al final se envía data: [DONE].

stream = client.chat.completions.create(
    model=os.environ["ARK_MODEL_ID"],
    messages=[{"role": "user", "content": "Escribe un ejemplo de Python que lea un CSV"}],
    stream=True,
    stream_options={"include_usage": True},
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")

Los bloques de razonamiento pueden traer content vacío; no supongas que todos los chunks llevan texto. Un max_tokens demasiado pequeño trunca la salida. Si se corta la red, lo ya recibido sigue disponible: el cliente debe guardar resultados parciales y decidir si reintenta.

7. Llamadas a herramientas: tools, tool_calls y orden de devolución

La forma de tools se parece a la de OpenAI: cada herramienta incluye type: "function" y function, donde name, description y parameters son obligatorios y parameters usa JSON Schema. Cuando el modelo decide llamar a una herramienta, finish_reason es tool_calls y message.tool_calls[i].function.arguments es una cadena JSON que hay que analizar por cuenta propia.

El orden de devolución no puede fallar: primero añade al diálogo el mensaje del asistente con tool_calls, luego agrega un mensaje con role: "tool" por cada llamada, con su tool_call_id y el resultado, y vuelve a pedir al modelo hasta que finish_reason deje de ser tool_calls. parallel_tool_calls es true por defecto; ponlo en false para limitar a una herramienta por turno. En streaming, los incrementos de llamadas a herramientas llevan index; acumula arguments según ese índice.

8. Errores habituales: cómo ubicar 401, 404 y 429

HTTP Código típico Significado Qué hacer
401 AuthenticationError Clave ausente, inválida o cabecera sin Bearer Vuelve a copiar la clave y revisa la cabecera Authorization
404 InvalidEndpointOrModel.NotFound El modelo o endpoint no existe, o el espacio actual no tiene permiso Copia el ID completo desde la consola y verifica región y espacio
404 ModelNotOpen El modelo aún no está habilitado en la cuenta Habilita el modelo en la consola de Ark
404 InvalidEndpointOrModel.ModelIDAccessDisabled La cuenta no admite llamadas directas por ID de modelo Crea un endpoint personalizado y usa el ID ep-
400 InvalidParameter, MissingParameter Falta un parámetro, el tipo es incorrecto o el modelo no lo admite Ajusta los campos según la documentación y prueba primero la solicitud mínima
403 AccessDenied, AccountOverdueError Sin permiso o saldo negativo Revisa permisos de la clave y del espacio, y regulariza la deuda
429 RateLimitExceeded.* Se superó el RPM/TPM o hay un pico de tráfico Reduce concurrencia, aplica retroceso exponencial y solicita más cuota si hace falta
500/503 InternalServiceError, ServerOverloaded Anomalía o sobrecarga upstream Espera un poco y reintenta; ten un canal alternativo preparado

9. Lista de verificación para migrar

Al pasar de la conexión directa a un gateway, o de un gateway a otro, revisa en este orden:

  1. Fija un conjunto de prompts reales y registra la salida actual, el tiempo y usage como línea base de comparación.
  2. Cambia base_url y api_key al mismo tiempo; no modifiques solo uno.
  3. Reconfirma la asignación de nombres de modelo según los precios de modelos del sitio; no reutilices nombres antiguos.
  4. Verifica el streaming: tiempo hasta el primer token, delta.content, finish_reason y [DONE].
  5. Verifica las llamadas a herramientas: tools, tool_calls, devolución de tool_call_id y orden en varios turnos.
  6. Comprueba si se admiten parámetros extendidos como thinking o reasoning_effort y si los valores por defecto cambian.
  7. Configura tiempos de espera de conexión y lectura; reintenta solo 429, 5xx y timeouts; asegura idempotencia en operaciones de escritura.
  8. Migra primero el 5 % al 10 % del tráfico y observa de 3 a 7 días, manteniendo la conexión directa como canal de reversión.

Si necesitas una entrada unificada, puedes crear una clave de prueba en la página de registro; la cobertura de modelos y el criterio de cobro están en los precios de modelos. ZSQH AI Gateway es un servicio independiente de terceros y no tiene relación de afiliación con ByteDance.

Artículos relacionados:

Preguntas frecuentes

¿Hay que añadir /v1 a la Base URL?

No. La entrada compatible con OpenAI de Volcano Ark es https://ark.cn-beijing.volces.com/api/v3, con ruta de chat /chat/completions; añadir /v1 produce un 404. La dirección de un gateway suele terminar en /v1; no mezcles ambas.

¿model lleva el ID de modelo o el ID de endpoint que empieza por ep-?

Inferencia predefinida: ID de modelo. Endpoint personalizado: ID ep-. Con firma de Access Key, siempre el ID de endpoint. Si aparece ModelIDAccessDisabled, hay que crear un endpoint, no cambiar la clave.

¿Conviene activar el razonamiento profundo en la primera llamada?

Se recomienda desactivarlo y confirmar primero la conexión con la solicitud más corta. Algunos modelos Doubao lo activan por defecto; al hacerlo, la salida genera primero contenido de razonamiento y un max_tokens pequeño puede truncar el texto. Actívalo según la tarea una vez confirmada la conexión.

¿Por qué el streaming no devuelve contenido?

Confirma que stream=True y revisa si solo lees delta.content e ignoras los bloques vacíos; el razonamiento va en delta.reasoning_content. Comprueba también max_tokens, el búfer de red y si el proxy acumula el SSE en una sola respuesta.

¿Por qué hay que devolver los tool_calls del asistente?

El protocolo exige que el mensaje del asistente lleve tool_calls y que cada mensaje role: "tool" corresponda a un resultado mediante tool_call_id. Si se omite, el modelo puede repetir la llamada o devolver un 400. En streaming, además, hay que acumular los argumentos según index.