A maioria das pessoas não começa um projeto pensando em portabilidade de provedores. Elas pegam uma chave do fornecedor que for mais fácil de se cadastrar, escrevem a integração e publicam. Alguns meses depois, leem um benchmark, ou abrem uma fatura, ou batem em um limite de taxa, e querem experimentar outra coisa. É aí que a dor aparece, porque elas embutiram suposições sobre o primeiro provedor na camada HTTP.
A solução não é alguma biblioteca de abstração. É um formato de transmissão. A API de chat completions da OpenAI tornou-se silenciosamente o protocolo padrão para conversar com LLMs, e uma vez que você escreve contra ela, alternar entre provedores é basicamente uma questão de mudar duas strings.
A API que todo mundo copiou
A OpenAI definiu um contrato simples: você faz POST de JSON para /v1/chat/completions, e recebe de volta uma string choices[0].message.content. A requisição se parece com isto:
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Explain DNS in one paragraph"}]
}'Quase todo modelo importante agora fala esse dialeto. DeepSeek, Qwen, ERNIE, Doubao e os outros todos expõem um endpoint que aceita o mesmo corpo e retorna a mesma estrutura. Isso significa que seu código cliente não se importa com quem está do outro lado. Você pode trocar a string model e o endpoint e nada mais muda.
Aqui está a mesma chamada apontada para um agregador que carrega vários desses modelos sob uma única URL base:
curl https://api.token8341.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKENWORKS_API_KEY" \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "Explain DNS in one paragraph"}]
}'Um cliente, muitos modelos
Em Python você normalmente instancia o SDK da OpenAI uma vez e passa um nome de modelo por requisição. Se o seu provedor suporta a API compatível, você define base_url uma vez e mantém todo o resto idêntico:
from openai import OpenAI
client = OpenAI(
base_url="https://api.token8341.com/v1",
api_key="sk-your-tokenworks-key",
)
def ask(model: str, prompt: str) -> str:
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
# Same function, three different model families.
print(ask("gpt-4o", "Summarize this log for me."))
print(ask("deepseek-chat", "Summarize this log for me."))
print(ask("qwen-max", "Summarize this log for me."))A função não muda. A string do modelo muda. Esse é todo o truque. Streaming funciona da mesma forma:
stream = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "Write a haiku about databases"}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content or ""
print(delta, end="")Function calling, modo JSON e embeddings todos trafegam pela mesma superfície compatível, então você não fica limitado a chat simples quando alterna.
O que a troca não te garante
Quero ser claro sobre a parte que não muda de graça. O transporte é portável; os modelos não são.
Modelos diferentes respondem a prompts de maneiras diferentes. Um system prompt ajustado para GPT-4o pode ter desempenho inferior em um modelo de raciocínio que quer uma estrutura diferente. As janelas de contexto diferem, às vezes bastante: um modelo pode aceitar 128k tokens enquanto outro aceita 32k. Os tokenizers também diferem, então o mesmo documento custa um número diferente de tokens em cada modelo. O comprimento máximo de saída é outro ajuste que é por modelo, não por protocolo.
Então "alternar sem mudar código" é na verdade "alternar sem reescrever seu cliente HTTP". Você ainda deve a si mesmo uma rodada de avaliação antes de apontar tráfego de produção para um novo modelo. A boa notícia é que a API compatível torna essa avaliação barata de executar, porque o harness é apenas um loop sobre nomes de modelos.
Um padrão prático
O que eu realmente faço em projetos paralelos é manter uma pequena tabela de configuração, não mudanças de código:
MODELS = {
"fast": "deepseek-chat",
"smart": "qwen-max",
"frontier": "gpt-4o",
}
def run(task, prompt):
return ask(MODELS[task], prompt)Quer experimentar um modelo mais barato para a via fast? Mude uma linha. Quer fazer fallback para um segundo provedor quando o primeiro estiver com limite de taxa atingido? Capture a exceção e tente novamente com a próxima string de modelo. Nada disso toca a construção da requisição.
Esse padrão de fallback é onde um endpoint multi-modelo se justifica. Com uma URL base e uma chave, você pode rotear, tentar novamente e fazer testes A/B entre provedores, tudo através das mesmas duas linhas de configuração de cliente que você escreveu no primeiro dia.
Peculiaridades de resposta a observar
O transporte é compatível, mas algumas diferenças no nível da resposta ainda vazam. Modelos de raciocínio às vezes retornam um campo extra como reasoning_content junto à mensagem normal. Código que lê choices[0].message.content continua funcionando independentemente, mas você pode querer exibir esse texto de raciocínio em uma visualização de depuração. As mensagens de erro também variam: um provedor retorna uma string útil insufficient_quota, outro retorna um 429 puro sem corpo. Se você está construindo retries, trate qualquer 429 ou 5xx como "recuar e tentar novamente" em vez de analisar texto específico do fornecedor.
Nada disso é motivo para ficar preso a um único provedor. É motivo para manter seu tratamento de erros genérico e sua escolha de modelo uma variável.
Esta é a posição da SiCore TokenWorks: um endpoint compatível com OpenAI em https://api.token8341.com/v1 onde a string do modelo é a única coisa que muda quando você alterna entre GPT-4o, Claude, Gemini, DeepSeek, Qwen, ERNIE, Doubao, Spark e Pangu.