Spaces:
Sleeping
Sleeping
File size: 9,118 Bytes
bf7fdad a1c3ab4 bf7fdad da8ff01 bf7fdad da8ff01 a1c3ab4 bf7fdad da8ff01 a1c3ab4 da8ff01 bf7fdad a1c3ab4 bf7fdad a1c3ab4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | # βββββββββββββββββββββββββββββββββββββββββ
# NovaDXB β rag_engine.py
# RAG Pipeline β LlamaIndex + Pinecone
# βββββββββββββββββββββββββββββββββββββββββ
import os
from dotenv import load_dotenv
# LlamaIndex
from llama_index.core import (
VectorStoreIndex,
SimpleDirectoryReader,
StorageContext,
Settings
)
from llama_index.core.node_parser import SentenceSplitter
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.vector_stores.pinecone import PineconeVectorStore
from llama_index.llms.openai import OpenAI
# Pinecone
from pinecone import Pinecone
# Load environment variables
load_dotenv()
# βββββββββββββββββββββββββββββββββββββββββ
# CONFIGURATION
# βββββββββββββββββββββββββββββββββββββββββ
KNOWLEDGE_BASE_DIR = "data"
PINECONE_INDEX_NAME = os.environ.get("PINECONE_INDEX_NAME", "novadxb")
EMBED_MODEL = "text-embedding-ada-002"
LLM_MODEL = "gpt-4o-mini"
CHUNK_SIZE = 512
CHUNK_OVERLAP = 64
# βββββββββββββββββββββββββββββββββββββββββ
# GLOBAL β query engine (loaded once)
# βββββββββββββββββββββββββββββββββββββββββ
query_engine = None
# βββββββββββββββββββββββββββββββββββββββββ
# STEP 1 β Connect to Pinecone
# βββββββββββββββββββββββββββββββββββββββββ
def get_pinecone_index():
"""Connect to existing Pinecone index."""
pc = Pinecone(api_key=os.environ.get("PINECONE_API_KEY"))
index = pc.Index(PINECONE_INDEX_NAME)
print(f"β
Connected to Pinecone index: {PINECONE_INDEX_NAME}")
return index
# βββββββββββββββββββββββββββββββββββββββββ
# STEP 2 β Configure LlamaIndex Settings
# βββββββββββββββββββββββββββββββββββββββββ
API_KEY = os.environ.get("OPENAI_API_KEY")
def configure_settings():
"""Set global LlamaIndex embedding and LLM settings."""
Settings.embed_model = OpenAIEmbedding(
model=EMBED_MODEL,
api_key=API_KEY
)
Settings.llm = OpenAI(
model=LLM_MODEL,
api_key=API_KEY
)
Settings.chunk_size = CHUNK_SIZE
Settings.chunk_overlap = CHUNK_OVERLAP
print(f"β
LlamaIndex settings configured")
# βββββββββββββββββββββββββββββββββββββββββ
# STEP 3 β Ingest Knowledge Base
# βββββββββββββββββββββββββββββββββββββββββ
def ingest_knowledge_base(pinecone_index):
"""
Load all KB files from /data folder,
chunk them and push embeddings to Pinecone.
Run this ONCE to populate the index.
"""
print(f"π Loading knowledge base from: {KNOWLEDGE_BASE_DIR}")
# Load TXT files via SimpleDirectoryReader
txt_documents = SimpleDirectoryReader(
input_dir=KNOWLEDGE_BASE_DIR,
required_exts=[".txt"]
).load_data()
# Load CSV files manually using pandas (avoids comma-in-field errors)
import pandas as pd
import glob
from llama_index.core import Document as LlamaDocument
csv_documents = []
for csv_file in glob.glob(f"{KNOWLEDGE_BASE_DIR}/*.csv"):
try:
df = pd.read_csv(csv_file, encoding="utf-8", engine="python", on_bad_lines="skip")
for _, row in df.iterrows():
text = "\n".join([f"{col}: {val}" for col, val in row.items()])
csv_documents.append(LlamaDocument(text=text))
print(f"β
Loaded CSV: {csv_file} ({len(df)} rows)")
except Exception as e:
print(f"β οΈ Skipped {csv_file}: {e}")
# Combine all documents
documents = txt_documents + csv_documents
print(f"π Loaded {len(documents)} documents")
# Chunk documents
splitter = SentenceSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=CHUNK_OVERLAP
)
# Connect to Pinecone vector store
vector_store = PineconeVectorStore(pinecone_index=pinecone_index)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
# Build index β embeds and pushes to Pinecone
index = VectorStoreIndex.from_documents(
documents,
storage_context=storage_context,
transformations=[splitter],
show_progress=True
)
print(f"β
Knowledge base ingested into Pinecone successfully")
return index
# βββββββββββββββββββββββββββββββββββββββββ
# STEP 4 β Load Existing Index
# βββββββββββββββββββββββββββββββββββββββββ
def load_index(pinecone_index):
"""
Load already-ingested index from Pinecone.
Used on every app startup after first ingest.
"""
vector_store = PineconeVectorStore(pinecone_index=pinecone_index)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_vector_store(
vector_store,
storage_context=storage_context
)
print(f"β
Index loaded from Pinecone")
return index
# βββββββββββββββββββββββββββββββββββββββββ
# STEP 5 β Build Query Engine
# βββββββββββββββββββββββββββββββββββββββββ
def build_query_engine(index):
"""Build retriever query engine from index."""
from llama_index.core import PromptTemplate
# System prompt β tells GPT how to use retrieved context
SYSTEM_PROMPT = (
"You are NovaDXB, a premium AI concierge for Dubai tourism. "
"Use the provided context to give specific, helpful answers. "
"Always mention real area names, restaurant names, or attraction names from the context. "
"Be friendly, concise and specific. Never say 'mid-budget area' β always use real names. "
"If asked about areas, mention 2-3 specific Dubai neighborhoods with details. "
"If asked about food, mention specific restaurant names and dishes. "
"If asked about activities, mention specific attraction names and costs."
)
qa_template = PromptTemplate(
"Context from NovaDXB knowledge base:\n"
"---------------------\n"
"{context_str}\n"
"---------------------\n"
"System: " + SYSTEM_PROMPT + "\n"
"Question: {query_str}\n"
"Answer: "
)
engine = index.as_query_engine(
similarity_top_k=5,
streaming=False,
text_qa_template=qa_template
)
print(f"β
Query engine ready")
return engine
# βββββββββββββββββββββββββββββββββββββββββ
# STEP 6 β Initialize RAG (called on startup)
# βββββββββββββββββββββββββββββββββββββββββ
def initialize_rag():
"""
Full initialization pipeline.
Called once on app startup.
"""
global query_engine
print("π Initializing NovaDXB RAG engine...")
configure_settings()
pinecone_index = get_pinecone_index()
# Normal startup loads existing index; set INGEST=true in .env to re-ingest
if os.environ.get("INGEST", "false").lower() == "true":
index = ingest_knowledge_base(pinecone_index)
else:
index = load_index(pinecone_index)
query_engine = build_query_engine(index)
print("β
RAG engine initialized and ready")
# βββββββββββββββββββββββββββββββββββββββββ
# STEP 7 β Query Function (called by app.py)
# βββββββββββββββββββββββββββββββββββββββββ
def query_rag(user_message: str) -> str:
"""
Main function called by app.py /chat endpoint.
Takes user message, returns RAG response.
"""
global query_engine
if query_engine is None:
return "RAG engine not initialized yet. Please wait."
try:
response = query_engine.query(user_message)
return str(response)
except Exception as e:
# error logged by caller
return f"Sorry, I encountered an error: {str(e)}" |