Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,234 +1,61 @@
|
|
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
|
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 |
-
# 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 |
-
)
|
|
|
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 |
+
# --- Page Configuration (MUST BE THE FIRST STREAMLIT COMMAND) ---
|
13 |
+
st.set_page_config(page_title="RAG BITS Tutor", page_icon="🎓") # Set page title and icon
|
14 |
+
|
15 |
+
# --- Konfiguration und Modell-Laden (am besten ausserhalb von Funktionen, um Caching zu nutzen) ---
|
16 |
+
@st.cache_resource # Wichtig für das Caching von grossen Modellen und Daten
|
17 |
+
def load_models_and_data():
|
18 |
+
# Lade Umgebungsvariablen (falls .env Datei im Space vorhanden ist)
|
19 |
+
load_dotenv()
|
20 |
+
groq_api_key_app = os.getenv("GROQ_API_KEY") # Stelle sicher, dass der Key im Space verfügbar ist (siehe Schritt 3)
|
21 |
+
|
22 |
+
# Pfade zum Index und den Chunks
|
23 |
+
output_folder = "faiss_index_bits" # Muss im HF Space vorhanden sein
|
24 |
+
index_path = os.path.join(output_folder, "bits_tutor.index")
|
25 |
+
chunks_path = os.path.join(output_folder, "bits_chunks.pkl")
|
26 |
+
|
27 |
+
# Lade FAISS Index
|
28 |
+
if not os.path.exists(index_path):
|
29 |
+
st.error(f"FAISS Index nicht gefunden unter: {index_path}") # This is a Streamlit command
|
30 |
+
return None, None, None, None
|
31 |
+
index_loaded = faiss.read_index(index_path)
|
32 |
+
|
33 |
+
# Lade Chunks
|
34 |
+
if not os.path.exists(chunks_path):
|
35 |
+
st.error(f"Chunks-Datei nicht gefunden unter: {chunks_path}") # This is a Streamlit command
|
36 |
+
return None, None, None, None
|
37 |
+
with open(chunks_path, "rb") as f:
|
38 |
+
chunks_loaded = pickle.load(f)
|
39 |
+
|
40 |
+
# Lade Embedding-Modell
|
41 |
+
embedding_model_name_app = "Sahajtomar/German-semantic"
|
42 |
+
embedding_model_loaded = SentenceTransformer(embedding_model_name_app)
|
43 |
+
|
44 |
+
# Initialisiere Groq Client
|
45 |
+
if not groq_api_key_app:
|
46 |
+
st.error("GROQ_API_KEY nicht gefunden. Bitte im Hugging Face Space als Secret hinzufügen.") # This is a Streamlit command
|
47 |
+
return None, None, None, None
|
48 |
+
groq_client_loaded = Groq(api_key=groq_api_key_app)
|
49 |
+
|
50 |
+
return index_loaded, chunks_loaded, embedding_model_loaded, groq_client_loaded
|
51 |
+
|
52 |
+
# Lade Modelle und Daten beim Start der App
|
53 |
+
# Wichtig: Die Funktion load_models_and_data() verwendet st.error(), was ein Streamlit-Befehl ist.
|
54 |
+
# Daher muss st.set_page_config() VOR dem ersten möglichen Aufruf von st.error() stehen.
|
55 |
+
faiss_index, chunks_data, embedding_model, groq_client = load_models_and_data()
|
56 |
+
|
57 |
+
# ... (Rest deines app.py Skripts bleibt gleich) ...
|
58 |
+
|
59 |
+
# --- Streamlit UI (kommt nach load_models_and_data) ---
|
60 |
+
st.title("🎓 RAG Study Tutor for Business IT Strategy")
|
61 |
+
# ... etc. ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|