Adedoyinjames commited on
Commit
4a73a5c
·
1 Parent(s): e44d821

Add RAG system code, API, requirements, folder structure, and placeholder documents

Browse files
.gitattributes CHANGED
@@ -34,3 +34,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  documents/meeting_notes.docx filter=lfs diff=lfs merge=lfs -text
 
 
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  documents/meeting_notes.docx filter=lfs diff=lfs merge=lfs -text
37
+ documents/reports/meeting_notes.docx filter=lfs diff=lfs merge=lfs -text
app.py CHANGED
@@ -1 +1,210 @@
1
- # Your RAG and API code from the previous steps should be copied here.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import torch
3
+ from transformers import T5ForConditionalGeneration, T5Tokenizer
4
+ from sentence_transformers import SentenceTransformer
5
+ from sklearn.metrics.pairwise import cosine_similarity
6
+ from fastapi import FastAPI, File, UploadFile, HTTPException
7
+ from pydantic import BaseModel
8
+ import os
9
+ import shutil
10
+ import PyPDF2
11
+ import docx
12
+ import math
13
+
14
+ # --- RAG System Code ---
15
+
16
+ # Define functions for document loading
17
+ def load_document(file_path):
18
+ """Loads text content from various document types."""
19
+ if file_path.endswith(".pdf"):
20
+ return load_pdf(file_path)
21
+ elif file_path.endswith(".docx"):
22
+ return load_docx(file_path)
23
+ elif file_path.endswith(".txt"):
24
+ return load_txt(file_path)
25
+ else:
26
+ print(f"Unsupported file type: {file_path}")
27
+ return None
28
+
29
+ def load_pdf(file_path):
30
+ """Loads text from a PDF file."""
31
+ text = ""
32
+ try:
33
+ with open(file_path, 'rb') as file:
34
+ reader = PyPDF2.PdfReader(file)
35
+ for page_num in range(len(reader.pages)):
36
+ text += reader.pages[page_num].extract_text()
37
+ return text
38
+ except Exception as e:
39
+ print(f"Error loading PDF {file_path}: {e}")
40
+ return None
41
+
42
+ def load_docx(file_path):
43
+ """Loads text from a DOCX file."""
44
+ text = ""
45
+ try:
46
+ doc = docx.Document(file_path)
47
+ for paragraph in doc.paragraphs:
48
+ text += paragraph.text + "\n"
49
+ return text
50
+ except Exception as e:
51
+ print(f"Error loading DOCX {file_path}: {e}")
52
+ return None
53
+
54
+ def load_txt(file_path):
55
+ """Loads text from a TXT file."""
56
+ try:
57
+ with open(file_path, 'r', encoding='utf-8') as file:
58
+ text = file.read()
59
+ return text
60
+ except Exception as e:
61
+ print(f"Error loading TXT {file_path}: {e}")
62
+ return None
63
+
64
+ # Implement a text chunking function
65
+ def chunk_text(text, chunk_size=500, overlap=50):
66
+ """Splits text into smaller chunks."""
67
+ chunks = []
68
+ start = 0
69
+ while start < len(text):
70
+ end = start + chunk_size
71
+ chunk = text[start:end]
72
+ chunks.append(chunk)
73
+ start += chunk_size - overlap
74
+ if start >= len(text): # Handle the last chunk
75
+ break
76
+ return chunks
77
+
78
+ # Load the chosen embedding model and language model
79
+ embedding_model_name = "sentence-transformers/all-MiniLM-L6-v2"
80
+ language_model_name = "google/flan-t5-small"
81
+
82
+ # Load models outside of the request handlers to avoid reloading on each request
83
+ embedding_model = SentenceTransformer(embedding_model_name)
84
+ tokenizer = T5Tokenizer.from_pretrained(language_model_name)
85
+ language_model = T5ForConditionalGeneration.from_pretrained(language_model_name)
86
+
87
+ # Create a function to generate embeddings
88
+ def generate_embeddings(texts):
89
+ """Generates embeddings for a list of text chunks."""
90
+ return embedding_model.encode(texts, convert_to_tensor=True)
91
+
92
+ # Implement a similarity search function
93
+ def find_similar_chunks(query_embedding, chunk_embeddings, top_k=3):
94
+ """Finds the top_k most similar chunks to the query."""
95
+ # Ensure top_k is not greater than the number of available chunks
96
+ actual_top_k = min(top_k, chunk_embeddings.size(0))
97
+ if actual_top_k == 0:
98
+ return [] # Return empty list if no chunks available
99
+
100
+ similarities = cosine_similarity(query_embedding.unsqueeze(0), chunk_embeddings)[0]
101
+ # Use torch.topk for efficiency
102
+ top_k_indices = torch.topk(torch.tensor(similarities), int(actual_top_k)).indices.tolist()
103
+ return top_k_indices
104
+
105
+ # Create a function to generate a response
106
+ def generate_response(query, context_chunks):
107
+ """Generates a response based on the query and context."""
108
+ context = " ".join(context_chunks)
109
+ prompt = f"Context: {context}\n\nQuestion: {query}\n\nAnswer:"
110
+ inputs = tokenizer(prompt, return_tensors="pt", max_length=512, truncation=True)
111
+ outputs = language_model.generate(**inputs, max_length=150, num_return_sequences=1)
112
+ response = tokenizer.decode(outputs[0], skip_special_tokens=True)
113
+ return response
114
+
115
+ # --- FastAPI App Code ---
116
+
117
+ # Initialize FastAPI app
118
+ app = FastAPI()
119
+
120
+ # Global variable to store chunks and embeddings
121
+ # This will act as our in-memory vector store for the API's current session
122
+ document_chunks = []
123
+ chunk_embeddings = None
124
+
125
+ # Define request model for question answering
126
+ class QueryRequest(BaseModel):
127
+ query: str
128
+
129
+ # API endpoint for document ingestion
130
+ @app.post("/ingest-document/")
131
+ async def ingest_document(file: UploadFile = File(...)):
132
+ global document_chunks, chunk_embeddings
133
+
134
+ # Create a temporary directory to save the uploaded file
135
+ upload_folder = "temp_uploads"
136
+ os.makedirs(upload_folder, exist_ok=True)
137
+ file_path = os.path.join(upload_folder, file.filename)
138
+
139
+ try:
140
+ # Save the uploaded file
141
+ with open(file_path, "wb") as f:
142
+ shutil.copyfileobj(file.file, f)
143
+
144
+ # Load and process the document
145
+ document_text = load_document(file_path)
146
+
147
+ if document_text is None:
148
+ raise HTTPException(status_code=400, detail="Unsupported file type or error loading document.")
149
+
150
+ # Chunk the text
151
+ chunks = chunk_text(document_text)
152
+
153
+ if not chunks:
154
+ raise HTTPException(status_code=400, detail="No text extracted or document is empty.")
155
+
156
+ # Generate embeddings for the chunks
157
+ new_chunk_embeddings = generate_embeddings(chunks)
158
+
159
+ # Append new chunks and embeddings to the global storage
160
+ document_chunks.extend(chunks)
161
+ if chunk_embeddings is None:
162
+ chunk_embeddings = new_chunk_embeddings
163
+ else:
164
+ # Ensure embeddings are on the same device if applicable (e.g., CPU)
165
+ chunk_embeddings = torch.cat((chunk_embeddings.to(new_chunk_embeddings.device), new_chunk_embeddings), dim=0)
166
+
167
+
168
+ return {"message": f"Successfully ingested {len(chunks)} chunks from {file.filename}"}
169
+
170
+ except Exception as e:
171
+ raise HTTPException(status_code=500, detail=f"An error occurred during document ingestion: {e}")
172
+ finally:
173
+ # Clean up the temporary file and directory
174
+ if os.path.exists(upload_folder):
175
+ shutil.rmtree(upload_folder)
176
+
177
+
178
+ # API endpoint for answering questions
179
+ @app.post("/answer-query/")
180
+ async def answer_query(query_request: QueryRequest):
181
+ global document_chunks, chunk_embeddings
182
+
183
+ if not document_chunks or chunk_embeddings is None:
184
+ raise HTTPException(status_code=400, detail="No documents have been ingested yet. Please ingest a document first.")
185
+
186
+ try:
187
+ # Generate embedding for the query
188
+ query_embedding = generate_embeddings([query_request.query])[0]
189
+
190
+ # Find relevant chunks
191
+ relevant_chunk_indices = find_similar_chunks(query_embedding, chunk_embeddings)
192
+
193
+ if not relevant_chunk_indices:
194
+ return {"answer": "Could not find relevant information in the ingested documents."}
195
+
196
+ relevant_chunks = [document_chunks[i] for i in relevant_chunk_indices]
197
+
198
+ # Generate response
199
+ response = generate_response(query_request.query, relevant_chunks)
200
+
201
+ return {"answer": response}
202
+
203
+ except Exception as e:
204
+ raise HTTPException(status_code=500, detail=f"An error occurred during query processing: {e}")
205
+
206
+ # Add a root endpoint for testing
207
+ @app.get("/")
208
+ async def read_root():
209
+ return {"message": "NORA AI Agent API is running. Use /ingest-document/ to upload documents and /answer-query/ to ask questions."}
210
+
documents/general/research_benefits.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ This document discusses the benefits of using AI in research.
2
+ AI can help analyze large datasets and identify patterns.
3
+ It can also automate tedious tasks, speeding up the research process.
4
+ Benefits include increased efficiency and accuracy.
documents/reports/meeting_notes.docx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:58b116ed5c74f3f0f7f1afb1f8d3599416af4e73972e6504e92f4e04c77c1ba6
3
+ size 36710
documents/technical_docs/project_x_summary.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Technical Documentation Summary:
2
+ This document outlines the technical specifications of Project X.
3
+ It includes details about the software architecture and deployment process.
4
+ Key components are the data ingestion module and the API endpoint.
5
+ Deployment is planned for a cloud-based platform.
requirements.txt CHANGED
@@ -10,7 +10,6 @@ fastapi>=0.95.0
10
  uvicorn>=0.21.0
11
  pypdf2>=3.0.0
12
  python-docx>=0.8.11
13
- textract>=1.6.0
14
  faiss-cpu>=1.7.0
15
  chromadb>=0.3.0
16
  tiktoken>=0.4.0
 
10
  uvicorn>=0.21.0
11
  pypdf2>=3.0.0
12
  python-docx>=0.8.11
 
13
  faiss-cpu>=1.7.0
14
  chromadb>=0.3.0
15
  tiktoken>=0.4.0