Pular para o conteúdo
Novo

DeepSeek V4.1 Flash grátis por 7 dias: 10M tokens por dia após qualquer recarga. Até 30/11.

Como funciona

O que é uma API de LLM? Requisições, mensagens, tokens e erros

Como é uma requisição de API de chat: modelo, mensagens, tokens, streaming, chaves de API, limites e erros HTTP, com exemplos anotados.

Iniciante13 min de leituraPor NotHoly

Uma API de LLM é um endereço web que recebe texto e devolve a resposta de um modelo. Seu programa envia uma requisição HTTP com o nome do modelo, uma lista de mensagens e um limite de tokens. O serviço devolve o texto gerado, o motivo de ter parado e a contagem de tokens que leu e escreveu. Um app de chat faz o mesmo trabalho por trás de uma caixa de texto. A API entrega as peças cruas, então é o seu código que decide o que entra e o que acontece com a resposta.

O que é uma API de LLM

Num app de chat, o modelo parece um interlocutor. Por baixo, cada mensagem enviada é uma requisição HTTP completa. O app junta as mensagens anteriores, acrescenta a nova, envia tudo a um servidor e mostra o texto que volta. O modelo não guarda memória da sua última mensagem, a menos que o app envie o histórico de novo.

A API pula a interface. Seu código monta a lista de mensagens, define o limite de tokens e trata a resposta. O serviço não se importa se quem chama é um navegador, um script ou uma tarefa agendada. Ele verifica o formato da requisição e a chave.

Três termos importam desde o início:

  • Modelo: o modelo nomeado que gera a resposta. Cada serviço publica os próprios IDs de modelo, e essa lista muda com o tempo.
  • Mensagens: uma lista ordenada de turnos, cada um com um papel, como usuário ou assistente.
  • Tokens: os pedaços de texto que o modelo lê e escreve. Um token costuma ser menor que uma palavra. Limites, contagens de uso e preços são todos medidos em tokens.

Por que isso importa para quem desenvolve

Uma janela de chat serve para uma pessoa fazer perguntas. Código precisa de entradas repetíveis, de saídas que o programa consiga interpretar e de erros que ele consiga capturar. Quando um script chama um modelo, a chave, a cobrança e os modos de falha passam a ser sua responsabilidade. A maior parte dos problemas de uma primeira integração vem de um corpo malformado, de uma chave ausente ou errada, de um limite que ninguém verificou ou de uma resposta tratada como se sempre tivesse dado certo.

Como uma requisição funciona, passo a passo

1. Seu código envia uma requisição HTTP

Quase toda API de LLM que você vai encontrar é uma API web sobre HTTP. A requisição tem método, URL, cabeçalhos e, geralmente, um corpo JSON. A chamada ao modelo quase sempre é um POST. A RFC 9110, a norma atual de semântica do HTTP, define o que essas partes significam e como o servidor deve responder. Os códigos de status e os cabeçalhos que você vai ver depois vêm dela.

2. O corpo informa o modelo e as mensagens

O corpo é um objeto JSON. No mínimo, ele nomeia o modelo e envia a conversa até ali. Este é o formato de mensagens, um dos dois estilos de requisição comparados mais abaixo. Os valores são marcadores, e nada aqui é executado.

json
{
  "model": "<MODEL_ID_FROM_CURRENT_CATALOG>",
  "max_tokens": 300,
  "system": "You explain security concepts in plain English. Keep answers under 120 words.",
  "messages": [
    {
      "role": "user",
      "content": "What is the difference between hashing and encryption?"
    }
  ],
  "stream": false
}

Anotações:

  • model é o ID do modelo que gera a resposta. Copie-o da lista atual do provedor, não de um tutorial antigo.
  • max_tokens limita quantos tokens o modelo pode escrever. Não limita o seu prompt.
  • system guarda as instruções permanentes. Nesse formato, ele fica fora da lista de mensagens.
  • messages guarda a conversa. Aqui há um único turno do usuário.
  • stream está como false, então o servidor espera a resposta completa antes de responder.

3. As mensagens têm papéis

Cada mensagem tem um papel que diz quem está falando:

  • user: texto do usuário final ou do seu programa.
  • assistant: uma resposta que o modelo escreveu antes. Você a envia de volta para continuar a conversa.
  • system ou developer: instruções permanentes. O nome depende do formato.
  • tool: resultado de uma chamada de ferramenta, tema de um artigo futuro.

Para continuar uma conversa, acrescente a última resposta do modelo como mensagem assistant e depois a nova mensagem user. Se você esquecer os turnos anteriores, o modelo responde à pergunta nova sem contexto, e nada avisa que houve erro. Esse é um dos bugs mais comuns.

4. O limite de tokens restringe a resposta

max_tokens (ou o campo equivalente) limita quanto o modelo escreve. Se a resposta chegar a esse limite, o texto para no meio de uma frase e a resposta informa isso. Verifique o motivo de parada, descrito no passo 6, em vez de supor que toda resposta está completa. O limite não reduz o que você paga pelo prompt, porque os tokens de entrada continuam contando para o uso.

5. O streaming muda a entrega, não o sentido

Com "stream": true, o servidor envia a resposta em pedaços conforme a gera, usando eventos enviados pelo servidor (server-sent events). O usuário vê as palavras aparecerem uma após a outra. Seu código lê os eventos e junta os pedaços, e os números de uso e o motivo de parada chegam nos eventos finais. Use streaming quando uma pessoa estiver acompanhando a saída. Para um job em lote que guarda a resposta, uma requisição normal é mais simples de escrever e de testar.

6. A resposta traz o texto, o motivo e as contagens

Uma resposta típica contém:

  • Um ID da requisição ou da mensagem, útil quando você precisa reportar um problema.
  • O conteúdo gerado. No formato de mensagens, é uma lista de blocos. No formato de chat completions, fica dentro de um array choices.
  • Um motivo de parada, que explica por que a geração terminou: fim natural do turno, limite de tokens ou chamada de ferramenta.
  • Contagens de uso para tokens de entrada e de saída.

Leia o motivo de parada antes de confiar no texto. Uma resposta que parou no limite de tokens está incompleta. Uma resposta que termina numa chamada de ferramenta não é a resposta final, e seu código precisa executar a ferramenta e enviar o resultado de volta.

7. Registre o uso, não o conteúdo

O objeto de uso define a sua cobrança. Registre as contagens, o ID do modelo, o horário, o motivo de parada e o código de status. Não registre prompts nem respostas por padrão, porque podem conter dados de clientes, código privado ou segredos. Metadados bastam para achar picos de custo e falhas.

Dois formatos de requisição lado a lado

Dois estilos de requisição são muito usados na documentação pública. Muitos serviços copiam um deles para que o código cliente existente continue funcionando com outra URL base. Conhecer os dois ajuda a ler exemplos de qualquer fonte.

O primeiro é o formato de mensagens, documentado na primeira referência de Fontes. Ele recebe system como campo de primeiro nível, exige max_tokens, devolve blocos de conteúdo com stop_reason e informa input_tokens e output_tokens.

O segundo é o formato de chat completions, documentado na segunda referência de Fontes. Ele coloca as instruções de sistema ou de developer dentro de messages, aceita max_completion_tokens e devolve um array choices, em que cada item traz um finish_reason e uma message. O objeto de uso informa prompt_tokens, completion_tokens e total_tokens.

Tema Formato de mensagens Formato de chat completions
Instruções permanentes Campo system no topo Mensagem system ou developer dentro de messages
Campos obrigatórios model, messages, max_tokens model, messages
Limite de saída max_tokens (obrigatório) max_completion_tokens (opcional)
Onde fica a resposta Lista content de blocos choices[0].message
Por que parou stop_reason choices[0].finish_reason
Contagem de tokens usage.input_tokens, usage.output_tokens usage.prompt_tokens, usage.completion_tokens, usage.total_tokens
Streaming stream: true, eventos enviados pelo servidor stream: true, pedaços em eventos enviados pelo servidor

As diferenças são de forma, não de sentido. Os dois enviam modelo, conversa e limite, e devolvem texto, motivo e contagens. Quando um exemplo usa o outro formato, relacione os campos pelo papel que cada um cumpre nesta tabela, em vez de copiar o JSON.

A mesma pergunta no formato de chat completions:

json
{
  "model": "<MODEL_ID_FROM_CURRENT_CATALOG>",
  "max_completion_tokens": 300,
  "messages": [
    {
      "role": "developer",
      "content": "You explain security concepts in plain English. Keep answers under 120 words."
    },
    {
      "role": "user",
      "content": "What is the difference between hashing and encryption?"
    }
  ],
  "stream": false
}

Uma resposta nesse formato tem a seguinte forma. Os valores são marcadores, não uma resposta observada:

json
{
  "id": "<RESPONSE_ID>",
  "object": "chat.completion",
  "model": "<MODEL_ID_FROM_CURRENT_CATALOG>",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "<GENERATED_TEXT>" },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "total_tokens": 0
  }
}

Seu código lê choices[0].message.content, choices[0].finish_reason e depois o objeto de uso. O formato de mensagens guarda as mesmas informações em content, stop_reason e usage, com outros nomes.

Autenticação com chaves de API

Uma chave de API identifica a conta que paga a chamada e define quais modelos e limites valem. Os dois formatos a enviam de jeitos diferentes. O formato de chat completions usa um cabeçalho Authorization com o esquema Bearer. O formato de mensagens usa um cabeçalho próprio para a chave e também exige um cabeçalho de versão da API, para que o provedor mantenha estável o comportamento das versões antigas.

Os nomes dos cabeçalhos mudam conforme o provedor e a versão, então copie-os da documentação atual. As formas ficam assim:

bash
# Placeholder shape only. Do not run this with a real key in a shared shell history.
curl https://<API_HOST>/<MESSAGES_PATH> \
  -H "x-api-key: <YOUR_API_KEY>" \
  -H "anthropic-version: <VERSION_FROM_CURRENT_DOCS>" \
  -H "content-type: application/json" \
  -d '{"model":"<MODEL_ID>","max_tokens":300,"messages":[{"role":"user","content":"Hello"}]}'
bash
# Placeholder shape only. Same caution as above.
curl https://<API_HOST>/<CHAT_COMPLETIONS_PATH> \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"model":"<MODEL_ID>","messages":[{"role":"user","content":"Hello"}]}'

Anotações:

  • A chave é uma credencial. Quem a tiver pode gastar seu saldo e ler o que a conta pode ler.
  • Authorization: Bearer é o esquema padrão do HTTP para tokens. A RFC 9110 define o cabeçalho Authorization na seção de autenticação.
  • Comandos digitados no shell podem ir parar no histórico. Use uma variável de ambiente, como mostrado abaixo, em vez de uma chave escrita no comando.

Mantendo as chaves fora do código e do Git

É aqui que começa a maior parte dos custos e vazamentos evitáveis. As regras abaixo seguem o Secrets Management Cheat Sheet da OWASP.

Mantenha as chaves fora do código-fonte. Uma chave escrita num arquivo Python existe em toda cópia desse arquivo: no seu notebook, nas máquinas da equipe, no sistema de CI e em cada commit que o tocou. A OWASP aponta segredos fixos no código e em arquivos de configuração como prática arriscada.

Nunca faça commit de uma chave. O Git guarda o histórico. Apagar a chave num commit posterior não a remove da versão antiga. A OWASP observa que tirar um segredo do histórico pode exigir reescrevê-lo, o que tem efeitos colaterais. Trate uma chave que chegou a um repositório compartilhado como exposta. Revogue, crie outra e verifique na conta se há uso que você não fez.

Mantenha as chaves fora de logs, capturas de tela e código de navegador. Qualquer coisa numa linha de log, numa mensagem de erro ou num script de página pode ser lida por outras pessoas. Chamadas que precisam de chave devem rodar num servidor que você controla.

Não embuta chaves em imagens de container. A OWASP alerta contra segredos em instruções ENV ou ARG do Docker, porque o valor acaba nos metadados da imagem e no histórico do build.

Prefira um cofre de segredos. A OWASP diz que variáveis de ambiente costumam ser acessíveis a todos os processos e podem aparecer em logs ou dumps, então recomenda usá-las só quando não há opção melhor. Num notebook, funciona um gerenciador de senhas ou o cofre de credenciais do sistema operacional. Em servidores, use um serviço gerenciado de segredos ou arquivos montados com permissões restritas.

Limite e troque. Dê a cada chave só o acesso de que ela precisa e substitua as chaves periodicamente. A OWASP diz que segredos devem ter o mínimo de privilégios necessário e recomenda rotação automatizada quando possível.

Um padrão local seguro mantém o valor no ambiente e o lê pelo nome:

python
# Read the key from the environment. Fail early if it is missing.
import os

api_key = os.environ.get("LLM_API_KEY")
if not api_key:
    raise SystemExit("LLM_API_KEY is not set. Set it in your shell or secret store.")

Adicione os arquivos de configuração local ao .gitignore antes de criá-los. Faça commit de um modelo, como .env.example, que liste os nomes das variáveis com valores vazios, e nunca do arquivo real.

Limites de taxa e cotas

Os provedores limitam quantas requisições e quantos tokens uma conta pode usar numa janela de tempo. Esses limites costumam ser chamados de limites de taxa (rate limits), e algumas contas também têm teto de gasto. Os números variam por conta, por modelo e ao longo do tempo, então consulte o console do provedor e a documentação atual. Este artigo não informa nenhum número.

Ao bater num limite, o servidor normalmente responde com 429 Too Many Requests. A RFC 6585 define esse código, que estende o conjunto básico do HTTP. A resposta pode trazer Retry-After, que a RFC 9110 define na seção de campos de resposta e que indica quanto esperar.

Seu código deve ler o status antes do corpo, esperar antes de tentar de novo (usando Retry-After quando existir, ou um atraso crescente com alguma variação aleatória) e parar após algumas tentativas, em vez de tentar para sempre. Conte os tokens antes de enviar prompts muito grandes, porque uma requisição excessiva falha mesmo com saldo.

Erros HTTP comuns

O primeiro dígito de um código de status indica a classe: problema do lado do cliente, falha do servidor ou outra coisa. A RFC 9110 define as classes e os códigos individuais. Estes são os erros que integrações novas encontram com mais frequência.

Status Nome O que costuma significar O que verificar
400 Bad Request O corpo está malformado ou um campo é inválido Sintaxe do JSON, nomes de campos, campos obrigatórios, faixas de valores
401 Unauthorized A chave está ausente, malformada ou revogada Nome do cabeçalho, prefixo Bearer, valor da chave no ambiente
403 Forbidden A chave é válida, mas a conta pode não usar esse recurso Acesso ao modelo, permissões da conta, situação do faturamento
404 Not Found O caminho está errado, ou o ID do modelo não existe para a sua conta URL base, caminho, ID exato do modelo no catálogo atual
429 Too Many Requests Limite de taxa ou cota atingidos Retry-After, página de uso, volume de requisições
500 Internal Server Error O servidor falhou do lado dele Tentar de novo com espera crescente e depois consultar a página de status do provedor
503 Service Unavailable O serviço está sobrecarregado ou em manutenção Tentar de novo com espera crescente e um número máximo de tentativas

Um erro 4xx significa que a requisição precisa mudar, então reenviar a mesma não ajuda. Um 5xx ou 429 pode funcionar depois, por isso a espera crescente faz sentido ali. Registre o código de status, o ID do modelo e o ID da requisição. Nunca registre a chave nem o prompt.

Erros comuns

Confiar na primeira resposta. Uma resposta pode ser cortada pelo limite de tokens ou parar por outro motivo. Verifique o motivo de parada sempre.

Esquecer os turnos anteriores. Uma API sem estado não sabe nada da requisição anterior, a não ser que você a envie de novo. Monte a lista de mensagens a partir do seu próprio armazenamento.

Fixar o ID do modelo no código. IDs de modelo são descontinuados e renomeados. Mantenha o ID na configuração, para que a troca seja uma linha editada.

Misturar os dois formatos. Enviar um corpo de chat completions a um endpoint de mensagens, ou o contrário, gera erros 400. Confirme qual formato o endpoint documenta antes de copiar um exemplo.

Como seguir em frente

Leia a referência de um provedor do início ao fim. As seções de requisição, resposta e erros são curtas e respondem à maioria das dúvidas da primeira semana. Depois, leia sobre streaming e janelas de contexto, que decidem quanta conversa você consegue enviar antes de uma requisição falhar.

O gateway da NotHoly documenta os próprios campos e códigos de erro. Compare-os com as duas formas acima, e use a documentação dele como referência para o comportamento.

Se você quer que o modelo chame o seu código, o próximo passo é o uso de ferramentas: um turno do assistente cujo motivo de parada pede ao seu programa que execute algo e devolva o resultado. O próximo artigo cobre esse ciclo.

Fontes

Fontes

  1. Messages API reference
  2. Chat Completions API reference
  3. RFC 9110: HTTP Semantics
  4. RFC 6585: Additional HTTP Status Codes
  5. OWASP Secrets Management Cheat Sheet

Mais em Modelos de LLM e APIs

Modelos de LLM e APIs

O que é um modelo de linguagem grande (LLM)?

Como modelos de linguagem grandes transformam texto em tokens, preveem o próximo e por que soam confiantes quando erram.

Iniciante13 min de leitura