IA RAG LLM Embeddings Agentes

RAG na prática: como dar memória à IA com os documentos da sua empresa

Retrieval-Augmented Generation (RAG) explicado sem buzzwords: o que é, por que funciona melhor que fine-tuning para a maioria dos casos.

N
Neryx Digital Architects
25 de janeiro de 2026
15 min de leitura
250 profissionais leram
Categoria: Arquitetura Público: Times de engenharia e produto Etapa: Aprendizado

Um dos problemas mais comuns ao tentar usar IA na empresa é este: o modelo sabe muita coisa sobre o mundo, mas não sabe nada sobre você. Não conhece seus produtos, seus processos, seus contratos, sua base de conhecimento interna. Quando você pergunta sobre algo específico do seu negócio, o modelo inventa — o fenômeno conhecido como alucinação.

Há duas formas tradicionais de resolver isso:

  • Fine-tuning: treinar (ou ajustar) o modelo com seus dados. Caro, lento, e os dados ficam "congelados" no momento do treinamento.
  • Colocar tudo no contexto: enviar seus documentos inteiros na chamada ao modelo. Funciona para documentos pequenos, mas fica caro e impraticável com grandes volumes.

RAG (Retrieval-Augmented Generation) é a terceira via — e na maioria dos casos é a certa. A ideia é simples: antes de chamar o modelo, você busca apenas os trechos relevantes dos seus documentos e passa só eles como contexto. O modelo responde com base no que foi recuperado, não com base no que "imagina".

Por que RAG funciona bem

A intuição é a mesma de um consultor humano com acesso a uma boa base de conhecimento. Você não contrata alguém que memorizou todos os seus contratos — você contrata alguém inteligente que sabe onde procurar a informação certa e como raciocinar sobre ela.

RAG funciona bem porque:

  • Documentos podem ser atualizados sem retreinar o modelo
  • O modelo cita as fontes usadas para responder (rastreabilidade)
  • O custo marginal de adicionar novos documentos é baixo
  • Alucinações diminuem drasticamente — o modelo tem âncoras de texto reais

Arquitetura de um sistema RAG

Um pipeline RAG tem duas fases:

Fase de indexação (offline)

  1. Carregamento: ler os documentos (PDFs, Word, páginas web, registros de banco)
  2. Chunking: dividir em pedaços menores (chunks) com sobreposição para não perder contexto nas bordas
  3. Embedding: transformar cada chunk em um vetor numérico que representa seu significado semântico
  4. Armazenamento: salvar os vetores num banco de dados vetorial (pgvector, Pinecone, Qdrant, Weaviate)

Fase de recuperação (online, em tempo real)

  1. Embedding da pergunta: transformar a pergunta do usuário no mesmo espaço vetorial
  2. Busca por similaridade: encontrar os chunks mais próximos semanticamente da pergunta
  3. Augmentation: montar o prompt com os chunks recuperados como contexto
  4. Generation: o LLM gera a resposta baseada no contexto fornecido

Implementando RAG do zero com pgvector e Python

Vamos construir um sistema RAG simples para indexar documentos internos e responder perguntas sobre eles.

1. Setup do banco com pgvector

-- Habilita a extensão no PostgreSQL
CREATE EXTENSION IF NOT EXISTS vector;

-- Tabela para armazenar chunks e seus embeddings
CREATE TABLE documentos_chunks (
    id          BIGSERIAL PRIMARY KEY,
    fonte       TEXT NOT NULL,          -- nome do arquivo original
    pagina      INTEGER,               -- página (se PDF)
    conteudo    TEXT NOT NULL,          -- texto do chunk
    embedding   vector(1536),          -- vetor do embedding (1536 dims = OpenAI/Cohere)
    metadados   JSONB DEFAULT '{}',
    criado_em   TIMESTAMPTZ DEFAULT NOW()
);

-- Índice HNSW para busca aproximada de alta performance
CREATE INDEX ON documentos_chunks
  USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

2. Pipeline de indexação

import anthropic
import psycopg2
import PyPDF2
from pathlib import Path

client = anthropic.Anthropic()

def extrair_texto_pdf(caminho: str) -> list[dict]:
    """Extrai texto de um PDF, página por página."""
    chunks = []
    with open(caminho, "rb") as f:
        reader = PyPDF2.PdfReader(f)
        for i, pagina in enumerate(reader.pages):
            texto = pagina.extract_text()
            if texto.strip():
                chunks.append({
                    "fonte": Path(caminho).name,
                    "pagina": i + 1,
                    "conteudo": texto.strip()
                })
    return chunks

def chunkar_texto(texto: str, tamanho: int = 800, sobreposicao: int = 100) -> list[str]:
    """Divide o texto em chunks com sobreposição."""
    palavras = texto.split()
    chunks = []
    inicio = 0
    while inicio < len(palavras):
        fim = min(inicio + tamanho, len(palavras))
        chunk = " ".join(palavras[inicio:fim])
        chunks.append(chunk)
        if fim == len(palavras):
            break
        inicio += tamanho - sobreposicao
    return chunks

def gerar_embedding(texto: str) -> list[float]:
    """Gera embedding usando a API da Anthropic (via Voyage AI integrado)."""
    # Anthropic usa Voyage AI para embeddings
    # Alternativa: usar OpenAI text-embedding-3-small ou Cohere embed-v3
    import voyageai
    vo = voyageai.Client()
    resultado = vo.embed([texto], model="voyage-3")
    return resultado.embeddings[0]

def indexar_documento(caminho_pdf: str, conn):
    """Indexa um documento PDF completo."""
    print(f"Indexando {caminho_pdf}...")
    paginas = extrair_texto_pdf(caminho_pdf)

    with conn.cursor() as cur:
        for pagina in paginas:
            chunks = chunkar_texto(pagina["conteudo"])
            for chunk in chunks:
                if len(chunk.split()) < 20:  # ignora chunks muito pequenos
                    continue
                embedding = gerar_embedding(chunk)
                cur.execute("""
                    INSERT INTO documentos_chunks (fonte, pagina, conteudo, embedding)
                    VALUES (%s, %s, %s, %s::vector)
                """, (pagina["fonte"], pagina["pagina"], chunk, embedding))
        conn.commit()
    print(f"  ✓ {caminho_pdf} indexado")

3. Motor de busca e resposta

def buscar_chunks_relevantes(
    pergunta: str,
    conn,
    top_k: int = 5,
    threshold: float = 0.7
) -> list[dict]:
    """Busca os chunks mais semanticamente próximos da pergunta."""
    embedding_pergunta = gerar_embedding(pergunta)

    with conn.cursor() as cur:
        cur.execute("""
            SELECT
                fonte,
                pagina,
                conteudo,
                1 - (embedding <=> %s::vector) AS similaridade
            FROM documentos_chunks
            WHERE 1 - (embedding <=> %s::vector) > %s
            ORDER BY embedding <=> %s::vector
            LIMIT %s
        """, (embedding_pergunta, embedding_pergunta, threshold, embedding_pergunta, top_k))

        return [
            {
                "fonte": row[0],
                "pagina": row[1],
                "conteudo": row[2],
                "similaridade": float(row[3])
            }
            for row in cur.fetchall()
        ]

def responder_com_rag(pergunta: str, conn) -> str:
    """Responde uma pergunta usando RAG."""
    # 1. Recupera chunks relevantes
    chunks = buscar_chunks_relevantes(pergunta, conn)

    if not chunks:
        return "Não encontrei informações relevantes nos documentos indexados para responder essa pergunta."

    # 2. Monta o contexto
    contexto = "\n\n---\n\n".join([
        f"Fonte: {c['fonte']}, Página {c['pagina']} (similaridade: {c['similaridade']:.2f})\n\n{c['conteudo']}"
        for c in chunks
    ])

    # 3. Chama o modelo com o contexto
    resposta = client.messages.create(
        model="claude-opus-4-5-20251101",
        max_tokens=2048,
        system="""Você é um assistente especializado nos documentos da empresa.
Responda APENAS com base nos trechos fornecidos como contexto.
Se a informação não estiver no contexto, diga claramente que não encontrou essa informação nos documentos.
Sempre cite a fonte (nome do arquivo e página) ao final da resposta.""",
        messages=[{
            "role": "user",
            "content": f"""Contexto dos documentos:

{contexto}

---

Pergunta: {pergunta}"""
        }]
    )

    return resposta.content[0].text

# Exemplo de uso
conn = psycopg2.connect("postgresql://usuario:senha@localhost/empresa_db")

# Indexar documentos
indexar_documento("contratos/contrato_servico_2026.pdf", conn)
indexar_documento("manuais/politica_rh.pdf", conn)
indexar_documento("financeiro/relatorio_q1_2026.pdf", conn)

# Responder perguntas
pergunta = "Qual é o prazo de aviso prévio previsto na política de RH?"
print(responder_com_rag(pergunta, conn))

RAG avançado: técnicas que melhoram a qualidade

Chunking híbrido

Em vez de dividir por tamanho fixo, use estrutura do documento: parágrafos, seções, tabelas. Um contrato dividido por cláusula funciona muito melhor do que dividido por 800 palavras no meio de uma cláusula.

Re-ranking

Após recuperar os top-K chunks por similaridade vetorial, use um modelo de re-ranking (Cohere Rerank, cross-encoders) para reordenar por relevância real. A busca vetorial é rápida mas aproximada — o re-ranking é mais preciso.

Busca híbrida (vetorial + BM25)

Combine busca semântica (vetorial) com busca por palavra-chave (BM25/FTS). Busca vetorial é melhor para significado; busca por keyword é melhor para termos técnicos específicos, números e siglas. O pgvector combinado com o FTS nativo do PostgreSQL resolve isso sem precisar de um segundo banco.

HyDE (Hypothetical Document Embeddings)

Antes de buscar, peça ao modelo para gerar um "documento hipotético" que responderia à pergunta, e use o embedding desse documento hipotético para a busca. Parece contra-intuitivo, mas melhora significativamente a recuperação para perguntas abstratas.

def busca_com_hyde(pergunta: str, conn) -> list[dict]:
    """RAG com HyDE: gera documento hipotético antes de buscar."""
    # Gera resposta hipotética para melhorar o embedding de busca
    hipotetico = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=300,
        messages=[{
            "role": "user",
            "content": f"Escreva um parágrafo técnico que responderia diretamente à seguinte pergunta:\n\n{pergunta}\n\nEscreva como se fosse um trecho de um documento oficial."
        }]
    ).content[0].text

    # Usa o documento hipotético para busca (não a pergunta original)
    return buscar_chunks_relevantes(hipotetico, conn)

Casos de uso por tipo de empresa

Escritório de advocacia

Base de dados de jurisprudência, contratos-modelo e pareceres indexados. Advogados consultam precedentes em segundos em vez de horas de pesquisa manual.

E-commerce com catálogo grande

Fichas técnicas, manuais e especificações de produtos indexados. Atendimento responde perguntas técnicas específicas ("esse produto funciona com 110V?") sem precisar de especialista humano.

RH e onboarding

Políticas, benefícios, guias de processos e FAQs indexados. Novos funcionários consultam o assistente em vez de abrir chamado para o RH para cada dúvida.

Suporte técnico de software

Documentação, changelogs, tickets resolvidos e notas de release indexados. Suporte N1 resolve mais chamados sem escalar para engenharia.

Quando RAG não é suficiente

RAG tem limitações. Se você precisa que o modelo aprenda um estilo específico (tom de voz, formato de resposta muito particular), RAG sozinho pode não bastar e fine-tuning faz sentido. Se a base de conhecimento é muito pequena e estática, colocar tudo no contexto do sistema pode ser mais simples. E se a pergunta requer raciocínio sobre todos os documentos ao mesmo tempo (estatísticas agregadas, análises comparativas), RAG recupera amostras — para isso, um data pipeline analítico é mais adequado.

Para a maioria dos casos de "IA que sabe sobre nossa empresa", porém, RAG é o ponto de partida correto. É o projeto de IA com melhor custo-benefício disponível hoje para empresas que querem resultados práticos sem investir meses em infraestrutura de ML.

Precisa desenhar a próxima fase com menos retrabalho?

Fazemos discovery técnico para mapear riscos, arquitetura-alvo e sequência de execução antes de investir pesado.

Solicitar Discovery

Newsletter

Receba artigos como este no seu e-mail

Conteúdo técnico sobre arquitetura de software, .NET, IA e gestão de produto. Sem spam.