RobertoBarrosoLuque commited on
Commit
6903420
·
1 Parent(s): 75361de
Files changed (3) hide show
  1. src/app.py +35 -20
  2. src/config.py +66 -30
  3. src/constants/code_snippets.py +0 -120
src/app.py CHANGED
@@ -14,13 +14,6 @@ from src.search.vector_search import (
14
  search_vector_with_reranking,
15
  )
16
  from src.data_prep.data_prep import load_clean_amazon_product_data
17
- from src.constants.code_snippets import (
18
- CODE_STAGE_1,
19
- CODE_STAGE_2,
20
- CODE_STAGE_3,
21
- CODE_STAGE_4,
22
- )
23
-
24
 
25
  _FILE_PATH = Path(__file__).parents[1]
26
 
@@ -268,11 +261,37 @@ def generate_comparison_table(all_metrics: List[Dict]) -> str:
268
 
269
  html += "\n---\n\n"
270
  html += "## Key Insights\n\n"
271
- html += f"- **Top-1 Score** improves by **{top1_improvement:.0f}%** from baseline to final stage\n"
272
- html += f"- **Top-5 Average** improves by **{top5_improvement:.0f}%** from baseline to final stage\n"
273
- html += f"- **Latency** stays under **{max(m['latency_ms'] for m in all_metrics)}ms** maintaining fast performance\n"
274
- html += "- Each stage progressively enhances search relevance while keeping response times low\n"
275
- html += "- Vector embeddings provide the biggest jump in semantic understanding\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
  return html
278
 
@@ -376,6 +395,10 @@ with gr.Blocks(
376
  """
377
  <h1 class="header-title" style="font-size: 2.5em; text-align: left;">Search Alchemy</h1>
378
  <p style="color: #64748B; font-size: 1.1em; margin-top: 0; text-align: left;">Building Production Search Pipelines with Fireworks AI</p>
 
 
 
 
379
  """
380
  )
381
  with gr.Row(elem_classes="compact-header"):
@@ -430,23 +453,15 @@ with gr.Blocks(
430
 
431
  with gr.Tab("Stage 1: BM25 Baseline"):
432
  stage1_output = gr.Markdown(label="Results")
433
- with gr.Accordion("Show Code", open=False):
434
- gr.Markdown(CODE_STAGE_1)
435
 
436
  with gr.Tab("Stage 2: + Vector Embeddings"):
437
  stage2_output = gr.Markdown(label="Results")
438
- with gr.Accordion("Show Code", open=False):
439
- gr.Markdown(CODE_STAGE_2)
440
 
441
  with gr.Tab("Stage 3: + Query Expansion"):
442
  stage3_output = gr.Markdown(label="Results")
443
- with gr.Accordion("Show Code", open=False):
444
- gr.Markdown(CODE_STAGE_3)
445
 
446
  with gr.Tab("Stage 4: + LLM Reranking"):
447
  stage4_output = gr.Markdown(label="Results")
448
- with gr.Accordion("Show Code", open=False):
449
- gr.Markdown(CODE_STAGE_4)
450
 
451
  with gr.Tab("Compare All Stages"):
452
  comparison_output = gr.Markdown(label="Comparison")
 
14
  search_vector_with_reranking,
15
  )
16
  from src.data_prep.data_prep import load_clean_amazon_product_data
 
 
 
 
 
 
 
17
 
18
  _FILE_PATH = Path(__file__).parents[1]
19
 
 
261
 
262
  html += "\n---\n\n"
263
  html += "## Key Insights\n\n"
264
+ html += f"- **Relevance Improvement**: Top-1 score increases by **{top1_improvement:.0f}%**, Top-5 average by **{top5_improvement:.0f}%**\n"
265
+ html += f"- **Production-Ready Performance**: All stages complete in under **{max(m['latency_ms'] for m in all_metrics)}ms**\n"
266
+ html += "- **Semantic Understanding**: Vector embeddings provide the largest single improvement in search quality\n"
267
+ html += "- **Progressive Enhancement**: Each stage builds upon the previous, creating a robust pipeline\n"
268
+ html += "- **Real-World Applicability**: This architecture scales to millions of documents with proper infrastructure\n"
269
+
270
+ html += "\n\n---\n\n"
271
+ html += """
272
+ <div style="background: #FFF7ED; border-left: 4px solid #F97316; padding: 20px; border-radius: 8px; margin-top: 24px;">
273
+ <h3 style="color: #C2410C; margin-top: 0; font-size: 1.2em;">
274
+ 💡 Understanding Reranker Scores
275
+ </h3>
276
+ <p style="color: #7C2D12; font-size: 1.0em; line-height: 1.6; margin-bottom: 12px;">
277
+ <strong>Note:</strong> You may notice that reranking shows the <em>same cosine similarity scores</em> from Stage 2
278
+ despite improved result ordering. This is intentional and highlights an important concept:
279
+ </p>
280
+ <ul style="color: #7C2D12; font-size: 0.95em; line-height: 1.7; margin-left: 20px;">
281
+ <li><strong>Different ranking mechanisms:</strong> Cosine similarity measures vector distance in embedding space,
282
+ while rerankers use cross-encoder models that analyze query-document pairs directly for deeper semantic understanding.</li>
283
+ <li><strong>Why reranking works better:</strong> Cross-encoders examine token-level interactions between the query
284
+ and each document, capturing nuances that simple vector distance misses.</li>
285
+ <li><strong>The scores displayed:</strong> We preserve cosine scores to show that reranking <em>reorders</em> results
286
+ based on relevance, not similarity. A document with slightly lower cosine similarity might be more contextually relevant.</li>
287
+ <li><strong>Production best practice:</strong> Use fast vector search to retrieve candidates (~100-1000 results),
288
+ then apply computationally expensive reranking to the top results for maximum accuracy.</li>
289
+ </ul>
290
+ <p style="color: #7C2D12; font-size: 0.9em; margin-top: 12px; font-style: italic;">
291
+ This two-stage approach (retrieve + rerank) is the industry standard for building high-quality search systems at scale.
292
+ </p>
293
+ </div>
294
+ """
295
 
296
  return html
297
 
 
395
  """
396
  <h1 class="header-title" style="font-size: 2.5em; text-align: left;">Search Alchemy</h1>
397
  <p style="color: #64748B; font-size: 1.1em; margin-top: 0; text-align: left;">Building Production Search Pipelines with Fireworks AI</p>
398
+ <p style="color: #475569; font-size: 1.0em; line-height: 1.6; margin: 0;">
399
+ Four progressive stages demonstrating production-grade semantic search:
400
+ <strong>BM25</strong> → <strong>Vector Embeddings</strong> → <strong>Query Expansion</strong> → <strong>Reranking</strong>
401
+ </p>
402
  """
403
  )
404
  with gr.Row(elem_classes="compact-header"):
 
453
 
454
  with gr.Tab("Stage 1: BM25 Baseline"):
455
  stage1_output = gr.Markdown(label="Results")
 
 
456
 
457
  with gr.Tab("Stage 2: + Vector Embeddings"):
458
  stage2_output = gr.Markdown(label="Results")
 
 
459
 
460
  with gr.Tab("Stage 3: + Query Expansion"):
461
  stage3_output = gr.Markdown(label="Results")
 
 
462
 
463
  with gr.Tab("Stage 4: + LLM Reranking"):
464
  stage4_output = gr.Markdown(label="Results")
 
 
465
 
466
  with gr.Tab("Compare All Stages"):
467
  comparison_output = gr.Markdown(label="Comparison")
src/config.py CHANGED
@@ -69,33 +69,35 @@ CUSTOM_CSS = """
69
  .result-card {
70
  background: white;
71
  border-radius: 12px;
72
- padding: 16px;
73
- margin: 8px 0;
74
- box-shadow: 0 2px 4px rgba(103, 32, 255, 0.08);
75
  border: 1px solid #E6EAF4;
76
- transition: all 0.2s ease;
77
  }
78
 
79
  .result-card:hover {
80
- box-shadow: 0 4px 12px rgba(103, 32, 255, 0.12);
 
81
  border-color: #C4B5FD;
82
  }
83
 
84
  .metric-box {
85
- background: linear-gradient(to right, #F3F0FF, #FFFFFF);
86
- border-left: 3px solid #6720FF;
87
- padding: 12px;
88
  margin: 8px 0;
89
- border-radius: 8px;
90
  font-size: 0.9em;
 
91
  }
92
 
93
  .code-section {
94
- background: linear-gradient(to right, #F3F0FF, #FFFFFF);
95
- border-left: 3px solid #6720FF;
96
- padding: 16px;
97
  margin: 12px 0;
98
- border-radius: 8px;
99
  font-family: 'JetBrains Mono', monospace;
100
  font-size: 0.9em;
101
  }
@@ -104,62 +106,76 @@ CUSTOM_CSS = """
104
  width: 100%;
105
  border-collapse: collapse;
106
  margin: 20px 0;
 
 
 
107
  }
108
 
109
  .comparison-table th {
110
- background: #6720FF;
111
  color: white;
112
- padding: 12px;
113
  text-align: left;
114
  font-weight: 600;
 
 
 
115
  }
116
 
117
  .comparison-table td {
118
- padding: 12px;
119
  border-bottom: 1px solid #E6EAF4;
 
120
  }
121
 
122
- .comparison-table tr:hover {
123
- background: #F3F0FF;
 
 
 
 
124
  }
125
 
126
  ::-webkit-scrollbar {
127
- width: 8px;
128
- height: 8px;
129
  }
130
 
131
  ::-webkit-scrollbar-track {
132
  background: #F3F0FF;
133
- border-radius: 4px;
134
  }
135
 
136
  ::-webkit-scrollbar-thumb {
137
- background: #C4B5FD;
138
- border-radius: 4px;
139
  }
140
 
141
  ::-webkit-scrollbar-thumb:hover {
142
- background: #A78BFA;
143
  }
144
 
145
  details {
146
  border: 1px solid #E6EAF4;
147
- border-radius: 10px;
148
- padding: 12px;
149
- margin: 10px 0;
150
  background: white;
 
151
  }
152
 
153
  details[open] {
154
- border-color: #6720FF;
155
- box-shadow: 0 4px 12px rgba(103, 32, 255, 0.15);
156
  }
157
 
158
  summary {
159
  font-weight: 600;
160
  color: #6720FF;
161
  cursor: pointer;
162
- padding: 4px;
 
 
163
  }
164
 
165
  summary:hover {
@@ -181,6 +197,26 @@ summary:hover {
181
  font-size: 0.85em;
182
  padding: 8px 12px;
183
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  """
185
 
186
 
 
69
  .result-card {
70
  background: white;
71
  border-radius: 12px;
72
+ padding: 18px;
73
+ margin: 10px 0;
74
+ box-shadow: 0 2px 6px rgba(103, 32, 255, 0.08);
75
  border: 1px solid #E6EAF4;
76
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
77
  }
78
 
79
  .result-card:hover {
80
+ transform: translateY(-2px);
81
+ box-shadow: 0 6px 16px rgba(103, 32, 255, 0.15);
82
  border-color: #C4B5FD;
83
  }
84
 
85
  .metric-box {
86
+ background: linear-gradient(135deg, #F3F0FF 0%, #FFFFFF 100%);
87
+ border-left: 4px solid #6720FF;
88
+ padding: 16px;
89
  margin: 8px 0;
90
+ border-radius: 10px;
91
  font-size: 0.9em;
92
+ box-shadow: 0 2px 4px rgba(103, 32, 255, 0.05);
93
  }
94
 
95
  .code-section {
96
+ background: linear-gradient(135deg, #F3F0FF 0%, #FFFFFF 100%);
97
+ border-left: 4px solid #6720FF;
98
+ padding: 18px;
99
  margin: 12px 0;
100
+ border-radius: 10px;
101
  font-family: 'JetBrains Mono', monospace;
102
  font-size: 0.9em;
103
  }
 
106
  width: 100%;
107
  border-collapse: collapse;
108
  margin: 20px 0;
109
+ box-shadow: 0 2px 8px rgba(103, 32, 255, 0.08);
110
+ border-radius: 10px;
111
+ overflow: hidden;
112
  }
113
 
114
  .comparison-table th {
115
+ background: linear-gradient(135deg, #6720FF 0%, #7B2FFF 100%);
116
  color: white;
117
+ padding: 14px;
118
  text-align: left;
119
  font-weight: 600;
120
+ text-transform: uppercase;
121
+ font-size: 0.85em;
122
+ letter-spacing: 0.5px;
123
  }
124
 
125
  .comparison-table td {
126
+ padding: 14px;
127
  border-bottom: 1px solid #E6EAF4;
128
+ background: white;
129
  }
130
 
131
+ .comparison-table tr:hover td {
132
+ background: #F8F7FF;
133
+ }
134
+
135
+ .comparison-table tr:last-child td {
136
+ border-bottom: none;
137
  }
138
 
139
  ::-webkit-scrollbar {
140
+ width: 10px;
141
+ height: 10px;
142
  }
143
 
144
  ::-webkit-scrollbar-track {
145
  background: #F3F0FF;
146
+ border-radius: 5px;
147
  }
148
 
149
  ::-webkit-scrollbar-thumb {
150
+ background: linear-gradient(135deg, #C4B5FD 0%, #A78BFA 100%);
151
+ border-radius: 5px;
152
  }
153
 
154
  ::-webkit-scrollbar-thumb:hover {
155
+ background: linear-gradient(135deg, #A78BFA 0%, #8B5CF6 100%);
156
  }
157
 
158
  details {
159
  border: 1px solid #E6EAF4;
160
+ border-radius: 12px;
161
+ padding: 14px;
162
+ margin: 12px 0;
163
  background: white;
164
+ transition: all 0.3s ease;
165
  }
166
 
167
  details[open] {
168
+ border-color: #C4B5FD;
169
+ box-shadow: 0 4px 16px rgba(103, 32, 255, 0.12);
170
  }
171
 
172
  summary {
173
  font-weight: 600;
174
  color: #6720FF;
175
  cursor: pointer;
176
+ padding: 6px;
177
+ user-select: none;
178
+ transition: color 0.2s ease;
179
  }
180
 
181
  summary:hover {
 
197
  font-size: 0.85em;
198
  padding: 8px 12px;
199
  }
200
+
201
+ /* Tab styling */
202
+ .tabs button {
203
+ transition: all 0.2s ease;
204
+ }
205
+
206
+ .tabs button[aria-selected="true"] {
207
+ border-bottom: 3px solid #6720FF !important;
208
+ }
209
+
210
+ /* Button enhancements */
211
+ button.primary {
212
+ background: linear-gradient(135deg, #6720FF 0%, #7B2FFF 100%) !important;
213
+ transition: all 0.3s ease !important;
214
+ }
215
+
216
+ button.primary:hover {
217
+ transform: translateY(-1px);
218
+ box-shadow: 0 4px 12px rgba(103, 32, 255, 0.25) !important;
219
+ }
220
  """
221
 
222
 
src/constants/code_snippets.py DELETED
@@ -1,120 +0,0 @@
1
- """
2
- Code snippets for displaying implementation examples in the Gradio UI.
3
- Each snippet shows the actual implementation approach for each search stage.
4
- """
5
-
6
- CODE_STAGE_1 = """
7
- ```python
8
- import bm25s
9
- import pandas as pd
10
-
11
- # Step 1: Create BM25 index (one-time setup)
12
- df = pd.read_parquet("data/amazon_products.parquet")
13
- corpus = df["FullText"].tolist()
14
- corpus_tokens = bm25s.tokenize(corpus, stopwords="en")
15
-
16
- retriever = bm25s.BM25()
17
- retriever.index(corpus_tokens)
18
- retriever.save("data/bm25_index")
19
-
20
- # Step 2: Load index and search
21
- bm25_index = bm25s.BM25.load("data/bm25_index", load_corpus=False)
22
- query_tokens = bm25s.tokenize(query, stopwords="en")
23
- results, scores = bm25_index.retrieve(query_tokens, k=5)
24
-
25
- # Extract top results
26
- top_products = [df.iloc[idx] for idx in results[0]]
27
- ```
28
- """
29
-
30
- CODE_STAGE_2 = """
31
- ```python
32
- from openai import OpenAI
33
- import faiss
34
- import numpy as np
35
-
36
- # Initialize Fireworks AI client
37
- client = OpenAI(
38
- api_key="your_fireworks_api_key",
39
- base_url="https://api.fireworks.ai/inference/v1"
40
- )
41
-
42
- # Generate query embedding
43
- response = client.embeddings.create(
44
- model="accounts/fireworks/models/qwen3-embedding-8b",
45
- input=query
46
- )
47
- query_embedding = np.array(response.data[0].embedding, dtype=np.float32)
48
- query_vector = query_embedding.reshape(1, -1)
49
-
50
- # Normalize for cosine similarity using L2 distance
51
- faiss.normalize_L2(query_vector)
52
-
53
- # Load pre-built FAISS index
54
- index = faiss.read_index("data/faiss_index.bin")
55
-
56
- # Search for top-k similar documents
57
- distances, indices = index.search(query_vector, k=10)
58
-
59
- # Convert L2 distances to cosine similarity scores
60
- # After normalization: L2_distance = 2 * (1 - cosine_similarity)
61
- # So: cosine_similarity = 1 - (L2_distance / 2)
62
- similarity_scores = 1 - (distances[0] / 2)
63
-
64
- # Get top results
65
- top_results = [
66
- {
67
- "product": df.iloc[idx],
68
- "score": float(score)
69
- }
70
- for idx, score in zip(indices[0], similarity_scores)
71
- ]
72
- ```
73
- """
74
-
75
- CODE_STAGE_3 = """
76
- ```python
77
- # Query expansion with LLM
78
- response = client.chat.completions.create(
79
- model="accounts/fireworks/models/llama-v3p1-8b-instruct",
80
- messages=[{
81
- "role": "user",
82
- "content": f"Extract 2-3 key search concepts from: {query}"
83
- }]
84
- )
85
-
86
- expanded_query = response.choices[0].message.content
87
-
88
- # Search with expanded query
89
- response = client.embeddings.create(
90
- model="accounts/fireworks/models/qwen3-embedding-8b",
91
- input=[expanded_query] + documents
92
- )
93
-
94
- # Continue with embedding search...
95
- ```
96
- """
97
-
98
- CODE_STAGE_4 = """
99
- ```python
100
- # First get top 20 candidates from Stage 3
101
- top_20_results = get_stage_3_results(query, k=20)
102
-
103
- # Rerank with Fireworks reranker
104
- rerank_response = client.post(
105
- "https://api.fireworks.ai/inference/v1/rerank",
106
- json={
107
- "model": "fireworks/qwen3-reranker-8b",
108
- "query": query,
109
- "documents": [r["text"] for r in top_20_results],
110
- "top_n": 5
111
- }
112
- )
113
-
114
- # Get final ranked results
115
- final_results = [
116
- top_20_results[r["index"]]
117
- for r in rerank_response.json()["results"]
118
- ]
119
- ```
120
- """