Como usar o Instructor para extrair dados estruturados de LLMs

1. Introdução ao Instructor e sua importância na extração estruturada

O Instructor é uma biblioteca Python que revoluciona a forma como extraímos dados estruturados de Large Language Models (LLMs). Em vez de depender de prompts verbosos e parsing manual de JSON, o Instructor permite definir esquemas de dados com Pydantic e garante que a saída do modelo seja validada automaticamente.

Em sistemas de produção, a extração estruturada é essencial para automação, integração com APIs legadas e manutenção de consistência entre sistemas. Abordagens tradicionais como prompts com "responda em JSON" frequentemente falham devido a inconsistências de formatação, campos ausentes ou valores fora do tipo esperado. O Instructor resolve isso ao aplicar validação rigorosa e re-prompt automático quando a saída não corresponde ao esquema definido.

2. Instalação, configuração básica e primeiros passos

A instalação é simples via pip:

pip install instructor pydantic

Para dependências específicas de provedores:

pip install instructor[openai]  # Para OpenAI
pip install instructor[anthropic]  # Para Anthropic
pip install instructor[ollama]  # Para Ollama local

Configuração básica com OpenAI:

import instructor
from openai import OpenAI
from pydantic import BaseModel

# Configura o cliente
client = instructor.from_openai(OpenAI())

# Define o modelo de saída
class Pessoa(BaseModel):
    nome: str
    idade: int

# Extrai dados de um texto não estruturado
texto = "João tem 28 anos e mora em São Paulo."
pessoa = client.chat.completions.create(
    model="gpt-3.5-turbo",
    response_model=Pessoa,
    messages=[{"role": "user", "content": f"Extraia os dados: {texto}"}]
)

print(pessoa.nome)   # João
print(pessoa.idade)  # 28

3. Modelagem de dados com Pydantic para definição de esquemas

A verdadeira potência do Instructor está na modelagem com Pydantic. É possível criar estruturas complexas com validações personalizadas:

from pydantic import BaseModel, Field, validator
from typing import List, Optional
from datetime import date

class Experiencia(BaseModel):
    empresa: str
    cargo: str
    inicio: date
    fim: Optional[date] = None
    descricao: str = Field(..., min_length=10)

class Habilidade(BaseModel):
    nome: str
    nivel: str = Field(..., pattern="^(iniciante|intermediário|avançado)$")

class Curriculo(BaseModel):
    nome_completo: str
    email: str
    telefone: Optional[str] = None
    habilidades: List[Habilidade]
    experiencias: List[Experiencia]

    @validator('email')
    def email_valido(cls, v):
        if '@' not in v:
            raise ValueError('Email inválido')
        return v

# Exemplo de extração
texto_curriculo = """
Maria Silva, maria@email.com, (11) 99999-8888
Habilidades: Python (avançado), SQL (intermediário), Docker (iniciante)
Experiência: Engenheira de Dados na DataCorp (2020-2023) - Desenvolvimento de pipelines ETL
"""

curriculo = client.chat.completions.create(
    model="gpt-3.5-turbo",
    response_model=Curriculo,
    messages=[{"role": "user", "content": f"Extraia os dados deste currículo:\n{texto_curriculo}"}]
)

4. Extração de dados com modos de resposta (JSON, Tools, Function Calling)

O Instructor suporta diferentes modos de extração, cada um com vantagens específicas:

from pydantic import BaseModel
from typing import Optional
from datetime import datetime

class Email(BaseModel):
    remetente: str
    destinatario: str
    assunto: str
    data: datetime
    corpo: str
    prioridade: Optional[str] = None

# Modo JSON (mais simples)
email_json = client.chat.completions.create(
    model="gpt-3.5-turbo",
    response_model=Email,
    mode="json",
    messages=[{"role": "user", "content": "Extraia os dados do email..."}]
)

# Modo tools (mais preciso para modelos que suportam function calling)
email_tools = client.chat.completions.create(
    model="gpt-4",
    response_model=Email,
    mode="tools",
    messages=[{"role": "user", "content": "Extraia os dados do email..."}]
)

# Modo function_calling (legado, compatível com versões anteriores)
email_fc = client.chat.completions.create(
    model="gpt-3.5-turbo-0613",
    response_model=Email,
    mode="function_calling",
    messages=[{"role": "user", "content": "Extraia os dados do email..."}]
)

5. Validação avançada, correção automática e iteração

Uma das funcionalidades mais poderosas é a iteração para extrair múltiplos registros:

from typing import List
from pydantic import BaseModel, Field

class ItemNota(BaseModel):
    descricao: str
    quantidade: int = Field(gt=0)
    valor_unitario: float = Field(gt=0)
    valor_total: float = Field(gt=0)

class NotaFiscal(BaseModel):
    cnpj_emitente: str = Field(pattern=r'^\d{14}$')
    cnpj_destinatario: str = Field(pattern=r'^\d{14}$')
    valor_total: float = Field(gt=0)
    itens: List[ItemNota]

# Extrai múltiplas notas de uma vez
texto_notas = """
NF 001: Emitente 11222333000181, Destinatário 44555666000199
Itens: Teclado (2x R$150,00 = R$300,00), Mouse (1x R$80,00 = R$80,00)
Total: R$380,00

NF 002: Emitente 77888999000111, Destinatário 11222333000181
Itens: Monitor (1x R$1200,00 = R$1200,00)
Total: R$1200,00
"""

# Usando Iteration para extrair múltiplos registros
from instructor import Iteration

notas = client.chat.completions.create(
    model="gpt-3.5-turbo",
    response_model=Iteration[NotaFiscal],
    messages=[{"role": "user", "content": f"Extraia todas as notas fiscais:\n{texto_notas}"}]
)

for nota in notas:
    print(f"NF: {nota.cnpj_emitente} - Total: R${nota.valor_total:.2f}")

6. Integração com sistemas legados e armazenamento dos dados extraídos

Após extrair os dados, é crucial integrá-los com sistemas existentes:

import sqlite3
import json

# Conecta ao banco SQLite
conn = sqlite3.connect('crm.db')
cursor = conn.cursor()

cursor.execute('''
CREATE TABLE IF NOT EXISTS contatos (
    id INTEGER PRIMARY KEY,
    nome TEXT,
    email TEXT,
    telefone TEXT,
    cargo TEXT,
    empresa TEXT,
    metadata TEXT
)
''')

# Extrai dados de texto de CRM
texto_crm = """
Cliente: Roberto Lima, roberto@techcorp.com, (21) 98888-7777
Cargo: CTO na TechCorp Solutions
"""

class ContatoCRM(BaseModel):
    nome: str
    email: str
    telefone: str
    cargo: str
    empresa: str

contato = client.chat.completions.create(
    model="gpt-3.5-turbo",
    response_model=ContatoCRM,
    messages=[{"role": "user", "content": f"Extraia os dados do contato:\n{texto_crm}"}]
)

# Insere no banco
cursor.execute('''
INSERT INTO contatos (nome, email, telefone, cargo, empresa, metadata)
VALUES (?, ?, ?, ?, ?, ?)
''', (
    contato.nome,
    contato.email,
    contato.telefone,
    contato.cargo,
    contato.empresa,
    json.dumps(contato.dict())
))

conn.commit()
conn.close()

# Exporta para CSV
import csv
with open('contatos_extraidos.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f)
    writer.writerow(['Nome', 'Email', 'Telefone', 'Cargo', 'Empresa'])
    writer.writerow([contato.nome, contato.email, contato.telefone, contato.cargo, contato.empresa])

7. Boas práticas, limitações e otimização de custos

Para obter melhores resultados, siga estas práticas:

# Boa prática: prompts claros e específicos
prompt_eficaz = """
Extraia os dados estruturados do seguinte texto.
Seja preciso e não invente informações.
Texto: {texto}
"""

# Estratégia de cache para reduzir custos
import hashlib
import json

cache = {}

def extrair_com_cache(texto, modelo):
    hash_texto = hashlib.md5(texto.encode()).hexdigest()
    if hash_texto in cache:
        return cache[hash_texto]

    resultado = client.chat.completions.create(
        model=modelo,
        response_model=modelo,
        messages=[{"role": "user", "content": prompt_eficaz.format(texto=texto)}]
    )

    cache[hash_texto] = resultado
    return resultado

Limitações importantes:
- Modelos pequenos (GPT-3.5-turbo) podem falhar em extrações complexas
- Ambiguidade em textos mal escritos reduz a precisão
- Custo por token pode ser alto para extrações em larga escala
- Latência aumenta com esquemas muito complexos

8. Casos de uso reais e próximos passos

O Instructor é ideal para:
- Extração de dados de contratos jurídicos
- Processamento de formulários escaneados
- Análise de logs de sistemas
- Estruturação de e-mails não formatados
- Extração de dados de documentos financeiros

Comparado com alternativas como LangChain e LlamaIndex, o Instructor oferece maior simplicidade e validação mais rigorosa. Enquanto JSON mode nativo dos LLMs requer parsing manual, o Instructor automatiza todo o processo.

Próximos passos incluem integração com agentes como CrewAI para pipelines autônomos, uso com plataformas no-code como Dify, e combinação com bancos vetoriais para memória de longo prazo.

Referências