GLM-5.3 FlashX and MiniMax H3 Max are now live on CometAPI →
technology/Investigación de CometAPI

Cómo crear aplicaciones de IA que no dependan de un único proveedor

Cómo crear aplicaciones de IA que no estén atadas a un solo proveedor: Evita la dependencia de proveedores de IA estructurando tu código en torno a un enfoque agnóstico del proveedor. Pruébalo en CometAPI.

CometAPI
AnnaEquipo de investigación de modelos de IA y API
Actualizado Sep 3, 2026 11 min de lectura
Cómo crear aplicaciones de IA que no dependan de un único proveedor
Usa este patrón

Haz la primera llamada a la API.

from openai import OpenAI

client = OpenAI(
    api_key="YOUR_COMETAPI_KEY",
    base_url="https://api.cometapi.com/v1",
)

response = client.chat.completions.create(
    model="gpt-5-mini",
    messages=[{"role": "user", "content": "Build this workflow."}],
)

print(response.choices[0].message.content)

El bloqueo de proveedor en las aplicaciones de IA normalmente no ocurre de golpe. Se va filtrando — un import openai directo por aquí, un nombre de modelo codificado de forma rígida por allá, un campo de respuesta que analizas sin comprobar si otros proveedores devuelven lo mismo. Seis meses después, cambiar de proveedor significa reescribir la mitad de tu backend.

Las cuatro formas en que ocurre el bloqueo

La mayoría de los desarrolladores piensa que el bloqueo significa «Estoy usando el SDK de OpenAI». Ese es el tipo menos peligroso. Las trampas reales son más sutiles:

Tipo de bloqueoCómo sucedeConsecuencia
Bloqueo por SDKfrom openai import OpenAI en todas partesCambiar de SDK implica tocar cada archivo
Bloqueo por nombre de modelomodel="gpt-4o" fijo en la lógica de negocioCada cambio de modelo es un cambio de código
Bloqueo por parámetrosUsar logprobs, n>1, o reasoning_effortEsto no existe en Claude ni en Gemini
Bloqueo por formato de respuestaAnalizar campos de respuesta específicos del proveedorDistintos proveedores devuelven estructuras diferentes

El objetivo no es eliminar todos estos — algunos son compromisos aceptables. El objetivo es saber cuáles estás asumiendo.

Usa un endpoint compatible con OpenAI como capa de abstracción

La forma más limpia de evitar el bloqueo por SDK es usar un único endpoint compatible con OpenAI que enrute a varios proveedores. Mantienes el SDK de OpenAI, pero el backend puede ser cualquier proveedor.

CometAPI hace esto: un endpoint, una clave, más de 500 modelos entre OpenAI, Anthropic, Google, DeepSeek, xAI y otros:

import osfrom openai import OpenAIfrom dotenv import load_dotenv​load_dotenv()​api_key = os.environ.get("AI_API_KEY")if not api_key:    raise ValueError("AI_API_KEY environment variable is not set")​client = OpenAI(    base_url=os.environ.get("AI_BASE_URL", "https://api.cometapi.com/v1"),    api_key=api_key,)

Cambiar de GPT a Claude o a Gemini es un cambio de una sola línea:

# Beforeresponse = client.chat.completions.create(model="gpt-5.4", messages=[...])​# After — same code, different modelresponse = client.chat.completions.create(model="claude-sonnet-4-6", messages=[...])

Nota: Los nombres de modelo como gpt-5.4 y claude-sonnet-4-6 son identificadores de plataforma de CometAPI: solo funcionan a través de https://api.cometapi.com/v1, no directamente con las API de OpenAI o Anthropic. Consulta la lista completa de modelos para ver el catálogo completo y los precios.

Mantén los nombres de modelo fuera de tu lógica de negocio

Tener nombres de modelo dispersos por tu código es la forma más común de bloqueo. La solución es una configuración central que lea de variables de entorno:

# config.py — one place to change model assignmentsimport os​MODEL_CONFIG = {    "summarize": os.environ.get("MODEL_SUMMARIZE", "claude-opus-4-7"),    "code":      os.environ.get("MODEL_CODE",      "gpt-5.4"),    "classify":  os.environ.get("MODEL_CLASSIFY",  "claude-haiku-4-5"),    "chat":      os.environ.get("MODEL_CHAT",       "gpt-5.4-mini"),}​# Validate at startup — fail fast rather than getting mysterious API errorsfor task, model in MODEL_CONFIG.items():    if not model:        raise ValueError(f"Model config for '{task}' is not set")

Tu lógica de negocio nunca hace referencia a un nombre de modelo directamente:

from config import MODEL_CONFIG​def summarize(text: str) -> str:    response = client.chat.completions.create(        model=MODEL_CONFIG["summarize"],        messages=[{"role": "user", "content": f"Summarize: {text}"}],        max_tokens=300  # move to config in production    )    return response.choices[0].message.content

Para cambiar el modelo de resumen en toda tu aplicación, cambia una variable de entorno. Sin grep, sin buscar y reemplazar.

Encapsula la respuesta para que tu código no dependa de campos específicos del proveedor

Distintos proveedores devuelven formas de respuesta ligeramente diferentes. Si analizas respuestas crudas de la API por todo tu código, quedas atado al formato de ese proveedor.

Encápsulalo en un dataclass normalizado:

from dataclasses import dataclassfrom typing import Optionalfrom openai import OpenAI, APIStatusError, APIConnectionError, APITimeoutErrorfrom openai.types.chat import ChatCompletionimport logging​@dataclassclass AIResponse:    content: str    model: str    input_tokens: int    output_tokens: int​def call_model(task: str, messages: list, **kwargs) -> AIResponse:    """    Single entry point for all LLM calls.    Returns a normalized AIResponse regardless of which model handled it.    Raises on 4xx (client errors). Logs and re-raises on 5xx/network errors.    """    model = MODEL_CONFIG.get(task, "gpt-5.4-mini")    if not model:        raise ValueError(f"No model configured for task '{task}'")​    try:        response: ChatCompletion = client.chat.completions.create(            model=model,            messages=messages,            **kwargs        )    except APIStatusError as e:        logging.error(f"API error for task={task} model={model}: {e.status_code} {e.message}")        raise    except (APIConnectionError, APITimeoutError) as e:        logging.error(f"Network error for task={task} model={model}: {e}")        raise​    # content is None when the model triggers a tool call instead of returning text    content = response.choices[0].message.content or ""​    # usage is None in streaming mode — default to 0 if not available    usage = response.usage    input_tokens = usage.prompt_tokens if usage else 0    output_tokens = usage.completion_tokens if usage else 0​    logging.info(        f"task={task} model={model} "        f"input_tokens={input_tokens} output_tokens={output_tokens}"    )​    return AIResponse(        content=content,        model=response.model,        input_tokens=input_tokens,        output_tokens=output_tokens,    )

Ahora tu lógica de negocio trabaja con objetos AIResponse, no con respuestas crudas de la API. Si un proveedor cambia su formato de respuesta, lo corriges en un solo lugar.

Añade compatibilidad con streaming al wrapper

Para interfaces de chat, querrás streaming. El wrapper lo gestiona como una ruta separada:

from typing import Iterator​def stream_model(task: str, messages: list, **kwargs) -> Iterator[str]:    """    Stream tokens from the routed model.    Note: streaming doesn't return usage data.    Fallback is not supported in streaming mode — you've already    started yielding tokens before you know if the full request succeeds.    """    model = MODEL_CONFIG.get(task, "gpt-5.4-mini")    if not model:        raise ValueError(f"No model configured for task '{task}'")​    stream = client.chat.completions.create(        model=model,        messages=messages,        stream=True,        **kwargs    )​    for chunk in stream:        delta = chunk.choices[0].delta.content        if delta:            yield delta​# Usagefor token in stream_model("chat", [{"role": "user", "content": "Hello"}]):    print(token, end="", flush=True)

Conoce qué parámetros generan bloqueo

Algunos parámetros solo existen en proveedores específicos. Usarlos está bien; solo ten presente que estás tomando una decisión deliberada:

ParámetroFunciona enRiesgo de bloqueo
logprobsSolo GPTAlto — no hay equivalente en Claude o Gemini
n > 1GPT, Gemini (no Claude)Medio — Claude requiere iterar
reasoning_effortSolo la serie o de GPTAlto — no hay equivalente en otros
temperature > 1.0GPT, Gemini (no Claude)Bajo — Claude limita a 1.0
toolsTodos los proveedores principalesNinguno — seguro de usar
response_formatTodos los proveedores principalesBajo — diferencias menores de esquema

Si estás usando logprobs para puntuar la confianza, quedas atado a GPT para esa función. Es un compromiso razonable; solo documéntalo para que el próximo desarrollador sepa por qué.

Haz configurable el endpoint del proveedor

Fijar en código base_url="https://api.cometapi.com/v1" sigue siendo una forma de bloqueo. Conviértelo en una variable de entorno:

# .env — using CometAPIAI_BASE_URL=https://api.cometapi.com/v1AI_API_KEY=your_cometapi_key​# To switch to OpenAI directly, change two lines:# AI_BASE_URL=https://api.openai.com/v1# AI_API_KEY=your_openai_key

La inicialización del cliente del Paso 1 ya lee estas variables. Cambiar entre CometAPI y una conexión directa a un proveedor ahora es un cambio de configuración, no de código.

Versión para Node.js

import OpenAI from 'openai';​const apiKey = process.env.AI_API_KEY;if (!apiKey) throw new Error('AI_API_KEY is not set');​const client = new OpenAI({  baseURL: process.env.AI_BASE_URL ?? 'https://api.cometapi.com/v1',  apiKey,});​// Model IDs are CometAPI platform identifiers — see cometapi.com/modelsconst MODEL_CONFIG = {  summarize: process.env.MODEL_SUMMARIZE ?? 'claude-opus-4-7',  code:      process.env.MODEL_CODE      ?? 'gpt-5.4',  classify:  process.env.MODEL_CLASSIFY  ?? 'claude-haiku-4-5',  chat:      process.env.MODEL_CHAT      ?? 'gpt-5.4-mini',};​// Validate at startupfor (const [task, model] of Object.entries(MODEL_CONFIG)) {  if (!model) throw new Error(`Model config for '${task}' is not set`);}​/** * Single entry point for all LLM calls. * Returns normalized response. Raises on 4xx, logs and re-raises on 5xx/network. */async function callModel(task, messages, options = {}) {  const model = MODEL_CONFIG[task] ?? 'gpt-5.4-mini';​  let response;  try {    response = await client.chat.completions.create({      model,      messages,      ...options,    });  } catch (err) {    // Don't swallow errors — log and re-raise    console.error(`API error task=${task} model=${model}:`, err.message);    throw err;  }​  // content is null when model triggers a tool call  const content = response.choices[0].message.content ?? '';​  // usage may be absent in some configurations  const inputTokens  = response.usage?.prompt_tokens     ?? 0;  const outputTokens = response.usage?.completion_tokens ?? 0;​  console.log(`task=${task} model=${model} input=${inputTokens} output=${outputTokens}`);​  return { content, model: response.model, inputTokens, outputTokens };}​/** * Stream tokens from the routed model. * Usage data is not available in streaming mode. */async function* streamModel(task, messages, options = {}) {  const model = MODEL_CONFIG[task] ?? 'gpt-5.4-mini';​  const stream = await client.chat.completions.create({    model,    messages,    stream: true,    ...options,  });​  for await (const chunk of stream) {    const delta = chunk.choices[0]?.delta?.content;    if (delta) yield delta;  }}​// Usage — blockingconst result = await callModel('classify', [  { role: 'user', content: 'Positive or negative? "Loved it!"' }]);console.log(result.content);​// Usage — streamingfor await (const token of streamModel('chat', [  { role: 'user', content: 'Hello' }])) {  process.stdout.write(token);}

Qué bloqueo es aceptable

No todo bloqueo merece ser combatido. Algunos compromisos tienen sentido:

  • Usar el OpenAI SDK — Es el estándar de facto. La mayoría de los proveedores lo admiten. Bloqueo de bajo riesgo.
  • Funciones específicas del proveedor que realmente necesitas — Si necesitas logprobs, úsalas. Aísla ese código para que sea fácil de encontrar y reemplazar después.
  • Modelos afinados — Un modelo afinado está intrínsecamente ligado a un proveedor. Es lo esperado.

El bloqueo que conviene evitar es el accidental: nombres de modelo en la lógica de negocio, análisis de respuestas crudas disperso por archivos, claves de API incrustadas en el código fuente.

Qué sigue

Ahora tienes una capa de abstracción que mantiene los detalles del proveedor fuera de tu lógica de negocio. El último artículo de esta serie cubre qué ocurre cuando las cosas salen mal: cómo depurar generaciones fallidas, interpretar códigos de error y crear un manejo de errores que realmente te diga qué se rompió.

Sigue: Cómo depurar generaciones fallidas de la API de IA

Preguntas frecuentes

P: ¿Cuál es la diferencia entre el bloqueo por SDK y el bloqueo por modelo?

El bloqueo por SDK significa que tu código importa una biblioteca específica y tendría que cambiar si cambias de SDK. El bloqueo por modelo significa que los nombres de modelo están dispersos en tu lógica de negocio. El bloqueo por SDK es menos peligroso porque la mayoría de los proveedores ahora admiten el formato del SDK de OpenAI. El bloqueo por modelo es más insidioso porque es más difícil de encontrar y corregir.

P: Si uso CometAPI, ¿solo estoy cambiando el bloqueo de OpenAI por el bloqueo de CometAPI?

Parcialmente. Estás cambiando el bloqueo a un proveedor directo por una capa proxy. La ventaja: una clave, un endpoint, cambio de modelo sencillo. El riesgo: si CometAPI sufre una caída, todos tus proveedores caen a la vez. La mitigación ya está en el código de arriba: AI_BASE_URL es una variable de entorno. Si necesitas omitir CometAPI y llamar a un proveedor directamente, es un cambio de configuración, no de código.

P: ¿Puedo usar el extended thinking de Claude o el reasoning_effort de OpenAI con este patrón?

Sí, pásalos como **kwargs a call_model. Solo ten en cuenta que, si enrutas esa tarea a un modelo diferente, esos parámetros se ignorarán o provocarán un error. Documenta qué tareas usan funciones específicas del proveedor para que el próximo desarrollador sepa por qué.

P: ¿Cómo gestiono el límite de temperature de Claude en 1.0 al enrutar entre Claude y GPT**?**

Mantén temperature en 1.0 o por debajo para permanecer en el rango seguro de ambos. Si necesitas una temperatura más alta para tareas creativas en GPT específicamente, enruta esas tareas explícitamente a GPT en MODEL_CONFIG en lugar de dejarlas pasar por el enrutador genérico.

P: ¿Debería abstraer las API de generación de imágenes y video del mismo modo?

Se aplican los mismos principios: configuración central, wrapper de respuesta normalizada, sin campos específicos del proveedor en la lógica de negocio. Las API de imagen y video tienen más diferencias estructurales (async vs. sync, conjuntos de parámetros distintos), por lo que la capa de abstracción requiere más trabajo. Empieza con texto y luego extiende el patrón cuando la estructura esté probada.

P: ¿Qué hay de las diferencias de ventana de contexto entre modelos?

Este es un riesgo real al enrutar. GPT-5.5 tiene una ventana de contexto de 1 M de tokens, los modelos de Claude admiten hasta 200 K y Gemini 3.5 Flash admite hasta 1 M. Si enrutas una tarea de documento largo a un modelo con una ventana de contexto más corta, la entrada se trunca silenciosamente. Agrega una comprobación de longitud de contexto antes de enrutar si tus tareas implican entradas largas, o enruta siempre las tareas de largo contexto a un modelo específico en MODEL_CONFIG en lugar de dejarlas pasar a un valor predeterminado.

Seguir aprendiendo

Conecta este artículo con la siguiente decisión.

Ver todos los temas
Publicado el Jun 7, 2026
Última actualización Sep 3, 2026
15 visitas
Revisado para mayor claridad, atribución de fuentes y terminología API actual.

¿Listo para reducir los costos de desarrollo de IA en un 20%?

Comienza gratis en minutos. Créditos de prueba gratuitos incluidos. No se requiere tarjeta de crédito.

Leer Más