RAG vs SQL: Por Que Seu Agente de IA Deveria Consultar, Não Gerar Embeddings
Toda semana, outra equipe anuncia que está “construindo RAG para seu data warehouse”. Eles dividem suas tabelas em vetores, armazenam no Pinecone e pedem a um LLM para responder perguntas de negócio. Funciona para documentos. Falha catastroficamente para dados estruturados.
O Problema: RAG em Tabelas
Veja o que acontece quando você usa RAG para dados estruturados:
# The RAG approach to answering "What was revenue in Q3?"
# Step 1: Chunk your tables into textchunks = []for row in orders_table: chunks.append(f"Order {row.id}: customer {row.customer}, " f"amount ${row.amount}, date {row.date}")
# Step 2: Embed chunksembeddings = openai.embed(chunks) # $0.0001 per chunk, 1M rows = $100
# Step 3: Store in vector DBpinecone_index.upsert(vectors=embeddings) # $70/month for 1M vectors
# Step 4: At query time, retrieve "relevant" chunksquery_embedding = openai.embed("What was revenue in Q3?")results = pinecone_index.query(query_embedding, top_k=20)
# Step 5: Ask LLM to synthesizeanswer = openai.chat( f"Based on these records: {results}\n" f"Answer: What was revenue in Q3?")# Returns: "Based on the retrieved records, Q3 revenue appears to be# approximately $2.3M" (actual answer: $4.7M)Por que falha:
- Recuperação perde linhas. Similaridade vetorial encontra chunks “relevantes”, não TODAS as linhas correspondentes. Se você tem 50.000 pedidos no Q3, recuperou 20.
- Agregação é impossível. Você não pode SUM, AVG ou COUNT a partir de 20 amostras aleatórias.
- Dados ficam desatualizados. Você precisa re-embed cada vez que dados mudam.
- É caro. Embeddings de milhões de linhas + hospedagem de vector DB + tokens LLM para contexto.
- Alucina com confiança. O LLM vê 20 linhas e extrapola.
A Abordagem Correta: SQL
A mesma pergunta respondida com SQL:
SELECT SUM(amount) AS revenueFROM ordersWHERE created_at >= '2024-07-01' AND created_at < '2024-10-01'Resultado: $4.712.843,00 — exato, instantâneo, grátis.
MCP + SQL com DataSpoc Lens
O DataSpoc Lens expõe seu data lake como um servidor MCP. Agentes de IA executam consultas SQL reais em vez de adivinhar a partir de embeddings.
Setup
pip install dataspoc-lens[mcp]dataspoc-lens add-bucket s3://company-lakedataspoc-lens mcpComo o Agente Funciona
from dataspoc_lens import LensClient
client = LensClient()
# Agent discovers what data existstables = client.list_tables()# ['raw.postgres.orders', 'raw.postgres.customers', 'raw.postgres.products']
# Agent inspects schemaschema = client.get_schema("raw.postgres.orders")# [{"name": "id", "type": "INTEGER"},# {"name": "customer_id", "type": "INTEGER"},# {"name": "amount", "type": "DECIMAL"},# {"name": "created_at", "type": "TIMESTAMP"}, ...]
# Agent writes and executes SQLresult = client.query(""" SELECT SUM(amount) AS revenue FROM raw.postgres.orders WHERE created_at >= '2024-07-01' AND created_at < '2024-10-01'""")# [{"revenue": 4712843.00}]Sem embeddings. Sem vector DB. Sem alucinação. Dados reais, respostas reais.
Comparação de Código Lado a Lado
Pipeline RAG (o que equipes estão construindo)
# ingest.py — runs daily, takes 45 minutes for 1M rowsimport pandas as pdfrom langchain.text_splitter import RecursiveCharacterTextSplitterfrom langchain_openai import OpenAIEmbeddingsfrom langchain_pinecone import PineconeVectorStore
df = pd.read_parquet("s3://company-lake/raw/postgres/orders/")
# Convert rows to text chunksdocuments = []for _, row in df.iterrows(): text = f"Order {row['id']}: {row['customer_name']} purchased " text += f"${row['amount']:.2f} on {row['created_at']}" documents.append(text)
# Split and embedsplitter = RecursiveCharacterTextSplitter(chunk_size=500)chunks = splitter.create_documents(documents)embeddings = OpenAIEmbeddings()vectorstore = PineconeVectorStore.from_documents(chunks, embeddings, index_name="orders")
# query.py — at query timeretriever = vectorstore.as_retriever(search_kwargs={"k": 50})docs = retriever.get_relevant_documents("What was Q3 revenue?")# Gets 50 random-ish order records out of 250,000 Q3 ordersPipeline SQL (o que você deveria construir)
# query.py — that's it, no ingest step neededfrom dataspoc_lens import LensClient
client = LensClient()result = client.query("SELECT SUM(amount) FROM orders WHERE quarter = 'Q3-2024'")# Scans all 250,000 rows in 0.3 seconds, returns exact answerA Comparação
| Dimensão | RAG em Tabelas | MCP + SQL |
|---|---|---|
| Precisão | ~60% (perde linhas, não agrega) | 99%+ (computação exata) |
| Velocidade | 3-8 segundos (embed query + recuperar + LLM) | 0,1-2 segundos (execução SQL) |
| Atualidade | Desatualizado até re-embed (horas/dias) | Tempo real (consulta dados vivos) |
| Custo por consulta | $0,02-0,10 (embedding + tokens LLM) | $0,001 (apenas compute DuckDB) |
| Infra mensal | $200+ (vector DB + API de embedding) | $0 (DuckDB é grátis) |
| Auditabilidade | ”Baseado no contexto recuperado…” | Consulta SQL exata, reproduzível |
| Agregações | Impossível (SUM de 20 linhas != SUM de 250K) | Nativo (é pra isso que SQL serve) |
| Joins | Não dá para fazer join entre embeddings | Joins multi-tabela nativos |
| Atualizações | Re-embed dataset inteiro | Automático (lê o Parquet mais recente) |
Quando RAG É a Escolha Certa
RAG é excelente para documentos não estruturados:
- Relatórios em PDF, contratos legais, tickets de suporte
- Busca em documentação
- Bases de conhecimento com conteúdo em prosa
- Qualquer dado onde busca por palavra-chave/semântica é o padrão de acesso
A regra é simples:
Tabelas e números = SQL. Documentos e prosa = RAG.
Uma Arquitetura Híbrida
Para a maioria das empresas, a melhor arquitetura combina ambos:
┌─────────────────────────────────────────────────┐│ AI Agent │├─────────────────────────────────────────────────┤│ ││ Structured data? ──► DataSpoc Lens (MCP + SQL) ││ ││ Unstructured docs? ──► RAG (vector search) ││ │└─────────────────────────────────────────────────┘from dataspoc_lens import LensClientfrom langchain_pinecone import PineconeVectorStore
lens = LensClient()vectorstore = PineconeVectorStore(index_name="docs")
def route_question(question: str) -> str: """Route to SQL for data questions, RAG for document questions.""" data_keywords = ["revenue", "count", "average", "total", "growth", "last month", "quarterly", "how many", "top 10"]
if any(kw in question.lower() for kw in data_keywords): # SQL path — exact answers from structured data result = lens.ask(question) return result["answer"] else: # RAG path — semantic search over documents docs = vectorstore.similarity_search(question, k=5) return synthesize_from_docs(docs, question)O Custo de Errar Isso
Um exemplo real de uma startup Series B:
| Métrica | Setup RAG deles | Após mudar para SQL |
|---|---|---|
| Custo mensal | $2.400 (Pinecone + embeddings OpenAI) | $0 (DuckDB em Parquet) |
| Precisão das consultas | 58% (validado contra números reais) | 99,7% |
| Atualidade dos dados | Atraso de 6 horas (re-embedding diário) | Tempo real |
| Manutenção | 2 engenheiros parcialmente | Zero (self-service) |
| Tempo para responder | 4,2 segundos em média | 0,8 segundos em média |
Eles estavam gastando $2.400/mês para obter respostas erradas lentamente.
Começando
# If your data is already in Parquet/S3:pip install dataspoc-lens[mcp]dataspoc-lens add-bucket s3://your-bucketdataspoc-lens mcp # Start MCP server for AI agents
# If your data is in Postgres/MySQL:pip install dataspoc-pipedataspoc-pipe init && dataspoc-pipe add postgres --tables ordersdataspoc-pipe run # Extracts to Parquet
# Then query with SQLdataspoc-lens query "SELECT SUM(amount) FROM raw.postgres.orders WHERE created_at > '2024-01-01'"Pare de gerar embeddings das suas tabelas. Comece a consultá-las.