import os import sys import subprocess import re from collections.abc import Iterator import gradio as gr from huggingface_hub import hf_hub_download # Install llama-cpp-python if not present try: from llama_cpp import Llama except ModuleNotFoundError: subprocess.check_call([sys.executable, "-m", "pip", "install", "llama-cpp-python"]) from llama_cpp import Llama # Install requests if not present try: import requests except ModuleNotFoundError: subprocess.check_call([sys.executable, "-m", "pip", "install", "requests"]) import requests # Install pypdf if not present try: from pypdf import PdfReader except ModuleNotFoundError: subprocess.check_call([sys.executable, "-m", "pip", "install", "pypdf"]) from pypdf import PdfReader MAX_MAX_NEW_TOKENS = 512 # Adjusted for document summaries DEFAULT_MAX_NEW_TOKENS = 256 MAX_INPUT_TOKEN_LENGTH = int(os.getenv("MAX_INPUT_TOKEN_LENGTH", "4096")) # Increased for longer context DESCRIPTION = """ # DocChat: PDF Summarizer and Q&A (CPU-Only, Ultra-Fast Optimization) This application delivers an interactive chat interface powered by a highly efficient, small AI model adapted for summarizing and answering questions about any publicly accessible PDF document through specialized prompt engineering. Provide a PDF URL in your message (e.g., 'Load this PDF: https://example.com/document.pdf'), and the bot will download, process it, and be ready for summaries or questions. It ensures rapid, reasoned responses to user queries. Duplicate this Space for customization or queue-free deployment. 🔎 Model details are available at the [google/gemma-2b-it](https://huggingface.co/google/gemma-2b-it) repository; pre-trained and instruction-tuned, further adapted here for document summarization.
Running on CPU 🥶 Inference is heavily optimized for responses in under 10 seconds for simple queries, with output limited to 512 tokens maximum. For longer, more complete responses, increase 'Max New Tokens' in Advanced Settings. Brief delays may occur in free-tier environments due to shared resources, but typical generation speeds reach 20-40 tokens per second.
""" LICENSE = """ --- This application employs the [google/gemma-2b-it](https://huggingface.co/google/gemma-2b-it) model, governed by Google's Gemma Terms of Use. Refer to the [model card](https://huggingface.co/google/gemma-2b-it) and [Gemma documentation](https://ai.google.dev/gemma/terms) for details. """ # Download the GGUF model file model_repo = "mradermacher/gemma-2b-it-GGUF" # Adjusted to match common repo; update if needed model_filename = "gemma-2b-it.Q4_K_M.gguf" model_path = hf_hub_download(repo_id=model_repo, filename=model_filename) # Load the model with optimizations llm = Llama( model_path=model_path, n_ctx=8192, # Increased context window for document content n_batch=512, # Balanced for CPU efficiency n_threads=2, # Set to 2 for Hugging Face free tier (2 vCPUs) n_gpu_layers=0 # Enforce CPU-only execution ) DEFAULT_SYSTEM_PROMPT = """You are DocChat, a knowledgeable AI assistant specializing in summarizing and analyzing PDF documents. Provide accurate, helpful, reasoned, detailed, and comprehensive summaries or answers based on the provided document. Always base responses on the document content. If no document is loaded, ask the user to provide a publicly accessible PDF URL.""" def process_pdf(url: str) -> str: try: response = requests.get(url, timeout=10) response.raise_for_status() pdf_path = "temp.pdf" with open(pdf_path, "wb") as f: f.write(response.content) reader = PdfReader(pdf_path) document_text = "" for page in reader.pages: text = page.extract_text() if text: document_text += text + "\n\n" # Truncate document text if too long max_doc_tokens = 6000 # Leave room for prompt and response doc_tokens = llm.tokenize(document_text.encode("utf-8")) if len(doc_tokens) > max_doc_tokens: document_text = llm.detokenize(doc_tokens[:max_doc_tokens]).decode("utf-8", errors="ignore") + "\n[Document truncated for context length]" os.remove(pdf_path) return document_text except Exception as e: return f"Error processing PDF: {str(e)}" def generate( message: str, chat_history: list[tuple[str, str]], system_prompt: str, max_new_tokens: int = DEFAULT_MAX_NEW_TOKENS, temperature: float = 0.6, top_p: float = 0.9, top_k: int = 50, repetition_penalty: float = 1.2, ) -> Iterator[str]: if message.lower().strip() == "hi": response = "Hello, I am your document summarizer assistant. Provide a publicly accessible PDF URL to get started." yield response return # Build conversation for Gemma prompt format conversation = [] if system_prompt and not chat_history: # Prepend system prompt to first user message for Gemma first_message = system_prompt + "\n\n" + message conversation.append({"role": "user", "content": first_message}) else: if system_prompt: conversation.append({"role": "user", "content": system_prompt}) for user, assistant in chat_history: conversation.append({"role": "user", "content": user}) conversation.append({"role": "model", "content": assistant}) conversation.append({"role": "user", "content": message}) # Construct prompt in Gemma format prompt_parts = ["