github-actions[bot] commited on
Commit
158badf
Β·
1 Parent(s): 7055a93

Deploy from GitHub Actions 2025-12-11_03:14:30

Browse files
Files changed (1) hide show
  1. app.py +69 -17
app.py CHANGED
@@ -12,6 +12,7 @@ SUPABASE_ANON_KEY = os.environ.get("SUPABASE_ANON_KEY")
12
  EMBEDDING_MODEL = os.environ.get("EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2")
13
  LLM_MODEL = os.environ.get("LLM_MODEL", "HuggingFaceH4/zephyr-7b-beta")
14
  RESULTS_K = int(os.environ.get("RESULTS_K", 5))
 
15
 
16
  # -------- VALIDATE ----------
17
  if not HF_API_TOKEN or not SUPABASE_URL or not SUPABASE_ANON_KEY:
@@ -27,10 +28,12 @@ SYSTEM_PROMPT = """You are an SAP documentation assistant. Your job is to answer
27
 
28
  STRICT RULES:
29
  1. ONLY use information from the provided context to answer
30
- 2. If the context doesn't contain enough information to answer, say "I don't have enough information in my knowledge base to answer this question."
31
  3. DO NOT use any prior knowledge - only the provided documents
32
  4. Always be helpful and format your answers clearly
33
  5. If relevant, mention which source document the information came from
 
 
34
 
35
  Remember: You are grounded to the provided context only. Do not make up information."""
36
 
@@ -67,6 +70,15 @@ def search_supabase(query_vector: List[float], k: int = RESULTS_K):
67
  return resp.data or []
68
 
69
 
 
 
 
 
 
 
 
 
 
70
  def format_context(chunks: List[dict]) -> str:
71
  """
72
  Format retrieved chunks into a context string for the LLM.
@@ -75,7 +87,9 @@ def format_context(chunks: List[dict]) -> str:
75
  for i, chunk in enumerate(chunks, 1):
76
  title = chunk.get("title", "Unknown")
77
  content = chunk.get("content", "")
78
- context_parts.append(f"[Document {i}: {title}]\n{content}\n")
 
 
79
  return "\n---\n".join(context_parts)
80
 
81
 
@@ -90,7 +104,7 @@ def generate_answer(question: str, context: str) -> str:
90
 
91
  Question: {question}
92
 
93
- Please answer the question based ONLY on the context documents provided above."""
94
 
95
  messages = [
96
  {"role": "system", "content": SYSTEM_PROMPT},
@@ -126,7 +140,7 @@ for message in st.session_state.messages:
126
  if message.get("sources"):
127
  with st.expander("πŸ“š View Sources"):
128
  for source in message["sources"]:
129
- st.markdown(f"**{source['title']}** (similarity: {source['similarity']:.4f})")
130
  st.caption(source['content'][:500] + "..." if len(source['content']) > 500 else source['content'])
131
  st.divider()
132
 
@@ -147,16 +161,33 @@ if question := st.chat_input("Ask a question about SAP..."):
147
  query_vector = compute_embedding(question)
148
 
149
  # Step 2: Search Supabase for relevant chunks
150
- chunks = search_supabase(query_vector, RESULTS_K)
 
 
 
151
 
152
  if not chunks:
153
- answer = "I couldn't find any relevant documents in my knowledge base for your question. Please try rephrasing or ask about a different SAP topic."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  sources = []
155
  else:
156
- # Step 3: Format context from retrieved chunks
157
  context = format_context(chunks)
158
 
159
- # Step 4: Generate answer using LLM
160
  with st.spinner("πŸ€” Generating answer..."):
161
  answer = generate_answer(question, context)
162
 
@@ -165,7 +196,8 @@ if question := st.chat_input("Ask a question about SAP..."):
165
  {
166
  "title": chunk.get("title", "Unknown"),
167
  "content": chunk.get("content", ""),
168
- "similarity": chunk.get("similarity", 0.0)
 
169
  }
170
  for chunk in chunks
171
  ]
@@ -175,10 +207,18 @@ if question := st.chat_input("Ask a question about SAP..."):
175
 
176
  # Display sources
177
  if sources:
178
- with st.expander("πŸ“š View Sources"):
179
  for source in sources:
180
- st.markdown(f"**{source['title']}** (similarity: {source['similarity']:.4f})")
181
- st.caption(source['content'][:500] + "..." if len(source['content']) > 500 else source['content'])
 
 
 
 
 
 
 
 
182
  st.divider()
183
 
184
  # Add to history
@@ -205,17 +245,29 @@ with st.sidebar:
205
 
206
  1. πŸ” **Search**: Your question is converted to embeddings and matched against our SAP knowledge base
207
  2. πŸ“š **Retrieve**: The most relevant document chunks are retrieved from Supabase
208
- 3. πŸ€– **Generate**: An LLM generates an answer based *only* on the retrieved documents
 
209
 
210
  This ensures answers are grounded in real documentation, not hallucinated!
211
  """)
212
 
213
  st.divider()
214
 
215
- st.header("βš™οΈ Settings")
216
- st.caption(f"Embedding Model: `{EMBEDDING_MODEL}`")
217
- st.caption(f"LLM Model: `{LLM_MODEL}`")
218
- st.caption(f"Results per query: `{RESULTS_K}`")
 
 
 
 
 
 
 
 
 
 
 
219
 
220
  st.divider()
221
 
 
12
  EMBEDDING_MODEL = os.environ.get("EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2")
13
  LLM_MODEL = os.environ.get("LLM_MODEL", "HuggingFaceH4/zephyr-7b-beta")
14
  RESULTS_K = int(os.environ.get("RESULTS_K", 5))
15
+ SIMILARITY_THRESHOLD = float(os.environ.get("SIMILARITY_THRESHOLD", 0.35)) # Minimum similarity score
16
 
17
  # -------- VALIDATE ----------
18
  if not HF_API_TOKEN or not SUPABASE_URL or not SUPABASE_ANON_KEY:
 
28
 
29
  STRICT RULES:
30
  1. ONLY use information from the provided context to answer
31
+ 2. If the context doesn't contain enough information to answer, say "I don't have enough information in my knowledge base to answer this question. Please try asking about a different SAP topic or rephrase your question."
32
  3. DO NOT use any prior knowledge - only the provided documents
33
  4. Always be helpful and format your answers clearly
34
  5. If relevant, mention which source document the information came from
35
+ 6. For SAP transaction codes, explain what they do and when to use them
36
+ 7. Keep answers concise but comprehensive
37
 
38
  Remember: You are grounded to the provided context only. Do not make up information."""
39
 
 
70
  return resp.data or []
71
 
72
 
73
+ def filter_by_similarity(chunks: List[dict], threshold: float = SIMILARITY_THRESHOLD) -> List[dict]:
74
+ """
75
+ Filter chunks by minimum similarity threshold.
76
+ Only return chunks with similarity >= threshold.
77
+ """
78
+ filtered = [c for c in chunks if c.get("similarity", 0) >= threshold]
79
+ return filtered
80
+
81
+
82
  def format_context(chunks: List[dict]) -> str:
83
  """
84
  Format retrieved chunks into a context string for the LLM.
 
87
  for i, chunk in enumerate(chunks, 1):
88
  title = chunk.get("title", "Unknown")
89
  content = chunk.get("content", "")
90
+ similarity = chunk.get("similarity", 0)
91
+ source = chunk.get("source", "unknown")
92
+ context_parts.append(f"[Document {i}: {title}]\nSource: {source}\nRelevance: {similarity:.2%}\n\n{content}\n")
93
  return "\n---\n".join(context_parts)
94
 
95
 
 
104
 
105
  Question: {question}
106
 
107
+ Please answer the question based ONLY on the context documents provided above. If the documents don't contain relevant information, say so clearly."""
108
 
109
  messages = [
110
  {"role": "system", "content": SYSTEM_PROMPT},
 
140
  if message.get("sources"):
141
  with st.expander("πŸ“š View Sources"):
142
  for source in message["sources"]:
143
+ st.markdown(f"**{source['title']}** (similarity: {source['similarity']:.2%})")
144
  st.caption(source['content'][:500] + "..." if len(source['content']) > 500 else source['content'])
145
  st.divider()
146
 
 
161
  query_vector = compute_embedding(question)
162
 
163
  # Step 2: Search Supabase for relevant chunks
164
+ all_chunks = search_supabase(query_vector, RESULTS_K)
165
+
166
+ # Step 3: Filter by similarity threshold
167
+ chunks = filter_by_similarity(all_chunks, SIMILARITY_THRESHOLD)
168
 
169
  if not chunks:
170
+ # Check if we got results but they were all below threshold
171
+ if all_chunks:
172
+ best_score = max(c.get("similarity", 0) for c in all_chunks)
173
+ answer = f"""I couldn't find sufficiently relevant information in my knowledge base for your question.
174
+
175
+ **What I found:** The best matching documents had only {best_score:.1%} relevance, which is below my confidence threshold of {SIMILARITY_THRESHOLD:.0%}.
176
+
177
+ **Suggestions:**
178
+ - Try rephrasing your question with different keywords
179
+ - Ask about a specific SAP topic like "SAP Basis administration", "SAP authorization", or "SAP HANA"
180
+ - Check if you're asking about a very specific transaction code - my knowledge base may not cover all of them yet
181
+
182
+ Would you like to try a different question?"""
183
+ else:
184
+ answer = "I couldn't find any relevant documents in my knowledge base for your question. Please try asking about a different SAP topic."
185
  sources = []
186
  else:
187
+ # Step 4: Format context from retrieved chunks
188
  context = format_context(chunks)
189
 
190
+ # Step 5: Generate answer using LLM
191
  with st.spinner("πŸ€” Generating answer..."):
192
  answer = generate_answer(question, context)
193
 
 
196
  {
197
  "title": chunk.get("title", "Unknown"),
198
  "content": chunk.get("content", ""),
199
+ "similarity": chunk.get("similarity", 0.0),
200
+ "source": chunk.get("source", "unknown")
201
  }
202
  for chunk in chunks
203
  ]
 
207
 
208
  # Display sources
209
  if sources:
210
+ with st.expander(f"πŸ“š View Sources ({len(sources)} relevant documents)"):
211
  for source in sources:
212
+ sim_pct = source['similarity'] * 100
213
+ if sim_pct >= 70:
214
+ badge = "🟒"
215
+ elif sim_pct >= 50:
216
+ badge = "🟑"
217
+ else:
218
+ badge = "🟠"
219
+ st.markdown(f"{badge} **{source['title']}** ({source['similarity']:.1%} match)")
220
+ st.caption(f"Source: {source.get('source', 'unknown')}")
221
+ st.text(source['content'][:600] + "..." if len(source['content']) > 600 else source['content'])
222
  st.divider()
223
 
224
  # Add to history
 
245
 
246
  1. πŸ” **Search**: Your question is converted to embeddings and matched against our SAP knowledge base
247
  2. πŸ“š **Retrieve**: The most relevant document chunks are retrieved from Supabase
248
+ 3. 🎯 **Filter**: Only documents above the similarity threshold are used
249
+ 4. πŸ€– **Generate**: An LLM generates an answer based *only* on the retrieved documents
250
 
251
  This ensures answers are grounded in real documentation, not hallucinated!
252
  """)
253
 
254
  st.divider()
255
 
256
+ st.header("βš™οΈ Configuration")
257
+ st.caption(f"**Embedding Model:** `{EMBEDDING_MODEL}`")
258
+ st.caption(f"**LLM Model:** `{LLM_MODEL}`")
259
+ st.caption(f"**Results per query:** `{RESULTS_K}`")
260
+ st.caption(f"**Similarity threshold:** `{SIMILARITY_THRESHOLD:.0%}`")
261
+
262
+ st.divider()
263
+
264
+ st.header("πŸ’‘ Tips")
265
+ st.markdown("""
266
+ - Ask specific questions about SAP topics
267
+ - Try questions about SAP Basis, HANA, Security, etc.
268
+ - Mention transaction codes (SM50, SU01, etc.)
269
+ - Check the sources to verify answers
270
+ """)
271
 
272
  st.divider()
273