Spaces:
Running
Running
File size: 4,843 Bytes
bc90b6b |
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 |
import os
import pickle
import zipfile
import sys
import streamlit as st
from dotenv import load_dotenv
# --- IMPORTS ---
from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings
from langchain_community.retrievers import BM25Retriever
from langchain_pinecone import PineconeVectorStore
from langchain_core.prompts import PromptTemplate
from langchain.chains import RetrievalQA
# Robust Import for Hybrid Search (Handles different LangChain versions)
try:
from langchain.retrievers import EnsembleRetriever
except ImportError:
from langchain_community.retrievers import EnsembleRetriever
load_dotenv()
# --- CONFIGURATION ---
INDEX_NAME = "branham-index"
CHUNKS_FILE = "sermon_chunks.pkl"
CHUNKS_ZIP = "sermon_chunks.zip"
def setup_keyword_file():
"""
Automatic Unzipper.
GitHub has a 100MB limit, so we upload the zip.
This extracts 'sermon_chunks.pkl' when the app starts.
"""
if not os.path.exists(CHUNKS_FILE):
if os.path.exists(CHUNKS_ZIP):
print(f"📦 Unzipping {CHUNKS_ZIP}...")
try:
with zipfile.ZipFile(CHUNKS_ZIP, 'r') as zip_ref:
zip_ref.extractall(".")
print("✅ Unzip complete.")
except Exception as e:
print(f"❌ Error unzipping file: {e}")
else:
print(f"⚠️ Warning: Neither {CHUNKS_FILE} nor {CHUNKS_ZIP} found.")
def get_rag_chain():
"""
Initializes the Brain of the AI.
1. Connects to Pinecone (Cloud)
2. Loads BM25 Keywords (Local)
3. Merges them into a Hybrid Search
"""
# 1. SETUP & KEYS
setup_keyword_file()
# Check Streamlit Secrets first (Cloud), then .env (Local)
pinecone_key = st.secrets.get("PINECONE_API_KEY") or os.getenv("PINECONE_API_KEY")
google_key = st.secrets.get("GOOGLE_API_KEY") or os.getenv("GOOGLE_API_KEY")
if not pinecone_key or not google_key:
raise ValueError("❌ Missing API Keys. Please set PINECONE_API_KEY and GOOGLE_API_KEY in Secrets.")
# Set keys for LangChain to use automatically
os.environ["PINECONE_API_KEY"] = pinecone_key
os.environ["GOOGLE_API_KEY"] = google_key
# 2. CLOUD VECTOR SEARCH (Pinecone)
# This finds "concepts" (e.g., searching for 'marriage' finds 'wedding')
print("🔌 Connecting to Pinecone...")
embeddings = GoogleGenerativeAIEmbeddings(model="models/text-embedding-004")
vector_store = PineconeVectorStore(
index_name=INDEX_NAME,
embedding=embeddings
)
vector_retriever = vector_store.as_retriever(search_kwargs={"k": 5})
# 3. LOCAL KEYWORD SEARCH (BM25)
# This finds "exact matches" (e.g., searching for 'E-53' finds exactly E-53)
print("🔌 Loading Keyword Search...")
keyword_retriever = None
try:
if os.path.exists(CHUNKS_FILE):
with open(CHUNKS_FILE, "rb") as f:
chunks = pickle.load(f)
keyword_retriever = BM25Retriever.from_documents(chunks)
keyword_retriever.k = 5
else:
print("⚠️ Keyword file missing. Running on Pinecone only.")
except Exception as e:
print(f"❌ Failed to load keyword file: {e}")
# 4. HYBRID RETRIEVER (The Merge)
if keyword_retriever:
print("🔗 Linking Hybrid System...")
final_retriever = EnsembleRetriever(
retrievers=[vector_retriever, keyword_retriever],
weights=[0.7, 0.3] # 70% Vector, 30% Keyword
)
else:
final_retriever = vector_retriever
# 5. THE MODEL (Gemini)
llm = ChatGoogleGenerativeAI(
model="gemini-1.5-flash",
temperature=0.3,
convert_system_message_to_human=True
)
# 6. THE PERSONA PROMPT
template = """You are William Marion Branham.
INSTRUCTIONS:
- Answer the user's question based ONLY on the context provided below.
- Speak in the first person ("I said," "The Lord showed me").
- Use a humble, 1950s Southern preaching dialect.
- If the answer is not in the text, say: "Brother, I don't recall preaching specifically on that detail in these messages."
CONTEXT:
{context}
USER QUESTION: {question}
BROTHER BRANHAM'S REPLY:"""
PROMPT = PromptTemplate(template=template, input_variables=["context", "question"])
chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=final_retriever,
return_source_documents=True,
chain_type_kwargs={"prompt": PROMPT}
)
return chain
|