File size: 5,246 Bytes
120fa46 436dbdd 120fa46 9c0bd3b 120fa46 deb5a88 436dbdd 14a9b91 3f08eff 79d9660 120fa46 c982278 2ad630b c982278 89111aa 14a9b91 c982278 89111aa 14a9b91 c982278 89111aa 14a9b91 89111aa 14a9b91 2ad630b 14a9b91 2ad630b 14a9b91 2ad630b 293ed8c 51720d8 c982278 89111aa 14a9b91 51720d8 436dbdd 2ad630b 14a9b91 436dbdd 120fa46 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | ---
title: Repair Guy
emoji: π§
colorFrom: purple
colorTo: red
sdk: gradio
# 6.17.3 is the newest gradio that allows huggingface-hub<1.0, which
# transformers 4.57.x (required by the MiniCPM/ColEmbed remote code) pins;
# 6.18.0 bumped to huggingface-hub>=1.2 and makes the Space build unresolvable.
sdk_version: 6.17.3
python_version: '3.12'
app_file: app.py
pinned: false
preload_from_hub:
- nvidia/nemotron-colembed-vl-4b-v2
- openbmb/MiniCPM5-1B
- openbmb/MiniCPM-V-4_5
- nvidia/llama-nemotron-embed-vl-1b-v2
license: mit
---
# Repair Guy β a hands-busy mechanic's manual assistant
A page viewer over repair manuals driven by short requests β *"go to brake
bleeding"*, *"next page"*, *"circle the brake hoses"*. It **finds the right
page and points at it**; it never writes answers (the manual does the talking).
It keeps a compact memory of the turn-so-far, used only to resolve references
(*"circle the other one"*, *"go back to that bolt"*). Everything runs inside the
Space (no external endpoints).
- **Obvious nav (client, instant)** β next/previous page, *page 412*, back,
next/previous section: pure frontend state over the rendered page images.
- **Everything else β `/find`, one ZeroGPU call** (`app/pipelines/agent_ask.py`):
**MiniCPM5-1B** β the small text "brain" β sees the conversation so far, the
manual's table of contents, and the **whole text of the page being viewed**
(parsed page β text, figures/tables as their descriptions), and calls tools in
a loop until a page is shown:
- **search** β semantic-search the manual; the best page is shown and its text
fed back so the agent can then circle on it;
- **go_to_section** β jump to a table-of-contents section's first page;
- **circle** β circle something on the current page: **MiniCPM-V 4.5** grounds
the target visually (β bbox β SVG circle);
- **done** β nothing to do, or it isn't in the manual.
Streamed as events; the UI shows tool chips and the resulting page/circle,
never model prose. The table of contents is the manual's clean PDF-bookmark
chapters plus a per-request fuzzy shortlist of fine parse headings
(`app/core/sections.py`).
**Retrieval is fused** β the search tool needs both indexes of a manual, so a
manual must be indexed both ways:
- **Visual** β every page is embedded as an image with
[Nemotron ColEmbed v2](https://huggingface.co/nvidia/nemotron-colembed-vl-4b-v2)
(multi-vector, late interaction). The search tool embeds the query and scores
it against every page with MaxSim (batched torch matmuls, pages streamed from
disk via numpy memmap) to shortlist candidate pages β the top page is shown.
(Late-interaction visual ranking beat a 1B text rerank of the shortlist in the
eval, 0.84 vs 0.68 hit@1, so the search tool takes ColEmbed's top page.)
- **Parsed** β pages are parsed with
[Nemotron Parse v1.2](https://huggingface.co/nvidia/NVIDIA-Nemotron-Parse-v1.2),
figures and tables are described by MiniCPM-V, and heading-based section
chunks are embedded with
[Llama Nemotron Embed VL 1B v2](https://huggingface.co/nvidia/llama-nemotron-embed-vl-1b-v2)
(used by the offline answer eval). On the Space the parsed pages supply the
**whole-page text** the agent reasons over (`app/core/page_context.py`).
## Indexing (offline only)
All ingestion runs offline on Modal GPUs β `scripts/index_modal.py` in the
GitHub repo (`--method visual|parsed`) β and is pushed to the
[library dataset](https://huggingface.co/datasets/build-small-hackathon/repair-guy-library),
laid out as `<method>/<doc_id>/`. The Space syncs it to `/data/preindexed`
at startup (and via the π button). The parsed method runs as two Modal
functions because Nemotron Parse requires transformers 5.x while the rest of
the stack is on 4.x.
## Local UI development (mock mode)
To iterate on the UI without GPUs, model downloads or the HF library sync, run
with `MOCK_MODELS=1`. Drop any PDF into `app/data/mock_pdfs/` (or set
`MOCK_PDF_DIR`) and the whole find-and-point UX works over real rendered pages
of that PDF β navigation, and every event the agent emits (go-to-section,
circle-on-this-page, searchβshowβcircle, plus the can't-find reply). A keyword
heuristic stands in for the LLM agent; the section structure and bboxes are faked.
```bash
cd app
uv pip install gradio pymupdf pillow numpy huggingface_hub # light deps only
MOCK_MODELS=1 python app.py
```
The π Sync library button just re-scans the folder, so PDFs added while the app
is running show up without a restart. See `pipelines/mock_ask.py`.
## Space setup
- **Persistent storage** must be enabled (the library sync lives under
`/data`). The visual index is the big one: roughly 5β12 MB per page of
float16 token embeddings (a 1000-page manual is ~6β12 GB); the parsed index
is a few MB per manual.
- Optional env vars: `LIBRARY_DATASET_ID`, `COLEMBED_MODEL_ID`,
`MINICPM_MODEL_ID` (the VLM "eyes"), `MINICPM_AGENT_MODEL_ID` (the 1B "brain"),
`NEMOTRON_EMBED_MODEL_ID`, `COLEMBED_ATTN` (defaults to `sdpa`; set
`flash_attention_2` if flash-attn is installed).
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|