patronmoses commited on
Commit
07d413c
·
verified ·
1 Parent(s): 0994540

Upload 4 files

Browse files
.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