Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -18,6 +18,7 @@ try:
|
|
| 18 |
GROQ_OK = True
|
| 19 |
except ImportError:
|
| 20 |
GROQ_OK = False
|
|
|
|
| 21 |
|
| 22 |
logging.basicConfig(level=logging.INFO)
|
| 23 |
logger = logging.getLogger(__name__)
|
|
@@ -31,20 +32,34 @@ groq_client = None
|
|
| 31 |
if GROQ_OK:
|
| 32 |
try:
|
| 33 |
print("DEBUG β Initializing Groq client...")
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
except Exception as e:
|
| 37 |
groq_client = None
|
| 38 |
-
print("β Groq initialization error:
|
|
|
|
|
|
|
| 39 |
|
| 40 |
class AgenticRAGAgent:
|
| 41 |
def __init__(self):
|
| 42 |
self.chunks = []
|
| 43 |
self.index = None
|
| 44 |
self.embedder = SentenceTransformer('all-MiniLM-L6-v2')
|
|
|
|
| 45 |
|
| 46 |
-
# Remove emojis completely
|
| 47 |
def remove_emojis(self, text: str) -> str:
|
|
|
|
| 48 |
emoji_pattern = re.compile("["
|
| 49 |
u"\U0001F600-\U0001F64F"
|
| 50 |
u"\U0001F300-\U0001F5FF"
|
|
@@ -56,19 +71,21 @@ class AgenticRAGAgent:
|
|
| 56 |
return emoji_pattern.sub(r'', text)
|
| 57 |
|
| 58 |
def clean_for_voice(self, text: str) -> str:
|
|
|
|
| 59 |
text = self.remove_emojis(text)
|
| 60 |
text = re.sub(r'[\*_`#\[\]]', '', text)
|
| 61 |
text = re.sub(r'\s+', ' ', text).strip()
|
| 62 |
return text
|
| 63 |
|
| 64 |
def generate_voice(self, text: str):
|
|
|
|
| 65 |
if not text or not text.strip():
|
| 66 |
return None
|
| 67 |
clean = self.clean_for_voice(text)
|
| 68 |
if len(clean) < 5:
|
| 69 |
return None
|
| 70 |
try:
|
| 71 |
-
tts = gTTS(text=clean, lang='en')
|
| 72 |
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3")
|
| 73 |
tts.save(tmp.name)
|
| 74 |
return tmp.name
|
|
@@ -77,6 +94,7 @@ class AgenticRAGAgent:
|
|
| 77 |
return None
|
| 78 |
|
| 79 |
def upload_pdfs(self, files):
|
|
|
|
| 80 |
if not files:
|
| 81 |
return "No files selected."
|
| 82 |
|
|
@@ -86,14 +104,17 @@ class AgenticRAGAgent:
|
|
| 86 |
count = 0
|
| 87 |
|
| 88 |
for file in files:
|
| 89 |
-
|
|
|
|
| 90 |
continue
|
| 91 |
-
|
|
|
|
| 92 |
try:
|
| 93 |
-
content = file.read() if hasattr(file, 'read') else open(
|
| 94 |
with open(dest, "wb") as f:
|
| 95 |
f.write(content)
|
| 96 |
except Exception as e:
|
|
|
|
| 97 |
continue
|
| 98 |
|
| 99 |
text = ""
|
|
@@ -104,7 +125,8 @@ class AgenticRAGAgent:
|
|
| 104 |
t = page.extract_text()
|
| 105 |
if t:
|
| 106 |
text += t + " "
|
| 107 |
-
except:
|
|
|
|
| 108 |
continue
|
| 109 |
|
| 110 |
if text.strip():
|
|
@@ -115,33 +137,43 @@ class AgenticRAGAgent:
|
|
| 115 |
if not all_chunks:
|
| 116 |
return "No readable text found in the PDFs."
|
| 117 |
|
| 118 |
-
|
|
|
|
|
|
|
| 119 |
vecs = vecs / np.linalg.norm(vecs, axis=1, keepdims=True)
|
| 120 |
dim = vecs.shape[1]
|
|
|
|
| 121 |
self.index = faiss.IndexFlatIP(dim)
|
| 122 |
self.index.add(vecs.astype('float32'))
|
| 123 |
self.chunks = all_chunks
|
| 124 |
|
| 125 |
-
|
|
|
|
|
|
|
| 126 |
|
| 127 |
def ask(self, question: str, history: List):
|
|
|
|
| 128 |
global groq_client
|
|
|
|
| 129 |
if not question.strip():
|
| 130 |
return history, None
|
| 131 |
|
| 132 |
if not history:
|
| 133 |
history = []
|
| 134 |
|
|
|
|
| 135 |
if question.strip().lower() in ["hi", "hello", "hey", "hola", "howdy"]:
|
| 136 |
reply = "Hi there! I am AI Research Agent with agentic capabilities. Upload PDF documents and ask complex questions!"
|
| 137 |
history.append([question, reply])
|
| 138 |
return history, self.generate_voice(reply)
|
| 139 |
|
|
|
|
| 140 |
if not self.index:
|
| 141 |
reply = "Please upload a PDF document first!"
|
| 142 |
history.append([question, reply])
|
| 143 |
return history, self.generate_voice(reply)
|
| 144 |
|
|
|
|
| 145 |
q_vec = self.embedder.encode([question])
|
| 146 |
q_vec = q_vec / np.linalg.norm(q_vec)
|
| 147 |
D, I = self.index.search(q_vec.astype('float32'), k=6)
|
|
@@ -150,9 +182,11 @@ class AgenticRAGAgent:
|
|
| 150 |
prompt = f"Context from documents:\n{context}\n\nQuestion: {question}\nAnswer clearly and accurately:"
|
| 151 |
|
| 152 |
if groq_client is None:
|
| 153 |
-
reply = "
|
|
|
|
| 154 |
else:
|
| 155 |
try:
|
|
|
|
| 156 |
resp = groq_client.chat.completions.create(
|
| 157 |
model="llama-3.3-70b-versatile",
|
| 158 |
messages=[{"role": "user", "content": prompt}],
|
|
@@ -160,56 +194,108 @@ class AgenticRAGAgent:
|
|
| 160 |
max_tokens=700
|
| 161 |
)
|
| 162 |
reply = resp.choices[0].message.content.strip()
|
|
|
|
| 163 |
except Exception as e:
|
| 164 |
reply = f"Groq API error: {str(e)}"
|
|
|
|
| 165 |
|
| 166 |
history.append([question, reply])
|
| 167 |
return history, self.generate_voice(reply)
|
| 168 |
|
|
|
|
| 169 |
# =========================================
|
| 170 |
# GRADIO UI
|
| 171 |
# =========================================
|
| 172 |
def create_interface():
|
| 173 |
agent = AgenticRAGAgent()
|
| 174 |
|
| 175 |
-
with gr.Blocks(title="
|
| 176 |
gr.HTML("""
|
| 177 |
<div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px;">
|
| 178 |
-
<h1 style="color: white; margin: 0;">
|
| 179 |
-
<p style="color: white; margin: 10px 0;">Advanced Multi-Tool Research Assistant with Voice Support
|
| 180 |
</div>
|
| 181 |
""")
|
| 182 |
|
| 183 |
with gr.Row():
|
| 184 |
with gr.Column(scale=2):
|
| 185 |
-
chatbot = gr.Chatbot(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
|
| 187 |
with gr.Row():
|
| 188 |
-
msg = gr.Textbox(
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
|
| 191 |
with gr.Row():
|
| 192 |
-
clear_btn = gr.Button("
|
| 193 |
|
| 194 |
-
audio_output = gr.Audio(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
with gr.Column(scale=1):
|
| 197 |
with gr.Group():
|
| 198 |
-
gr.HTML("<h3 style='text-align: center;'>
|
| 199 |
-
file_upload = gr.Files(
|
| 200 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
|
| 202 |
def respond(message, history):
|
|
|
|
| 203 |
new_hist, audio_file = agent.ask(message, history)
|
| 204 |
return "", new_hist, audio_file
|
| 205 |
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
|
| 211 |
return interface
|
| 212 |
|
|
|
|
| 213 |
if __name__ == "__main__":
|
|
|
|
| 214 |
app = create_interface()
|
| 215 |
-
app.launch(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
GROQ_OK = True
|
| 19 |
except ImportError:
|
| 20 |
GROQ_OK = False
|
| 21 |
+
print("β Groq library not installed!")
|
| 22 |
|
| 23 |
logging.basicConfig(level=logging.INFO)
|
| 24 |
logger = logging.getLogger(__name__)
|
|
|
|
| 32 |
if GROQ_OK:
|
| 33 |
try:
|
| 34 |
print("DEBUG β Initializing Groq client...")
|
| 35 |
+
# Initialize with just api_key - most compatible approach
|
| 36 |
+
groq_client = Groq(api_key=GROQ_API_KEY)
|
| 37 |
+
print("β
DEBUG β Groq client initialized successfully!")
|
| 38 |
+
except TypeError as te:
|
| 39 |
+
# Fallback for version compatibility issues
|
| 40 |
+
print(f"β οΈ TypeError during init: {te}")
|
| 41 |
+
try:
|
| 42 |
+
print("π Attempting fallback initialization...")
|
| 43 |
+
groq_client = Groq(api_key=GROQ_API_KEY)
|
| 44 |
+
print("β
Fallback initialization successful!")
|
| 45 |
+
except Exception as e:
|
| 46 |
+
groq_client = None
|
| 47 |
+
print(f"β Groq initialization failed: {e}")
|
| 48 |
except Exception as e:
|
| 49 |
groq_client = None
|
| 50 |
+
print(f"β Groq initialization error: {e}")
|
| 51 |
+
else:
|
| 52 |
+
print("β Groq library import failed!")
|
| 53 |
|
| 54 |
class AgenticRAGAgent:
|
| 55 |
def __init__(self):
|
| 56 |
self.chunks = []
|
| 57 |
self.index = None
|
| 58 |
self.embedder = SentenceTransformer('all-MiniLM-L6-v2')
|
| 59 |
+
print("β
AgenticRAGAgent initialized with SentenceTransformer")
|
| 60 |
|
|
|
|
| 61 |
def remove_emojis(self, text: str) -> str:
|
| 62 |
+
"""Remove emojis from text for clean voice output"""
|
| 63 |
emoji_pattern = re.compile("["
|
| 64 |
u"\U0001F600-\U0001F64F"
|
| 65 |
u"\U0001F300-\U0001F5FF"
|
|
|
|
| 71 |
return emoji_pattern.sub(r'', text)
|
| 72 |
|
| 73 |
def clean_for_voice(self, text: str) -> str:
|
| 74 |
+
"""Clean text for voice synthesis"""
|
| 75 |
text = self.remove_emojis(text)
|
| 76 |
text = re.sub(r'[\*_`#\[\]]', '', text)
|
| 77 |
text = re.sub(r'\s+', ' ', text).strip()
|
| 78 |
return text
|
| 79 |
|
| 80 |
def generate_voice(self, text: str):
|
| 81 |
+
"""Generate voice output from text"""
|
| 82 |
if not text or not text.strip():
|
| 83 |
return None
|
| 84 |
clean = self.clean_for_voice(text)
|
| 85 |
if len(clean) < 5:
|
| 86 |
return None
|
| 87 |
try:
|
| 88 |
+
tts = gTTS(text=clean, lang='en', slow=False)
|
| 89 |
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3")
|
| 90 |
tts.save(tmp.name)
|
| 91 |
return tmp.name
|
|
|
|
| 94 |
return None
|
| 95 |
|
| 96 |
def upload_pdfs(self, files):
|
| 97 |
+
"""Upload and process PDF files"""
|
| 98 |
if not files:
|
| 99 |
return "No files selected."
|
| 100 |
|
|
|
|
| 104 |
count = 0
|
| 105 |
|
| 106 |
for file in files:
|
| 107 |
+
filename = str(file.name) if hasattr(file, 'name') else str(file)
|
| 108 |
+
if not filename.lower().endswith('.pdf'):
|
| 109 |
continue
|
| 110 |
+
|
| 111 |
+
dest = folder / Path(filename).name
|
| 112 |
try:
|
| 113 |
+
content = file.read() if hasattr(file, 'read') else open(filename, 'rb').read()
|
| 114 |
with open(dest, "wb") as f:
|
| 115 |
f.write(content)
|
| 116 |
except Exception as e:
|
| 117 |
+
logger.warning(f"Failed to save file {filename}: {e}")
|
| 118 |
continue
|
| 119 |
|
| 120 |
text = ""
|
|
|
|
| 125 |
t = page.extract_text()
|
| 126 |
if t:
|
| 127 |
text += t + " "
|
| 128 |
+
except Exception as e:
|
| 129 |
+
logger.warning(f"Failed to extract text from {filename}: {e}")
|
| 130 |
continue
|
| 131 |
|
| 132 |
if text.strip():
|
|
|
|
| 137 |
if not all_chunks:
|
| 138 |
return "No readable text found in the PDFs."
|
| 139 |
|
| 140 |
+
# Create embeddings and FAISS index
|
| 141 |
+
print(f"Creating embeddings for {len(all_chunks)} chunks...")
|
| 142 |
+
vecs = self.embedder.encode([c["content"] for c in all_chunks], show_progress_bar=True)
|
| 143 |
vecs = vecs / np.linalg.norm(vecs, axis=1, keepdims=True)
|
| 144 |
dim = vecs.shape[1]
|
| 145 |
+
|
| 146 |
self.index = faiss.IndexFlatIP(dim)
|
| 147 |
self.index.add(vecs.astype('float32'))
|
| 148 |
self.chunks = all_chunks
|
| 149 |
|
| 150 |
+
status_msg = f"β
Loaded {count} PDF(s) β {len(all_chunks)} chunks ready!"
|
| 151 |
+
print(status_msg)
|
| 152 |
+
return status_msg
|
| 153 |
|
| 154 |
def ask(self, question: str, history: List):
|
| 155 |
+
"""Process user question and generate response"""
|
| 156 |
global groq_client
|
| 157 |
+
|
| 158 |
if not question.strip():
|
| 159 |
return history, None
|
| 160 |
|
| 161 |
if not history:
|
| 162 |
history = []
|
| 163 |
|
| 164 |
+
# Handle greeting
|
| 165 |
if question.strip().lower() in ["hi", "hello", "hey", "hola", "howdy"]:
|
| 166 |
reply = "Hi there! I am AI Research Agent with agentic capabilities. Upload PDF documents and ask complex questions!"
|
| 167 |
history.append([question, reply])
|
| 168 |
return history, self.generate_voice(reply)
|
| 169 |
|
| 170 |
+
# Check if PDFs are loaded
|
| 171 |
if not self.index:
|
| 172 |
reply = "Please upload a PDF document first!"
|
| 173 |
history.append([question, reply])
|
| 174 |
return history, self.generate_voice(reply)
|
| 175 |
|
| 176 |
+
# Retrieve relevant chunks
|
| 177 |
q_vec = self.embedder.encode([question])
|
| 178 |
q_vec = q_vec / np.linalg.norm(q_vec)
|
| 179 |
D, I = self.index.search(q_vec.astype('float32'), k=6)
|
|
|
|
| 182 |
prompt = f"Context from documents:\n{context}\n\nQuestion: {question}\nAnswer clearly and accurately:"
|
| 183 |
|
| 184 |
if groq_client is None:
|
| 185 |
+
reply = "ERROR: Groq client is not initialized. Check your API key and connection."
|
| 186 |
+
print("β Groq client is None - cannot process request")
|
| 187 |
else:
|
| 188 |
try:
|
| 189 |
+
print(f"π€ Sending request to Groq API for question: {question[:50]}...")
|
| 190 |
resp = groq_client.chat.completions.create(
|
| 191 |
model="llama-3.3-70b-versatile",
|
| 192 |
messages=[{"role": "user", "content": prompt}],
|
|
|
|
| 194 |
max_tokens=700
|
| 195 |
)
|
| 196 |
reply = resp.choices[0].message.content.strip()
|
| 197 |
+
print(f"β
Received response from Groq API")
|
| 198 |
except Exception as e:
|
| 199 |
reply = f"Groq API error: {str(e)}"
|
| 200 |
+
print(f"β Groq API error: {e}")
|
| 201 |
|
| 202 |
history.append([question, reply])
|
| 203 |
return history, self.generate_voice(reply)
|
| 204 |
|
| 205 |
+
|
| 206 |
# =========================================
|
| 207 |
# GRADIO UI
|
| 208 |
# =========================================
|
| 209 |
def create_interface():
|
| 210 |
agent = AgenticRAGAgent()
|
| 211 |
|
| 212 |
+
with gr.Blocks(title="AI Research Agent", theme=gr.themes.Soft()) as interface:
|
| 213 |
gr.HTML("""
|
| 214 |
<div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px;">
|
| 215 |
+
<h1 style="color: white; margin: 0;">AI Research Agent - Agentic RAG</h1>
|
| 216 |
+
<p style="color: white; margin: 10px 0;">Advanced Multi-Tool Research Assistant with Voice Support</p>
|
| 217 |
</div>
|
| 218 |
""")
|
| 219 |
|
| 220 |
with gr.Row():
|
| 221 |
with gr.Column(scale=2):
|
| 222 |
+
chatbot = gr.Chatbot(
|
| 223 |
+
label="Chat",
|
| 224 |
+
height=500,
|
| 225 |
+
type="tuples"
|
| 226 |
+
)
|
| 227 |
|
| 228 |
with gr.Row():
|
| 229 |
+
msg = gr.Textbox(
|
| 230 |
+
label="",
|
| 231 |
+
placeholder="Ask a complex research question...",
|
| 232 |
+
scale=4,
|
| 233 |
+
lines=1
|
| 234 |
+
)
|
| 235 |
+
submit_btn = gr.Button("Send", variant="primary", scale=1)
|
| 236 |
|
| 237 |
with gr.Row():
|
| 238 |
+
clear_btn = gr.Button("Clear Chat", variant="secondary")
|
| 239 |
|
| 240 |
+
audio_output = gr.Audio(
|
| 241 |
+
label="Voice Response",
|
| 242 |
+
autoplay=True,
|
| 243 |
+
interactive=False
|
| 244 |
+
)
|
| 245 |
|
| 246 |
with gr.Column(scale=1):
|
| 247 |
with gr.Group():
|
| 248 |
+
gr.HTML("<h3 style='text-align: center;'>Upload Documents</h3>")
|
| 249 |
+
file_upload = gr.Files(
|
| 250 |
+
label="",
|
| 251 |
+
file_types=[".pdf"],
|
| 252 |
+
file_count="multiple"
|
| 253 |
+
)
|
| 254 |
+
upload_status = gr.Textbox(
|
| 255 |
+
label="Status",
|
| 256 |
+
interactive=False,
|
| 257 |
+
max_lines=10
|
| 258 |
+
)
|
| 259 |
|
| 260 |
def respond(message, history):
|
| 261 |
+
"""Handle user message"""
|
| 262 |
new_hist, audio_file = agent.ask(message, history)
|
| 263 |
return "", new_hist, audio_file
|
| 264 |
|
| 265 |
+
def clear_chat():
|
| 266 |
+
"""Clear chat history"""
|
| 267 |
+
return [], None
|
| 268 |
+
|
| 269 |
+
# Connect events
|
| 270 |
+
submit_btn.click(
|
| 271 |
+
respond,
|
| 272 |
+
inputs=[msg, chatbot],
|
| 273 |
+
outputs=[msg, chatbot, audio_output]
|
| 274 |
+
)
|
| 275 |
+
msg.submit(
|
| 276 |
+
respond,
|
| 277 |
+
inputs=[msg, chatbot],
|
| 278 |
+
outputs=[msg, chatbot, audio_output]
|
| 279 |
+
)
|
| 280 |
+
clear_btn.click(
|
| 281 |
+
clear_chat,
|
| 282 |
+
outputs=[chatbot, audio_output]
|
| 283 |
+
)
|
| 284 |
+
file_upload.change(
|
| 285 |
+
agent.upload_pdfs,
|
| 286 |
+
inputs=[file_upload],
|
| 287 |
+
outputs=[upload_status]
|
| 288 |
+
)
|
| 289 |
|
| 290 |
return interface
|
| 291 |
|
| 292 |
+
|
| 293 |
if __name__ == "__main__":
|
| 294 |
+
print("π Starting AI Research Agent...")
|
| 295 |
app = create_interface()
|
| 296 |
+
app.launch(
|
| 297 |
+
server_name="0.0.0.0",
|
| 298 |
+
server_port=7860,
|
| 299 |
+
show_error=True,
|
| 300 |
+
share=False
|
| 301 |
+
)
|