Como usar o LlamaIndex para indexar e consultar documentos privados
1. Introdução ao LlamaIndex e contextualização no ecossistema RAG
O LlamaIndex (anteriormente GPT Index) é um framework especializado na construção de sistemas de Retrieval-Augmented Generation (RAG) para documentos privados. Diferentemente do LangChain, que oferece uma abordagem mais genérica para orquestração de LLMs, o LlamaIndex foca exclusivamente na indexação eficiente e consulta de dados estruturados e não estruturados.
Principais diferenciais:
- Pipeline otimizado: do parsing ao armazenamento vetorial em poucas linhas de código
- Múltiplas estratégias de indexação: VectorStoreIndex, SummaryIndex, KeywordTableIndex
- Controle granular: metadados, filtros e estratégias de chunking personalizadas
Cenários típicos incluem: contratos confidenciais, bases de conhecimento internas, documentação técnica proprietária e dados regulatórios.
2. Configuração do ambiente e instalação do LlamaIndex
Para garantir privacidade total, utilizaremos modelos locais. Instale as dependências essenciais:
pip install llama-index-core llama-index-embeddings-huggingface llama-index-llms-ollama
Para processamento de PDFs e DOCXs, adicione:
pip install pypdf python-docx markdown
Configuração inicial do ambiente:
import logging
import os
from llama_index.core import Settings
# Logs para depuração
logging.basicConfig(level=logging.INFO)
# Configuração para execução 100% local
Settings.llm = None # Será configurado com Ollama posteriormente
Settings.embed_model = "local:BAAI/bge-small-en-v1.5"
3. Carregamento e parsing de documentos privados
O LlamaIndex suporta múltiplos formatos nativamente. Exemplo de carregamento de um contrato em PDF:
from llama_index.core import SimpleDirectoryReader
from llama_index.core.node_parser import SentenceSplitter
# Carregar documentos de uma pasta segura
reader = SimpleDirectoryReader(
input_dir="./documentos_privados",
recursive=True,
required_exts=[".pdf", ".docx", ".txt", ".md"]
)
documents = reader.load_data()
# Estratégia de chunking: 512 tokens com sobreposição de 128
parser = SentenceSplitter(
chunk_size=512,
chunk_overlap=128,
separator=" ",
paragraph_separator="\n\n"
)
nodes = parser.get_nodes_from_documents(documents)
print(f"Total de chunks criados: {len(nodes)}")
Tratamento de metadados sensíveis:
for node in nodes:
# Remover metadados sensíveis antes da indexação
if "email" in node.metadata:
del node.metadata["email"]
if "cpf" in node.metadata:
del node.metadata["cpf"]
4. Criação de embeddings e indexação dos documentos
Seleção de modelo de embedding local (HuggingFace) para máxima privacidade:
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core import VectorStoreIndex, StorageContext
from llama_index.core.storage.docstore import SimpleDocumentStore
# Modelo de embedding local (sem envio de dados para nuvem)
embed_model = HuggingFaceEmbedding(
model_name="BAAI/bge-small-en-v1.5",
device="cpu" # ou "cuda" se tiver GPU
)
# Criação do índice vetorial
index = VectorStoreIndex(
nodes=nodes,
embed_model=embed_model,
show_progress=True
)
# Persistência do índice em disco
index.storage_context.persist(persist_dir="./indice_privado")
print("Índice salvo em ./indice_privado")
Estrutura dos índices disponíveis:
- VectorStoreIndex: melhor para similaridade semântica
- SummaryIndex: ideal para sumarização de documentos longos
- KeywordTableIndex: útil para buscas por palavras-chave exatas
5. Implementação do mecanismo de consulta (Query Engine)
Configuração do query engine com parâmetros otimizados:
from llama_index.core import QueryBundle
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core.retrievers import VectorIndexRetriever
# Configuração do retriever
retriever = VectorIndexRetriever(
index=index,
similarity_top_k=5, # Recupera os 5 chunks mais similares
filters=None, # Pode adicionar filtros de metadados
)
# Query engine básico
query_engine = RetrieverQueryEngine.from_args(
retriever=retriever,
response_mode="compact", # "tree_summarize", "refine", "compact"
verbose=True
)
# Exemplo de consulta
response = query_engine.query("Quais são as cláusulas de rescisão do contrato?")
print(response)
Filtros de metadados para consultas específicas:
from llama_index.core.vector_stores import MetadataFilters, ExactMatchFilter
filters = MetadataFilters(
filters=[
ExactMatchFilter(key="categoria", value="contrato"),
ExactMatchFilter(key="ano", value="2024")
]
)
retriever_filtrado = VectorIndexRetriever(
index=index,
similarity_top_k=3,
filters=filters
)
6. Garantia de privacidade e segurança dos dados
Execução 100% local com Ollama para LLM:
from llama_index.llms.ollama import Ollama
# LLM local via Ollama (sem envio de dados para internet)
llm = Ollama(
model="llama3.1:8b", # ou "mistral", "gemma:2b"
temperature=0.1,
request_timeout=60.0
)
# Atualizar o query engine com LLM local
query_engine = RetrieverQueryEngine.from_args(
retriever=retriever,
llm=llm,
response_mode="compact"
)
Sanitização adicional de documentos:
import re
def sanitizar_documento(texto):
# Remover padrões de dados sensíveis
texto = re.sub(r'\b\d{3}\.\d{3}\.\d{3}-\d{2}\b', '[CPF REMOVIDO]', texto) # CPF
texto = re.sub(r'\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b', '[EMAIL REMOVIDO]', texto) # Email
return texto
# Aplicar antes da indexação
for node in nodes:
node.text = sanitizar_documento(node.text)
Controle de acesso por níveis:
# Adicionar metadados de permissão
for node in nodes:
node.metadata["nivel_acesso"] = "confidencial" # ou "restrito", "publico"
7. Otimização e boas práticas para documentos privados
Compressão de índice para grandes volumes:
from llama_index.core.indices.postprocessor import SentenceTransformerRerank
# Re-ranking para melhorar precisão
rerank = SentenceTransformerRerank(
model="cross-encoder/ms-marco-MiniLM-L-2-v2",
top_n=3
)
query_engine = RetrieverQueryEngine.from_args(
retriever=retriever,
node_postprocessors=[rerank],
llm=llm
)
Caching de consultas frequentes:
from functools import lru_cache
@lru_cache(maxsize=100)
def consulta_cacheada(pergunta: str) -> str:
return str(query_engine.query(pergunta))
# Uso
resposta = consulta_cacheada("Qual o valor do contrato?")
8. Exemplo prático completo: do documento à consulta funcional
Código completo para indexar e consultar um contrato confidencial:
import os
from llama_index.core import (
SimpleDirectoryReader,
VectorStoreIndex,
Settings
)
from llama_index.core.node_parser import SentenceSplitter
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.ollama import Ollama
# 1. Configuração
Settings.embed_model = HuggingFaceEmbedding(
model_name="BAAI/bge-small-en-v1.5"
)
Settings.llm = Ollama(model="llama3.1:8b", temperature=0.1)
# 2. Carregamento
reader = SimpleDirectoryReader(input_dir="./contratos")
documents = reader.load_data()
# 3. Parsing
parser = SentenceSplitter(chunk_size=512, chunk_overlap=128)
nodes = parser.get_nodes_from_documents(documents)
# 4. Indexação
index = VectorStoreIndex(nodes=nodes)
index.storage_context.persist(persist_dir="./indice_contratos")
# 5. Consulta
query_engine = index.as_query_engine(
similarity_top_k=5,
response_mode="compact"
)
# Teste
pergunta = "Qual a data de vigência do contrato?"
resposta = query_engine.query(pergunta)
print(f"Pergunta: {pergunta}")
print(f"Resposta: {resposta}")
Para integrar com Streamlit:
# app.py (Streamlit)
import streamlit as st
from llama_index.core import StorageContext, load_index_from_storage
st.title("Consulta de Documentos Privados")
# Carregar índice persistido
storage_context = StorageContext.from_defaults(persist_dir="./indice_contratos")
index = load_index_from_storage(storage_context)
pergunta = st.text_input("Digite sua pergunta sobre os contratos:")
if pergunta:
query_engine = index.as_query_engine()
resposta = query_engine.query(pergunta)
st.write(resposta.response)
Referências
- Documentação Oficial do LlamaIndex — Guia completo de instalação, indexação e consulta com exemplos práticos
- Tutorial: RAG com Documentos Privados (LlamaIndex + Ollama) — Artigo técnico sobre execução 100% local com modelos open-source
- LlamaIndex: Vector Store Index Documentation — Documentação detalhada sobre índices vetoriais e parâmetros de configuração
- HuggingFace Embeddings no LlamaIndex — Tutorial sobre uso de embeddings locais via HuggingFace para privacidade
- Ollama + LlamaIndex: Guia Prático — Notebook Jupyter completo com integração Ollama para LLM local
- Boas Práticas de Chunking para RAG — Documentação sobre estratégias de divisão de documentos e tratamento de metadados
- LlamaIndex: Filtros de Metadados e Segurança — Guia sobre controle de acesso e filtros para documentos sensíveis