repair-guy / pipelines /visual_ask.py
airayven7's picture
Sync from GitHub 7e6c395
14a9b91 verified
Raw
History Blame Contribute Delete
1.93 kB
"""Visual ask pipeline: question -> MaxSim retrieval over page embeddings ->
top-K page images -> MiniCPM answer grounded in those pages.
The whole question runs in ONE @spaces.GPU call (query embedding + MaxSim +
page rendering + answer generation), so each question pays the ZeroGPU
allocation wait once.
"""
from __future__ import annotations
import spaces
from core.constants import ASK_GPU_DURATION
from core.pdf import render_page
from core.visual_store import VisualStore
from models.colembed import maxsim_search
from models.minicpm import generate_answer
@spaces.GPU(duration=ASK_GPU_DURATION)
def _ask_on_gpu(
question: str,
store: VisualStore,
doc_ids: list[str] | None,
top_k: int,
names: dict[str, str],
):
hits = maxsim_search(question, store, doc_ids, top_k)
pages = [
(f"{names[doc_id]} — p.{page}", render_page(store.pdf_path(doc_id), page), score)
for doc_id, page, score in hits
]
answer = generate_answer(question, [(label, img) for label, img, _ in pages])
gallery = [(img, f"{label} (score {score:.1f})") for label, img, score in pages]
page_refs = [(doc_id, page) for doc_id, page, _ in hits]
return answer, gallery, page_refs
class VisualAskPipeline:
"""Stateless: the store is passed per call."""
def run(self, store: VisualStore, question: str, doc_ids: list[str] | None, top_k: int):
"""Return (answer markdown, gallery items [(image, caption)], page_refs
[(doc_id, page_num)] for the retrieved pages, in answer order)."""
question = (question or "").strip()
if not question:
raise ValueError("Please enter a question.")
docs = store.list_docs()
if not docs:
raise ValueError("No manuals in this library yet.")
names = {d["doc_id"]: d["name"] for d in docs}
return _ask_on_gpu(question, store, doc_ids or None, int(top_k), names)