Spaces:
Build error
Build error
| import os | |
| import datetime | |
| import time | |
| import gradio as gr | |
| from langchain_community.document_loaders import PyPDFLoader | |
| from langchain_text_splitters import RecursiveCharacterTextSplitter | |
| # Replacing vector components with a statistical BM25 (vectorless) retriever | |
| from langchain_community.retrievers import BM25Retriever | |
| from langchain_community.llms import LlamaCpp | |
| from langchain.chains import create_retrieval_chain | |
| from langchain.chains.combine_documents import create_stuff_documents_chain | |
| from langchain_core.prompts import ChatPromptTemplate | |
| # ------------------------------------------------------------------------- | |
| # 1. Global Setup (LLM Loaded Once - Embeddings Removed) | |
| # ------------------------------------------------------------------------- | |
| print("Initializing Local LLM Runtime (Vectorless Mode)...") | |
| MODEL_PATH = "model/gemma-4-E2B-it-Q8_0.gguf" # Ensure this path is correct | |
| llm = LlamaCpp( | |
| model_path=MODEL_PATH, | |
| n_ctx=4096, | |
| temperature=0.1, | |
| max_tokens=512, | |
| verbose=False | |
| ) | |
| # Build the rigid system prompt structure | |
| system_prompt = ( | |
| "You are a strict Document Retrieval analysis assistant.\n" | |
| "Answer the user's question using ONLY the following pieces of retrieved financial context. " | |
| "If you do not know the answer or if it is not explicitly stated in the context, " | |
| "state clearly that the book does not provide this information. Do not make up facts.\n\n" | |
| "Context:\n{context}" | |
| ) | |
| prompt = ChatPromptTemplate.from_messages([ | |
| ("system", system_prompt), | |
| ("human", "{input}"), | |
| ]) | |
| question_answer_chain = create_stuff_documents_chain(llm, prompt) | |
| # ------------------------------------------------------------------------- | |
| # 2. Core Processing Functions | |
| # ------------------------------------------------------------------------- | |
| def process_pdf(pdf_file): | |
| """Loads, splits, and indexes the PDF using BM25 string-matching instead of vectors.""" | |
| if pdf_file is None: | |
| return "No file uploaded.", None, "" | |
| start_time = time.time() | |
| current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| log_output = f"[{current_time}] ๐ Loading PDF: {os.path.basename(pdf_file.name)}...\n" | |
| try: | |
| # Load and Split | |
| loader = PyPDFLoader(pdf_file.name) | |
| docs = loader.load() | |
| text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) | |
| final_chunks = text_splitter.split_documents(docs) | |
| log_output += f" -> Successfully generated {len(final_chunks)} semantic chunks.\n" | |
| # Build Vectorless BM25 Text Index | |
| log_output += f" -> Indexing text chunks into local BM25 Search Engine...\n" | |
| bm25_retriever = BM25Retriever.from_documents(final_chunks) | |
| # Configure to return top 3 keyword-matched document chunks | |
| bm25_retriever.k = 3 | |
| elapsed = time.time() - start_time | |
| log_output += f"โ Success! Vectorless processing finished in {elapsed:.2f} seconds." | |
| return log_output, bm25_retriever, "PDF text indexed! You can now ask questions." | |
| except Exception as e: | |
| return f"โ Error processing PDF: {str(e)}", None, "Processing failed." | |
| def answer_query(query, bm25_retriever, chat_history): | |
| """Retrieves context using BM25 and runs inference against the local GGUF model.""" | |
| current_time = datetime.datetime.now().strftime("%H:%M:%S") | |
| if bm25_retriever is None: | |
| chat_history.append((query, "โ ๏ธ Please upload and process a financial PDF first!")) | |
| return "", chat_history | |
| if not query.strip(): | |
| return "", chat_history | |
| # Dynamically bind the pre-computed BM25 engine into the RAG chain | |
| rag_chain = create_retrieval_chain(bm25_retriever, question_answer_chain) | |
| # Run Inference | |
| response = rag_chain.invoke({"input": query}) | |
| answer = response['answer'] | |
| # Format answer with timestamp marker | |
| formatted_answer = f"[{current_time}]\n{answer}" | |
| chat_history.append((query, formatted_answer)) | |
| return "", chat_history | |
| # ------------------------------------------------------------------------- | |
| # 3. Gradio UI Architecture | |
| # ------------------------------------------------------------------------- | |
| with gr.Blocks(theme=gr.themes.Soft()) as demo: | |
| # State now holds the BM25Retriever object instead of a FAISS DB | |
| retriever_state = gr.State(None) | |
| gr.Markdown( | |
| """ | |
| # ๐ฆ Secure Document Retrieve Vectorless RAG Engine | |
| Analyze private documents completely offline using high-performance local AI and keyword-based retrieval. | |
| """ | |
| ) | |
| with gr.Row(): | |
| # Left Panel: Document Ingestion and Pipeline Metrics | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 1. Document Ingestion") | |
| file_input = gr.File(label="Upload PDF/ Book", file_types=[".pdf"]) | |
| process_btn = gr.Button("Build Text Keyword Base", variant="primary") | |
| status_text = gr.Textbox(label="Engine Status", interactive=False, value="Awaiting document...") | |
| log_monitor = gr.TextArea(label="Pipeline Terminal Logs", interactive=False, lines=8) | |
| # Right Panel: Interactive Chat Interface | |
| with gr.Column(scale=2): | |
| gr.Markdown("### 2. Conversational Analysis") | |
| chatbot = gr.Chatbot(label="Document Retrieval Chat", height=450) | |
| with gr.Row(): | |
| query_input = gr.Textbox( | |
| label="Ask a document retrieval question...", | |
| placeholder="e.g., What is the primary difference between working capital and capital budgeting?", | |
| scale=4 | |
| ) | |
| submit_btn = gr.Button("Submit Query", variant="secondary", scale=1) | |
| # --- UI Event Wire-up --- | |
| # File processing sequence | |
| process_btn.click( | |
| fn=process_pdf, | |
| inputs=[file_input], | |
| outputs=[log_monitor, retriever_state, status_text] | |
| ) | |
| # Query execution sequences (Triggers on Click or hitting Enter) | |
| submit_btn.click( | |
| fn=answer_query, | |
| inputs=[query_input, retriever_state, chatbot], | |
| outputs=[query_input, chatbot] | |
| ) | |
| query_input.submit( | |
| fn=answer_query, | |
| inputs=[query_input, retriever_state, chatbot], | |
| outputs=[query_input, chatbot] | |
| ) | |
| # Launch local server | |
| if __name__ == "__main__": | |
| demo.launch(server_name="127.0.0.1", server_port=7860, share=False) |