Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- .gitattributes +1 -0
- app.py +234 -0
- faiss_index_bits/bits_chunks.pkl +3 -0
- faiss_index_bits/bits_tutor.index +3 -0
- requirements.txt +0 -0
.gitattributes
CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
faiss_index_bits/bits_tutor.index filter=lfs diff=lfs merge=lfs -text
|
app.py
ADDED
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app.py
|
2 |
+
|
3 |
+
import streamlit as st
|
4 |
+
import os
|
5 |
+
import faiss
|
6 |
+
import pickle
|
7 |
+
from sentence_transformers import SentenceTransformer
|
8 |
+
from groq import Groq
|
9 |
+
from dotenv import load_dotenv
|
10 |
+
import re # Import regular expressions for expand_query_with_llm_app
|
11 |
+
|
12 |
+
# --- Configuration and Model Loading (best outside functions to use caching) ---
|
13 |
+
@st.cache_resource # Important for caching large models and data
|
14 |
+
def load_models_and_data():
|
15 |
+
# Load environment variables (if .env file is present in the Space)
|
16 |
+
load_dotenv()
|
17 |
+
groq_api_key_app = os.getenv("GROQ_API_KEY") # Ensure the key is available in the Space (see Step 3 of deployment)
|
18 |
+
|
19 |
+
# Paths to the index and chunks
|
20 |
+
output_folder = "faiss_index_bits" # Must be present in the HF Space
|
21 |
+
index_path = os.path.join(output_folder, "bits_tutor.index")
|
22 |
+
chunks_path = os.path.join(output_folder, "bits_chunks.pkl")
|
23 |
+
|
24 |
+
# Load FAISS Index
|
25 |
+
if not os.path.exists(index_path):
|
26 |
+
st.error(f"FAISS Index not found at: {index_path}")
|
27 |
+
return None, None, None, None
|
28 |
+
index_loaded = faiss.read_index(index_path)
|
29 |
+
|
30 |
+
# Load Chunks
|
31 |
+
if not os.path.exists(chunks_path):
|
32 |
+
st.error(f"Chunks file not found at: {chunks_path}")
|
33 |
+
return None, None, None, None
|
34 |
+
with open(chunks_path, "rb") as f:
|
35 |
+
chunks_loaded = pickle.load(f)
|
36 |
+
|
37 |
+
# Load Embedding Model
|
38 |
+
embedding_model_name_app = "Sahajtomar/German-semantic"
|
39 |
+
embedding_model_loaded = SentenceTransformer(embedding_model_name_app)
|
40 |
+
|
41 |
+
# Initialize Groq Client
|
42 |
+
if not groq_api_key_app:
|
43 |
+
st.error("GROQ_API_KEY not found. Please add it as a Secret in the Hugging Face Space settings.")
|
44 |
+
return None, None, None, None
|
45 |
+
groq_client_loaded = Groq(api_key=groq_api_key_app)
|
46 |
+
|
47 |
+
return index_loaded, chunks_loaded, embedding_model_loaded, groq_client_loaded
|
48 |
+
|
49 |
+
# Load models and data when the app starts
|
50 |
+
faiss_index, chunks_data, embedding_model, groq_client = load_models_and_data()
|
51 |
+
|
52 |
+
# --- Functions from your notebook (slightly adapted for Streamlit) ---
|
53 |
+
|
54 |
+
# retrieve_relevant_chunks (adapted for app)
|
55 |
+
def retrieve_relevant_chunks_app(query, k=5):
|
56 |
+
if embedding_model is None or faiss_index is None or chunks_data is None:
|
57 |
+
return []
|
58 |
+
query_embedding = embedding_model.encode([query], convert_to_numpy=True)
|
59 |
+
distances, indices = faiss_index.search(query_embedding, k)
|
60 |
+
retrieved_chunks_data = [(chunks_data[i], distances[0][j]) for j, i in enumerate(indices[0])]
|
61 |
+
return retrieved_chunks_data
|
62 |
+
|
63 |
+
# generate_answer (adapted for app - prompt remains German for German tutor functionality)
|
64 |
+
def generate_answer_app(query, retrieved_chunks_data):
|
65 |
+
if groq_client is None:
|
66 |
+
return "Groq Client not initialized." # Developer-facing error
|
67 |
+
context = "\n\n".join([chunk_text for chunk_text, dist in retrieved_chunks_data])
|
68 |
+
# This prompt_template remains in German as it instructs the LLM for the German-speaking tutor
|
69 |
+
prompt_template = f"""Beantworte die folgende Frage ausschliesslich basierend auf dem bereitgestellten Kontext aus den Lehrmaterialien zur Business IT Strategie.
|
70 |
+
Antworte auf Deutsch.
|
71 |
+
|
72 |
+
Kontext:
|
73 |
+
{context}
|
74 |
+
|
75 |
+
Frage: {query}
|
76 |
+
|
77 |
+
Antwort:
|
78 |
+
"""
|
79 |
+
try:
|
80 |
+
chat_completion = groq_client.chat.completions.create(
|
81 |
+
messages=[{"role": "user", "content": prompt_template}],
|
82 |
+
model="llama3-70b-8192",
|
83 |
+
temperature=0.3,
|
84 |
+
)
|
85 |
+
return chat_completion.choices[0].message.content
|
86 |
+
except Exception as e:
|
87 |
+
# Developer-facing error
|
88 |
+
return f"Error during LLM request: {e}"
|
89 |
+
|
90 |
+
# expand_query_with_llm (adapted for app - prompt for expansion remains German)
|
91 |
+
def expand_query_with_llm_app(original_query, llm_client_app):
|
92 |
+
"""
|
93 |
+
Expands a given user query using an LLM
|
94 |
+
to generate alternative formulations or relevant keywords.
|
95 |
+
Cleans the LLM's output.
|
96 |
+
(This function needs to use the llm_client_app passed to it)
|
97 |
+
"""
|
98 |
+
if llm_client_app is None:
|
99 |
+
st.warning("LLM client for query expansion not initialized.")
|
100 |
+
return [original_query]
|
101 |
+
|
102 |
+
# This prompt_template remains in German as it instructs the LLM for the German-speaking tutor's expansion
|
103 |
+
prompt_template_expansion = f"""Gegeben ist die folgende Nutzerfrage zum Thema "Business IT Strategie": "{original_query}"
|
104 |
+
|
105 |
+
Bitte generiere 2-3 alternative Formulierungen dieser Frage ODER eine Liste von 3-5 sehr relevanten Schlüsselbegriffen/Konzepten,
|
106 |
+
die helfen würden, in einer Wissensdatenbank nach Antworten zu dieser Frage zu suchen.
|
107 |
+
Formatiere die Ausgabe klar, z.B. als nummerierte Liste für alternative Fragen oder als kommaseparierte Liste für Schlüsselbegriffe.
|
108 |
+
Gib NUR die alternativen Formulierungen oder die Schlüsselbegriffe aus. Keine Einleitungssätze.
|
109 |
+
"""
|
110 |
+
try:
|
111 |
+
chat_completion = llm_client_app.chat.completions.create(
|
112 |
+
messages=[
|
113 |
+
{
|
114 |
+
"role": "user",
|
115 |
+
"content": prompt_template_expansion,
|
116 |
+
}
|
117 |
+
],
|
118 |
+
model="llama3-8b-8192",
|
119 |
+
temperature=0.5,
|
120 |
+
)
|
121 |
+
expanded_terms_text = chat_completion.choices[0].message.content
|
122 |
+
|
123 |
+
cleaned_queries = []
|
124 |
+
potential_queries = expanded_terms_text.split('\n')
|
125 |
+
|
126 |
+
for line in potential_queries:
|
127 |
+
line = line.strip()
|
128 |
+
line = re.sub(r"^\s*\d+\.\s*", "", line)
|
129 |
+
line = re.sub(r"^\s*[-\*]\s*", "", line)
|
130 |
+
line = line.strip()
|
131 |
+
|
132 |
+
if not line or \
|
133 |
+
line.lower().startswith("here are") or \
|
134 |
+
line.lower().startswith("sicher, hier sind") or \
|
135 |
+
line.lower().startswith("alternative formulierungen:") or \
|
136 |
+
line.lower().startswith("*alternative formulierungen:**") or \
|
137 |
+
len(line) < 5:
|
138 |
+
continue
|
139 |
+
cleaned_queries.append(line)
|
140 |
+
|
141 |
+
if len(cleaned_queries) == 1 and ',' in cleaned_queries[0] and len(cleaned_queries[0].split(',')) > 1:
|
142 |
+
final_expanded_list = [term.strip() for term in cleaned_queries[0].split(',') if term.strip() and len(term.strip()) > 4]
|
143 |
+
else:
|
144 |
+
final_expanded_list = cleaned_queries
|
145 |
+
|
146 |
+
all_queries = [original_query]
|
147 |
+
for q_exp in final_expanded_list:
|
148 |
+
is_duplicate = False
|
149 |
+
for q_all in all_queries:
|
150 |
+
if q_all.lower() == q_exp.lower():
|
151 |
+
is_duplicate = True
|
152 |
+
break
|
153 |
+
if not is_duplicate:
|
154 |
+
all_queries.append(q_exp)
|
155 |
+
|
156 |
+
return all_queries[:4]
|
157 |
+
|
158 |
+
except Exception as e:
|
159 |
+
st.warning(f"Error during Query Expansion with LLM: {e}")
|
160 |
+
return [original_query]
|
161 |
+
|
162 |
+
def retrieve_with_expanded_queries_app(original_query, llm_client_app, retrieve_func, k_per_expansion=2):
|
163 |
+
"""
|
164 |
+
Performs Query Expansion and retrieves chunks for each expanded query.
|
165 |
+
Collects and de-duplicates the chunks.
|
166 |
+
(This function needs to use the llm_client_app passed to it)
|
167 |
+
"""
|
168 |
+
expanded_queries = expand_query_with_llm_app(original_query, llm_client_app)
|
169 |
+
# st.write(f"Using the following queries for retrieval after expansion:") # For debugging
|
170 |
+
# for i, eq_query in enumerate(expanded_queries):
|
171 |
+
# st.caption(f" ExpQuery {i}: {eq_query}")
|
172 |
+
|
173 |
+
all_retrieved_chunks_data = []
|
174 |
+
for eq_query in expanded_queries:
|
175 |
+
retrieved_for_eq = retrieve_func(eq_query, k=k_per_expansion)
|
176 |
+
all_retrieved_chunks_data.extend(retrieved_for_eq)
|
177 |
+
|
178 |
+
unique_chunks_dict = {}
|
179 |
+
for chunk_text, distance in all_retrieved_chunks_data:
|
180 |
+
if chunk_text not in unique_chunks_dict or distance < unique_chunks_dict[chunk_text]:
|
181 |
+
unique_chunks_dict[chunk_text] = distance
|
182 |
+
|
183 |
+
sorted_unique_chunks_data = sorted(unique_chunks_dict.items(), key=lambda item: item[1])
|
184 |
+
|
185 |
+
final_chunks_for_context = sorted_unique_chunks_data[:5]
|
186 |
+
# st.write(f"\n{len(final_chunks_for_context)} unique chunks were selected for the context.") # For debugging
|
187 |
+
return final_chunks_for_context
|
188 |
+
|
189 |
+
# --- Streamlit UI ---
|
190 |
+
st.set_page_config(page_title="RAG BITS Tutor", page_icon="🎓") # Set page title and icon
|
191 |
+
|
192 |
+
st.title("🎓 RAG Study Tutor for Business IT Strategy")
|
193 |
+
st.write("Ask your questions about the content of the lecture notes and case studies (in German).")
|
194 |
+
|
195 |
+
# User query input field (remains German for the user)
|
196 |
+
user_query_streamlit = st.text_input("Deine Frage:", "")
|
197 |
+
|
198 |
+
# Option to use query expansion
|
199 |
+
use_expansion = st.checkbox("Use Query Expansion (may improve results for some questions)", value=True) # Default to True
|
200 |
+
|
201 |
+
if user_query_streamlit:
|
202 |
+
if faiss_index and chunks_data and embedding_model and groq_client:
|
203 |
+
st.write("Searching for relevant information...")
|
204 |
+
|
205 |
+
if use_expansion:
|
206 |
+
st.caption("Query expansion is active...")
|
207 |
+
retrieved_chunks = retrieve_with_expanded_queries_app(user_query_streamlit, groq_client, retrieve_relevant_chunks_app, k_per_expansion=2)
|
208 |
+
else:
|
209 |
+
st.caption("Direct retrieval...")
|
210 |
+
retrieved_chunks = retrieve_relevant_chunks_app(user_query_streamlit, k=3) # Number of chunks to retrieve
|
211 |
+
|
212 |
+
if retrieved_chunks:
|
213 |
+
# Optional display of retrieved context snippets (for debugging or transparency)
|
214 |
+
# with st.expander("Show retrieved context snippets"):
|
215 |
+
# for i, (chunk, dist) in enumerate(retrieved_chunks):
|
216 |
+
# st.caption(f"Chunk {i+1} (Distance: {dist:.2f})")
|
217 |
+
# st.markdown(f"_{chunk[:200]}..._") # Displaying German chunk
|
218 |
+
# st.divider()
|
219 |
+
|
220 |
+
st.write("Generating answer...")
|
221 |
+
answer = generate_answer_app(user_query_streamlit, retrieved_chunks)
|
222 |
+
st.subheader("Tutor's Answer:") # UI element
|
223 |
+
st.markdown(answer) # Displaying German answer
|
224 |
+
else:
|
225 |
+
st.warning("No relevant information could be found for your query.") # UI element
|
226 |
+
else:
|
227 |
+
st.error("The application could not be initialized correctly. Please check the error messages above.") # UI element
|
228 |
+
|
229 |
+
st.sidebar.header("About this Project") # UI element
|
230 |
+
st.sidebar.info( # UI element
|
231 |
+
"This RAG application was developed as part of the 'AI Applications' module. "
|
232 |
+
"It uses Sentence Transformers for embeddings, FAISS for vector search, "
|
233 |
+
"and an LLM via Groq for answer generation."
|
234 |
+
)
|
faiss_index_bits/bits_chunks.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:2e9f9166dc883ddfe7bfbbaacfadac8efffb8d84e8d104e55edbd5d1f18cbbef
|
3 |
+
size 243476
|
faiss_index_bits/bits_tutor.index
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:40e43ea4ecf58bbfe6a0c40f826a365af01559c1f1d9cb97970e5b589dd1e887
|
3 |
+
size 831533
|
requirements.txt
ADDED
Binary file (5.71 kB). View file
|
|