thomasht86 commited on
Commit
368feb2
1 Parent(s): d446962

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. backend/vespa_app.py +10 -55
  2. frontend/app.py +133 -34
  3. main.py +1 -1
  4. static/full_images/02b30f947c2530e86fcc6f1617b284cb.jpg +0 -0
  5. static/full_images/04a954607e09c13ca53d62c9506b454d.jpg +0 -0
  6. static/full_images/0754449971c2415d0993dba99b610fb8.jpg +0 -0
  7. static/full_images/15441af3f266efefee2e1fa65da0ad5f.jpg +0 -0
  8. static/full_images/2722b68e9863dec57bd14b18ec5b6597.jpg +0 -0
  9. static/full_images/2e20778433bb7b41f2b3ff9d273ddedd.jpg +0 -0
  10. static/full_images/428d4ccc84859c8407ceaf66b5caebb1.jpg +0 -0
  11. static/full_images/470037d1296e5e9634a47d9e3c1c25e8.jpg +0 -0
  12. static/full_images/573506dd06553465e28617136e1aef34.jpg +0 -0
  13. static/full_images/641cf96aed6c75a938ca0303ed0723fb.jpg +0 -0
  14. static/full_images/65babd1b1ae12a8be4af8621473595cb.jpg +0 -0
  15. static/full_images/6be2405bc05a52291cb13d47f529ee03.jpg +0 -0
  16. static/full_images/6d5eac97f945ec5241c00eb3fa1eb348.jpg +0 -0
  17. static/full_images/95f14a79b6df72466b8ed6473758a23c.jpg +0 -0
  18. static/full_images/98db7f03fac252c78f142dc26780d7e1.jpg +0 -0
  19. static/full_images/a2b1647b5ff2a8485933a51866473a91.jpg +0 -0
  20. static/full_images/b23c60be45be26509dafd38e9fc77d6f.jpg +0 -0
  21. static/full_images/cb7e8833775e2cbe11bc116219f7e09d.jpg +0 -0
  22. static/full_images/e491fa7c526511cf6420ca54665e52a4.jpg +0 -0
  23. static/full_images/ea2bb0b2de938854888ac8e52124b077.jpg +0 -0
  24. static/full_images/f7785696541fc185bc1d10c8608b8100.jpg +0 -0
  25. static/img/colpali_child.png +0 -0
  26. static/img/visual-retrieval-demoapp-arch.png +0 -0
  27. static/sim_maps/-1764568205672507394_0_10.png +0 -0
  28. static/sim_maps/-1764568205672507394_0_11.png +0 -0
  29. static/sim_maps/-1764568205672507394_0_12.png +0 -0
  30. static/sim_maps/-1764568205672507394_0_14.png +0 -0
  31. static/sim_maps/-1764568205672507394_0_15.png +0 -0
  32. static/sim_maps/-1764568205672507394_0_16.png +0 -0
  33. static/sim_maps/-1764568205672507394_0_17.png +0 -0
  34. static/sim_maps/-1764568205672507394_0_3.png +0 -0
  35. static/sim_maps/-1764568205672507394_0_4.png +0 -0
  36. static/sim_maps/-1764568205672507394_0_5.png +0 -0
  37. static/sim_maps/-1764568205672507394_0_6.png +0 -0
  38. static/sim_maps/-1764568205672507394_0_7.png +0 -0
  39. static/sim_maps/-1764568205672507394_0_9.png +0 -0
  40. static/sim_maps/-1764568205672507394_1_10.png +0 -0
  41. static/sim_maps/-1764568205672507394_1_11.png +0 -0
  42. static/sim_maps/-1764568205672507394_1_12.png +0 -0
  43. static/sim_maps/-1764568205672507394_1_14.png +0 -0
  44. static/sim_maps/-1764568205672507394_1_15.png +0 -0
  45. static/sim_maps/-1764568205672507394_1_16.png +0 -0
  46. static/sim_maps/-1764568205672507394_1_17.png +0 -0
  47. static/sim_maps/-1764568205672507394_1_3.png +0 -0
  48. static/sim_maps/-1764568205672507394_1_4.png +0 -0
  49. static/sim_maps/-1764568205672507394_1_5.png +0 -0
  50. static/sim_maps/-1764568205672507394_1_6.png +0 -0
backend/vespa_app.py CHANGED
@@ -104,54 +104,6 @@ class VespaQueryClient:
104
  self.logger.debug(result_text)
105
  return response.json
106
 
107
- async def query_vespa_default(
108
- self,
109
- query: str,
110
- q_emb: torch.Tensor,
111
- hits: int = 3,
112
- timeout: str = "10s",
113
- sim_map: bool = False,
114
- **kwargs,
115
- ) -> dict:
116
- """
117
- Query Vespa using the default ranking profile.
118
- This corresponds to the "Hybrid ColPali+BM25" radio button in the UI.
119
-
120
- Args:
121
- query (str): The query text.
122
- q_emb (torch.Tensor): Query embeddings.
123
- hits (int, optional): Number of hits to retrieve. Defaults to 3.
124
- timeout (str, optional): Query timeout. Defaults to "10s".
125
-
126
- Returns:
127
- dict: The formatted query results.
128
- """
129
- async with self.app.asyncio(connections=1) as session:
130
- query_embedding = self.format_q_embs(q_emb)
131
-
132
- start = time.perf_counter()
133
- response: VespaQueryResponse = await session.query(
134
- body={
135
- "yql": (
136
- f"select {self.get_fields(sim_map=sim_map)} from {self.VESPA_SCHEMA_NAME} where userQuery();"
137
- ),
138
- "ranking": self.get_rank_profile("default", sim_map),
139
- "query": query,
140
- "timeout": timeout,
141
- "hits": hits,
142
- "input.query(qt)": query_embedding,
143
- "presentation.timing": True,
144
- **kwargs,
145
- },
146
- )
147
- assert response.is_successful(), response.json
148
- stop = time.perf_counter()
149
- self.logger.debug(
150
- f"Query time + data transfer took: {stop - start} s, Vespa reported searchtime was "
151
- f"{response.json.get('timing', {}).get('searchtime', -1)} s"
152
- )
153
- return self.format_query_results(query, response)
154
-
155
  async def query_vespa_bm25(
156
  self,
157
  query: str,
@@ -286,12 +238,14 @@ class VespaQueryClient:
286
 
287
  rank_method = ranking.split("_")[0]
288
  sim_map: bool = len(ranking.split("_")) > 1 and ranking.split("_")[1] == "sim"
289
- if rank_method == "nn+colpali":
290
- result = await self.query_vespa_nearest_neighbor(
291
- query, q_embs, sim_map=sim_map
 
 
 
 
292
  )
293
- elif rank_method == "bm25+colpali":
294
- result = await self.query_vespa_default(query, q_embs, sim_map=sim_map)
295
  elif rank_method == "bm25":
296
  result = await self.query_vespa_bm25(query, q_embs, sim_map=sim_map)
297
  else:
@@ -419,9 +373,10 @@ class VespaQueryClient:
419
  else:
420
  return ranking
421
 
422
- async def query_vespa_nearest_neighbor(
423
  self,
424
  query: str,
 
425
  q_emb: torch.Tensor,
426
  target_hits_per_query_tensor: int = 100,
427
  hnsw_explore_additional_hits: int = 300,
@@ -467,7 +422,7 @@ class VespaQueryClient:
467
  f"select {self.get_fields(sim_map=sim_map)} from {self.VESPA_SCHEMA_NAME} where {nn_string} or userQuery()"
468
  ),
469
  "ranking.profile": self.get_rank_profile(
470
- "retrieval-and-rerank", sim_map
471
  ),
472
  "timeout": timeout,
473
  "hits": hits,
 
104
  self.logger.debug(result_text)
105
  return response.json
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  async def query_vespa_bm25(
108
  self,
109
  query: str,
 
238
 
239
  rank_method = ranking.split("_")[0]
240
  sim_map: bool = len(ranking.split("_")) > 1 and ranking.split("_")[1] == "sim"
241
+ if rank_method == "colpali": # ColPali
242
+ result = await self.query_vespa_colpali(
243
+ query=query, ranking=rank_method, q_emb=q_embs, sim_map=sim_map
244
+ )
245
+ elif rank_method == "hybrid": # Hybrid ColPali+BM25
246
+ result = await self.query_vespa_colpali(
247
+ query=query, ranking=rank_method, q_emb=q_embs, sim_map=sim_map
248
  )
 
 
249
  elif rank_method == "bm25":
250
  result = await self.query_vespa_bm25(query, q_embs, sim_map=sim_map)
251
  else:
 
373
  else:
374
  return ranking
375
 
376
+ async def query_vespa_colpali(
377
  self,
378
  query: str,
379
+ ranking: str,
380
  q_emb: torch.Tensor,
381
  target_hits_per_query_tensor: int = 100,
382
  hnsw_explore_additional_hits: int = 300,
 
422
  f"select {self.get_fields(sim_map=sim_map)} from {self.VESPA_SCHEMA_NAME} where {nn_string} or userQuery()"
423
  ),
424
  "ranking.profile": self.get_rank_profile(
425
+ ranking=ranking, sim_map=sim_map
426
  ),
427
  "timeout": timeout,
428
  "hits": hits,
frontend/app.py CHANGED
@@ -1,7 +1,30 @@
1
  from typing import Optional
2
  from urllib.parse import quote_plus
3
 
4
- from fasthtml.components import H1, H2, H3, Br, Div, Form, Img, NotStr, P, Span, Strong
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  from fasthtml.xtend import A, Script
6
  from lucide_fasthtml import Lucide
7
  from shad4fast import Badge, Button, Input, Label, RadioGroup, RadioGroupItem, Separator
@@ -175,7 +198,7 @@ def ShareButtons():
175
  )
176
 
177
 
178
- def SearchBox(with_border=False, query_value="", ranking_value="nn+colpali"):
179
  grid_cls = "grid gap-2 items-center p-3 bg-muted w-full"
180
 
181
  if with_border:
@@ -203,7 +226,7 @@ def SearchBox(with_border=False, query_value="", ranking_value="nn+colpali"):
203
  Span("Ranking by:", cls="text-muted-foreground text-xs font-semibold"),
204
  RadioGroup(
205
  Div(
206
- RadioGroupItem(value="nn+colpali", id="nn+colpali"),
207
  Label("ColPali", htmlFor="ColPali"),
208
  cls="flex items-center space-x-2",
209
  ),
@@ -213,7 +236,7 @@ def SearchBox(with_border=False, query_value="", ranking_value="nn+colpali"):
213
  cls="flex items-center space-x-2",
214
  ),
215
  Div(
216
- RadioGroupItem(value="bm25+colpali", id="bm25+colpali"),
217
  Label("Hybrid ColPali + BM25", htmlFor="Hybrid ColPali + BM25"),
218
  cls="flex items-center space-x-2",
219
  ),
@@ -308,48 +331,124 @@ def Home():
308
  )
309
 
310
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  def AboutThisDemo():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  return Div(
313
- Div(
314
- Div(
315
- H1(
316
- "Vespa.ai + ColPali",
317
- cls="text-5xl font-bold tracking-wide md:tracking-wider",
318
- ),
319
- P(
320
- "Efficient Document Retrieval with Vision Language Models",
321
- cls="text-lg text-muted-foreground md:tracking-wide",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  ),
323
- Div(
324
- Img(
325
- src="/static/img/vespa-colpali.png",
326
- alt="Vespa and ColPali",
327
- cls="object-contain h-[377px]",
328
- ),
329
- cls="grid justify-center",
330
  ),
331
- Div(
332
- P(
333
- "This is a demo application showcasing the integration of Vespa.ai and ColPali for visual retrieval of documents.",
334
- cls="text-base",
335
- ),
336
- P(
337
- "The application uses a combination of neural networks and traditional search algorithms to retrieve relevant documents based on visual and textual queries.",
338
- cls="text-base",
339
- ),
340
- cls="grid gap-2 text-center",
341
  ),
342
- cls="grid gap-5 text-center",
343
  ),
344
- cls="grid gap-8 content-start mt-[8vh]",
345
  ),
346
- cls="grid w-full h-full max-w-screen-md gap-4 mx-auto",
 
 
 
 
 
 
 
 
 
 
 
 
347
  )
348
 
349
 
350
  def Search(request, search_results=[]):
351
  query_value = request.query_params.get("query", "").strip()
352
- ranking_value = request.query_params.get("ranking", "nn+colpali")
353
  return Div(
354
  Div(
355
  Div(
 
1
  from typing import Optional
2
  from urllib.parse import quote_plus
3
 
4
+ from fasthtml.components import (
5
+ H1,
6
+ H2,
7
+ H3,
8
+ Br,
9
+ Div,
10
+ Form,
11
+ Img,
12
+ NotStr,
13
+ P,
14
+ Hr,
15
+ Span,
16
+ A,
17
+ Script,
18
+ Button,
19
+ Label,
20
+ RadioGroup,
21
+ RadioGroupItem,
22
+ Separator,
23
+ Ul,
24
+ Li,
25
+ Strong,
26
+ Iframe,
27
+ )
28
  from fasthtml.xtend import A, Script
29
  from lucide_fasthtml import Lucide
30
  from shad4fast import Badge, Button, Input, Label, RadioGroup, RadioGroupItem, Separator
 
198
  )
199
 
200
 
201
+ def SearchBox(with_border=False, query_value="", ranking_value="hybrid"):
202
  grid_cls = "grid gap-2 items-center p-3 bg-muted w-full"
203
 
204
  if with_border:
 
226
  Span("Ranking by:", cls="text-muted-foreground text-xs font-semibold"),
227
  RadioGroup(
228
  Div(
229
+ RadioGroupItem(value="colpali", id="colpali"),
230
  Label("ColPali", htmlFor="ColPali"),
231
  cls="flex items-center space-x-2",
232
  ),
 
236
  cls="flex items-center space-x-2",
237
  ),
238
  Div(
239
+ RadioGroupItem(value="hybrid", id="hybrid"),
240
  Label("Hybrid ColPali + BM25", htmlFor="Hybrid ColPali + BM25"),
241
  cls="flex items-center space-x-2",
242
  ),
 
331
  )
332
 
333
 
334
+ def LinkResource(text, href):
335
+ return Li(
336
+ A(
337
+ Lucide(icon="external-link", size="18"),
338
+ text,
339
+ href=href,
340
+ target="_blank",
341
+ cls="flex items-center gap-1.5 hover:underline bold text-md",
342
+ ),
343
+ )
344
+
345
+
346
  def AboutThisDemo():
347
+ resources = [
348
+ {
349
+ "text": "Vespa Blog: How we built this demo",
350
+ "href": "https://blog.vespa.ai/visual-rag-in-practice",
351
+ },
352
+ {
353
+ "text": "Notebook to set up Vespa application and feed dataset",
354
+ "href": "https://pyvespa.readthedocs.io/en/latest/examples/visual_pdf_rag_with_vespa_colpali_cloud.html",
355
+ },
356
+ {
357
+ "text": "Web App (FastHTML) Code",
358
+ "href": "https://github.com/vespa-engine/sample-apps/tree/master/visual-retrieval-colpali",
359
+ },
360
+ {
361
+ "text": "Vespa Blog: Scaling ColPali to Billions",
362
+ "href": "https://blog.vespa.ai/scaling-colpali-to-billions/",
363
+ },
364
+ {
365
+ "text": "Vespa Blog: Retrieval with Vision Language Models",
366
+ "href": "https://blog.vespa.ai/retrieval-with-vision-language-models-colpali/",
367
+ },
368
+ ]
369
  return Div(
370
+ H1(
371
+ "About This Demo",
372
+ cls="text-3xl md:text-5xl font-bold tracking-wide md:tracking-wider",
373
+ ),
374
+ P(
375
+ "This demo showcases a Visual Retrieval-Augmented Generation (RAG) application over PDFs using ColPali embeddings in Vespa, built entirely in Python, using FastHTML. The code is fully open source.",
376
+ cls="text-base",
377
+ ),
378
+ Img(
379
+ src="/static/img/colpali_child.png",
380
+ alt="Example of token level similarity map",
381
+ cls="w-full",
382
+ ),
383
+ H2("Resources", cls="text-2xl font-semibold"),
384
+ Ul(
385
+ *[
386
+ LinkResource(resource["text"], resource["href"])
387
+ for resource in resources
388
+ ],
389
+ cls="space-y-2 list-disc pl-5",
390
+ ),
391
+ H2("Architecture Overview", cls="text-2xl font-semibold"),
392
+ Img(
393
+ src="/static/img/visual-retrieval-demoapp-arch.png",
394
+ alt="Architecture Overview",
395
+ cls="w-full",
396
+ ),
397
+ Ul(
398
+ Li(
399
+ Strong("Vespa Application: "),
400
+ "Vespa Application that handles indexing, search, ranking and queries, leveraging features like phased ranking and multivector MaxSim calculations.",
401
+ ),
402
+ Li(
403
+ Strong("Frontend: "),
404
+ "Built with FastHTML, offering a professional and responsive user interface without the complexity of separate frontend frameworks.",
405
+ ),
406
+ Li(
407
+ Strong("Backend: "),
408
+ "Also built with FastHTML. Handles query embedding inference using ColPali, serves static files, and is responsible for orchestrating interactions between Vespa and the frontend.",
409
+ ),
410
+ Li(
411
+ Strong("Gemini API: "),
412
+ "VLM for the AI response, providing responses based on the top results from Vespa.",
413
+ cls="list-disc list-inside",
414
+ ),
415
+ H2("User Experience Highlights", cls="text-2xl font-semibold"),
416
+ Ul(
417
+ Li(
418
+ Strong("Fast and Responsive: "),
419
+ "Optimized for quick loading times, with phased content delivery to display essential information immediately while loading detailed data in the background.",
420
  ),
421
+ Li(
422
+ Strong("Similarity Maps: "),
423
+ "Provides visual highlights of the most relevant parts of a page in response to a query, enhancing interpretability.",
 
 
 
 
424
  ),
425
+ Li(
426
+ Strong("Type-Ahead Suggestions: "),
427
+ "Offers query suggestions to assist users in formulating effective searches.",
 
 
 
 
 
 
 
428
  ),
429
+ cls="list-disc list-inside",
430
  ),
431
+ cls="grid gap-5",
432
  ),
433
+ H2("Dataset", cls="text-2xl font-semibold"),
434
+ P(
435
+ "The dataset used in this demo is retrieved from reports published by the Norwegian Government Pension Fund Global. It contains 6,992 pages from 116 PDF reports (2000–2024). The information is often presented in visual formats, making it an ideal dataset for visual retrieval applications.",
436
+ cls="text-base",
437
+ ),
438
+ Iframe(
439
+ src="https://huggingface.co/datasets/vespa-engine/gpfg-QA/embed/viewer",
440
+ frameborder="0",
441
+ width="100%",
442
+ height="500",
443
+ ),
444
+ Hr(), # To add some margin to bottom. Probably a much better way to do this, but the mb-[16vh] class doesn't seem to be applied
445
+ cls="w-full h-full max-w-screen-md gap-4 mx-auto mt-[8vh] mb-[16vh] grid gap-8 content-start",
446
  )
447
 
448
 
449
  def Search(request, search_results=[]):
450
  query_value = request.query_params.get("query", "").strip()
451
+ ranking_value = request.query_params.get("ranking", "hybrid")
452
  return Div(
453
  Div(
454
  Div(
main.py CHANGED
@@ -156,7 +156,7 @@ def get():
156
 
157
 
158
  @rt("/search")
159
- def get(request, query: str = "", ranking: str = "nn+colpali"):
160
  logger.info(f"/search: Fetching results for query: {query}, ranking: {ranking}")
161
 
162
  # Always render the SearchBox first
 
156
 
157
 
158
  @rt("/search")
159
+ def get(request, query: str = "", ranking: str = "hybrid"):
160
  logger.info(f"/search: Fetching results for query: {query}, ranking: {ranking}")
161
 
162
  # Always render the SearchBox first
static/full_images/02b30f947c2530e86fcc6f1617b284cb.jpg ADDED
static/full_images/04a954607e09c13ca53d62c9506b454d.jpg ADDED
static/full_images/0754449971c2415d0993dba99b610fb8.jpg ADDED
static/full_images/15441af3f266efefee2e1fa65da0ad5f.jpg ADDED
static/full_images/2722b68e9863dec57bd14b18ec5b6597.jpg ADDED
static/full_images/2e20778433bb7b41f2b3ff9d273ddedd.jpg ADDED
static/full_images/428d4ccc84859c8407ceaf66b5caebb1.jpg ADDED
static/full_images/470037d1296e5e9634a47d9e3c1c25e8.jpg ADDED
static/full_images/573506dd06553465e28617136e1aef34.jpg ADDED
static/full_images/641cf96aed6c75a938ca0303ed0723fb.jpg ADDED
static/full_images/65babd1b1ae12a8be4af8621473595cb.jpg ADDED
static/full_images/6be2405bc05a52291cb13d47f529ee03.jpg ADDED
static/full_images/6d5eac97f945ec5241c00eb3fa1eb348.jpg ADDED
static/full_images/95f14a79b6df72466b8ed6473758a23c.jpg ADDED
static/full_images/98db7f03fac252c78f142dc26780d7e1.jpg ADDED
static/full_images/a2b1647b5ff2a8485933a51866473a91.jpg ADDED
static/full_images/b23c60be45be26509dafd38e9fc77d6f.jpg ADDED
static/full_images/cb7e8833775e2cbe11bc116219f7e09d.jpg ADDED
static/full_images/e491fa7c526511cf6420ca54665e52a4.jpg ADDED
static/full_images/ea2bb0b2de938854888ac8e52124b077.jpg ADDED
static/full_images/f7785696541fc185bc1d10c8608b8100.jpg ADDED
static/img/colpali_child.png ADDED
static/img/visual-retrieval-demoapp-arch.png ADDED
static/sim_maps/-1764568205672507394_0_10.png ADDED
static/sim_maps/-1764568205672507394_0_11.png ADDED
static/sim_maps/-1764568205672507394_0_12.png ADDED
static/sim_maps/-1764568205672507394_0_14.png ADDED
static/sim_maps/-1764568205672507394_0_15.png ADDED
static/sim_maps/-1764568205672507394_0_16.png ADDED
static/sim_maps/-1764568205672507394_0_17.png ADDED
static/sim_maps/-1764568205672507394_0_3.png ADDED
static/sim_maps/-1764568205672507394_0_4.png ADDED
static/sim_maps/-1764568205672507394_0_5.png ADDED
static/sim_maps/-1764568205672507394_0_6.png ADDED
static/sim_maps/-1764568205672507394_0_7.png ADDED
static/sim_maps/-1764568205672507394_0_9.png ADDED
static/sim_maps/-1764568205672507394_1_10.png ADDED
static/sim_maps/-1764568205672507394_1_11.png ADDED
static/sim_maps/-1764568205672507394_1_12.png ADDED
static/sim_maps/-1764568205672507394_1_14.png ADDED
static/sim_maps/-1764568205672507394_1_15.png ADDED
static/sim_maps/-1764568205672507394_1_16.png ADDED
static/sim_maps/-1764568205672507394_1_17.png ADDED
static/sim_maps/-1764568205672507394_1_3.png ADDED
static/sim_maps/-1764568205672507394_1_4.png ADDED
static/sim_maps/-1764568205672507394_1_5.png ADDED
static/sim_maps/-1764568205672507394_1_6.png ADDED