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:
- Fija un conjunto de prompts reales y registra la salida actual, el tiempo y
usagecomo línea base de comparación. - Cambia
base_urlyapi_keyal mismo tiempo; no modifiques solo uno. - Reconfirma la asignación de nombres de modelo según los precios de modelos del sitio; no reutilices nombres antiguos.
- Verifica el streaming: tiempo hasta el primer token,
delta.content,finish_reasony[DONE]. - Verifica las llamadas a herramientas:
tools,tool_calls, devolución detool_call_idy orden en varios turnos. - Comprueba si se admiten parámetros extendidos como
thinkingoreasoning_efforty si los valores por defecto cambian. - Configura tiempos de espera de conexión y lectura; reintenta solo 429, 5xx y timeouts; asegura idempotencia en operaciones de escritura.
- 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:
- Guía completa de la API compatible con OpenAI
- Qué es un gateway de API
- Cómo elegir una plataforma de API de modelos
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.
