Spaces:
Build error
Build error
Upload folder using huggingface_hub
Browse files- README.md +12 -6
- app.py +259 -0
- requirements.txt +4 -0
README.md
CHANGED
|
@@ -1,12 +1,18 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
|
|
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: RAG Playground
|
| 3 |
+
emoji: 📚
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.9.1
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
short_description: Upload docs, ask questions, see RAG in action
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# RAG Playground
|
| 15 |
+
|
| 16 |
+
Upload your documents, ask questions, and see how RAG retrieves and generates answers.
|
| 17 |
+
|
| 18 |
+
Part of the **AI for Product Managers** course by Data Trainers LLC.
|
app.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import os
|
| 3 |
+
from sentence_transformers import SentenceTransformer
|
| 4 |
+
import chromadb
|
| 5 |
+
from chromadb.config import Settings
|
| 6 |
+
import hashlib
|
| 7 |
+
import re
|
| 8 |
+
|
| 9 |
+
# Initialize embedding model
|
| 10 |
+
model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 11 |
+
|
| 12 |
+
# Initialize ChromaDB
|
| 13 |
+
chroma_client = chromadb.Client(Settings(anonymized_telemetry=False))
|
| 14 |
+
|
| 15 |
+
# Sample documents
|
| 16 |
+
SAMPLE_DOCS = {
|
| 17 |
+
"Support FAQ": """
|
| 18 |
+
Q: What is your return policy?
|
| 19 |
+
A: You can return most items within 30 days of purchase for a full refund. Items must be in original condition with tags attached. Electronics have a 15-day return window.
|
| 20 |
+
|
| 21 |
+
Q: How long does shipping take?
|
| 22 |
+
A: Standard shipping takes 5-7 business days. Express shipping takes 2-3 business days. Free shipping on orders over $50.
|
| 23 |
+
|
| 24 |
+
Q: How do I track my order?
|
| 25 |
+
A: Once your order ships, you'll receive an email with a tracking number. You can also log into your account to view order status.
|
| 26 |
+
|
| 27 |
+
Q: What payment methods do you accept?
|
| 28 |
+
A: We accept Visa, Mastercard, American Express, PayPal, and Apple Pay. All transactions are encrypted and secure.
|
| 29 |
+
|
| 30 |
+
Q: How do I contact customer support?
|
| 31 |
+
A: You can reach us via email at support@example.com, phone at 1-800-EXAMPLE, or live chat on our website. Support hours are 9am-6pm EST.
|
| 32 |
+
""",
|
| 33 |
+
"Product Manual": """
|
| 34 |
+
Product: Smart Home Hub X1
|
| 35 |
+
|
| 36 |
+
Setup Instructions:
|
| 37 |
+
1. Unbox the device and connect the power adapter
|
| 38 |
+
2. Download the SmartHome app from your app store
|
| 39 |
+
3. Create an account or sign in
|
| 40 |
+
4. Press the pairing button on the hub for 5 seconds until the light blinks blue
|
| 41 |
+
5. Follow the in-app instructions to complete setup
|
| 42 |
+
|
| 43 |
+
Troubleshooting:
|
| 44 |
+
- If the hub won't connect: Ensure your WiFi is 2.4GHz (not 5GHz). The hub doesn't support 5GHz networks.
|
| 45 |
+
- If lights are unresponsive: Check that the hub firmware is updated in the app settings.
|
| 46 |
+
- If voice commands fail: Verify that your voice assistant is linked in the Integrations menu.
|
| 47 |
+
|
| 48 |
+
Specifications:
|
| 49 |
+
- Dimensions: 4" x 4" x 1.5"
|
| 50 |
+
- Weight: 8 oz
|
| 51 |
+
- Connectivity: WiFi 2.4GHz, Bluetooth 5.0, Zigbee
|
| 52 |
+
- Power: 5V DC, 2A adapter included
|
| 53 |
+
- Compatible with: Alexa, Google Home, Apple HomeKit
|
| 54 |
+
|
| 55 |
+
Warranty: 2-year limited warranty. Register at example.com/warranty within 30 days of purchase.
|
| 56 |
+
""",
|
| 57 |
+
"Company Policy": """
|
| 58 |
+
Remote Work Policy - Effective January 2024
|
| 59 |
+
|
| 60 |
+
Eligibility:
|
| 61 |
+
All full-time employees who have completed their 90-day probation period are eligible for remote work. Certain roles requiring physical presence (e.g., facilities, reception) are exempt.
|
| 62 |
+
|
| 63 |
+
Guidelines:
|
| 64 |
+
- Core hours: All remote employees must be available 10am-3pm in their local timezone for meetings and collaboration.
|
| 65 |
+
- Equipment: The company provides a laptop and monitor. Employees are responsible for reliable internet (minimum 25 Mbps).
|
| 66 |
+
- Workspace: Employees must have a dedicated workspace that allows for video calls without disruption.
|
| 67 |
+
|
| 68 |
+
Expectations:
|
| 69 |
+
- Respond to messages within 2 hours during core hours
|
| 70 |
+
- Attend all scheduled meetings with camera on
|
| 71 |
+
- Complete weekly status updates in the project management tool
|
| 72 |
+
- Be available for occasional in-office days (minimum 2 per month)
|
| 73 |
+
|
| 74 |
+
Expenses:
|
| 75 |
+
- Home office stipend: $500 one-time for setup
|
| 76 |
+
- Internet reimbursement: Up to $50/month
|
| 77 |
+
- Coworking space: Pre-approved expenses reimbursed
|
| 78 |
+
|
| 79 |
+
Violations of this policy may result in revocation of remote work privileges.
|
| 80 |
+
"""
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def chunk_text(text, chunk_size=500, overlap=100):
|
| 85 |
+
"""Split text into overlapping chunks."""
|
| 86 |
+
chunks = []
|
| 87 |
+
start = 0
|
| 88 |
+
while start < len(text):
|
| 89 |
+
end = start + chunk_size
|
| 90 |
+
chunk = text[start:end]
|
| 91 |
+
chunks.append(chunk.strip())
|
| 92 |
+
start = end - overlap
|
| 93 |
+
return [c for c in chunks if c]
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def process_documents(doc_text, chunk_size, overlap_pct):
|
| 97 |
+
"""Process documents into chunks and store in ChromaDB."""
|
| 98 |
+
overlap = int(chunk_size * overlap_pct / 100)
|
| 99 |
+
chunks = chunk_text(doc_text, chunk_size, overlap)
|
| 100 |
+
|
| 101 |
+
# Create unique collection name
|
| 102 |
+
collection_name = f"docs_{hashlib.md5(doc_text.encode()).hexdigest()[:8]}"
|
| 103 |
+
|
| 104 |
+
# Delete if exists
|
| 105 |
+
try:
|
| 106 |
+
chroma_client.delete_collection(collection_name)
|
| 107 |
+
except:
|
| 108 |
+
pass
|
| 109 |
+
|
| 110 |
+
collection = chroma_client.create_collection(name=collection_name)
|
| 111 |
+
|
| 112 |
+
# Generate embeddings and store
|
| 113 |
+
embeddings = model.encode(chunks).tolist()
|
| 114 |
+
ids = [f"chunk_{i}" for i in range(len(chunks))]
|
| 115 |
+
|
| 116 |
+
collection.add(
|
| 117 |
+
embeddings=embeddings,
|
| 118 |
+
documents=chunks,
|
| 119 |
+
ids=ids
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
return collection, chunks
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def query_rag(question, doc_text, chunk_size, overlap_pct, top_k, use_openai):
|
| 126 |
+
"""Query the RAG system."""
|
| 127 |
+
if not doc_text.strip():
|
| 128 |
+
return "Please provide documents first.", "", ""
|
| 129 |
+
|
| 130 |
+
# Process documents
|
| 131 |
+
collection, chunks = process_documents(doc_text, chunk_size, overlap_pct)
|
| 132 |
+
|
| 133 |
+
# Embed query
|
| 134 |
+
query_embedding = model.encode([question]).tolist()
|
| 135 |
+
|
| 136 |
+
# Retrieve
|
| 137 |
+
results = collection.query(
|
| 138 |
+
query_embeddings=query_embedding,
|
| 139 |
+
n_results=min(top_k, len(chunks))
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
retrieved_chunks = results['documents'][0]
|
| 143 |
+
distances = results['distances'][0]
|
| 144 |
+
|
| 145 |
+
# Format retrieved context
|
| 146 |
+
context_display = ""
|
| 147 |
+
for i, (chunk, dist) in enumerate(zip(retrieved_chunks, distances)):
|
| 148 |
+
similarity = 1 - dist # Convert distance to similarity
|
| 149 |
+
context_display += f"**Chunk {i+1}** (similarity: {similarity:.2f})\n"
|
| 150 |
+
context_display += f"```\n{chunk}\n```\n\n"
|
| 151 |
+
|
| 152 |
+
# Generate answer
|
| 153 |
+
context = "\n\n".join(retrieved_chunks)
|
| 154 |
+
|
| 155 |
+
if use_openai and os.environ.get("OPENAI_API_KEY"):
|
| 156 |
+
try:
|
| 157 |
+
from openai import OpenAI
|
| 158 |
+
client = OpenAI()
|
| 159 |
+
|
| 160 |
+
prompt = f"""Based on the following context, answer the question. Only use information from the context. If the answer is not in the context, say "I don't have information about that in the provided documents."
|
| 161 |
+
|
| 162 |
+
Context:
|
| 163 |
+
{context}
|
| 164 |
+
|
| 165 |
+
Question: {question}
|
| 166 |
+
|
| 167 |
+
Answer:"""
|
| 168 |
+
|
| 169 |
+
response = client.chat.completions.create(
|
| 170 |
+
model="gpt-4o-mini",
|
| 171 |
+
messages=[{"role": "user", "content": prompt}],
|
| 172 |
+
temperature=0
|
| 173 |
+
)
|
| 174 |
+
answer = response.choices[0].message.content
|
| 175 |
+
except Exception as e:
|
| 176 |
+
answer = f"OpenAI API error: {str(e)}. Using fallback response."
|
| 177 |
+
answer += f"\n\nBased on the retrieved context, the relevant information is:\n{context[:500]}..."
|
| 178 |
+
else:
|
| 179 |
+
# Fallback: simple extractive response
|
| 180 |
+
answer = f"**[Demo Mode - No API Key]**\n\nBased on semantic search, the most relevant information for your question is:\n\n{retrieved_chunks[0]}"
|
| 181 |
+
if len(retrieved_chunks) > 1:
|
| 182 |
+
answer += f"\n\nAdditional relevant context:\n{retrieved_chunks[1][:200]}..."
|
| 183 |
+
|
| 184 |
+
# Groundedness analysis
|
| 185 |
+
groundedness = f"Retrieved {len(retrieved_chunks)} chunks. Top similarity: {1-distances[0]:.2f}"
|
| 186 |
+
|
| 187 |
+
return answer, context_display, groundedness
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def load_sample(sample_name):
|
| 191 |
+
"""Load a sample document."""
|
| 192 |
+
return SAMPLE_DOCS.get(sample_name, "")
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
# Build Gradio interface
|
| 196 |
+
with gr.Blocks(title="RAG Playground", theme=gr.themes.Soft()) as demo:
|
| 197 |
+
gr.Markdown(
|
| 198 |
+
"# RAG Playground\n\n"
|
| 199 |
+
"**PM Decision:** Should your team build RAG? Use this to understand what they're "
|
| 200 |
+
"proposing, see where it fails, and estimate costs before committing.\n\n"
|
| 201 |
+
"Upload YOUR documents, ask questions, see how RAG retrieves and generates answers."
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
with gr.Row():
|
| 205 |
+
with gr.Column(scale=1):
|
| 206 |
+
gr.Markdown("### 1. Documents")
|
| 207 |
+
sample_dropdown = gr.Dropdown(
|
| 208 |
+
choices=list(SAMPLE_DOCS.keys()),
|
| 209 |
+
label="Load Sample Document",
|
| 210 |
+
value="Support FAQ"
|
| 211 |
+
)
|
| 212 |
+
doc_input = gr.Textbox(
|
| 213 |
+
label="Document Text",
|
| 214 |
+
placeholder="Paste your document here or select a sample above...",
|
| 215 |
+
lines=10,
|
| 216 |
+
value=SAMPLE_DOCS["Support FAQ"]
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
gr.Markdown("### 2. RAG Configuration")
|
| 220 |
+
chunk_size = gr.Slider(100, 1000, value=500, step=50, label="Chunk Size (characters)")
|
| 221 |
+
overlap = gr.Slider(0, 50, value=20, step=5, label="Overlap (%)")
|
| 222 |
+
top_k = gr.Slider(1, 10, value=3, step=1, label="Chunks to Retrieve")
|
| 223 |
+
use_openai = gr.Checkbox(label="Use OpenAI for generation (requires API key)", value=True)
|
| 224 |
+
|
| 225 |
+
with gr.Column(scale=1):
|
| 226 |
+
gr.Markdown("### 3. Ask a Question")
|
| 227 |
+
question_input = gr.Textbox(
|
| 228 |
+
label="Your Question",
|
| 229 |
+
placeholder="e.g., What is the return policy?",
|
| 230 |
+
lines=2
|
| 231 |
+
)
|
| 232 |
+
query_btn = gr.Button("Ask RAG", variant="primary")
|
| 233 |
+
|
| 234 |
+
gr.Markdown("### 4. Results")
|
| 235 |
+
answer_output = gr.Markdown(label="Generated Answer")
|
| 236 |
+
|
| 237 |
+
with gr.Accordion("Retrieved Chunks", open=False):
|
| 238 |
+
chunks_output = gr.Markdown()
|
| 239 |
+
|
| 240 |
+
groundedness_output = gr.Textbox(label="Retrieval Stats", interactive=False)
|
| 241 |
+
|
| 242 |
+
# Event handlers
|
| 243 |
+
sample_dropdown.change(load_sample, sample_dropdown, doc_input)
|
| 244 |
+
|
| 245 |
+
query_btn.click(
|
| 246 |
+
query_rag,
|
| 247 |
+
inputs=[question_input, doc_input, chunk_size, overlap, top_k, use_openai],
|
| 248 |
+
outputs=[answer_output, chunks_output, groundedness_output]
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
gr.Markdown(
|
| 252 |
+
"---\n"
|
| 253 |
+
"**PM Takeaway:** RAG quality depends on chunking and retrieval - ask your team "
|
| 254 |
+
"how they're handling documents that don't fit neatly into chunks.\n\n"
|
| 255 |
+
"*AI for Product Managers*"
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
if __name__ == "__main__":
|
| 259 |
+
demo.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
sentence-transformers
|
| 2 |
+
chromadb
|
| 3 |
+
pypdf
|
| 4 |
+
openai
|