Pular para o conteúdo
Categoria: Desenvolvendo com IA14 min de leitura

"RAG na prática: dê memória e contexto ao seu LLM"

Por Schematize Blog ·

Implemente Retrieval-Augmented Generation para responder com base nos seus próprios documentos, reduzir alucinações, manter o modelo atualizado e medir a qualidade do pipeline.

Um modelo de linguagem sozinho só sabe o que viu durante o treinamento — ele não conhece os seus documentos, sua política interna ou o produto que você lançou ontem. RAG (Retrieval-Augmented Generation) resolve isso conectando o modelo a uma base de conhecimento que você controla. Neste guia prático você vai entender o fluxo completo de um sistema RAG, desde a indexação dos documentos até a geração da resposta, com as decisões, o código e as armadilhas que aparecem na vida real.

O problema que o RAG resolve

LLMs têm duas limitações incômodas. Primeiro, o conhecimento congelado: o que não estava nos dados de treino, o modelo não sabe. Segundo, a alucinação: quando não sabe, o modelo frequentemente inventa uma resposta confiante e errada.

RAG ataca os dois de uma vez. Em vez de confiar só na "memória" interna do modelo, você busca trechos relevantes de uma base externa e os injeta no prompt antes de pedir a resposta. O modelo passa a responder com base em fatos que você forneceu. A técnica foi formalizada por Lewis et al. (2020), que combinaram um recuperador de documentos com um gerador e demonstraram ganhos consistentes em tarefas que dependem de conhecimento específico. Se você ainda não tem o conceito firme, vale ler antes o que é RAG (Retrieval-Augmented Generation)?.

Há um terceiro ganho que costuma decidir a adoção em empresas: rastreabilidade. Como a resposta vem de trechos concretos, você pode citar a fonte de cada afirmação. Num suporte técnico ou num assistente jurídico, poder apontar "isto veio do documento X, seção Y" é a diferença entre uma ferramenta confiável e um gerador de texto bonito.

A anatomia de um sistema RAG

Todo sistema RAG tem duas fases. A fase de indexação acontece offline, uma vez (e a cada atualização dos documentos). A fase de consulta acontece a cada pergunta do usuário.

INDEXAÇÃO (offline)
documentos → divisão em pedaços → embeddings → banco vetorial

CONSULTA (a cada pergunta)
pergunta → embedding → busca por similaridade → trechos relevantes
        → prompt (pergunta + trechos) → LLM → resposta

Vamos destrinchar cada parte.

Passo 1: divida os documentos em pedaços (chunking)

Você não joga documentos inteiros na busca — divide em pedaços menores, os chunks. Por quê? Porque você quer recuperar só os trechos relevantes, não páginas inteiras de ruído. O tamanho do chunk é uma decisão delicada:

    Um ponto de partida comum é dividir por parágrafos ou em blocos de algumas centenas de tokens, com uma pequena sobreposição entre chunks vizinhos para não cortar uma ideia no meio. Ajuste conforme o seu conteúdo.

    Estratégias de chunking

    Nem todo documento se divide bem do mesmo jeito. Vale conhecer as abordagens:

      Um cuidado prático: preserve metadados junto de cada chunk (título do documento, seção, URL, data). Eles servem tanto para filtrar a busca quanto para citar a fonte na resposta.

      def dividir_em_chunks(texto: str, tamanho: int = 500, sobreposicao: int = 75) -> list[str]:
          palavras = texto.split()
          chunks = []
          passo = tamanho - sobreposicao
          for inicio in range(0, len(palavras), passo):
              pedaco = palavras[inicio:inicio + tamanho]
              if pedaco:
                  chunks.append(" ".join(pedaco))
          return chunks

      Passo 2: transforme texto em embeddings

      Cada chunk vira um vetor numérico que representa seu significado — um embedding. Textos com significado parecido geram vetores próximos no espaço, o que permite busca por similaridade semântica em vez de busca por palavra exata. Esse é o coração do RAG. Se a ideia de "significado virar número" ainda é nebulosa, leia o que são embeddings? Representando significado em vetores.

      from openai import OpenAI
      
      client = OpenAI()
      
      def gerar_embedding(texto: str) -> list[float]:
          resp = client.embeddings.create(
              model="text-embedding-3-small",
              input=texto,
          )
          return resp.data[0].embedding

      Você gera embeddings para todos os chunks na indexação e, depois, para cada pergunta na consulta.

      Duas regras de ouro aqui. Primeiro, use o mesmo modelo de embedding na indexação e na consulta — vetores de modelos diferentes não são comparáveis. Segundo, se você trocar o modelo de embedding, reindexe tudo: os vetores antigos viram lixo no novo espaço. Por isso a escolha do modelo de embedding não é trivial e vale testar alguns antes de comprometer a base inteira.

      Passo 3: guarde os vetores em um banco vetorial

      Com milhares de embeddings, você precisa de uma estrutura que faça busca por similaridade rápido. É o papel do banco de dados vetorial, que indexa os vetores e responde "quais são os k mais parecidos com este?" em milissegundos. Entenda as opções em o que é um banco de dados vetorial?.

      A busca por similaridade no RAG é um caso de recuperação densa (dense retrieval): em vez de casar palavras, você compara vetores. Karpukhin et al. (2020) mostraram que essa abordagem supera a busca por palavras-chave tradicional em perguntas de domínio aberto, porque captura significado e não só termos literais.

      Vale entender o trade-off do índice. Bancos vetoriais usam algoritmos de busca aproximada de vizinhos (ANN, como HNSW) para serem rápidos em escala — o preço é abrir mão de uma fração da precisão em troca de muita velocidade. Para a maioria das aplicações, esse trade é excelente; só em bases pequenas faz sentido uma busca exata.

      Passo 4: recupere os trechos relevantes

      Na hora da pergunta, o fluxo é:

        O valor de k é um ajuste: poucos trechos podem deixar de fora a resposta; trechos demais diluem o contexto e elevam o custo. Comece com algo entre 3 e 5 e calibre.

        def recuperar(pergunta: str, k: int = 4) -> list[str]:
            emb = gerar_embedding(pergunta)
            resultados = banco_vetorial.buscar(vetor=emb, top_k=k)
            return [r.texto for r in resultados]

        Indo além do top-k simples: busca híbrida e reranking

        Quando a recuperação por embedding falha, costuma ser por dois motivos: a pergunta usa um termo exato (um código de produto, um nome próprio, uma sigla) que a busca semântica não prioriza, ou os top-k trazem trechos parecidos mas não os mais úteis. Duas técnicas atacam isso:

          Essas duas adições são, na prática, o que mais diferencia um RAG "de demo" de um RAG que aguenta perguntas reais.

          Passo 5: monte o prompt e gere a resposta

          Agora você combina os trechos recuperados com a pergunta num prompt bem estruturado. A instrução-chave é ancorar o modelo no contexto e permitir que ele admita ignorância.

          def responder(pergunta: str) -> str:
              trechos = recuperar(pergunta)
              contexto = "\n\n".join(trechos)
              prompt = (
                  "Use APENAS o contexto abaixo para responder. "
                  "Se a resposta não estiver no contexto, diga que não sabe.\n\n"
                  f"Contexto:\n{contexto}\n\n"
                  f"Pergunta: {pergunta}"
              )
              resp = client.chat.completions.create(
                  model="gpt-4o-mini",
                  messages=[{"role": "user", "content": prompt}],
              )
              return resp.choices[0].message.content

          Essa instrução de "use apenas o contexto" é o que mais reduz alucinação — assunto que aprofundo em o que é alucinação em IA e como reduzi-la.

          Para tornar a resposta rastreável, vale incluir um identificador em cada trecho e pedir que o modelo cite a fonte:

          contexto = "\n\n".join(f"[Fonte {i+1}] {t}" for i, t in enumerate(trechos))
          # e na instrução: "Cite a fonte entre colchetes ao usar uma informação."

          Assim a resposta vem com "[Fonte 2]" ao lado de cada afirmação, e o usuário consegue verificar.

          Reescrevendo a pergunta antes de buscar

          Um ganho de qualidade que muita gente esquece está antes da busca: a pergunta do usuário nem sempre é o melhor texto para gerar o embedding de consulta. Perguntas reais são curtas, cheias de pronomes e dependentes do histórico da conversa. "E o segundo caso?" não tem como recuperar nada útil sozinho.

          Duas técnicas resolvem isso:

            def reescrever_consulta(pergunta: str, historico: str) -> str:
                prompt = (
                    "Reescreva a pergunta abaixo como uma consulta de busca "
                    "autossuficiente, incorporando o contexto do histórico.\n\n"
                    f"Histórico:\n{historico}\n\nPergunta: {pergunta}\n\nConsulta:"
                )
                resp = client.chat.completions.create(
                    model="gpt-4o-mini",
                    messages=[{"role": "user", "content": prompt}],
                )
                return resp.choices[0].message.content

            Essa etapa custa uma chamada extra ao modelo, mas em sistemas conversacionais costuma ser a diferença entre uma recuperação que funciona e uma que falha silenciosamente.

            Por que o RAG vence o fine-tuning em muitos casos

            Uma dúvida frequente: por que não treinar o modelo nos meus dados? RAG costuma ser melhor quando:

              Fine-tuning ainda tem seu lugar (ensinar formato, tom, tarefas específicas), mas para "responder com base nos meus documentos", RAG quase sempre é o caminho. Os dois também não são excludentes: você pode usar fine-tuning para fixar o estilo das respostas e RAG para alimentar os fatos atualizados.

              Onde os sistemas RAG falham

              Conhecer as falhas comuns economiza semanas de frustração:

                Como depurar quando a resposta vem errada

                Quando um RAG erra, o reflexo de muitos é mexer no modelo gerador — quase sempre o lugar errado. Depure na ordem do pipeline:

                  Separar "falha de recuperação" de "falha de geração" é a habilidade de depuração mais importante em RAG.

                  Medindo a qualidade

                  Não confie no "parece que está bom". Avalie em duas frentes:

                    Monte um conjunto de perguntas de teste com respostas conhecidas e rode-o sempre que mudar chunking, modelo de embedding ou k.

                    Na avaliação da geração, três dimensões são úteis de medir separadamente:

                      Você pode automatizar parte dessa avaliação usando um LLM como juiz, comparando resposta e contexto — desde que valide o juiz contra um conjunto rotulado por humanos antes de confiar nele.

                      Mantendo a base saudável ao longo do tempo

                      Um RAG não é um projeto que você entrega e esquece — é um sistema vivo que degrada se a base não for cuidada. Alguns hábitos de manutenção evitam o apodrecimento silencioso:

                        A frase "lixo entra, lixo sai" vale em dose dupla para RAG: a qualidade da resposta nunca supera a qualidade e a organização dos documentos que você indexou.

                        Segurança e privacidade no pipeline

                        RAG levanta questões de segurança que valem atenção desde o início. Como você injeta conteúdo recuperado no prompt, sua aplicação fica exposta a prompt injection indireto: um documento malicioso na base pode conter instruções ocultas que o modelo segue ao processá-lo. Trate o conteúdo recuperado como dado não confiável e delimite-o claramente no prompt.

                        Há também a dimensão de controle de acesso. Se diferentes usuários têm permissão para ver diferentes documentos, a busca precisa respeitar isso. O padrão é filtrar por permissão no momento da recuperação — usando metadados de cada chunk — para que um usuário nunca receba, no contexto, um trecho que ele não poderia ler. Aplicar o filtro só na resposta final é tarde demais: o dado sensível já passou pelo modelo.

                        Perguntas frequentes

                        Qual o melhor tamanho de chunk? Não existe número universal. Para texto corrido, blocos de algumas centenas de tokens com 10–20% de sobreposição funcionam bem como ponto de partida. Documentos muito estruturados pedem chunking por seção. Meça com o seu conteúdo e ajuste.

                        Posso usar RAG sem banco vetorial? Em bases pequenas (poucas centenas de chunks), uma busca exata em memória, calculando a similaridade contra todos os vetores, resolve. O banco vetorial passa a valer a pena quando o volume cresce e a latência importa.

                        RAG elimina alucinação? Reduz drasticamente, mas não elimina. Se a recuperação falha ou o prompt não ancora bem o modelo, ele ainda pode inventar. Por isso a instrução de "use apenas o contexto" e a medição de fidelidade são essenciais.

                        Preciso reindexar quando adiciono um documento novo? Só o documento novo: gere os chunks e os embeddings dele e insira no banco. Reindexação completa só é necessária quando você troca o modelo de embedding ou a estratégia de chunking.

                        Conclusão

                        RAG é a forma mais prática e econômica de dar a um LLM conhecimento próprio, atualizado e rastreável. O segredo é entender que a qualidade do sistema depende sobretudo da recuperação: chunking bem-feito, bons embeddings, um banco vetorial eficiente, técnicas como busca híbrida e reranking, e um k calibrado. Com a recuperação certa e um prompt que ancora o modelo no contexto, você reduz alucinações drasticamente e ganha um assistente que responde sobre o que importa para você. Depure na ordem do pipeline, meça com rigor as dimensões de fidelidade e relevância, e refine o pipeline antes de culpar o modelo — é nele, não no gerador, que mora o ganho.

                        Referências

                          Leituras relacionadas

                          Nenhum comentário ainda

                          Seja o primeiro a comentar.

                          Deixe seu comentário

                          Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

                          Entrar com Canverly