airayven7 commited on
Commit
79d9660
Β·
verified Β·
1 Parent(s): 2ad630b

Sync from GitHub 72cbdd8

Browse files
Files changed (2) hide show
  1. README.md +1 -0
  2. app.py +58 -18
README.md CHANGED
@@ -11,6 +11,7 @@ pinned: false
11
  preload_from_hub:
12
  - nvidia/nemotron-colembed-vl-4b-v2
13
  - openbmb/MiniCPM-V-4_5
 
14
  license: mit
15
  ---
16
 
 
11
  preload_from_hub:
12
  - nvidia/nemotron-colembed-vl-4b-v2
13
  - openbmb/MiniCPM-V-4_5
14
+ - nvidia/llama-nemotron-embed-vl-1b-v2
15
  license: mit
16
  ---
17
 
app.py CHANGED
@@ -12,7 +12,8 @@ the pre-indexed library and answers questions):
12
  retrieval is dense cosine over chunks with parent-page lookup.
13
 
14
  Both hand the retrieved page images to MiniCPM-V for the grounded answer,
15
- in one ZeroGPU call per question.
 
16
 
17
  Module layout:
18
  models/colembed.py ColEmbed β€” visual: page embeddings + MaxSim
@@ -21,7 +22,7 @@ Module layout:
21
  core/visual_store.py VisualStore β€” on-disk per-page token embeddings
22
  core/parsed_store.py ParsedStore β€” chunks + dense embedding matrix
23
  pipelines/visual_ask.py VisualAskPipeline
24
- pipelines/parsed_ask.py ParsedAskPipeline (not wired into the UI yet)
25
  """
26
 
27
  import os
@@ -37,12 +38,28 @@ from core.constants import (
37
  PREINDEXED_DIR,
38
  VISUAL_SUBDIR,
39
  )
 
40
  from core.visual_store import VisualStore
 
41
  from pipelines.visual_ask import VisualAskPipeline
42
 
43
- # Construct once at startup (the models load onto cuda here, in the main process).
44
- library = VisualStore(os.path.join(PREINDEXED_DIR, VISUAL_SUBDIR))
45
- ask_pipeline = VisualAskPipeline()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
 
48
  def sync_library() -> None:
@@ -70,22 +87,32 @@ def sync_library() -> None:
70
  sync_library()
71
 
72
 
73
- def _choices(store) -> list[tuple[str, str]]:
74
- return [(d["name"], d["doc_id"]) for d in store.list_docs()]
 
 
 
 
 
75
 
76
 
77
- def refresh_library():
 
 
 
 
78
  """Re-pull the library dataset (incremental) and refresh the dropdown,
79
  so manuals indexed after the Space booted show up without a restart."""
80
  sync_library()
81
- return gr.update(choices=_choices(library))
82
 
83
 
84
- def ask_library(question, doc_id):
85
  if not doc_id:
86
  raise gr.Error("Pick a manual first.")
 
87
  try:
88
- return ask_pipeline.run(library, question, [doc_id], DEFAULT_TOP_K)
89
  except ValueError as e:
90
  raise gr.Error(str(e)) from e
91
 
@@ -93,13 +120,23 @@ def ask_library(question, doc_id):
93
  with gr.Blocks(title="Repair Guy") as demo:
94
  gr.Markdown(
95
  "# πŸ”§ Repair Guy\n"
96
- "Ask questions over repair manuals. Pages are retrieved visually with "
 
 
97
  "[Nemotron ColEmbed v2](https://huggingface.co/nvidia/nemotron-colembed-vl-4b-v2) "
98
- "(late interaction, no parsing) and answered by MiniCPM-V reading the "
99
- "most relevant pages."
 
 
 
 
 
100
  )
101
  with gr.Row():
102
  with gr.Column(scale=1):
 
 
 
103
  manual_in = gr.Dropdown(label="Manual", choices=[])
104
  lib_refresh_btn = gr.Button("πŸ”„ Sync library", size="sm")
105
  lib_question_in = gr.Textbox(
@@ -112,16 +149,19 @@ with gr.Blocks(title="Repair Guy") as demo:
112
  lib_answer_out = gr.Markdown(label="Answer")
113
  lib_pages_out = gr.Gallery(label="Pages used", columns=3, height=420)
114
 
115
- lib_refresh_btn.click(refresh_library, outputs=[manual_in])
 
 
 
116
  lib_ask_btn.click(
117
- ask_library, inputs=[lib_question_in, manual_in],
118
  outputs=[lib_answer_out, lib_pages_out],
119
  )
120
  lib_question_in.submit(
121
- ask_library, inputs=[lib_question_in, manual_in],
122
  outputs=[lib_answer_out, lib_pages_out],
123
  )
124
- demo.load(lambda: gr.update(choices=_choices(library)), outputs=[manual_in])
125
 
126
 
127
  if __name__ == "__main__":
 
12
  retrieval is dense cosine over chunks with parent-page lookup.
13
 
14
  Both hand the retrieved page images to MiniCPM-V for the grounded answer,
15
+ in one ZeroGPU call per question. The approach is picked per question in
16
+ the UI; each approach has its own store directory and manual list.
17
 
18
  Module layout:
19
  models/colembed.py ColEmbed β€” visual: page embeddings + MaxSim
 
22
  core/visual_store.py VisualStore β€” on-disk per-page token embeddings
23
  core/parsed_store.py ParsedStore β€” chunks + dense embedding matrix
24
  pipelines/visual_ask.py VisualAskPipeline
25
+ pipelines/parsed_ask.py ParsedAskPipeline
26
  """
27
 
28
  import os
 
38
  PREINDEXED_DIR,
39
  VISUAL_SUBDIR,
40
  )
41
+ from core.parsed_store import ParsedStore
42
  from core.visual_store import VisualStore
43
+ from pipelines.parsed_ask import ParsedAskPipeline
44
  from pipelines.visual_ask import VisualAskPipeline
45
 
46
+ # Construct once at startup (the models load onto cuda here, in the main
47
+ # process). Both pipelines expose the same run(store, question, doc_ids, top_k).
48
+ LIBRARIES = {
49
+ "visual": (
50
+ VisualStore(os.path.join(PREINDEXED_DIR, VISUAL_SUBDIR)),
51
+ VisualAskPipeline(),
52
+ ),
53
+ "parsed": (
54
+ ParsedStore(os.path.join(PREINDEXED_DIR, PARSED_SUBDIR)),
55
+ ParsedAskPipeline(),
56
+ ),
57
+ }
58
+
59
+ METHOD_CHOICES = [
60
+ ("Visual β€” ColEmbed page embeddings + MaxSim", "visual"),
61
+ ("Parsed β€” Nemotron Parse chunks + dense retrieval", "parsed"),
62
+ ]
63
 
64
 
65
  def sync_library() -> None:
 
87
  sync_library()
88
 
89
 
90
+ def _manuals_update(method: str, current: str | None = None):
91
+ """Dropdown update for the chosen approach's library; keeps the current
92
+ selection when the same manual is indexed under both approaches."""
93
+ store, _ = LIBRARIES[method]
94
+ choices = [(d["name"], d["doc_id"]) for d in store.list_docs()]
95
+ ids = [doc_id for _, doc_id in choices]
96
+ return gr.update(choices=choices, value=current if current in ids else None)
97
 
98
 
99
+ def switch_method(method, doc_id):
100
+ return _manuals_update(method, doc_id)
101
+
102
+
103
+ def refresh_library(method, doc_id):
104
  """Re-pull the library dataset (incremental) and refresh the dropdown,
105
  so manuals indexed after the Space booted show up without a restart."""
106
  sync_library()
107
+ return _manuals_update(method, doc_id)
108
 
109
 
110
+ def ask_library(question, doc_id, method):
111
  if not doc_id:
112
  raise gr.Error("Pick a manual first.")
113
+ store, pipeline = LIBRARIES[method]
114
  try:
115
+ return pipeline.run(store, question, [doc_id], DEFAULT_TOP_K)
116
  except ValueError as e:
117
  raise gr.Error(str(e)) from e
118
 
 
120
  with gr.Blocks(title="Repair Guy") as demo:
121
  gr.Markdown(
122
  "# πŸ”§ Repair Guy\n"
123
+ "Ask questions over repair manuals, comparing two local-only retrieval "
124
+ "approaches over the same library:\n"
125
+ "- **Visual** β€” pages retrieved as images with "
126
  "[Nemotron ColEmbed v2](https://huggingface.co/nvidia/nemotron-colembed-vl-4b-v2) "
127
+ "(late interaction, no parsing).\n"
128
+ "- **Parsed** β€” pages parsed with "
129
+ "[Nemotron Parse](https://huggingface.co/nvidia/NVIDIA-Nemotron-Parse-v1.2), "
130
+ "figures/tables described by MiniCPM-V, section chunks retrieved with "
131
+ "[Llama Nemotron Embed](https://huggingface.co/nvidia/llama-nemotron-embed-vl-1b-v2) "
132
+ "and mapped back to their pages.\n\n"
133
+ "Either way, MiniCPM-V reads the retrieved pages and answers."
134
  )
135
  with gr.Row():
136
  with gr.Column(scale=1):
137
+ method_in = gr.Radio(
138
+ METHOD_CHOICES, value="visual", label="Retrieval approach"
139
+ )
140
  manual_in = gr.Dropdown(label="Manual", choices=[])
141
  lib_refresh_btn = gr.Button("πŸ”„ Sync library", size="sm")
142
  lib_question_in = gr.Textbox(
 
149
  lib_answer_out = gr.Markdown(label="Answer")
150
  lib_pages_out = gr.Gallery(label="Pages used", columns=3, height=420)
151
 
152
+ method_in.change(switch_method, inputs=[method_in, manual_in], outputs=[manual_in])
153
+ lib_refresh_btn.click(
154
+ refresh_library, inputs=[method_in, manual_in], outputs=[manual_in]
155
+ )
156
  lib_ask_btn.click(
157
+ ask_library, inputs=[lib_question_in, manual_in, method_in],
158
  outputs=[lib_answer_out, lib_pages_out],
159
  )
160
  lib_question_in.submit(
161
+ ask_library, inputs=[lib_question_in, manual_in, method_in],
162
  outputs=[lib_answer_out, lib_pages_out],
163
  )
164
+ demo.load(switch_method, inputs=[method_in, manual_in], outputs=[manual_in])
165
 
166
 
167
  if __name__ == "__main__":