Spaces:
Running
Running
File size: 8,295 Bytes
1a86be4 c6b59c0 29569c2 30f96fe 1a86be4 29569c2 1a86be4 29569c2 1a86be4 c6b59c0 1a86be4 29569c2 1a86be4 3af1614 1a86be4 3af1614 1a86be4 3af1614 1a86be4 3af1614 1a86be4 3af1614 e7b7240 3af1614 e7b7240 3af1614 e7b7240 3af1614 29569c2 3af1614 29569c2 3af1614 29569c2 e7b7240 29569c2 3af1614 1a86be4 e7b7240 29569c2 e7b7240 c6b59c0 e7b7240 1a86be4 e7b7240 1a86be4 e7b7240 2279cee 29569c2 b4f2b93 29569c2 b4f2b93 29569c2 b4f2b93 29569c2 b4f2b93 29569c2 b4f2b93 29569c2 b4f2b93 29569c2 2279cee 29569c2 b4f2b93 2279cee 29569c2 2279cee 29569c2 b4f2b93 2279cee 29569c2 b4f2b93 2279cee 29569c2 1a86be4 d77afce 1a86be4 d77afce 1a86be4 d77afce 1a86be4 d77afce 1a86be4 d77afce 1a86be4 d77afce 1a86be4 |
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 207 208 209 210 211 212 213 214 |
import os
import time
import json
import logging
import threading
import gradio as gr
import google.generativeai as genai
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload
from google.oauth2 import service_account
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader, TextLoader, Docx2txtLoader
from langchain.chains import RetrievalQA
from langchain_google_genai import ChatGoogleGenerativeAI
from PyPDF2 import PdfReader
from gtts import gTTS
from sentence_transformers import SentenceTransformer
import concurrent.futures
# ✅ Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
temp_file_map = {}
logging.info("🔑 Loading API keys...")
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY_1")
SERVICE_ACCOUNT_JSON = os.getenv("SERVICE_ACCOUNT_JSON")
if not GOOGLE_API_KEY or not SERVICE_ACCOUNT_JSON:
logging.error("❌ Missing API Key or Service Account JSON.")
raise ValueError("❌ Missing API Key or Service Account JSON. Please add them as environment variables.")
os.environ["GOOGLE_API_KEY"] = GOOGLE_API_KEY
SERVICE_ACCOUNT_FILE = json.loads(SERVICE_ACCOUNT_JSON)
SCOPES = ["https://www.googleapis.com/auth/drive"]
FOLDER_ID = "1xqOpwgwUoiJYf9GkeuB4dayme4zJcujf"
creds = service_account.Credentials.from_service_account_info(SERVICE_ACCOUNT_FILE)
drive_service = build("drive", "v3", credentials=creds)
vector_store = None
file_id_map = {}
temp_dir = "./temp_downloads"
os.makedirs(temp_dir, exist_ok=True)
def get_files_from_drive():
logging.info("📂 Fetching files from Google Drive...")
query = f"'{FOLDER_ID}' in parents and trashed = false"
results = drive_service.files().list(q=query, fields="files(id, name)").execute()
files = results.get("files", [])
global file_id_map
file_id_map = {file["name"]: file["id"] for file in files}
return list(file_id_map.keys()) if files else []
def download_file(file_id, file_name):
logging.info(f"📥 Downloading file: {file_name}")
file_path = os.path.join(temp_dir, file_name)
request = drive_service.files().get_media(fileId=file_id)
with open(file_path, "wb") as f:
downloader = MediaIoBaseDownload(f, request)
done = False
while not done:
_, done = downloader.next_chunk()
return file_path
def load_document(file_name, file_path):
try:
if file_name.endswith(".pdf"):
return PyPDFLoader(file_path).load()
elif file_name.endswith(".txt"):
return TextLoader(file_path).load()
elif file_name.endswith(".docx"):
return Docx2txtLoader(file_path).load()
else:
logging.warning(f"⚠️ Unsupported file type: {file_name}")
return []
except Exception as e:
logging.error(f"❌ Error loading {file_name}: {e}")
return []
def process_documents(selected_files):
global vector_store
# ✅ Clear the existing vector store before processing new documents
if vector_store is not None:
logging.info("🗑️ Clearing previous document embeddings...")
vector_store.delete_collection() # Clears existing stored data
docs = []
with concurrent.futures.ThreadPoolExecutor() as executor:
future_to_file = {
executor.submit(load_document, file_name, download_file(file_id_map[file_name], file_name)): file_name
for file_name in selected_files
}
for future in concurrent.futures.as_completed(future_to_file):
docs.extend(future.result())
total_words = sum(len(doc.page_content.split()) for doc in docs)
if total_words < 1000:
chunk_size, chunk_overlap, file_size_category = 500, 50, "small"
elif total_words < 5000:
chunk_size, chunk_overlap, file_size_category = 1000, 100, "medium"
else:
chunk_size, chunk_overlap, file_size_category = 2000, 200, "large"
logging.info(f"📄 Document Size: {total_words} words | Category: {file_size_category} | Chunk Size: {chunk_size}")
text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
split_docs = text_splitter.split_documents(docs)
embedding_model = (
"sentence-transformers/all-MiniLM-L6-v2" if file_size_category == "small" else "sentence-transformers/paraphrase-MiniLM-L3-v2"
)
logging.info(f"🧠 Using Transformer Model: {embedding_model}")
embeddings = HuggingFaceEmbeddings(model_name=embedding_model)
# ✅ Create a new Chroma vector store for new documents
vector_store = Chroma.from_documents(split_docs, embeddings)
return "✅ Documents processed successfully!"
def query_document(question):
if vector_store is None:
return "❌ No documents processed.", None
# ✅ Fetch stored documents
stored_docs = vector_store.get()["documents"]
# ✅ Calculate total word count safely
total_words = sum(len(doc.split()) if isinstance(doc, str) else len(doc.page_content.split()) for doc in stored_docs)
# ✅ Categorize file size and set retrieval depth
if total_words < 500:
file_size_category = "small"
k_value = 3
prompt_prefix = "Provide a **concise** response focusing on key points."
elif total_words < 2000:
file_size_category = "medium"
k_value = 5
prompt_prefix = "Provide a **detailed response** with examples and key insights."
else:
file_size_category = "large"
k_value = 10
prompt_prefix = "Provide a **comprehensive and structured response**, including step-by-step analysis and explanations."
logging.info(f"🔎 Querying Vector Store | File Size: {file_size_category} | Search Depth: {k_value}")
# ✅ Setup retriever
retriever = vector_store.as_retriever(search_type="similarity", search_kwargs={"k": k_value})
# ✅ Dynamically select model based on file size
if file_size_category in ["small", "medium"]:
model_name = "gemini-2.0-pro-exp-02-05"
else:
model_name = "gemini-2.0-flash"
logging.info(f"🤖 Using LLM Model: {model_name}")
# ✅ Create detailed prompt
detailed_prompt = f"""{prompt_prefix}
- Ensure clarity and completeness.
- Highlight the most relevant information.
**Question:** {question}
"""
# ✅ Invoke LLM model
model = ChatGoogleGenerativeAI(model=model_name, google_api_key=GOOGLE_API_KEY)
qa_chain = RetrievalQA.from_chain_type(llm=model, retriever=retriever)
response = qa_chain.invoke({"query": detailed_prompt})["result"]
logging.info(f"📝 Bot Output: {response[:200]}...") # Log only first 200 chars for readability
# ✅ Convert response to speech
tts = gTTS(text=response, lang="en")
temp_audio_path = os.path.join(temp_dir, "response.mp3")
tts.save(temp_audio_path)
temp_file_map["response.mp3"] = time.time()
return response, temp_audio_path
# ✅ Gradio UI
with gr.Blocks() as demo:
gr.Markdown("# 📄 AI-Powered Multi-Document Chatbot with Voice Output")
file_dropdown = gr.Dropdown(choices=get_files_from_drive(), label="📂 Select Files", multiselect=True)
refresh_button = gr.Button("🔄 Refresh Files") # 🔄 Add Refresh Button
process_button = gr.Button("🚀 Process Documents")
user_input = gr.Textbox(label="🔎 Ask a Question")
submit_button = gr.Button("💬 Get Answer")
response_output = gr.Textbox(label="📝 Response")
audio_output = gr.Audio(label="🔊 Audio Response")
# 🔄 Function to Refresh File List
def refresh_files():
return gr.update(choices=get_files_from_drive())
# ✅ Connect Refresh Button
refresh_button.click(refresh_files, outputs=file_dropdown)
# ✅ Connect Process Button
process_button.click(process_documents, inputs=file_dropdown, outputs=response_output)
# ✅ Connect Query Button
submit_button.click(query_document, inputs=user_input, outputs=[response_output, audio_output])
demo.launch()
|