Claude Opus 5 is now live on CometAPI โ†’

Come sviluppare app di IA non vincolate a un unico fornitore

CometAPI
AnnaJun 7, 2026
Come sviluppare app di IA non vincolate a un unico fornitore

Il vendor lock-in nelle app di IA di solito non avviene tutto in una volta. Si insinua โ€” un import openai diretto qui, un nome modello hardcoded lร , un campo di risposta che analizzi senza verificare se altri provider restituiscono la stessa cosa. Sei mesi dopo, cambiare provider significa riscrivere metร  del tuo backend.

Le quattro modalitร  in cui il lock-in si verifica

La maggior parte degli sviluppatori pensa che il lock-in significhi "Sto usando l'SDK di OpenAI". Questo รจ il tipo meno pericoloso. Le vere trappole sono piรน sottili:

Tipo di lock-inCome accadeConseguenza
Lock-in dell'SDKfrom openai import OpenAI ovunqueCambiare SDK significa toccare ogni file
Lock-in del nome modellomodel="gpt-4o" hardcoded nella logica di businessOgni cambio di modello รจ un cambio di codice
Lock-in dei parametriUso di logprobs, n>1 o reasoning_effortQuesti non esistono su Claude o Gemini
Lock-in del formato di rispostaAnalisi di campi di risposta specifici del providerProvider diversi restituiscono strutture diverse

L'obiettivo non รจ eliminarli tutti โ€” alcuni sono compromessi accettabili. L'obiettivo รจ sapere quali stai accettando.

Usa un endpoint compatibile con OpenAI come livello di astrazione

Il modo piรน pulito per evitare il lock-in dell'SDK รจ usare un unico endpoint compatibile con OpenAI che instradi verso piรน provider. Mantieni l'SDK di OpenAI, ma il backend puรฒ essere qualsiasi provider.

CometAPI lo fa โ€” un endpoint, una chiave, oltre 500 modelli tra OpenAI, Anthropic, Google, DeepSeek, xAI e altri:

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("La variabile d'ambiente AI_API_KEY non รจ impostata")โ€‹client = OpenAI( ย  ย base_url=os.environ.get("AI_BASE_URL", "https://api.cometapi.com/v1"), ย  ย api_key=api_key,)

Passare da GPT a Claude a Gemini richiede una sola riga di modifica:

# Primaresponse = client.chat.completions.create(model="gpt-5.4", messages=[...])โ€‹# Dopo โ€” stesso codice, modello diversoresponse = client.chat.completions.create(model="claude-sonnet-4-6", messages=[...])

Nota: i nomi dei modelli come gpt-5.4 e claude-sonnet-4-6 sono identificatori della piattaforma CometAPI โ€” funzionano solo tramite https://api.cometapi.com/v1, non direttamente tramite le API di OpenAI o Anthropic. Vedi la lista completa dei modelli per il catalogo completo e i prezzi.

Tieni i nomi dei modelli fuori dalla logica di business

I nomi dei modelli sparsi nel codice sono la forma di lock-in piรน comune. La soluzione รจ una configurazione centrale che legge dalle variabili d'ambiente:

# config.py โ€” un solo posto per cambiare l'assegnazione dei modelliimport 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"),}โ€‹# Valida all'avvio โ€” fallisci subito invece di ottenere errori API misteriosifor task, model in MODEL_CONFIG.items(): ย  ย if not model: ย  ย  ย  ย raise ValueError(f"La configurazione del modello per '{task}' non รจ impostata")

La tua logica applicativa non fa mai riferimento direttamente a un nome modello:

from config import MODEL_CONFIGโ€‹def summarize(text: str) -> str: ย  ย response = client.chat.completions.create( ย  ย  ย  ย model=MODEL_CONFIG["summarize"], ย  ย  ย  ย messages=[{"role": "user", "content": f"Riassumi: {text}"}], ย  ย  ย  ย max_tokens=300 ย # sposta in config in produzione ย   ) ย  ย return response.choices[0].message.content

Per cambiare il modello di riassunto in tutta l'app, modifica una variabile d'ambiente. Niente grep, niente trova-e-sostituisci.

Incapsula la risposta cosรฌ che il tuo codice non dipenda da campi specifici del provider

Provider diversi restituiscono forme di risposta leggermente diverse. Se analizzi le risposte API raw in tutto il codice, sei legato al formato di quel provider.

Incapsulale in una dataclass normalizzata:

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: ย  ย """ ย   Punto di ingresso unico per tutte le chiamate LLM. ย   Restituisce un AIResponse normalizzato indipendentemente dal modello utilizzato. ย   Genera eccezioni su 4xx (errori client). Logga e rilancia su 5xx/errori di rete. ย   """ ย  ย model = MODEL_CONFIG.get(task, "gpt-5.4-mini") ย  ย if not model: ย  ย  ย  ย raise ValueError(f"Nessun modello configurato per il task '{task}'")โ€‹ ย  ย try: ย  ย  ย  ย response: ChatCompletion = client.chat.completions.create( ย  ย  ย  ย  ย  ย model=model, ย  ย  ย  ย  ย  ย messages=messages, ย  ย  ย  ย  ย  ย **kwargs ย  ย  ย   ) ย  ย except APIStatusError as e: ย  ย  ย  ย logging.error(f"Errore API per task={task} model={model}: {e.status_code} {e.message}") ย  ย  ย  ย raise ย  ย except (APIConnectionError, APITimeoutError) as e: ย  ย  ย  ย logging.error(f"Errore di rete per task={task} model={model}: {e}") ย  ย  ย  ย raiseโ€‹ ย  ย # content รจ None quando il modello attiva una chiamata a tool invece di restituire testo ย  ย content = response.choices[0].message.content or ""โ€‹ ย  ย # usage รจ None in modalitร  streaming โ€” imposta a 0 se non disponibile ย  ย 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, ย   )

Ora la tua logica applicativa lavora con oggetti AIResponse, non con risposte API raw. Se un provider cambia il proprio formato di risposta, lo correggi in un unico punto.

Aggiungi il supporto streaming al wrapper

Per le interfacce chat, vorrai lo streaming. Il wrapper lo gestisce come percorso separato:

from typing import Iteratorโ€‹def stream_model(task: str, messages: list, **kwargs) -> Iterator[str]: ย  ย """ ย   Emetti in streaming i token dal modello instradato. ย   Nota: lo streaming non restituisce i dati di utilizzo. ย   Il fallback non รจ supportato in modalitร  streaming โ€” hai giร  ย   iniziato a emettere token prima di sapere se la richiesta completa ha successo. ย   """ ย  ย model = MODEL_CONFIG.get(task, "gpt-5.4-mini") ย  ย if not model: ย  ย  ย  ย raise ValueError(f"Nessun modello configurato per il 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โ€‹# Utilizzoper token in stream_model("chat", [{"role": "user", "content": "Ciao"}]): ย  ย print(token, end="", flush=True)

Sapere quali parametri creano lock-in

Alcuni parametri esistono solo su provider specifici. Usarli va bene โ€” basta sapere che stai facendo una scelta deliberata:

ParametroFunziona suRischio di lock-in
logprobsSolo GPTAlto โ€” nessun equivalente su Claude o Gemini
n > 1GPT, Gemini (non Claude)Medio โ€” Claude richiede un loop
reasoning_effortSolo GPT serie oAlto โ€” nessun equivalente altrove
temperature > 1.0GPT, Gemini (non Claude)Basso โ€” Claude รจ limitato a 1.0
toolsTutti i principali providerNessuno โ€” sicuro da usare
response_formatTutti i principali providerBasso โ€” differenze di schema minori

Se stai usando logprobs per lo scoring di confidenza, sei legato a GPT per quella funzione. รˆ un compromesso ragionevole โ€” basta documentarlo, cosรฌ il prossimo sviluppatore saprร  il perchรฉ.

Rendi configurabile l'endpoint del provider

Hardcodare base_url="https://api.cometapi.com/v1" รจ comunque una forma di lock-in. Rendilo una variabile d'ambiente:

# .env โ€” usando CometAPIAI_BASE_URL=https://api.cometapi.com/v1AI_API_KEY=your_cometapi_keyโ€‹# Per passare a OpenAI direttamente, cambia due righe:# AI_BASE_URL=https://api.openai.com/v1# AI_API_KEY=your_openai_key

L'inizializzazione del client dallo Step 1 legge giร  da queste variabili. Passare tra CometAPI e una connessione diretta a un provider diventa cosรฌ una modifica di configurazione, non di codice.

Versione Node.js

import OpenAI from 'openai';โ€‹const apiKey = process.env.AI_API_KEY;if (!apiKey) throw new Error('AI_API_KEY non รจ impostata');โ€‹const client = new OpenAI({ ย baseURL: process.env.AI_BASE_URL ?? 'https://api.cometapi.com/v1', ย apiKey,});โ€‹// Gli ID dei modelli sono identificatori della piattaforma CometAPI โ€” vedi 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',};โ€‹// Valida all'avvioper (const [task, model] of Object.entries(MODEL_CONFIG)) { ย if (!model) throw new Error(`La configurazione del modello per '${task}' non รจ impostata`);}โ€‹/** * Punto di ingresso unico per tutte le chiamate LLM. * Restituisce una risposta normalizzata. Genera eccezioni su 4xx, logga e rilancia su 5xx/di rete. */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) { ย  ย // Non ingoiare gli errori โ€” logga e rilancia ย  ย console.error(`Errore API task=${task} model=${model}:`, err.message); ย  ย throw err;  }โ€‹ ย // content รจ null quando il modello attiva una tool call ย const content = response.choices[0].message.content ?? '';โ€‹ ย // usage potrebbe essere assente in alcune configurazioni ย 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 };}โ€‹/** * Emetti in streaming i token dal modello instradato. * I dati di utilizzo non sono disponibili in modalitร  streaming. */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;  }}โ€‹// Uso โ€” sincrono/bloccanteconst result = await callModel('classify', [  { role: 'user', content: 'Positivo o negativo? "Mi รจ piaciuto!"' }]);console.log(result.content);โ€‹// Uso โ€” streamingfor await (const token of streamModel('chat', [  { role: 'user', content: 'Ciao' }])) { ย process.stdout.write(token);}

Quale lock-in รจ accettabile

  • Usare l' OpenAI SDK โ€” รˆ lo standard de facto. La maggior parte dei provider lo supporta. Lock-in a basso rischio.
  • Funzionalitร  specifiche del provider di cui hai davvero bisogno โ€” Se ti servono logprobs, usali. Isola quel codice cosรฌ che sia facile da trovare e sostituire in seguito.
  • Modelli fine-tuned โ€” Un modello fine-tuned รจ intrinsecamente legato a un provider. รˆ previsto.

Il lock-in da evitare รจ quello accidentale โ€” nomi dei modelli nella logica di business, parsing delle risposte raw sparso tra i file, chiavi API hardcoded nel sorgente.

Cosa fare dopo

Ora hai un livello di astrazione che mantiene i dettagli del provider fuori dalla tua logica di business. L'ultimo articolo di questa serie tratta cosa succede quando qualcosa va storto: come fare il debug delle generazioni non riuscite, interpretare i codici di errore e costruire un error handling che ti dica davvero cosa si รจ rotto.

Prossimo: Come eseguire il debug delle generazioni API IA non riuscite

FAQ

D: Qual รจ la differenza tra lock-in dell'SDK e lock-in del modello?

Il lock-in dell'SDK significa che il tuo codice importa una libreria specifica e dovrebbe cambiare se passassi a un altro SDK. Il lock-in del modello significa che i nomi dei modelli sono sparsi nella logica di business. Il lock-in dell'SDK รจ meno pericoloso perchรฉ la maggior parte dei provider ora supporta il formato dell'SDK di OpenAI. Il lock-in del modello รจ piรน subdolo perchรฉ รจ piรน difficile da trovare e correggere.

D: Se uso CometAPI, sto solo scambiando il lock-in di OpenAI con quello di CometAPI?

Parzialmente. Stai scambiando il lock-in del provider diretto con uno strato proxy. Il vantaggio: una chiave, un endpoint, cambio di modello semplice. Il rischio: se CometAPI ha un outage, tutti i tuoi provider vanno giรน insieme. La mitigazione รจ giร  nel codice sopra โ€” AI_BASE_URL รจ una variabile d'ambiente. Se devi bypassare CometAPI e chiamare un provider direttamente, รจ una modifica di configurazione, non di codice.

D: Posso usare il pensiero esteso di Claude o il reasoning_effort di OpenAI con questo pattern?

Sรฌ, passali come **kwargs a call_model. Sappi solo che se instradi quel task a un modello diverso, quei parametri verranno ignorati o causeranno un errore. Documenta quali task usano funzionalitร  specifiche del provider, cosรฌ il prossimo sviluppatore saprร  il perchรฉ.

D: Come gestisco il limite di temperature a 1.0 quando instrado tra Claude e GPT**?****

Mantieni temperature a 1.0 o inferiore per rimanere nell'intervallo sicuro per entrambi. Se ti serve una temperatura piรน alta per task creativi specificamente su GPT, instrada quei task a GPT esplicitamente in MODEL_CONFIG invece di lasciarli passare dal router generico.

D: Devo astrarre allo stesso modo le API di generazione immagini e video?

Gli stessi principi si applicano โ€” configurazione centrale, wrapper di risposta normalizzato, nessun campo specifico del provider nella logica di business. Le API di immagini e video hanno differenze strutturali maggiori (async vs sync, set di parametri diversi), quindi lo strato di astrazione richiede piรน lavoro. Parti dal testo, poi estendi il pattern una volta comprovata la struttura.

D: E le differenze nella context window tra modelli?

รˆ un rischio reale quando instradi. GPT-5.5 ha una context window da 1M token, i modelli Claude supportano fino a 200K e Gemini 3.5 Flash supporta fino a 1M. Se instradi un task con documento lungo a un modello con finestra di contesto piรน corta, l'input viene troncato in silenzio. Aggiungi un controllo sulla lunghezza del contesto prima dell'instradamento se i tuoi task coinvolgono input lunghi โ€” oppure instrada sempre i task con contesto lungo a un modello specifico in MODEL_CONFIG invece di lasciarli passare a un default.

Pronto a ridurre i costi di sviluppo AI del 20%?

Inizia gratuitamente in pochi minuti. Crediti di prova gratuiti inclusi. Nessuna carta di credito richiesta.

Leggi di piรน