Skip to main content
LadeStack LogoLadeStack
Generative AI 10 min de leitura 8 de julho de 2024

Construindo Aplicações RAG com Bancos de Dados Vetoriais: Ingestão, Chunking e Arquitetura de Busca

Guia completo de arquitetura para projetar sistemas de Geração Aumentada por Recuperação (RAG) em produção com bancos vetoriais, busca híbrida e reranking.

Diagrama de arquitetura RAG: ingestão de documentos, indexação vetorial, busca por similaridade e aumento de contexto para LLMs

Modelos de Linguagem de Grande Porte (LLMs) demonstram capacidades notáveis de síntese linguística e raciocínio generalista. Contudo, ao serem implantados em ambientes corporativos de produção, os modelos de base esbarram em dois limites estruturais: a data de corte de seus dados de treino (knowledge cutoff) e a propensão a gerar afirmações verossímeis, porém factualmente falsas, conhecidas como alucinações (confabulation).

A arquitetura de Geração Aumentada por Recuperação (RAG - Retrieval-Augmented Generation) resolve esse dilema desacoplando a memória de armazenamento de conhecimento da memória paramétrica da rede neural. Em vez de exigir que o LLM memorize toda a base documental interna da organização, o sistema RAG extrai dinamicamente trechos textuais relevantes de um mecanismo de busca especializado (tipicamente um banco de dados vetorial) e os injeta na janela de contexto no momento da inferência.

Entretanto, elevar um pipeline RAG de um protótipo experimental para um serviço corporativo de alta confiabilidade exige rigor de engenharia. Divisões ingênuas de texto a cada 1.000 caracteres combinadas com buscas vetoriais simplórias provocam quebras de contexto, tabelas corrompidas, ruído excessivo e alucinações derivadas de dados conflitantes.

Neste tutorial técnico, detalhamos as camadas de engenharia de um pipeline RAG de nível de produção: ingestão estruturada, estratégias de particionamento (chunking), métricas de similaridade vetorial, reranking com cross-encoders e síntese de respostas com citações auditáveis de proveniência.


1. Arquitetura RAG de Ponta a Ponta

Um sistema RAG de produção opera através de dois ciclos assíncronos e desacoplados: o Pipeline de Ingestão e Indexação Offline e o Ciclo de Recuperação e Geração Online.

[ PIPELINE DE INGESTÃO OFFLINE ]
Documentos Brutos (PDF, Markdown, HTML, Bases Confluence)
       │
       ▼
Extração de Texto e Limpeza
       │
       ▼
Particionamento Sensível à Estrutura (400–800 tokens, 10–15% sobreposição)
       │
       ▼
Geração de Embeddings Densos (text-embedding-3-small, 1536 dim)
       │
       ▼
Indexação em Banco Vetorial (Grafo HNSW / Carga de Metadados)

─────────────────────────────────────────────────────────────────

[ CICLO DE RECUPERAÇÃO E GERAÇÃO ONLINE ]
Consulta do Usuário
       │
       ▼
Pré-processamento e Expansão de Consulta (HyDE)
       │
       ▼
Busca Híbrida (Similaridade de Cosseno Densa + Frequência de Palavras BM25)
       │
       ▼
Fusão por Classificação Recíproca (RRF) ──> Top 50 Candidatos
       │
       ▼
Reranker com Cross-Encoder ──> Top 4 Trechos Mais Relevantes
       │
       ▼
Montagem de Contexto e Tags de Citação
       │
       ▼
Geração Fundamentada pelo LLM (Exigência de [Fonte-N] e Recusa se Ausente)

2. Ingestão Documental e Estratégias de Chunking

A qualidade final da recuperação da informação é delimitada pela precisão da estratégia de particionamento (chunking). Se o trecho for pequeno demais, o contexto envolvente se perde; se for excessivamente longo, o embedding denso é diluído por ruídos irrelevantes, reduzindo a similaridade em consultas pontuais.

Comparação de Abordagens de Particionamento

Estratégia Alvo de Tokens Sobreposição Vantagens Desvantagens e Riscos
Janela Deslizante Fixa 256–512 tokens 10–20% Implementação trivial, custo previsível Corta frases, parágrafos e linhas de tabelas
Divisor Recurrente de Caracteres 400–800 tokens 50–100 tokens Respeita parágrafos (\n\n) e pontuações Pode dividir listas aninhadas e trechos de código
Particionamento AST Markdown Dinâmico Hierarquia de títulos Mantém títulos, blocos de código e tabelas intactos Requer agrupamento para documentos com muitas seções
Particionamento Semântico Dinâmico Nenhuma Baseia-se no desvio de embeddings entre frases Custo de inferência alto durante a ingestão inicial

Implementação de Chunker Estruturado em Markdown (TypeScript)

Para documentação de engenharia e bases de conhecimento, a preservação da árvore semântica do Markdown assegura o maior índice de acerto na recuperação:

// src/lib/rag/chunker.ts

export interface DocumentSection {
  title: string;
  sourceUrl: string;
  category: string;
  content: string;
}

export interface TextChunk {
  chunkId: string;
  sourceUrl: string;
  headingPath: string;
  content: string;
  tokenEstimate: number;
}

/**
 * Divide o texto respeitando os títulos Markdown (#, ##, ###) e as quebras de parágrafo
 */
export function chunkMarkdownDocument(
  doc: DocumentSection,
  maxTokens: number = 600,
  overlapTokens: number = 60
): TextChunk[] {
  const sections = doc.content.split(/\n(?=#{1,3}\s)/);
  const chunks: TextChunk[] = [];
  let chunkCounter = 0;

  for (const section of sections) {
    const lines = section.trim().split("\n");
    const heading = lines[0].startsWith("#") ? lines[0].replace(/^#+\s*/, "") : "General";
    const body = lines[0].startsWith("#") ? lines.slice(1).join("\n").trim() : section.trim();

    if (!body) continue;

    const estimatedTokens = Math.ceil(body.length / 4);

    if (estimatedTokens <= maxTokens) {
      chunkCounter++;
      chunks.push({
        chunkId: `${doc.sourceUrl}#chunk-${chunkCounter}`,
        sourceUrl: doc.sourceUrl,
        headingPath: `${doc.title} > ${heading}`,
        content: `### ${heading}\n${body}`,
        tokenEstimate: estimatedTokens
      });
    } else {
      const paragraphs = body.split(/\n\n+/);
      let currentBuffer: string[] = [];
      let currentTokenCount = 0;

      for (const paragraph of paragraphs) {
        const pTokens = Math.ceil(paragraph.length / 4);

        if (currentTokenCount + pTokens > maxTokens && currentBuffer.length > 0) {
          chunkCounter++;
          chunks.push({
            chunkId: `${doc.sourceUrl}#chunk-${chunkCounter}`,
            sourceUrl: doc.sourceUrl,
            headingPath: `${doc.title} > ${heading}`,
            content: `### ${heading}\n${currentBuffer.join("\n\n")}`,
            tokenEstimate: currentTokenCount
          });

          const overlap = currentBuffer.slice(-1);
          currentBuffer = [...overlap, paragraph];
          currentTokenCount = Math.ceil(currentBuffer.join("\n\n").length / 4);
        } else {
          currentBuffer.push(paragraph);
          currentTokenCount += pTokens;
        }
      }

      if (currentBuffer.length > 0) {
        chunkCounter++;
        chunks.push({
          chunkId: `${doc.sourceUrl}#chunk-${chunkCounter}`,
          sourceUrl: doc.sourceUrl,
          headingPath: `${doc.title} > ${heading}`,
          content: `### ${heading}\n${currentBuffer.join("\n\n")}`,
          tokenEstimate: currentTokenCount
        });
      }
    }
  }

  return chunks;
}

3. Métricas de Similaridade Vetorial e Indexação HNSW

Modelos de embedding projetam passagens de texto em um espaço contínuo multidimensional $\mathbb{R}^d$.

Similaridade de Cosseno vs Produto Escalar (Dot Product)

  • Similaridade de Cosseno: Avalia o cosseno do ângulo entre dois vetores, desconsiderando magnitudes absolutas, o que a torna ideal para trechos textuais de tamanhos heterogêneos.
  • Produto Escalar: Ao normalizar previamente os vetores com norma unitária L2 ($|\mathbf{u}|_2 = 1$), o produto escalar se torna matematicamente idêntico à similaridade de cosseno, sendo computado com altíssima eficiência em hardware moderno via instruções vetorizadas (AVX-512).

Busca Aproximada de Vizinhos Mais Próximos (ANN) e Parâmetros HNSW

Percorrer exaustivamente coleções de milhões de vetores possui complexidade de $O(N \cdot d)$, inviabilizando tempos de resposta em milissegundos. Os bancos vetoriais empregam a estrutura de grafos HNSW (Hierarchical Navigable Small World):

  • M: Número máximo de conexões bidirecionais por nó.
  • efConstruction: Tamanho da fila dinâmica de avaliação durante a geração do índice.
  • efSearch: Profundidade da fila durante a consulta em tempo real, permitindo calibrar o balanço entre velocidade e precisão de recuperação.

4. Busca Híbrida e Reranking com Cross-Encoder

Confiar exclusivamente em vetores densos introduz vulnerabilidades conhecidas: baixa sensibilidade a termos exatos, números de protocolo, códigos de erro e identificadores específicos.

A arquitetura de produção implementa a Busca Híbrida (Hybrid Search): disparando simultaneamente a busca semântica por vetores densos e a busca léxica BM25 por palavras-chave, unificando os resultados por meio de Reciprocal Rank Fusion (RRF).

Em seguida, o conjunto consolidado de candidatos (Top 40 a 50) é submetido a um modelo Cross-Encoder Reranker, que avalia a atenção direta entre a pergunta e cada passagem candidata, eliminando até 90% do ruído documental.


5. Implementação Completa do Pipeline RAG (TypeScript)

Apresentamos a implementação de um serviço RAG pronto para produção em TypeScript com Pinecone e OpenAI:

// src/lib/rag/pipeline.ts
import { Pinecone } from '@pinecone-database/pinecone';

export interface RetrievedChunk {
  chunkId: string;
  sourceUrl: string;
  headingPath: string;
  content: string;
  score: number;
}

export interface GroundedRAGResponse {
  answer: string;
  citations: Array<{ citationIndex: number; sourceUrl: string; headingPath: string }>;
  tokenUsage: { promptTokens: number; completionTokens: number };
  refusal: boolean;
}

export async function executeGroundedRAG(
  query: string,
  pineconeClient: Pinecone,
  pineconeIndexName: string,
  embeddingApiKey: string,
  llmApiKey: string
): Promise<GroundedRAGResponse> {
  // 1. Geração de embedding normalizado da consulta
  const embeddingResponse = await fetch('https://api.openai.com/v1/embeddings', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${embeddingApiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      input: query,
      model: 'text-embedding-3-small'
    })
  });

  if (!embeddingResponse.ok) {
    throw new Error(`Erro na API de Embeddings: ${embeddingResponse.statusText}`);
  }

  const embeddingData = await embeddingResponse.json();
  const queryVector: number[] = embeddingData.data[0].embedding;

  // 2. Consulta de candidatos no banco vetorial
  const index = pineconeClient.index(pineconeIndexName);
  const queryResult = await index.query({
    vector: queryVector,
    topK: 10,
    includeMetadata: true
  });

  const candidates: RetrievedChunk[] = (queryResult.matches || []).map((match) => ({
    chunkId: match.id,
    sourceUrl: (match.metadata?.sourceUrl as string) || 'Unknown Source',
    headingPath: (match.metadata?.headingPath as string) || 'General',
    content: (match.metadata?.content as string) || '',
    score: match.score || 0
  }));

  // 3. Filtro de similaridade mínima
  const MIN_SIMILARITY_SCORE = 0.65;
  const filteredChunks = candidates.filter(c => c.score >= MIN_SIMILARITY_SCORE);

  // Recusa explícita caso não haja evidências seguras para prevenir alucinação
  if (filteredChunks.length === 0) {
    return {
      answer: "A documentação fornecida não contém evidências suficientes para responder à sua solicitação com precisão.",
      citations: [],
      tokenUsage: { promptTokens: 0, completionTokens: 0 },
      refusal: true
    };
  }

  const selectedContext = filteredChunks.slice(0, 4);

  // 4. Montagem de prompt com rigor de citações
  const contextBlock = selectedContext
    .map((chunk, idx) => `[Fonte-${idx + 1}] (${chunk.headingPath})\n${chunk.content}`)
    .join("\n\n---\n\n");

  const systemPrompt = `Você é um assistente técnico documental rigoroso.
Responda à pergunta do usuário utilizando EXCLUSIVAMENTE os trechos verificados abaixo.
Regras inegociáveis:
1. Toda afirmação factual deve ser sucedida de uma citação correspondente no formato [Fonte-N].
2. Se as fontes fornecidas não contiverem evidências claras, responda: "A documentação fornecida não possui informações suficientes para responder."
3. Jamais especule ou traga conhecimentos externos à margem dos trechos disponibilizados.

FONTES VERIFICADAS:
${contextBlock}`;

  // 5. Síntese do LLM
  const llmResponse = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${llmApiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'gpt-4o-mini',
      messages: [
        { role: 'system', content: systemPrompt },
        { role: 'user', content: query }
      ],
      temperature: 0.1,
      max_tokens: 500
    })
  });

  if (!llmResponse.ok) {
    throw new Error(`Erro na síntese do LLM: ${llmResponse.statusText}`);
  }

  const completionData = await llmResponse.json();
  const answerText = completionData.choices[0].message.content;

  const citations = selectedContext.map((chunk, idx) => ({
    citationIndex: idx + 1,
    sourceUrl: chunk.sourceUrl,
    headingPath: chunk.headingPath
  }));

  return {
    answer: answerText,
    citations,
    tokenUsage: {
      promptTokens: completionData.usage.prompt_tokens,
      completionTokens: completionData.usage.completion_tokens
    },
    refusal: answerText.includes("não possui informações suficientes")
  };
}

6. Checagem de Fatos: RAG Elimina Totalmente as Alucinações?

A afirmação comercial de que “bancos de dados vetoriais e RAG eliminam 100% das alucinações de modelos de linguagem” é tecnicamente inverídica.

Embora o RAG ancore a geração em evidências factuais, ele introduz modos de falha próprios:

  1. Falhas de Recuperação (Retrieval Miss): Quando há divergência de vocabulário, o trecho correto não é resgatado. Sem regras de recusa, o modelo tenta adivinhar a resposta.
  2. Efeito ‘Lost in the Middle’: Modelos de atenção focam mais no início e no final de contextos extensos. Fornecer muitos trechos faz com que fatos essenciais localizados no meio passem despercebidos.
  3. Contaminação por Documentos Antigos: Caso a base guarde especificações de uma versão legada e da versão atual, o modelo pode fundir conceitos e inventar parâmetros inexistentes.
  4. Viés de Pré-treino (Faithfulness Failure): Quando uma evidência textual contraria uma convicção firmada durante o pré-treinamento, o modelo pode desconsiderar o trecho recuperado.

7. Conclusão

Bancos de dados vetoriais e pipelines RAG são elementos vitais para a construção de inteligências artificiais corporativas seguras. Ao associar chunking consciente de estrutura, busca híbrida, reranking e instruções de citação inegociáveis, engenheiros de software conseguem entregar sistemas com alto grau de auditabilidade e confiança.

Para explorar padrões de isolamento de dados entre clientes, leia nosso artigo sobre Padrões de Arquitetura SaaS Multi-Tenant, ou veja como especializar modelos de linguagem no guia sobre Fine-Tuning de LLMs para Tarefas de Domínio Específico.

More Engineering Articles