Skip to main content
Ler um documento é fácil. Obter os mesmos campos de todo documento, em um formato em que seu código possa confiar, é o trabalho de verdade. Existem dois caminhos de um arquivo até um registro. Você pode extrair o texto e entregá-lo a um modelo, ou pode mostrar a página a um modelo que enxerga. O caminho que você precisa depende de como o arquivo foi feito, e um PDF não te diz qual dos dois é só de olhar. Este tutorial constrói ambos e deixa a API decidir entre eles:
Ao longo do caminho, vamos:
  1. Tirar o texto de um PDF com /augment/text-parser
  2. Descrever o registro que queremos como um JSON schema
  3. Extraí-lo, com o schema imposto em vez de solicitado
  4. Lidar com o arquivo que não tem texto algum
  5. Comparar o que os dois caminhos produzem a partir da mesma página

Configuração

Você precisa do Python 3.9 ou superior, do pacote requests e de uma chave da API Venice. Veja Gerando uma Chave de API caso ainda não tenha uma.
Vamos usar um paper público como documento de amostra, para você acompanhar com o mesmo arquivo:
Crie extract.py:
Repare que AUTH e JSON_HEADERS são separados. O parser aceita um upload multipart, e definir Content-Type você mesmo numa requisição multipart impede que o requests adicione o boundary, o que falha de um jeito chato de diagnosticar.

1. Extraia o texto

/augment/text-parser aceita um arquivo PDF, DOCX, XLSX ou texto puro de até 25 MB e retorna o texto com uma contagem de tokens. Os documentos são processados em memória e o conteúdo não é retido.
A contagem em tokens é a parte útil dessa resposta. Ela te diz o que o documento vai custar na próxima requisição antes de você fazê-la, o que importa porque um PDF longo facilmente ultrapassa o que você pretendia gastar.

2. Descreva o registro que você quer

Pedir JSON a um modelo te dá um JSON com um formato aproximadamente parecido com o que você pediu. Passar um schema te dá um JSON que casa, porque o schema restringe a geração em vez de aconselhá-la.
Vale definir additionalProperties: False em todos os níveis. Sem isso, um modelo que ache algo interessante pode adicionar uma chave que você nunca planejou, e o código que lê o resultado não vai esperá-la.

3. Extraia

Uma chamada, com response_format carregando o schema e strict ligado:
Toda extração deste tutorial passa por um pequeno leitor, porque as duas formas de essa chamada falhar chegam como HTTP 200:
Olhe o último autor. O paper não dá afiliação para Illia Polosukhin, e o schema diz que affiliation é obrigatório, então o modelo retornou uma string vazia em vez de omitir. É o schema fazendo exatamente o que você mandou.
Uma string vazia e um valor ausente são fatos diferentes, e required os funde. Se você precisa distinguir “o documento não diz” de “o documento não diz nada aqui”, tipe o campo como {"type": ["string", "null"]} e peça null no system prompt. O modo strict aceita a união, e você recebe null em vez de "".

Desligue o raciocínio

disable_thinking é a linha dessa requisição sobre a qual vale a pena discutir, então aí vai o argumento. O modelo de texto padrão raciocina antes de responder, e o raciocínio é sacado do mesmo orçamento de completion que o JSON. Execute a mesma extração quatro vezes e veja o que o modelo gasta: Aumentar o orçamento não resolve o primeiro problema, apenas eleva o teto que o modelo pode encostar. A execução que gastou 4003 tokens voltou com finish_reason de length e uma string vazia. Desligar o raciocínio deixou essa extração cinco vezes mais barata e, mais útil ainda, fez com que fosse igual toda vez. O schema já está fazendo o trabalho que o raciocínio faria, que é decidir a forma que a resposta assume.
Quando o orçamento realmente acaba, o modelo normalmente já escreveu algum JSON, então você recebe um objeto truncado em vez de um erro. O json.loads então falha em uma string não terminada em algum lugar no meio, o que parece um bug de parsing e não é. read_record verifica primeiro o finish_reason para que a mensagem diga o que de fato aconteceu.

4. Quando não há texto para pegar

Um PDF produzido por um scanner contém imagens de páginas, não texto. Nada no nome do arquivo diz isso, e nada no tamanho do arquivo entrega também. Você não precisa detectar, porque o parser detecta:
Isso chega como HTTP 400, e é um sinal de roteamento em vez de uma falha. O caminho de texto está indisponível para este arquivo, então tome o outro: renderize a página e deixe um modelo olhá-la.
Agora os dois caminhos podem ser conectados, com o próprio erro do parser escolhendo entre eles:
O fallback lê uma página. Isso vale para um formulário, uma nota fiscal ou uma folha de rosto, e é errado para qualquer coisa mais longa, porque o resto do documento silenciosamente deixa de existir. Renderize toda página e envie-as como várias imagens quando a resposta pode não estar na página um.

5. No que os dois caminhos discordam

Rode os dois contra a mesma primeira página e os registros voltam quase idênticos. O “quase” é a parte interessante: O caminho do texto preservou o Ł. O caminho da visão retornou um L ASCII, porque ele está lendo formas de letra em vez de códigos de caractere, e um diacrítico é um pequeno detalhe visual que sobrevive mal. Se você está casando nomes extraídos contra um banco de dados, essa diferença decide se a linha é encontrada. O oitavo autor importa mais. A página não indica afiliação para Illia Polosukhin, e o caminho do texto reporta isso fielmente como uma string vazia toda vez. O caminho da visão, em algumas execuções, preencheu o campo com um vizinho plausível da mesma página. Ler pixels deixa mais espaço para inferência do que ler caracteres, e um campo obrigatório é um convite para preencher. Quando você não consegue conferir a saída à mão, isso é motivo para preferir texto parseado sempre que o documento oferecer. O custo é mais próximo do que parece. Com raciocínio desligado nos dois lados, os dois caminhos rodaram com prompts de tamanho parecido nesta página: A imagem tinha 923.732 caracteres de base64, e nada disso é o que você paga. Imagens são tokenizadas por tamanho, não pelo comprimento da codificação, então um PNG grande não custa o que parece que deveria. Prefira texto parseado quando o documento tem texto. Ele preserva os caracteres exatos, não custa nada a mais para ir além da primeira página, e não se importa com como a página foi diagramada. Recorra à visão quando o parser disser que não há nada para ler, ou quando o significado está no layout, como em um gráfico, um carimbo ou uma assinatura.

Extraindo outra coisa

Nada acima é específico a papers. Troque o schema e o system prompt, e o pipeline extrai notas fiscais:
Os campos description estão fazendo trabalho de verdade. Uma data só fica sem ambiguidade depois que você diz qual formato quer, e 03/04/2026 significa dois dias diferentes dependendo de quem escreveu.

Próximos passos

  • Valide o resultado contra o schema com pydantic ou jsonschema, para que um registro malformado falhe na borda em vez de três funções depois.
  • Armazene o texto extraído com Embeddings para buscar entre documentos em vez de reextraí-los.
  • Anexe documentos diretamente a uma chat completion com File Inputs quando você quiser respostas em vez de registros.
  • Entregue o extrator a um agente como ferramenta, usando Construindo um Agente que Usa Ferramentas com Function Calling.

Processamento de Documentos

Referência do endpoint text-parser.

Respostas Estruturadas

Como o json_schema restringe uma completion.

Visão

Enviando imagens a um modelo de chat.

File Inputs

Anexe um documento sem parseá-lo você mesmo.