File size: 6,652 Bytes
6a902ca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import os
import logging
import pandas as pd
import openai
from pinecone import Pinecone, ServerlessSpec
from langchain.document_loaders import PyMuPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# Configuration variables - update these as needed
PDF_PATH = "machine_learning.pdf"  # Path to your PDF file
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
PINECONE_INDEX_NAME = "miniproject2-multi-agent-chatbot"
CHUNK_SIZE = 2500
CHUNK_OVERLAP = 50


def load_pdf():
    """Task 1: Load PDF file and extract text."""
    logger.info(f"Loading PDF file: {PDF_PATH}")

    try:
        # Load the PDF document
        loader = PyMuPDFLoader(PDF_PATH)
        documents = loader.load()

        # Extract text and page numbers
        page_texts = [doc.page_content for doc in documents]
        page_numbers = [doc.metadata["page"] + 1 for doc in documents]  # Pages are 0-indexed in PyMuPDF

        logger.info(f"Successfully loaded {len(page_texts)} pages from PDF")
        return page_texts, page_numbers
    except Exception as e:
        logger.error(f"Error loading PDF: {e}")
        raise


def chunk_text(page_texts, page_numbers):
    """Task 2: Break down the extracted text into smaller chunks."""
    logger.info("Breaking text into chunks")

    # Initialize the text splitter
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=CHUNK_SIZE,
        chunk_overlap=CHUNK_OVERLAP
    )

    # Storage for chunks and their page numbers
    chunks = []
    chunk_page_numbers = []
    previous_page_tail = ""

    # Process each page
    for i, (text, page_num) in enumerate(zip(page_texts, page_numbers)):
        # Append previous page's tail to current page
        if previous_page_tail:
            text = previous_page_tail + " " + text
            previous_page_tail = ""

        # Split text into chunks
        page_chunks = text_splitter.split_text(text)

        # Store chunks with page numbers
        chunks.extend(page_chunks)
        chunk_page_numbers.extend([page_num] * len(page_chunks))

        # Save the tail of the current page
        if len(page_chunks) > 0:
            previous_page_tail = page_chunks[-1][-CHUNK_OVERLAP:]

    logger.info(f"Created {len(chunks)} chunks from {len(page_texts)} pages")
    return chunks, chunk_page_numbers


def prepare_data(chunks, chunk_page_numbers):
    """Task 2: Prepare the data and generate embeddings."""
    logger.info("Preparing data and generating embeddings")

    # Check if OpenAI API key is set
    if not OPENAI_API_KEY:
        raise ValueError("OpenAI API key is not set")

    # Create DataFrame
    df = pd.DataFrame({
        'text': chunks,
        'page_number': chunk_page_numbers
    })

    # Preprocess text
    df['processed_text'] = df['text'].apply(lambda x: x.replace('\n', ' ').replace('\r', ' '))

    # Initialize OpenAI client
    client = openai.OpenAI(api_key=OPENAI_API_KEY)

    # Function to generate embeddings
    def get_embedding(text):
        try:
            response = client.embeddings.create(
                model="text-embedding-ada-002",
                input=text
            )
            return response.data[0].embedding
        except Exception as e:
            logger.error(f"Error generating embedding: {e}")
            raise

    # Generate embeddings for each chunk
    logger.info("Generating embeddings. This may take some time...")
    df['embedding'] = df['processed_text'].apply(get_embedding)

    logger.info(f"Generated embeddings for {len(df)} chunks")
    return df


def create_pinecone_index(df):
    """Task 3: Create Pinecone index and insert data."""
    logger.info("Creating Pinecone index and inserting data")

    # Check if Pinecone API key is set
    if not PINECONE_API_KEY:
        raise ValueError("Pinecone API key is not set")

    # Initialize Pinecone
    pc = Pinecone(api_key=PINECONE_API_KEY)

    # Check if index exists
    index_exists = PINECONE_INDEX_NAME in pc.list_indexes().names()

    # Create index if it doesn't exist
    if not index_exists:
        logger.info(f"Creating new Pinecone index: {PINECONE_INDEX_NAME}")
        pc.create_index(
            name=PINECONE_INDEX_NAME,
            dimension=1536,  # OpenAI embedding dimension
            metric="cosine",
            spec=ServerlessSpec(cloud="aws", region="us-east-1")
        )

    # Connect to index
    index = pc.Index(PINECONE_INDEX_NAME)

    # Insert data in batches
    batch_size = 100
    total_rows = len(df)

    for i in range(0, total_rows, batch_size):
        end_idx = min(i + batch_size, total_rows)
        batch_df = df.iloc[i:end_idx]

        vectors = []
        for j, row in batch_df.iterrows():
            # Create metadata dictionary
            metadata = {
                "text": row['text'],
                "page_number": int(row['page_number'])
            }

            # Create vector
            vector = {
                "id": f"chunk_{j}",
                "values": row['embedding'],
                "metadata": metadata
            }
            vectors.append(vector)

        # Upsert batch
        index.upsert(vectors=vectors)
        logger.info(f"Inserted batch {i // batch_size + 1}/{(total_rows - 1) // batch_size + 1} into Pinecone")

    # Get index statistics
    stats = index.describe_index_stats()
    logger.info(f"Pinecone index stats: {stats}")

    return stats


def main():
    """Main function to execute all tasks."""
    try:
        # Task 1: Load PDF and extract text
        page_texts, page_numbers = load_pdf()

        # Task 2: Break text into chunks
        chunks, chunk_page_numbers = chunk_text(page_texts, page_numbers)

        # Task 2: Prepare data and generate embeddings
        df = prepare_data(chunks, chunk_page_numbers)

        # Task 3: Create Pinecone index and insert data
        stats = create_pinecone_index(df)

        logger.info("Data processing complete! Your chatbot is ready to use.")
        logger.info(f"Total vectors in Pinecone: {stats.get('total_vector_count', 0)}")

        return True
    except Exception as e:
        logger.error(f"Error in data processing pipeline: {e}")
        return False


if __name__ == "__main__":
    main()