File size: 5,413 Bytes
04b809a
 
21753a3
d2c6ac6
 
 
4431829
d2c6ac6
b14a2f9
21753a3
 
36eb467
b14a2f9
1fda785
b14a2f9
04b809a
d2c6ac6
b14a2f9
 
 
 
47994b5
04b809a
 
 
 
47994b5
04b809a
b74d72a
d2c6ac6
 
 
 
 
a0d55b9
 
 
94f2884
a0d55b9
21753a3
 
 
 
 
2d35ce4
 
 
 
d2c6ac6
 
2d35ce4
 
 
 
 
 
 
 
 
 
 
 
 
 
d2c6ac6
21753a3
 
 
 
 
 
 
 
 
 
 
 
 
 
d2c6ac6
21753a3
94f2884
21753a3
 
5e58171
 
 
 
 
ba35145
0b785f0
 
 
 
 
acda467
 
04b809a
 
 
 
 
 
acda467
 
04b809a
 
21753a3
 
 
 
 
 
 
 
b15d87a
a0d55b9
b15d87a
21753a3
b15d87a
a0d55b9
b15d87a
 
a0d55b9
 
 
 
 
 
 
 
 
 
 
 
 
b15d87a
21753a3
b15d87a
 
 
d2c6ac6
21753a3
 
 
 
 
 
04b809a
fb8d4f3
21753a3
 
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
from openai import OpenAI
from os import getenv
from flask import Flask, request, jsonify, render_template
import fitz  # PyMuPDF for PDF text extraction
import faiss  # FAISS for vector search
import numpy as np
import os
from sentence_transformers import SentenceTransformer
from huggingface_hub import InferenceClient
from typing import List, Tuple

app = Flask(__name__, template_folder=os.getcwd())

# Default settings
class ChatConfig:
    MODEL = "google/gemma-7b-it:free"  # Use OpenRouter's Gemma model
    DEFAULT_SYSTEM_MSG = "You are an AI assistant answering only based on the uploaded PDF."
    DEFAULT_MAX_TOKENS = 512
    DEFAULT_TEMP = 0.3
    DEFAULT_TOP_P = 0.95

# Get the token from environment variable
OPENROUTER_API_KEY = getenv('OPENROUTER_API_KEY')
client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=OPENROUTER_API_KEY,
)

embed_model = SentenceTransformer("all-MiniLM-L6-v2", cache_folder="/tmp")
vector_dim = 384  # Embedding size
index = faiss.IndexFlatL2(vector_dim)  # FAISS index

documents = []  # Store extracted text

def extract_text_from_pdf(pdf_stream):
    """Extracts text from PDF stream"""
    doc = fitz.open(stream=pdf_stream, filetype="pdf")
    text_chunks = [page.get_text("text") for page in doc]
    doc.close()
    return text_chunks

def create_vector_db(text_chunks):
    """Embeds text chunks and adds them to FAISS index"""
    global documents, index
    
    # Reinitialize the FAISS index
    index = faiss.IndexFlatL2(vector_dim)
    
    documents = text_chunks
    embeddings = embed_model.encode(text_chunks)

    # Convert embeddings to np.float32 for FAISS
    embeddings = np.array(embeddings, dtype=np.float32)

    # Ensure that embeddings have the correct shape (should be 2D, with each vector having the right dimension)
    if embeddings.ndim == 1:  # If only one embedding, reshape it
        embeddings = embeddings.reshape(1, -1)

    # Add embeddings to the FAISS index
    index.add(embeddings)

    # Check if adding was successful (optional)
    if index.ntotal == 0:
        print("Error: FAISS index is empty after adding embeddings.")

def search_relevant_text(query):
    """Finds the most relevant text chunk for the given query"""
    query_embedding = embed_model.encode([query])
    _, closest_idx = index.search(np.array(query_embedding, dtype=np.float32), k=3)
    return "\n".join([documents[i] for i in closest_idx[0]])

def generate_response(
    message: str,
    history: List[Tuple[str, str]],
    system_message: str = ChatConfig.DEFAULT_SYSTEM_MSG,
    max_tokens: int = ChatConfig.DEFAULT_MAX_TOKENS,
    temperature: float = ChatConfig.DEFAULT_TEMP,
    top_p: float = ChatConfig.DEFAULT_TOP_P
) -> str:
    if not documents:
        return "Please upload a PDF first."

    context = search_relevant_text(message)  # Get relevant content from PDF

    # Start with the system message in the first user message
    messages = []
    first_msg = f"{system_message}\n\nContext: {context}\nQuestion: {message}"
    messages.append({"role": "user", "content": first_msg})

    # Add conversation history ensuring alternating pattern (user, assistant, user, assistant...)
    for user_msg, bot_msg in history:
        if user_msg.strip():  # Check if user message is not empty
            messages.append({"role": "user", "content": user_msg})
        if bot_msg.strip():  # Check if assistant message is not empty
            messages.append({"role": "assistant", "content": bot_msg})

    try:
        # Use OpenRouter to get the response
        completion = client.chat.completions.create(
            model="google/gemma-7b-it:free",
            messages=messages
        )
        return completion.choices[0].message.content
    except Exception as e:
        print(f"Error generating response: {str(e)}")
        return "I apologize, but I encountered an error while generating the response. Please try again."

@app.route('/')
def index():
    """Serve the HTML page for the user interface"""
    return render_template('index.html')

@app.route('/upload_pdf', methods=['POST'])
def upload_pdf():
    """Handle PDF upload"""
    if 'pdf' not in request.files:
        return jsonify({"error": "No file part"}), 400

    file = request.files['pdf']
    if file.filename == "":
        return jsonify({"error": "No selected file"}), 400

    try:
        # Read the file directly into memory instead of saving to disk
        pdf_stream = file.read()
        
        # Create a BytesIO object to work with the PDF in memory
        from io import BytesIO
        pdf_stream = BytesIO(pdf_stream)
        
        # Use fitz to open the PDF from memory
        doc = fitz.open(stream=pdf_stream, filetype="pdf")
        text_chunks = [page.get_text("text") for page in doc]
        doc.close()
        
        # Create vector database
        create_vector_db(text_chunks)

        return jsonify({"message": "PDF uploaded and indexed successfully!"}), 200
    except Exception as e:
        return jsonify({"error": f"Error processing file: {str(e)}"}), 500

@app.route('/ask_question', methods=['POST'])
def ask_question():
    """Handle user question"""
    message = request.json.get('message')
    history = request.json.get('history', [])
    response = generate_response(message, history)
    return jsonify({"response": response})

if __name__ == '__main__':
    app.run(debug=True)