sql-books-rag / app.py
Krippa
Deploy RAG app with Groq LFS
eb22b1f
Raw
History Blame Contribute Delete
5.82 kB
"""
app.py - SQL Books RAG application powered by Gradio.
Retrieves relevant chunks from a FAISS index and generates answers
using Llama 3 via the Groq API (free tier).
"""
import json
import os
import faiss
import gradio as gr
import numpy as np
import requests
from sentence_transformers import SentenceTransformer
# -- Configuration -------------------------------------------------------------
INDEX_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "faiss_index")
EMBED_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
GEN_MODEL = "llama-3.1-8b-instant"
API_URL = "https://api.groq.com/openai/v1/chat/completions"
GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "")
TOP_K = 5
# -- Load resources once at startup --------------------------------------------
print("Loading embedding model ...")
embedder = SentenceTransformer(EMBED_MODEL)
print("Loading FAISS index ...")
index = faiss.read_index(os.path.join(INDEX_DIR, "index.faiss"))
print("Loading chunk metadata ...")
with open(os.path.join(INDEX_DIR, "chunks.json"), "r", encoding="utf-8") as f:
chunks = json.load(f)
print(f"Ready: {index.ntotal} vectors, {len(chunks)} chunks")
print(f"LLM: {GEN_MODEL} via Groq API")
print("App ready.")
# -- RAG pipeline ---------------------------------------------------------------
def retrieve(query: str, top_k: int = TOP_K):
"""Embed the query and retrieve the top-k most similar chunks."""
query_vec = embedder.encode([query]).astype("float32")
distances, indices = index.search(query_vec, top_k)
results = []
for dist, idx in zip(distances[0], indices[0]):
if idx < len(chunks):
results.append({
"text": chunks[idx]["text"],
"source": chunks[idx]["source"],
"distance": float(dist),
})
return results
def generate_answer(query: str, context_chunks: list) -> str:
"""Build a prompt from retrieved context and generate an answer via Groq API."""
context = "\n\n".join(
f"[Source: {c['source']}]\n{c['text'][:800]}" for c in context_chunks
)
system_message = (
"You are a helpful SQL tutor. Answer the user's question using ONLY the "
"provided context from SQL textbooks. Give clear, detailed explanations with "
"examples where appropriate. If the context doesn't contain enough information, "
"say so honestly. Format your answer using markdown."
)
user_message = (
f"## Context from SQL Textbooks\n\n{context}\n\n"
f"---\n\n## Question\n{query}"
)
headers = {
"Authorization": f"Bearer {GROQ_API_KEY}",
"Content-Type": "application/json",
}
payload = {
"model": GEN_MODEL,
"messages": [
{"role": "system", "content": system_message},
{"role": "user", "content": user_message},
],
"max_tokens": 1024,
"temperature": 0.3,
}
try:
response = requests.post(API_URL, headers=headers, json=payload, timeout=60)
response.raise_for_status()
result = response.json()
return result["choices"][0]["message"]["content"].strip()
except requests.exceptions.HTTPError:
return f"⚠️ API error ({response.status_code}): {response.text}"
except Exception as e:
return f"⚠️ Generation error: {e}"
def rag_query(question: str):
"""Full RAG pipeline: retrieve, generate, format output."""
if not question.strip():
return "Please enter a question.", ""
# Retrieve
retrieved = retrieve(question)
# Generate
answer = generate_answer(question, retrieved)
# Format sources
sources_text = "\n\n---\n\n".join(
f"**Source {i+1}** - *{r['source']}*\n\n{r['text'][:500]}{'...' if len(r['text']) > 500 else ''}"
for i, r in enumerate(retrieved)
)
return answer, sources_text
# -- Gradio UI ------------------------------------------------------------------
DESCRIPTION = """
# 📚 SQL Books RAG
Ask any question about SQL and get answers grounded in content from **5 SQL textbooks**:
- *Practical SQL: A Beginner's Guide to Storytelling with Data*
- *SQL for Data Scientists* (Renee M. Teate)
- *SQL for Data Analysis: Advanced Techniques for Transforming Data into Insights*
- *The Art of SQL*
- *Learning SQL: Generate, Manipulate, and Retrieve Data*
Powered by **FAISS** retrieval + **Llama 3.1** generation.
"""
EXAMPLES = [
"What is a JOIN in SQL?",
"Explain the difference between INNER JOIN and LEFT JOIN",
"How do window functions work in SQL?",
"What is a subquery and when should I use one?",
"How do I use GROUP BY with HAVING?",
"What are common table expressions (CTEs)?",
]
my_theme = gr.themes.Soft(
primary_hue="indigo",
secondary_hue="blue",
)
with gr.Blocks(title="SQL Books RAG", theme=my_theme) as demo:
gr.Markdown(DESCRIPTION)
with gr.Row():
with gr.Column(scale=3):
question = gr.Textbox(
label="Your SQL Question",
placeholder="e.g. What is a JOIN in SQL?",
lines=2,
)
submit_btn = gr.Button("Ask", variant="primary", size="lg")
with gr.Column(scale=1):
gr.Markdown("### Try these examples")
for ex in EXAMPLES:
gr.Button(ex, size="sm").click(
fn=lambda e=ex: e, outputs=question
)
answer_box = gr.Markdown(label="Answer")
with gr.Accordion("Retrieved Source Chunks", open=False):
sources_box = gr.Markdown()
submit_btn.click(fn=rag_query, inputs=question, outputs=[answer_box, sources_box])
question.submit(fn=rag_query, inputs=question, outputs=[answer_box, sources_box])
if __name__ == "__main__":
demo.launch()