O resultado que vamos construir é um pequeno pipeline para transformar um PDF visualmente bagunçado em texto estruturado, pronto para um modelo de linguagem analisar. A versão mínima lê uma página como imagem e pede ao modelo que reconstrua títulos, parágrafos, tabelas e campos. No fim, você terá um arquivo JSON que pode ser validado, armazenado ou enviado para outra etapa do sistema.

A ideia central é simples: não mande o PDF inteiro às cegas. Primeiro recupere a estrutura que o arquivo apresenta para uma pessoa. Depois peça interpretação.

O arquivo parece texto, mas pensa como desenho

Um PDF não é necessariamente uma sequência de parágrafos. Ele pode guardar caracteres posicionados em coordenadas, blocos independentes, imagens escaneadas e elementos que só parecem estar relacionados porque foram desenhados próximos uns dos outros.

Para um leitor humano, a ordem é óbvia. O título está no alto, a legenda está abaixo da figura e a coluna da direita vem depois da coluna da esquerda. Para um extrator simples, esses objetos podem aparecer em outra ordem. Um rodapé pode entrar no meio do parágrafo. Uma tabela pode virar uma fila de palavras sem relação entre linhas e colunas.

Esse problema explica por que enviar o conteúdo bruto diretamente a um modelo gera respostas erradas. O modelo recebe tokens, não a intenção visual da página. Se a entrada mistura cabeçalho, rodapé, colunas e células, ele precisa adivinhar a sequência antes de responder.

A solução deste guia usa duas representações complementares. A primeira é texto extraído, útil quando o PDF tem caracteres selecionáveis. A segunda é uma imagem da página, útil quando a posição dos elementos carrega informação. Neste roteiro, vamos trabalhar com a imagem, porque ela preserva a geometria que o texto linear perde.

Comece com uma página, não com o documento inteiro

A primeira versão deve processar uma única página. Isso reduz a depuração: se a saída estiver errada, você consegue comparar a imagem original com o JSON produzido sem procurar o problema em dezenas de páginas.

Também evita um erro comum: usar um modelo de linguagem para descobrir sozinho onde cada página começa e termina. Dividir o documento antes permite controlar contexto, custo e falhas. Depois, as páginas podem ser combinadas.

Exporte a página como uma imagem acessível por URL. Ela precisa estar disponível para a chamada HTTP. Em um sistema real, essa URL pode ser temporária e protegida. Para o exemplo, suponha que a imagem esteja em https://exemplo.invalid/pagina-1.png. Troque esse endereço por uma URL real antes de executar.

O código abaixo envia a imagem para leitura visual e exige uma resposta em JSON. O formato estruturado importa porque separa o conteúdo da explicação livre do modelo.

// extract-page.mjs
const imageUrl = process.argv[2];

if (!imageUrl) {
  throw new Error("Usage: node extract-page.mjs IMAGE_URL");
}

const response = await fetch("https://api.hinow.ai/v1/chat/completions", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.HINOW_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    model: "hinow/hivision",
    response_format: { type: "json_object" },
    temperature: 0,
    messages: [
      {
        role: "user",
        content: [
          {
            type: "text",
            text: `Reconstrua o conteúdo desta página sem inventar informações.

Retorne um objeto JSON com esta forma:
{
  "page": 1,
  "blocks": [
    {
      "type": "title|paragraph|table|list|caption|header|footer|unknown",
      "text": "conteúdo preservado",
      "order": 1,
      "confidence": "alta|media|baixa"
    }
  ],
  "warnings": ["problemas visuais ou trechos ilegíveis"]
}

Mantenha a ordem visual de leitura. Para tabelas, preserve linhas e colunas dentro do campo text. Não complete palavras cortadas. Se algo estiver ilegível, registre isso em warnings.`
          },
          {
            type: "image_url",
            image_url: { url: imageUrl }
          }
        ]
      }
    ]
  })
});

if (!response.ok) {
  throw new Error(await response.text());
}

const data = await response.json();
const text = data.choices?.[0]?.message?.content;

if (!text) {
  throw new Error("Response has no content");
}

const result = JSON.parse(text);
console.log(JSON.stringify(result, null, 2));

Execute com uma versão recente do ambiente de execução:

HINOW_API_KEY=hi_sua_chave node extract-page.mjs https://seu-endereco/pagina-1.png

A chave fica na variável de ambiente, não no arquivo. O modelo de leitura visual recebe uma tarefa de reconstrução, não uma pergunta vaga como “resuma esta imagem”. Essa diferença pode ser importante para preservar a estrutura. Neste estágio, priorize a preservação da estrutura em vez da interpretação.

O JSON precisa carregar os sinais de incerteza

Uma saída com apenas text parece conveniente, mas esconde o que aconteceu. Se uma célula estiver cortada ou um número for pouco legível, o programa seguinte pode tratá-lo como verdade.

Por isso o esquema inclui confidence e warnings. Esses campos não tornam a extração correta por mágica. Eles tornam a dúvida visível. Essa é uma prática recomendada para fluxos de documentos: a automação não deve apagar a fronteira entre o que foi lido e o que foi inferido.

Para validar a primeira execução, confira três coisas. O campo blocks deve existir. Cada bloco deve ter order. E qualquer trecho ilegível deve aparecer em warnings, em vez de ser preenchido silenciosamente. Compare também a sequência dos blocos com a página original. Se o rodapé entrou antes do corpo, o problema está na reconstrução, não na etapa posterior de perguntas e respostas.

Quando texto e imagem devem trabalhar juntos

A imagem preserva posição, mas não é sempre a melhor fonte para cada caractere. Um PDF digital pode oferecer texto nítido e uma imagem redundante. Um documento escaneado pode exigir leitura visual. Uma tabela pode precisar das duas representações: texto para copiar valores e imagem para entender as células.

Uma possível evolução é extrair as duas camadas e pedir ao modelo que as compare. Quando houver conflito, ele deve manter as alternativas e sinalizar a divergência. Não peça que escolha silenciosamente.

O fluxo fica assim: dividir o PDF em páginas, gerar uma imagem por página, obter o texto disponível, enviar texto e imagem juntos, salvar um JSON por página e só então consolidar o documento. A consolidação deve respeitar page e order; não basta concatenar strings.

Se cada chamada levar algumas centenas de milissegundos e cada rodada de inferência levar alguns segundos, processar várias páginas em chamadas separadas pode aumentar o tempo total, sem contar a conversão das páginas. Essa é uma conta ilustrativa com premissas explícitas, não uma medição universal. Ela mostra por que o processamento paralelo pode importar, mas também por que limites de requisição, revisão e armazenamento precisam entrar no desenho do produto.

O modelo não deve ser o juiz final

Mesmo quando a saída parece perfeita, preserve a página original e o JSON lado a lado. A extração visual pode ajudar a reconstruir a ordem dos elementos, mas não garante que um número pequeno esteja legível ou que uma tabela complexa tenha sido interpretada corretamente.

Uma validação útil é exigir regras simples antes de aceitar o resultado: toda página precisa ter número, todo bloco precisa ter tipo e ordem, e campos marcados com confiança baixa precisam de revisão humana. Para documentos financeiros, jurídicos ou médicos, essa revisão não é detalhe de implementação. É parte do sistema.

O primeiro passo que vale expandir é adicionar uma etapa de comparação entre páginas. Depois, você pode transformar os blocos em campos específicos, indexá-los por embedding ou enviar apenas os trechos relevantes para perguntas posteriores. O atalho é tentador, mas a base continua sendo a mesma: antes de pedir que um modelo raciocine sobre um documento, entregue a ele uma representação que preserve a maneira como o documento organiza significado.