Spaces:
Sleeping
Sleeping
Clean repository setup with PDF downloading
Browse files- .gitignore +5 -0
- Dockerfile +47 -0
- app.py +142 -0
- chainlit.md +3 -0
- download_pdfs.py +134 -0
- preprocess.py +65 -0
- pyproject.toml +14 -0
.gitignore
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
data/*
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
.env
|
| 5 |
+
data/preprocessed_data.pkl
|
Dockerfile
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Get a distribution that has uv already installed
|
| 2 |
+
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
|
| 3 |
+
|
| 4 |
+
# Add user - this is the user that will run the app
|
| 5 |
+
# If you do not set user, the app will run as root (undesirable)
|
| 6 |
+
RUN useradd -m -u 1000 user
|
| 7 |
+
USER user
|
| 8 |
+
|
| 9 |
+
# Set the home directory and path
|
| 10 |
+
ENV HOME=/home/user \
|
| 11 |
+
PATH=/home/user/.local/bin:$PATH
|
| 12 |
+
|
| 13 |
+
ENV UVICORN_WS_PROTOCOL=websockets
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# Set the working directory
|
| 17 |
+
WORKDIR $HOME/app
|
| 18 |
+
|
| 19 |
+
# Copy the app to the container
|
| 20 |
+
COPY --chown=user . $HOME/app
|
| 21 |
+
|
| 22 |
+
# Install the dependencies
|
| 23 |
+
# RUN uv sync --frozen
|
| 24 |
+
RUN uv sync
|
| 25 |
+
|
| 26 |
+
# Create data directory if it doesn't exist
|
| 27 |
+
RUN mkdir -p $HOME/app/data
|
| 28 |
+
|
| 29 |
+
# Install additional dependencies for downloading PDFs
|
| 30 |
+
RUN uv pip install huggingface_hub datasets
|
| 31 |
+
|
| 32 |
+
# Download PDFs from Hugging Face dataset
|
| 33 |
+
# This requires HF_TOKEN to be set as a build secret in Hugging Face Spaces
|
| 34 |
+
ARG HF_TOKEN
|
| 35 |
+
RUN HF_TOKEN=${HF_TOKEN} huggingface-cli login --token ${HF_TOKEN}
|
| 36 |
+
RUN python download_pdfs.py
|
| 37 |
+
|
| 38 |
+
# Run preprocessing to generate the embeddings
|
| 39 |
+
# Note: This requires the OPENAI_API_KEY environment variable to be set during build
|
| 40 |
+
# For Hugging Face, you'll need to use their build secrets feature
|
| 41 |
+
RUN OPENAI_API_KEY=${OPENAI_API_KEY} uv run python preprocess.py
|
| 42 |
+
|
| 43 |
+
# Expose the port
|
| 44 |
+
EXPOSE 7860
|
| 45 |
+
|
| 46 |
+
# Run the app
|
| 47 |
+
CMD ["uv", "run", "chainlit", "run", "app.py", "--host", "0.0.0.0", "--port", "7860"]
|
app.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import pickle
|
| 3 |
+
import time
|
| 4 |
+
from typing import List, Dict, Any
|
| 5 |
+
from chainlit.types import AskFileResponse
|
| 6 |
+
from aimakerspace.text_utils import CharacterTextSplitter, TextFileLoader, PDFLoader
|
| 7 |
+
from aimakerspace.openai_utils.prompts import (
|
| 8 |
+
UserRolePrompt,
|
| 9 |
+
SystemRolePrompt,
|
| 10 |
+
AssistantRolePrompt,
|
| 11 |
+
)
|
| 12 |
+
from aimakerspace.openai_utils.embedding import EmbeddingModel
|
| 13 |
+
from aimakerspace.vectordatabase import VectorDatabase
|
| 14 |
+
from aimakerspace.openai_utils.chatmodel import ChatOpenAI
|
| 15 |
+
import chainlit as cl
|
| 16 |
+
|
| 17 |
+
system_template = """\
|
| 18 |
+
Use the following context to answer a users question. If you cannot find the answer in the context, say you don't know the answer."""
|
| 19 |
+
system_role_prompt = SystemRolePrompt(system_template)
|
| 20 |
+
|
| 21 |
+
user_prompt_template = """\
|
| 22 |
+
Context:
|
| 23 |
+
{context}
|
| 24 |
+
|
| 25 |
+
Question:
|
| 26 |
+
{question}
|
| 27 |
+
"""
|
| 28 |
+
user_role_prompt = UserRolePrompt(user_prompt_template)
|
| 29 |
+
|
| 30 |
+
class RetrievalAugmentedQAPipeline:
|
| 31 |
+
def __init__(self, llm: ChatOpenAI(), vector_db_retriever: VectorDatabase, metadata: List[Dict[str, Any]] = None) -> None:
|
| 32 |
+
self.llm = llm
|
| 33 |
+
self.vector_db_retriever = vector_db_retriever
|
| 34 |
+
self.metadata = metadata or []
|
| 35 |
+
self.text_to_metadata = {}
|
| 36 |
+
|
| 37 |
+
# Create lookup for text to metadata
|
| 38 |
+
if metadata:
|
| 39 |
+
texts = [key for key in self.vector_db_retriever.vectors.keys()]
|
| 40 |
+
for i, text in enumerate(texts):
|
| 41 |
+
if i < len(metadata):
|
| 42 |
+
self.text_to_metadata[text] = metadata[i]
|
| 43 |
+
|
| 44 |
+
async def arun_pipeline(self, user_query: str):
|
| 45 |
+
context_list = self.vector_db_retriever.search_by_text(user_query, k=4)
|
| 46 |
+
|
| 47 |
+
context_prompt = ""
|
| 48 |
+
sources = []
|
| 49 |
+
|
| 50 |
+
for context in context_list:
|
| 51 |
+
text = context[0]
|
| 52 |
+
context_prompt += text + "\n"
|
| 53 |
+
|
| 54 |
+
# Get metadata for this text if available
|
| 55 |
+
if text in self.text_to_metadata:
|
| 56 |
+
sources.append(self.text_to_metadata[text])
|
| 57 |
+
else:
|
| 58 |
+
sources.append({"filename": "unknown", "page": "unknown"})
|
| 59 |
+
|
| 60 |
+
formatted_system_prompt = system_role_prompt.create_message()
|
| 61 |
+
|
| 62 |
+
formatted_user_prompt = user_role_prompt.create_message(question=user_query, context=context_prompt)
|
| 63 |
+
|
| 64 |
+
async def generate_response():
|
| 65 |
+
async for chunk in self.llm.astream([formatted_system_prompt, formatted_user_prompt]):
|
| 66 |
+
yield chunk
|
| 67 |
+
|
| 68 |
+
return {"response": generate_response(), "sources": sources}
|
| 69 |
+
|
| 70 |
+
text_splitter = CharacterTextSplitter()
|
| 71 |
+
|
| 72 |
+
def load_preprocessed_data():
|
| 73 |
+
# Check if preprocessed data exists
|
| 74 |
+
if not os.path.exists('data/preprocessed_data.pkl'):
|
| 75 |
+
raise FileNotFoundError("Preprocessed data not found. Please run the preprocess.py script first.")
|
| 76 |
+
|
| 77 |
+
# Load the pre-processed data
|
| 78 |
+
with open('data/preprocessed_data.pkl', 'rb') as f:
|
| 79 |
+
data = pickle.load(f)
|
| 80 |
+
|
| 81 |
+
# Create a new vector database
|
| 82 |
+
vector_db = VectorDatabase()
|
| 83 |
+
|
| 84 |
+
# Directly populate the vectors dictionary
|
| 85 |
+
for key, vector in data['vectors'].items():
|
| 86 |
+
vector_db.insert(key, vector)
|
| 87 |
+
|
| 88 |
+
# Get metadata if available
|
| 89 |
+
metadata = data.get('metadata', [])
|
| 90 |
+
|
| 91 |
+
return vector_db, metadata
|
| 92 |
+
|
| 93 |
+
@cl.on_chat_start
|
| 94 |
+
async def on_chat_start():
|
| 95 |
+
# Send welcome message
|
| 96 |
+
msg = cl.Message(content="Loading knowledge base from pre-processed PDF documents...")
|
| 97 |
+
await msg.send()
|
| 98 |
+
|
| 99 |
+
try:
|
| 100 |
+
# Load pre-processed data
|
| 101 |
+
start_time = time.time()
|
| 102 |
+
vector_db, metadata = load_preprocessed_data()
|
| 103 |
+
load_time = time.time() - start_time
|
| 104 |
+
print(f"Loaded vector database in {load_time:.2f} seconds")
|
| 105 |
+
|
| 106 |
+
chat_openai = ChatOpenAI()
|
| 107 |
+
|
| 108 |
+
# Create chain
|
| 109 |
+
retrieval_augmented_qa_pipeline = RetrievalAugmentedQAPipeline(
|
| 110 |
+
vector_db_retriever=vector_db,
|
| 111 |
+
llm=chat_openai,
|
| 112 |
+
metadata=metadata
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
# Let the user know that the system is ready
|
| 116 |
+
msg.content = "Knowledge base loaded! You can now ask questions about AB Testing. We'll use material written by Ronny Kohavi to answer your questions!"
|
| 117 |
+
await msg.update()
|
| 118 |
+
|
| 119 |
+
cl.user_session.set("chain", retrieval_augmented_qa_pipeline)
|
| 120 |
+
|
| 121 |
+
except Exception as e:
|
| 122 |
+
msg.content = f"Error loading knowledge base: {str(e)}"
|
| 123 |
+
await msg.update()
|
| 124 |
+
raise e
|
| 125 |
+
|
| 126 |
+
@cl.on_message
|
| 127 |
+
async def main(message):
|
| 128 |
+
chain = cl.user_session.get("chain")
|
| 129 |
+
|
| 130 |
+
msg = cl.Message(content="")
|
| 131 |
+
result = await chain.arun_pipeline(message.content)
|
| 132 |
+
|
| 133 |
+
async for stream_resp in result["response"]:
|
| 134 |
+
await msg.stream_token(stream_resp)
|
| 135 |
+
|
| 136 |
+
# Add source information after the response
|
| 137 |
+
sources_text = "\n\n**Sources:**"
|
| 138 |
+
for i, source in enumerate(result["sources"]):
|
| 139 |
+
sources_text += f"\n- {source['filename']} (Page {source['page']})"
|
| 140 |
+
|
| 141 |
+
await msg.stream_token(sources_text)
|
| 142 |
+
await msg.send()
|
chainlit.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Welcome to Chat with Your Text/PDF File
|
| 2 |
+
|
| 3 |
+
With this application, you can chat with an uploaded text/PDFfile that is smaller than 2MB!
|
download_pdfs.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datasets import load_dataset
|
| 2 |
+
import os
|
| 3 |
+
from huggingface_hub import hf_hub_download
|
| 4 |
+
import logging
|
| 5 |
+
|
| 6 |
+
# Configure logging
|
| 7 |
+
logging.basicConfig(level=logging.INFO)
|
| 8 |
+
logger = logging.getLogger(__name__)
|
| 9 |
+
|
| 10 |
+
def download_pdfs():
|
| 11 |
+
"""
|
| 12 |
+
Download PDF files from the Hugging Face dataset.
|
| 13 |
+
"""
|
| 14 |
+
logger.info("Creating data directory if it doesn't exist")
|
| 15 |
+
os.makedirs("data", exist_ok=True)
|
| 16 |
+
|
| 17 |
+
try:
|
| 18 |
+
logger.info("Loading the dataset from kamkol/ab_testing_pdfs")
|
| 19 |
+
# Try to load the dataset first
|
| 20 |
+
dataset = load_dataset("kamkol/ab_testing_pdfs", use_auth_token=True)
|
| 21 |
+
|
| 22 |
+
# Check if we have files in the dataset
|
| 23 |
+
if 'train' in dataset and len(dataset['train']) > 0:
|
| 24 |
+
logger.info(f"Found {len(dataset['train'])} files in dataset")
|
| 25 |
+
|
| 26 |
+
# Handle dataset format that uses binary field
|
| 27 |
+
if 'binary' in dataset['train'].features:
|
| 28 |
+
for i, item in enumerate(dataset['train']):
|
| 29 |
+
filename = item["filename"] if "filename" in item else f"document_{i}.pdf"
|
| 30 |
+
with open(f"data/{filename}", "wb") as f:
|
| 31 |
+
f.write(item["binary"])
|
| 32 |
+
logger.info(f"Downloaded: {filename}")
|
| 33 |
+
|
| 34 |
+
# Alternative approach for direct file access
|
| 35 |
+
else:
|
| 36 |
+
# List all PDF files in the repository
|
| 37 |
+
logger.info("Dataset doesn't have binary field, trying direct file download")
|
| 38 |
+
for i, item in enumerate(dataset['train']):
|
| 39 |
+
# Get filename from the dataset if available
|
| 40 |
+
if 'filename' in item:
|
| 41 |
+
filename = item['filename']
|
| 42 |
+
else:
|
| 43 |
+
logger.warning(f"No filename found for item {i}, using default")
|
| 44 |
+
filename = f"document_{i}.pdf"
|
| 45 |
+
|
| 46 |
+
# Download the file
|
| 47 |
+
try:
|
| 48 |
+
file_path = hf_hub_download(
|
| 49 |
+
repo_id="kamkol/ab_testing_pdfs",
|
| 50 |
+
filename=filename,
|
| 51 |
+
repo_type="dataset",
|
| 52 |
+
use_auth_token=True
|
| 53 |
+
)
|
| 54 |
+
# Copy to data directory
|
| 55 |
+
with open(file_path, 'rb') as src, open(f"data/{filename}", 'wb') as dst:
|
| 56 |
+
dst.write(src.read())
|
| 57 |
+
logger.info(f"Downloaded: {filename}")
|
| 58 |
+
except Exception as e:
|
| 59 |
+
logger.error(f"Error downloading {filename}: {str(e)}")
|
| 60 |
+
|
| 61 |
+
else:
|
| 62 |
+
# Fall back to direct file download from the repository
|
| 63 |
+
logger.info("No files found in dataset train split, trying direct repository access")
|
| 64 |
+
|
| 65 |
+
# List of PDF files to download
|
| 66 |
+
pdf_files = [
|
| 67 |
+
"trustworthy-online-controlled-experiments.pdf",
|
| 68 |
+
"2012-controllable-experimentation-paper.pdf",
|
| 69 |
+
"2011-Bing-Flight-KDD-final.pdf",
|
| 70 |
+
"best-practices-long.pdf",
|
| 71 |
+
"kohavi-dmkd-published-version.pdf",
|
| 72 |
+
"online-experimentation-at-microsoft.pdf",
|
| 73 |
+
"p609-kohavi.pdf",
|
| 74 |
+
"TechReport-2009-4.pdf",
|
| 75 |
+
"Seven-Pitfalls-to-Avoid.pdf",
|
| 76 |
+
"KDD-2009-ExP-Design-Pitfalls.pdf",
|
| 77 |
+
"wsdm-4page-paper.pdf",
|
| 78 |
+
"expAIsterdam-PValue.pdf",
|
| 79 |
+
"Interleaving-www11.pdf"
|
| 80 |
+
]
|
| 81 |
+
|
| 82 |
+
for filename in pdf_files:
|
| 83 |
+
try:
|
| 84 |
+
file_path = hf_hub_download(
|
| 85 |
+
repo_id="kamkol/ab_testing_pdfs",
|
| 86 |
+
filename=filename,
|
| 87 |
+
repo_type="dataset",
|
| 88 |
+
use_auth_token=True
|
| 89 |
+
)
|
| 90 |
+
# Copy to data directory
|
| 91 |
+
with open(file_path, 'rb') as src, open(f"data/{filename}", 'wb') as dst:
|
| 92 |
+
dst.write(src.read())
|
| 93 |
+
logger.info(f"Downloaded: {filename}")
|
| 94 |
+
except Exception as e:
|
| 95 |
+
logger.error(f"Error downloading {filename}: {str(e)}")
|
| 96 |
+
|
| 97 |
+
except Exception as e:
|
| 98 |
+
logger.error(f"Error loading dataset: {str(e)}")
|
| 99 |
+
logger.info("Falling back to direct file download")
|
| 100 |
+
|
| 101 |
+
# List of PDF files to download directly from the hub
|
| 102 |
+
pdf_files = [
|
| 103 |
+
"trustworthy-online-controlled-experiments.pdf",
|
| 104 |
+
"2012-controllable-experimentation-paper.pdf",
|
| 105 |
+
"2011-Bing-Flight-KDD-final.pdf",
|
| 106 |
+
"best-practices-long.pdf",
|
| 107 |
+
"kohavi-dmkd-published-version.pdf",
|
| 108 |
+
"online-experimentation-at-microsoft.pdf",
|
| 109 |
+
"p609-kohavi.pdf",
|
| 110 |
+
"TechReport-2009-4.pdf",
|
| 111 |
+
"Seven-Pitfalls-to-Avoid.pdf",
|
| 112 |
+
"KDD-2009-ExP-Design-Pitfalls.pdf",
|
| 113 |
+
"wsdm-4page-paper.pdf",
|
| 114 |
+
"expAIsterdam-PValue.pdf",
|
| 115 |
+
"Interleaving-www11.pdf"
|
| 116 |
+
]
|
| 117 |
+
|
| 118 |
+
for filename in pdf_files:
|
| 119 |
+
try:
|
| 120 |
+
file_path = hf_hub_download(
|
| 121 |
+
repo_id="kamkol/ab_testing_pdfs",
|
| 122 |
+
filename=filename,
|
| 123 |
+
repo_type="dataset",
|
| 124 |
+
use_auth_token=True
|
| 125 |
+
)
|
| 126 |
+
# Copy to data directory
|
| 127 |
+
with open(file_path, 'rb') as src, open(f"data/{filename}", 'wb') as dst:
|
| 128 |
+
dst.write(src.read())
|
| 129 |
+
logger.info(f"Downloaded: {filename}")
|
| 130 |
+
except Exception as e:
|
| 131 |
+
logger.error(f"Error downloading {filename}: {str(e)}")
|
| 132 |
+
|
| 133 |
+
if __name__ == "__main__":
|
| 134 |
+
download_pdfs()
|
preprocess.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import pickle
|
| 3 |
+
import numpy as np
|
| 4 |
+
from aimakerspace.text_utils import CharacterTextSplitter, PDFLoader
|
| 5 |
+
from aimakerspace.openai_utils.embedding import EmbeddingModel
|
| 6 |
+
from aimakerspace.vectordatabase import VectorDatabase
|
| 7 |
+
import asyncio
|
| 8 |
+
|
| 9 |
+
async def preprocess_files():
|
| 10 |
+
# Get all PDF files from the data directory
|
| 11 |
+
data_dir = "data"
|
| 12 |
+
pdf_files = [os.path.join(data_dir, f) for f in os.listdir(data_dir)
|
| 13 |
+
if f.lower().endswith('.pdf')]
|
| 14 |
+
|
| 15 |
+
if not pdf_files:
|
| 16 |
+
print("No PDF files found in the data directory!")
|
| 17 |
+
return
|
| 18 |
+
|
| 19 |
+
print(f"Found {len(pdf_files)} PDF files to process")
|
| 20 |
+
|
| 21 |
+
text_splitter = CharacterTextSplitter()
|
| 22 |
+
all_texts = []
|
| 23 |
+
all_metadata = []
|
| 24 |
+
|
| 25 |
+
# Load and process all PDF documents
|
| 26 |
+
for file_path in pdf_files:
|
| 27 |
+
print(f"Processing {file_path}")
|
| 28 |
+
loader = PDFLoader(file_path)
|
| 29 |
+
documents = loader.load_documents()
|
| 30 |
+
|
| 31 |
+
# Get pages from each document and extract text chunks
|
| 32 |
+
for doc_idx, doc in enumerate(documents):
|
| 33 |
+
# Extract page number if available
|
| 34 |
+
try:
|
| 35 |
+
page_num = doc_idx + 1 # Use document index as page number
|
| 36 |
+
texts = text_splitter.split_texts([doc])
|
| 37 |
+
|
| 38 |
+
for text in texts:
|
| 39 |
+
all_texts.append(text)
|
| 40 |
+
# Store metadata with each chunk
|
| 41 |
+
all_metadata.append({
|
| 42 |
+
"filename": os.path.basename(file_path),
|
| 43 |
+
"page": page_num
|
| 44 |
+
})
|
| 45 |
+
except Exception as e:
|
| 46 |
+
print(f"Error processing document: {e}")
|
| 47 |
+
|
| 48 |
+
print(f"Extracted {len(all_texts)} text chunks from all PDFs")
|
| 49 |
+
|
| 50 |
+
# Create vector database with embeddings
|
| 51 |
+
vector_db = VectorDatabase()
|
| 52 |
+
vector_db = await vector_db.abuild_from_list(all_texts)
|
| 53 |
+
|
| 54 |
+
# Save the processed data with metadata
|
| 55 |
+
with open('data/preprocessed_data.pkl', 'wb') as f:
|
| 56 |
+
pickle.dump({
|
| 57 |
+
'texts': all_texts,
|
| 58 |
+
'vectors': dict(vector_db.vectors),
|
| 59 |
+
'metadata': all_metadata
|
| 60 |
+
}, f)
|
| 61 |
+
|
| 62 |
+
print("Preprocessing complete. Data saved to data/preprocessed_data.pkl")
|
| 63 |
+
|
| 64 |
+
if __name__ == "__main__":
|
| 65 |
+
asyncio.run(preprocess_files())
|
pyproject.toml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "aie5-deploypythonicrag"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Simple Pythonic RAG App"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
requires-python = ">=3.13"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"chainlit==2.0.4",
|
| 9 |
+
"numpy==2.2.2",
|
| 10 |
+
"openai==1.59.9",
|
| 11 |
+
"pydantic==2.10.1",
|
| 12 |
+
"pypdf2==3.0.1",
|
| 13 |
+
"websockets==14.2",
|
| 14 |
+
]
|