Processar documentos fiscais e contratos manualmente ainda consome horas de trabalho em muitas empresas brasileiras. LLMs podem automatizar a extração de campos como CNPJ, valor total, datas e partes envolvidas, mas a adoção em produção exige cuidados: saída JSON validada, tratamento robusto de erros e conformidade com a LGPD na minimização de dados enviados.
Neste artigo, mostramos um cenário ilustrativo de integração com uma API compatível com OpenAI, usando Python. O foco é prático: código, validação e boas práticas de privacidade.
Cenário ilustrativo
Uma empresa recebe diariamente dezenas de PDFs de notas fiscais e contratos. O time de backoffice precisa lançar manualmente no ERP campos como CNPJ do emitente, valor total, data de emissão e número da nota. A ideia é enviar o texto extraído do PDF (via OCR ou parser) para um LLM e receber de volta um JSON estruturado, pronto para inserção no banco.
Com uma API compatível com a OpenAI, você usa a biblioteca oficial openai apenas trocando a base_url e a chave de API. Endereço e chave são fornecidos pela sua equipe de acesso; guarde-os em variáveis de ambiente (BASE_URL, API_KEY).
Extração com saída JSON
O primeiro passo é instruir o modelo a retornar apenas JSON válido. Para aumentar a confiabilidade, usamos response_format={"type": "json_object"} e definimos um schema claro no prompt.
from openai import OpenAI
import os
client = OpenAI(
api_key=os.getenv("API_KEY"),
base_url=os.getenv("BASE_URL")
)
def extrair_dados_nota_fiscal(texto_documento):
prompt = f"""
Extraia os seguintes campos da nota fiscal abaixo e retorne APENAS JSON válido, sem comentários:
- cnpj_emitente: string
- cnpj_destinatario: string
- valor_total: number
- data_emissao: string (formato ISO 8601)
- numero_nota: string
Documento:
{texto_documento}
"""
response = client.chat.completions.create(
model="gpt-4o-mini", # ou outro modelo disponível no seu contrato
messages=[{"role": "user", "content": prompt}],
temperature=0,
response_format={"type": "json_object"}
)
return response.choices[0].message.content
A saída esperada é algo como:
{
"cnpj_emitente": "12.345.678/0001-90",
"cnpj_destinatario": "98.765.432/0001-10",
"valor_total": 1500.75,
"data_emissao": "2026-09-05",
"numero_nota": "123456"
}
Validação de saída com schema
Não basta confiar que o LLM retornará exatamente o que pedimos. Valide o JSON com uma biblioteca como Pydantic para garantir tipos, formatos e presença de campos obrigatórios.
from pydantic import BaseModel, Field
from typing import Optional
import json
class NotaFiscal(BaseModel):
cnpj_emitente: str
cnpj_destinatario: str
valor_total: float = Field(gt=0)
data_emissao: str # validar depois com datetime
numero_nota: str
def validar_json_extraido(json_str: str) -> Optional[NotaFiscal]:
try:
dados = json.loads(json_str)
return NotaFiscal(**dados)
except (json.JSONDecodeError, ValidationError) as e:
print(f"Erro de validação: {e}")
return None
Se o JSON vier malformado ou faltar campos, a função retorna None e você pode acionar um fluxo de contingência.
Tratamento de erros e resiliência
Erros de rede, timeouts ou respostas inesperadas são comuns em integrações com APIs. Implemente retry com backoff exponencial e um fallback para revisão manual.
import time
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def chamar_llm_com_retry(texto_documento):
try:
return extrair_dados_nota_fiscal(texto_documento)
except Exception as e:
print(f"Tentativa falhou: {e}")
raise
def processar_documento(texto_documento):
try:
json_str = chamar_llm_com_retry(texto_documento)
nota = validar_json_extraido(json_str)
if nota is None:
return {"status": "revisao_manual", "documento": texto_documento[:200]}
return {"status": "ok", "dados": nota.dict()}
except Exception as e:
# Log estruturado para monitoramento
print(f"Erro final no processamento: {e}")
return {"status": "erro", "detalhe": str(e)}
No exemplo acima, usamos a biblioteca tenacity para simplificar o retry. Ajuste o número de tentativas e o tempo de espera conforme a criticidade do processo.
LGPD e minimização de dados enviados
A LGPD exige que o tratamento de dados pessoais seja adequado, necessário e limitado à finalidade. Ao enviar documentos para um LLM, você deve minimizar os dados que trafegam para a API.
Boas práticas:
- Envie apenas o necessário: se o campo de interesse é apenas o valor total e o CNPJ, não envie o documento inteiro com dados de terceiros. Extraia previamente trechos relevantes.
- Anonimize ou pseudonimize: substitua CPFs, e-mails e outros identificadores diretos por máscaras antes de enviar. Por exemplo, transforme
123.456.789-00em***.***.***-00. - Não armazene os textos brutos em logs: registre apenas metadados (ID do documento, status, timestamp), nunca o conteúdo completo.
- Use contratos de processamento: exija do fornecedor um contrato de tratamento de dados alinhado à LGPD, mas lembre que é sua responsabilidade como controlador garantir que a base legal para o envio esteja correta.
Exemplo de minimização antes do envio:
import re
def mascarar_dados_sensiveis(texto):
# Remove CPF (formato 000.000.000-00)
texto = re.sub(r'\d{3}\.\d{3}\.\d{3}-\d{2}', '***.***.***-**', texto)
# Remove e-mails
texto = re.sub(r'[\w\.-]+@[\w\.-]+\.\w+', '[EMAIL_REMOVIDO]', texto)
return texto
texto_original = extrair_texto_pdf("nota_fiscal.pdf")
texto_minimizado = mascarar_dados_sensiveis(texto_original)
resultado = processar_documento(texto_minimizado)
Dessa forma, o LLM nunca recebe dados pessoais desnecessários para a extração dos campos de interesse.
Conclusão
Extração estruturada de documentos fiscais e contratos com LLM é viável e pode reduzir trabalho manual, desde que você implemente validação de schema, tratamento de erros e minimização de dados.
Explore outros artigos no blog.
Quer entender como a sua empresa pode usar modelos de IA com atendimento e faturamento no Brasil? Fale com a gente — a equipe da TokenRecarga responde em português.