A troca parece trivial, porque o agente já fala o formato da OpenAI: basta apontar a base_url para o servidor próprio e trocar a chave.
Três coisas quebram antes de o agente ficar de pé.
A primeira é o formato de saída estruturada, que não existe em todo backend. A segunda é a convenção de coordenadas, que passa na validação estando errada.
A terceira é o custo do modo de pensamento, que depende da carga e é fácil de medir errado.
Todas as medições abaixo saem de execução real num gateway self-hosted com 45 modelos atrás de uma API compatível com a da OpenAI, e o roteiro para reproduzir está no fim do texto.
O cenário
O agente interpreta imagens de tela e devolve coordenadas para o passo seguinte da automação, que é um clique.
É o desenho de qualquer automação de interface que não quer depender de seletor de CSS, e ele depende de duas coisas ao mesmo tempo: uma resposta em formato previsível e uma unidade de medida combinada.
O consumidor recebe pontos em coordenadas de pixel absolutas e clica direto, sem conversão. Guarde esse detalhe, porque é ele que transforma a segunda armadilha numa falha silenciosa.
Armadilha 1: c2 não é universal
A primeira suposição é que o parâmetro de saída estruturada está disponível, afinal a API se diz compatível com a da OpenAI.
Não está. Ao pedir saída estrita contra o backend da DeepSeek, a resposta foi um 400 com a mensagem mais direta possível: This response_format type is unavailable now.
O que resolve é o caminho alternativo para json_object, em que o formato é descrito no prompt em vez de imposto pelo servidor.
A diferença entre os dois não é cosmética. Com json_schema quem garante a forma é o servidor.
Com json_object não há garantia nenhuma, e a validação passa a ser responsabilidade do cliente.
O erro de engenharia aqui é tratar esse caminho alternativo como exceção. Num gateway que mistura provedores ele é a rota normal para uma parte dos modelos, e a decisão precisa sair de detecção em tempo de execução, não de uma lista fixa mantida no código.
Armadilha 2: o schema valida a forma, não a escala
Esta foi a mais cara das três, justamente porque não gera erro.
O contrato de saída pedia dois inteiros, x e y. A resposta chegou válida, com dois inteiros, e o agente clicou no lugar errado.
O modelo devolveu (2,2). Numa grade de três colunas por três linhas, isso é um índice de célula.
O contrato, porém, era pixel absoluto, e o código que consome faz o clique sem conversão. O valor atravessou a validação porque 2 é um inteiro perfeitamente válido.
O schema não tinha limite inferior nem superior nos campos, então nada impedia que a resposta mais natural para o modelo fosse a inútil para o programa.
Para separar "modelo ruim" de "instrução ambígua", medimos as duas hipóteses com o mesmo schema, a mesma imagem e oito repetições por caso.
Com uma instrução que não diz a unidade, o GPT-4o-mini devolveu índice de célula em 8 de 8 respostas. Com a convenção fixada no texto, ele acertou o pixel em 8 de 8.
A DeepSeek se comportou parecido: 2 índices e 6 pixels errados no primeiro caso, e 8 de 8 corretos no segundo.
Nenhuma dessas respostas erradas foi barrada pelo schema. Todas eram válidas.
Há duas defesas, e as duas são baratas. A primeira é fixar a convenção na instrução, dizendo a faixa esperada e proibindo explicitamente o índice de célula.
A segunda é fechar o schema, com limites no tipo, para que a resposta fora da escala seja rejeitada na hora:
from pydantic import BaseModel, Field
class Posicao(BaseModel):
x: int = Field(ge=0, le=600, description="pixel absoluto na horizontal")
y: int = Field(ge=0, le=600, description="pixel absoluto na vertical")
Nenhuma das duas é redundante. A instrução reduz a chance de o modelo escolher a unidade errada, e o limite apara o que escapar dela.
Armadilha 3: o pensamento custa caro, e o gateway esconde o erro
A terceira armadilha não aparece como falha, aparece como lentidão.
Medimos a mesma tarefa com o modo de pensamento ligado e desligado, em duas cargas diferentes. Na carga leve, com uma imagem e uma instrução curta, o deepseek-flash ficou em 1,34s de mediana pensando contra 1,03s sem pensar, uma diferença de 1,3 vez.
Na carga pesada, com duas imagens e uma descrição longa do papel do agente, a diferença explode: 6,12s contra 1,29s, ou 4,7 vezes. O deepseek-v4-flash-vision-exp foi na mesma direção, com 6,36s contra 1,21s.
O número de tokens de raciocínio explica o salto. Na carga pesada o modelo gastou até 1.200 tokens pensando, e desligar o interruptor zerou a conta.
Vale dizer que o modo de pensamento não é defeito, e sim uma escolha. Para tarefa de raciocínio em várias etapas ele ajuda.
Para ler uma imagem e devolver uma coordenada, ele queima tempo e token sem melhorar a resposta, e foi desligá-lo que devolveu o agente a uma latência utilizável.
O detalhe que atrapalha o diagnóstico vem da configuração do gateway, que está com drop_params ligado. Esse ajuste faz um parâmetro não suportado ser descartado em silêncio, em vez de devolver erro.
Ou seja: se o interruptor não pegar, você não descobre pela resposta.
A prova precisa vir do campo de uso. Com o pensamento desligado, reasoning_tokens vem nulo.
É esse sinal, e não a ausência de erro, que confirma que a configuração chegou ao modelo.
Como medir antes de escolher
Quatro verificações resolvem a maior parte do risco, e cabem num script que roda em minutos.
A primeira é descobrir o que o backend aceita de verdade, tentando o formato estrito e caindo para o alternativo quando vier 400.
A segunda é fixar e testar a unidade. Monte uma imagem sintética com o alvo em posição conhecida e classifique a resposta em três caixas: pixel correto, pixel errado e índice de célula.
A terceira caixa é a que interessa, porque é a que passa no schema.
A terceira é medir latência com repetição, porque execução única não permite afirmar diferença. Vale registrar a dispersão: na nossa carga leve, o GPT-4o-mini teve mediana de 1,30s e um pico isolado de 61,43s na primeira chamada.
A quarta verificação é a que quase ninguém faz, e ela invalida as outras três se for ignorada. O gateway estava com cache ligado, e um prompt idêntico repetido voltava em 0,03s.
Sem perceber, a primeira rodada de medição registrou mediana de zero segundo, que é o cache respondendo, não o modelo.
A correção é trivial: um identificador único em cada requisição, que força cache miss. Confirmamos o mecanismo medindo os dois casos na mesma sessão, com o mesmo prompt voltando em 0,03s e a versão com identificador novo custando o tempo real de computação.
Nada disso substitui uma última conferência: comparar o arquivo de configuração com o que o servidor informa. No nosso caso, o arquivo não declarava suporte a imagem nos modelos self-hosted, e o endpoint de informações do modelo dizia o contrário.
A configuração estava desatualizada, e o arquivo mentia para quem confiasse nele.
O que isso muda na prática
Nenhuma das três armadilhas é específica deste projeto, e é isso que as torna úteis.
A primeira aparece em qualquer lugar que troque de provedor e dependa de saída estruturada. A segunda aparece em todo contrato que usa número sem faixa, que é a maioria dos que representam posição, medida ou coordenada.
A terceira aparece onde houver modo de pensamento e um gateway permissivo demais para reclamar.
O padrão comum é o mesmo. Compatibilidade de API resolve a camada de transporte e não diz nada sobre semântica, e é na semântica que o agente erra sem fazer barulho.
Um teste que compare o formato aceito, a unidade devolvida e a latência por configuração custa pouco e pega as três.
Referências
- Modo de pensamento da DeepSeek e o parâmetro
thinking: https://api-docs.deepseek.com/guides/thinking_mode - Saída estruturada na API da OpenAI: https://platform.openai.com/docs/guides/structured-outputs
- Restrições de campo no Pydantic: https://docs.pydantic.dev/latest/concepts/fields/
drop_paramsno proxy LiteLLM: https://docs.litellm.ai/docs/proxy/configs




