Adoption commited on
Commit
376c4d1
Β·
verified Β·
1 Parent(s): f555256

Update src/app.py

Browse files
Files changed (1) hide show
  1. src/app.py +31 -32
src/app.py CHANGED
@@ -1,25 +1,26 @@
1
  import os
2
  import pickle
3
  import streamlit as st
 
4
  from dotenv import load_dotenv
 
 
5
  from langchain_google_genai import GoogleGenerativeAIEmbeddings
6
  from langchain_groq import ChatGroq
7
  from langchain_pinecone import PineconeVectorStore
8
  from langchain_core.prompts import PromptTemplate
9
  from langchain.chains import RetrievalQA
10
  from langchain_core.documents import Document
 
 
11
 
12
  load_dotenv()
13
 
14
- # --- CONFIGURATION ---
15
  # --- CONFIGURATION ---
16
  INDEX_NAME = "branham-index"
17
 
18
  # GET ABSOLUTE PATH TO THE FILE
19
- # This finds the folder where app.py lives (the 'src' folder)
20
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
21
-
22
- # Builds the full path: .../7th_handle/src/sermon_chunks.pkl
23
  CHUNKS_FILE = os.path.join(BASE_DIR, "sermon_chunks.pkl")
24
 
25
  # Verify it exists immediately
@@ -28,12 +29,12 @@ if not os.path.exists(CHUNKS_FILE):
28
  else:
29
  print(f"βœ… SUCCESS: Pickle file found at: {CHUNKS_FILE}")
30
 
31
- # --- SEARCH ENGINE (The "Ctrl+F" Logic) ---
32
  def search_archives(query):
33
  status_log = []
34
  results = []
35
 
36
- # PHASE 1: LOCAL TEXT SCAN (The Logic that worked in your Test)
37
  if os.path.exists(CHUNKS_FILE):
38
  try:
39
  with open(CHUNKS_FILE, "rb") as f:
@@ -41,21 +42,17 @@ def search_archives(query):
41
 
42
  status_log.append(f"πŸ“‚ Loaded {len(chunks)} local chunks.")
43
 
44
- # BRUTE FORCE SCAN
45
  query_lower = query.lower().strip()
46
  count = 0
47
 
48
- # Simple, raw loop - exactly like your test script
49
  for doc in chunks:
50
  if query_lower in doc.page_content.lower():
51
  results.append(doc)
52
  count += 1
53
- if count >= 20: break # Limit to 20 matches
54
 
55
  if results:
56
  status_log.append(f"βœ… FOUND {len(results)} EXACT MATCHES LOCALLY.")
57
- # If we find exact matches, we return them and STOP.
58
- # We do not let Pinecone pollute the results.
59
  return results, status_log
60
  else:
61
  status_log.append("⚠️ No exact matches found locally.")
@@ -65,7 +62,7 @@ def search_archives(query):
65
  else:
66
  status_log.append("❌ Pickle file missing. Skipping local search.")
67
 
68
- # PHASE 2: PINECONE FALLBACK (Only if Phase 1 failed)
69
  status_log.append("☁️ Attempting Vector Search (Pinecone)...")
70
  try:
71
  pinecone_key = os.environ.get("PINECONE_API_KEY") or st.secrets.get("PINECONE_API_KEY")
@@ -77,47 +74,44 @@ def search_archives(query):
77
  embeddings = GoogleGenerativeAIEmbeddings(model="models/text-embedding-004")
78
  vector_store = PineconeVectorStore(index_name=INDEX_NAME, embedding=embeddings)
79
 
80
- # STRICT THRESHOLD: 0.80
81
- # If it's not 80% similar, we prefer to show nothing.
82
  retriever = vector_store.as_retriever(
83
  search_type="similarity_score_threshold",
84
- search_kwargs={"k": 10, "score_threshold": 0.80}
85
  )
86
 
87
  docs = retriever.invoke(query)
88
  if not docs:
89
- status_log.append("msg: No relevant results found in Cloud (Threshold 0.80).")
90
  return docs, status_log
91
 
92
  except Exception as e:
93
  status_log.append(f"❌ Cloud Error: {e}")
94
  return [], status_log
95
 
96
- # --- RAG CHAIN (Chat) ---
97
  # --- RAG CHAIN (The Chat Tool) ---
98
  def get_rag_chain():
99
- # 1. DEFINE A SMART RETRIEVER
100
- # This retriever tries Local First, then Cloud.
101
- class SmartRetriever:
102
- def invoke(self, query):
 
 
103
  print(f"🧠 Chat is thinking about: '{query}'")
104
 
105
- # PHASE A: LOCAL LOOKUP (For specific names/places)
106
  if os.path.exists(CHUNKS_FILE):
107
  try:
108
  with open(CHUNKS_FILE, "rb") as f:
109
  chunks = pickle.load(f)
110
 
111
- # Extract potential keywords (simple split)
112
  keywords = [w for w in query.split() if len(w) > 3]
113
  local_matches = []
114
 
115
- # Scan for matches
116
  for doc in chunks:
117
- # If any significant keyword from the question is in the text
118
  if any(k.lower() in doc.page_content.lower() for k in keywords):
119
  local_matches.append(doc)
120
- if len(local_matches) >= 15: break # Cap at 15 to fit in AI memory
121
 
122
  if local_matches:
123
  print(f"βœ… Chat found {len(local_matches)} local clues.")
@@ -125,9 +119,14 @@ def get_rag_chain():
125
  except:
126
  pass
127
 
128
- # PHASE B: CLOUD FALLBACK (For general concepts)
129
  print("☁️ Checking Cloud...")
130
  try:
 
 
 
 
 
131
  embeddings = GoogleGenerativeAIEmbeddings(model="models/text-embedding-004")
132
  vector_store = PineconeVectorStore(index_name=INDEX_NAME, embedding=embeddings)
133
  retriever = vector_store.as_retriever(search_kwargs={"k": 10})
@@ -136,20 +135,20 @@ def get_rag_chain():
136
  print(f"❌ Cloud Error: {e}")
137
  return []
138
 
139
- # 2. SETUP THE LLM
140
  groq_key = os.environ.get("GROQ_API_KEY") or st.secrets.get("GROQ_API_KEY")
141
  os.environ["GROQ_API_KEY"] = groq_key
142
 
143
  llm = ChatGroq(model="llama-3.3-70b-versatile", temperature=0.3)
144
 
145
- # 3. THE PROMPT (Brother Branham's Voice)
146
  template = """You are William Marion Branham.
147
 
148
  INSTRUCTIONS:
149
  1. Answer the question using ONLY the provided CONTEXT.
150
  2. If the Context contains the answer, quote it and explain it simply.
151
  3. If the Context is empty or irrelevant, say: "Brother, I do not find that specific record here."
152
- 4. Speak with humility (e.g., "The Lord showed me," "I said").
153
 
154
  CONTEXT:
155
  {context}
@@ -160,11 +159,11 @@ def get_rag_chain():
160
 
161
  PROMPT = PromptTemplate(template=template, input_variables=["context", "question"])
162
 
163
- # 4. BUILD THE CHAIN
164
  chain = RetrievalQA.from_chain_type(
165
  llm=llm,
166
  chain_type="stuff",
167
- retriever=SmartRetriever(), # <--- We use our custom smart logic
168
  return_source_documents=True,
169
  chain_type_kwargs={"prompt": PROMPT, "document_variable_name": "context"},
170
  input_key="question"
 
1
  import os
2
  import pickle
3
  import streamlit as st
4
+ from typing import List
5
  from dotenv import load_dotenv
6
+
7
+ # LangChain Imports
8
  from langchain_google_genai import GoogleGenerativeAIEmbeddings
9
  from langchain_groq import ChatGroq
10
  from langchain_pinecone import PineconeVectorStore
11
  from langchain_core.prompts import PromptTemplate
12
  from langchain.chains import RetrievalQA
13
  from langchain_core.documents import Document
14
+ from langchain_core.retrievers import BaseRetriever
15
+ from langchain_core.callbacks import CallbackManagerForRetrieverRun
16
 
17
  load_dotenv()
18
 
 
19
  # --- CONFIGURATION ---
20
  INDEX_NAME = "branham-index"
21
 
22
  # GET ABSOLUTE PATH TO THE FILE
 
23
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
 
 
24
  CHUNKS_FILE = os.path.join(BASE_DIR, "sermon_chunks.pkl")
25
 
26
  # Verify it exists immediately
 
29
  else:
30
  print(f"βœ… SUCCESS: Pickle file found at: {CHUNKS_FILE}")
31
 
32
+ # --- SEARCH ENGINE (The "Ctrl+F" Logic for Search Mode) ---
33
  def search_archives(query):
34
  status_log = []
35
  results = []
36
 
37
+ # PHASE 1: LOCAL TEXT SCAN
38
  if os.path.exists(CHUNKS_FILE):
39
  try:
40
  with open(CHUNKS_FILE, "rb") as f:
 
42
 
43
  status_log.append(f"πŸ“‚ Loaded {len(chunks)} local chunks.")
44
 
 
45
  query_lower = query.lower().strip()
46
  count = 0
47
 
 
48
  for doc in chunks:
49
  if query_lower in doc.page_content.lower():
50
  results.append(doc)
51
  count += 1
52
+ if count >= 20: break
53
 
54
  if results:
55
  status_log.append(f"βœ… FOUND {len(results)} EXACT MATCHES LOCALLY.")
 
 
56
  return results, status_log
57
  else:
58
  status_log.append("⚠️ No exact matches found locally.")
 
62
  else:
63
  status_log.append("❌ Pickle file missing. Skipping local search.")
64
 
65
+ # PHASE 2: PINECONE FALLBACK
66
  status_log.append("☁️ Attempting Vector Search (Pinecone)...")
67
  try:
68
  pinecone_key = os.environ.get("PINECONE_API_KEY") or st.secrets.get("PINECONE_API_KEY")
 
74
  embeddings = GoogleGenerativeAIEmbeddings(model="models/text-embedding-004")
75
  vector_store = PineconeVectorStore(index_name=INDEX_NAME, embedding=embeddings)
76
 
77
+ # STRICT THRESHOLD: 0.75
 
78
  retriever = vector_store.as_retriever(
79
  search_type="similarity_score_threshold",
80
+ search_kwargs={"k": 10, "score_threshold": 0.75}
81
  )
82
 
83
  docs = retriever.invoke(query)
84
  if not docs:
85
+ status_log.append("msg: No relevant results found in Cloud (Threshold 0.75).")
86
  return docs, status_log
87
 
88
  except Exception as e:
89
  status_log.append(f"❌ Cloud Error: {e}")
90
  return [], status_log
91
 
 
92
  # --- RAG CHAIN (The Chat Tool) ---
93
  def get_rag_chain():
94
+
95
+ # 1. DEFINE SMART RETRIEVER (Must inherit from BaseRetriever)
96
+ class SmartRetriever(BaseRetriever):
97
+ def _get_relevant_documents(
98
+ self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
99
+ ) -> List[Document]:
100
  print(f"🧠 Chat is thinking about: '{query}'")
101
 
102
+ # PHASE A: LOCAL LOOKUP (Precision)
103
  if os.path.exists(CHUNKS_FILE):
104
  try:
105
  with open(CHUNKS_FILE, "rb") as f:
106
  chunks = pickle.load(f)
107
 
 
108
  keywords = [w for w in query.split() if len(w) > 3]
109
  local_matches = []
110
 
 
111
  for doc in chunks:
 
112
  if any(k.lower() in doc.page_content.lower() for k in keywords):
113
  local_matches.append(doc)
114
+ if len(local_matches) >= 15: break
115
 
116
  if local_matches:
117
  print(f"βœ… Chat found {len(local_matches)} local clues.")
 
119
  except:
120
  pass
121
 
122
+ # PHASE B: CLOUD FALLBACK (Concepts)
123
  print("☁️ Checking Cloud...")
124
  try:
125
+ pinecone_key = os.environ.get("PINECONE_API_KEY") or st.secrets.get("PINECONE_API_KEY")
126
+ google_key = os.environ.get("GOOGLE_API_KEY") or st.secrets.get("GOOGLE_API_KEY")
127
+ os.environ["PINECONE_API_KEY"] = pinecone_key
128
+ os.environ["GOOGLE_API_KEY"] = google_key
129
+
130
  embeddings = GoogleGenerativeAIEmbeddings(model="models/text-embedding-004")
131
  vector_store = PineconeVectorStore(index_name=INDEX_NAME, embedding=embeddings)
132
  retriever = vector_store.as_retriever(search_kwargs={"k": 10})
 
135
  print(f"❌ Cloud Error: {e}")
136
  return []
137
 
138
+ # 2. SETUP LLM
139
  groq_key = os.environ.get("GROQ_API_KEY") or st.secrets.get("GROQ_API_KEY")
140
  os.environ["GROQ_API_KEY"] = groq_key
141
 
142
  llm = ChatGroq(model="llama-3.3-70b-versatile", temperature=0.3)
143
 
144
+ # 3. PROMPT
145
  template = """You are William Marion Branham.
146
 
147
  INSTRUCTIONS:
148
  1. Answer the question using ONLY the provided CONTEXT.
149
  2. If the Context contains the answer, quote it and explain it simply.
150
  3. If the Context is empty or irrelevant, say: "Brother, I do not find that specific record here."
151
+ 4. Speak with humility.
152
 
153
  CONTEXT:
154
  {context}
 
159
 
160
  PROMPT = PromptTemplate(template=template, input_variables=["context", "question"])
161
 
162
+ # 4. BUILD CHAIN
163
  chain = RetrievalQA.from_chain_type(
164
  llm=llm,
165
  chain_type="stuff",
166
+ retriever=SmartRetriever(), # Now a valid BaseRetriever
167
  return_source_documents=True,
168
  chain_type_kwargs={"prompt": PROMPT, "document_variable_name": "context"},
169
  input_key="question"