Shreyas094 commited on
Commit
e40971e
·
verified ·
1 Parent(s): ecb9aad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +3 -430
app.py CHANGED
@@ -1,432 +1,5 @@
1
- import os
2
- import json
3
- import re
4
- import gradio as gr
5
- import requests
6
- from duckduckgo_search import DDGS
7
- from typing import List
8
- from pydantic import BaseModel, Field
9
- from tempfile import NamedTemporaryFile
10
- from langchain_community.vectorstores import FAISS
11
- from langchain_community.document_loaders import PyPDFLoader
12
- from langchain_community.embeddings import HuggingFaceEmbeddings
13
- from llama_parse import LlamaParse
14
- from langchain_core.documents import Document
15
- from huggingface_hub import InferenceClient
16
- import inspect
17
- import logging
18
-
19
- # Set up basic configuration for logging
20
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
21
-
22
- # Environment variables and configurations
23
- huggingface_token = os.environ.get("HUGGINGFACE_TOKEN")
24
- llama_cloud_api_key = os.environ.get("LLAMA_CLOUD_API_KEY")
25
- ACCOUNT_ID = os.environ.get("CLOUDFARE_ACCOUNT_ID")
26
- API_TOKEN = os.environ.get("CLOUDFLARE_AUTH_TOKEN")
27
- API_BASE_URL = "https://api.cloudflare.com/client/v4/accounts/a17f03e0f049ccae0c15cdcf3b9737ce/ai/run/"
28
-
29
- print(f"ACCOUNT_ID: {ACCOUNT_ID}")
30
- print(f"CLOUDFLARE_AUTH_TOKEN: {API_TOKEN[:5]}..." if API_TOKEN else "Not set")
31
-
32
- MODELS = [
33
- "mistralai/Mistral-7B-Instruct-v0.3",
34
- "mistralai/Mixtral-8x7B-Instruct-v0.1",
35
- "@cf/meta/llama-3.1-8b-instruct"
36
- ]
37
-
38
- # Initialize LlamaParse
39
- llama_parser = LlamaParse(
40
- api_key=llama_cloud_api_key,
41
- result_type="markdown",
42
- num_workers=4,
43
- verbose=True,
44
- language="en",
45
- )
46
-
47
- def load_document(file: NamedTemporaryFile, parser: str = "llamaparse") -> List[Document]:
48
- """Loads and splits the document into pages."""
49
- if parser == "pypdf":
50
- loader = PyPDFLoader(file.name)
51
- return loader.load_and_split()
52
- elif parser == "llamaparse":
53
- try:
54
- documents = llama_parser.load_data(file.name)
55
- return [Document(page_content=doc.text, metadata={"source": file.name}) for doc in documents]
56
- except Exception as e:
57
- print(f"Error using Llama Parse: {str(e)}")
58
- print("Falling back to PyPDF parser")
59
- loader = PyPDFLoader(file.name)
60
- return loader.load_and_split()
61
- else:
62
- raise ValueError("Invalid parser specified. Use 'pypdf' or 'llamaparse'.")
63
-
64
- def get_embeddings():
65
- return HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
66
-
67
- def update_vectors(files, parser):
68
- global uploaded_documents
69
- logging.info(f"Entering update_vectors with {len(files)} files and parser: {parser}")
70
-
71
- if not files:
72
- logging.warning("No files provided for update_vectors")
73
- return "Please upload at least one PDF file.", gr.CheckboxGroup(
74
- choices=[doc["name"] for doc in uploaded_documents],
75
- value=[doc["name"] for doc in uploaded_documents if doc["selected"]],
76
- label="Select documents to query"
77
- )
78
-
79
- embed = get_embeddings()
80
- total_chunks = 0
81
-
82
- all_data = []
83
- for file in files:
84
- logging.info(f"Processing file: {file.name}")
85
- try:
86
- data = load_document(file, parser)
87
- logging.info(f"Loaded {len(data)} chunks from {file.name}")
88
- all_data.extend(data)
89
- total_chunks += len(data)
90
- if not any(doc["name"] == file.name for doc in uploaded_documents):
91
- uploaded_documents.append({"name": file.name, "selected": True})
92
- logging.info(f"Added new document to uploaded_documents: {file.name}")
93
- else:
94
- logging.info(f"Document already exists in uploaded_documents: {file.name}")
95
- except Exception as e:
96
- logging.error(f"Error processing file {file.name}: {str(e)}")
97
-
98
- logging.info(f"Total chunks processed: {total_chunks}")
99
-
100
- if os.path.exists("faiss_database"):
101
- logging.info("Updating existing FAISS database")
102
- database = FAISS.load_local("faiss_database", embed, allow_dangerous_deserialization=True)
103
- database.add_documents(all_data)
104
- else:
105
- logging.info("Creating new FAISS database")
106
- database = FAISS.from_documents(all_data, embed)
107
-
108
- database.save_local("faiss_database")
109
- logging.info("FAISS database saved")
110
-
111
- return f"Vector store updated successfully. Processed {total_chunks} chunks from {len(files)} files using {parser}.", gr.CheckboxGroup(
112
- choices=[doc["name"] for doc in uploaded_documents],
113
- value=[doc["name"] for doc in uploaded_documents if doc["selected"]],
114
- label="Select documents to query"
115
- )
116
-
117
- def duckduckgo_search(query):
118
- with DDGS() as ddgs:
119
- results = ddgs.text(query, max_results=5)
120
- return results
121
-
122
- class CitingSources(BaseModel):
123
- sources: List[str] = Field(
124
- ...,
125
- description="List of sources to cite. Should be an URL of the source."
126
- )
127
-
128
- def get_response_from_cloudflare(prompt, context, query, num_calls=3, temperature=0.2, search_type="pdf"):
129
- headers = {
130
- "Authorization": f"Bearer {API_TOKEN}",
131
- "Content-Type": "application/json"
132
- }
133
- model = "@cf/meta/llama-3.1-8b-instruct"
134
-
135
- if search_type == "pdf":
136
- instruction = f"""Using the following context from the PDF documents:
137
- {context}
138
- Write a detailed and complete response that answers the following user question: '{query}'"""
139
- else: # web search
140
- instruction = f"""Using the following context:
141
- {context}
142
- Write a detailed and complete research document that fulfills the following user request: '{query}'
143
- After writing the document, please provide a list of sources used in your response."""
144
-
145
- inputs = [
146
- {"role": "system", "content": instruction},
147
- {"role": "user", "content": query}
148
- ]
149
-
150
- payload = {
151
- "messages": inputs,
152
- "stream": True,
153
- "temperature": temperature
154
- }
155
-
156
- full_response = ""
157
- for i in range(num_calls):
158
- try:
159
- with requests.post(f"{API_BASE_URL}{model}", headers=headers, json=payload, stream=True) as response:
160
- if response.status_code == 200:
161
- for line in response.iter_lines():
162
- if line:
163
- try:
164
- json_response = json.loads(line.decode('utf-8').split('data: ')[1])
165
- if 'response' in json_response:
166
- chunk = json_response['response']
167
- full_response += chunk
168
- yield full_response
169
- except (json.JSONDecodeError, IndexError) as e:
170
- logging.error(f"Error parsing streaming response: {str(e)}")
171
- continue
172
- else:
173
- logging.error(f"HTTP Error: {response.status_code}, Response: {response.text}")
174
- yield f"I apologize, but I encountered an HTTP error: {response.status_code}. Please try again later."
175
- except Exception as e:
176
- logging.error(f"Error in generating response from Cloudflare: {str(e)}")
177
- yield f"I apologize, but an error occurred: {str(e)}. Please try again later."
178
-
179
- if not full_response:
180
- yield "I apologize, but I couldn't generate a response at this time. Please try again later."
181
-
182
- def get_response_with_search(query, model, num_calls=3, temperature=0.2):
183
- search_results = duckduckgo_search(query)
184
- context = "\n".join(f"{result['title']}\n{result['body']}\nSource: {result['href']}\n"
185
- for result in search_results if 'body' in result)
186
-
187
- prompt = f"""Using the following context:
188
- {context}
189
- Write a detailed and complete research document that fulfills the following user request: '{query}'
190
- After writing the document, please provide a list of sources used in your response."""
191
-
192
- if model == "@cf/meta/llama-3.1-8b-instruct":
193
- # Use Cloudflare API
194
- for response in get_response_from_cloudflare(prompt="", context=context, query=query, num_calls=num_calls, temperature=temperature, search_type="web"):
195
- yield response, "" # Yield streaming response without sources
196
- else:
197
- # Use Hugging Face API
198
- client = InferenceClient(model, token=huggingface_token)
199
-
200
- main_content = ""
201
- for i in range(num_calls):
202
- for message in client.chat_completion(
203
- messages=[{"role": "user", "content": prompt}],
204
- max_tokens=1000,
205
- temperature=temperature,
206
- stream=True,
207
- ):
208
- if message.choices and message.choices[0].delta and message.choices[0].delta.content:
209
- chunk = message.choices[0].delta.content
210
- main_content += chunk
211
- yield main_content, "" # Yield partial main content without sources
212
-
213
- def get_response_from_pdf(query, model, selected_docs, num_calls=3, temperature=0.2):
214
- logging.info(f"Entering get_response_from_pdf with query: {query}, model: {model}, selected_docs: {selected_docs}")
215
-
216
- embed = get_embeddings()
217
- if os.path.exists("faiss_database"):
218
- logging.info("Loading FAISS database")
219
- database = FAISS.load_local("faiss_database", embed, allow_dangerous_deserialization=True)
220
- else:
221
- logging.warning("No FAISS database found")
222
- yield "No documents available. Please upload PDF documents to answer questions."
223
- return
224
-
225
- retriever = database.as_retriever()
226
- logging.info(f"Retrieving relevant documents for query: {query}")
227
- relevant_docs = retriever.get_relevant_documents(query)
228
- logging.info(f"Number of relevant documents retrieved: {len(relevant_docs)}")
229
-
230
- # Filter relevant_docs based on selected documents
231
- filtered_docs = [doc for doc in relevant_docs if doc.metadata["source"] in selected_docs]
232
- logging.info(f"Number of filtered documents: {len(filtered_docs)}")
233
-
234
- if not filtered_docs:
235
- logging.warning(f"No relevant information found in the selected documents: {selected_docs}")
236
- yield "No relevant information found in the selected documents. Please try selecting different documents or rephrasing your query."
237
- return
238
-
239
- for doc in filtered_docs:
240
- logging.info(f"Document source: {doc.metadata['source']}")
241
- logging.info(f"Document content preview: {doc.page_content[:100]}...") # Log first 100 characters of each document
242
-
243
- context_str = "\n".join([doc.page_content for doc in filtered_docs])
244
- logging.info(f"Total context length: {len(context_str)}")
245
-
246
- if model == "@cf/meta/llama-3.1-8b-instruct":
247
- logging.info("Using Cloudflare API")
248
- # Use Cloudflare API with the retrieved context
249
- for response in get_response_from_cloudflare(prompt="", context=context_str, query=query, num_calls=num_calls, temperature=temperature, search_type="pdf"):
250
- yield response
251
- else:
252
- logging.info("Using Hugging Face API")
253
- # Use Hugging Face API
254
- prompt = f"""Using the following context from the PDF documents:
255
- {context_str}
256
- Write a detailed and complete response that answers the following user question: '{query}'"""
257
-
258
- client = InferenceClient(model, token=huggingface_token)
259
-
260
- response = ""
261
- for i in range(num_calls):
262
- logging.info(f"API call {i+1}/{num_calls}")
263
- for message in client.chat_completion(
264
- messages=[{"role": "user", "content": prompt}],
265
- max_tokens=1000,
266
- temperature=temperature,
267
- stream=True,
268
- ):
269
- if message.choices and message.choices[0].delta and message.choices[0].delta.content:
270
- chunk = message.choices[0].delta.content
271
- response += chunk
272
- yield response # Yield partial response
273
-
274
- logging.info("Finished generating response")
275
-
276
- def continue_response(last_response, context, query, model, temperature):
277
- prompt = f"""Using the following context and partial response:
278
-
279
- Context:
280
- {context}
281
-
282
- Partial Response:
283
- {last_response}
284
-
285
- Continue the response to fully answer the query: '{query}'
286
- Make sure the continuation flows smoothly from the previous part."""
287
-
288
- if model == "@cf/meta/llama-3.1-8b-instruct":
289
- return get_response_from_cloudflare(prompt="", context=context, query=prompt, num_calls=1, temperature=temperature, search_type="pdf")
290
- else:
291
- client = InferenceClient(model, token=huggingface_token)
292
- for message in client.chat_completion(
293
- messages=[{"role": "user", "content": prompt}],
294
- max_tokens=1000,
295
- temperature=temperature,
296
- stream=True,
297
- ):
298
- if message.choices and message.choices[0].delta and message.choices[0].delta.content:
299
- yield message.choices[0].delta.content
300
-
301
- def chatbot_interface(message, history, use_web_search, model, temperature, num_calls, selected_docs):
302
- if not message.strip():
303
- return "", history
304
-
305
- history = history + [(message, "")]
306
-
307
- try:
308
- last_response = ""
309
- for response in respond(message, history, model, temperature, num_calls, use_web_search, selected_docs):
310
- last_response = response
311
- history[-1] = (message, response)
312
- yield history
313
-
314
- # Check if the response seems truncated
315
- if not last_response.strip().endswith((".", "!", "?")):
316
- history.append((None, "Response may be incomplete. Type 'continue' to generate more."))
317
- yield history
318
- except gr.CancelledError:
319
- yield history
320
- except Exception as e:
321
- logging.error(f"Unexpected error in chatbot_interface: {str(e)}")
322
- history[-1] = (message, f"An unexpected error occurred: {str(e)}")
323
- yield history
324
-
325
- def continue_generation(history, use_web_search, model, temperature, selected_docs):
326
- if not history:
327
- return history, gr.Button.update(visible=False)
328
-
329
- last_message = history[-1][0]
330
- last_response = history[-1][1]
331
-
332
- if use_web_search:
333
- search_results = duckduckgo_search(last_message)
334
- context = "\n".join(f"{result['title']}\n{result['body']}\nSource: {result['href']}\n"
335
- for result in search_results if 'body' in result)
336
- else:
337
- embed = get_embeddings()
338
- database = FAISS.load_local("faiss_database", embed, allow_dangerous_deserialization=True)
339
- retriever = database.as_retriever()
340
- relevant_docs = retriever.get_relevant_documents(last_message)
341
- filtered_docs = [doc for doc in relevant_docs if doc.metadata["source"] in selected_docs]
342
- context = "\n".join([doc.page_content for doc in filtered_docs])
343
-
344
- continuation = ""
345
- for chunk in continue_response(last_response, context, last_message, model, temperature):
346
- continuation += chunk
347
- history[-1] = (last_message, last_response + continuation)
348
- yield history, gr.Button.update(visible=True)
349
-
350
- if not (last_response + continuation).strip().endswith((".", "!", "?")):
351
- yield history, gr.Button.update(visible=True, text="Continue Generation")
352
- else:
353
- yield history, gr.Button.update(visible=False)
354
-
355
- def respond(message, history, model, temperature, num_calls, use_web_search, selected_docs):
356
- logging.info(f"User Query: {message}")
357
- logging.info(f"Model Used: {model}")
358
- logging.info(f"Search Type: {'Web Search' if use_web_search else 'PDF Search'}")
359
- logging.info(f"Selected Documents: {selected_docs}")
360
-
361
- # Check if the user wants to continue the previous response
362
- if message.strip().lower() == "continue" and history:
363
- last_message = history[-2][0] # Get the last user message
364
- last_response = history[-2][1] # Get the last bot response
365
- context = get_context(last_message, use_web_search, selected_docs)
366
- for continuation in continue_response(last_response, context, last_message, model, temperature):
367
- yield last_response + continuation
368
- else:
369
- try:
370
- if use_web_search:
371
- for main_content, sources in get_response_with_search(message, model, num_calls=num_calls, temperature=temperature):
372
- response = f"{main_content}\n\n{sources}"
373
- first_line = response.split('\n')[0] if response else ''
374
- logging.info(f"Generated Response (first line): {first_line}")
375
- yield response
376
- else:
377
- for partial_response in get_response_from_pdf(message, model, selected_docs, num_calls=num_calls, temperature=temperature):
378
- first_line = partial_response.split('\n')[0] if partial_response else ''
379
- logging.info(f"Generated Response (first line): {first_line}")
380
- yield partial_response
381
- except Exception as e:
382
- logging.error(f"Error with {model}: {str(e)}")
383
- if "microsoft/Phi-3-mini-4k-instruct" in model:
384
- logging.info("Falling back to Mistral model due to Phi-3 error")
385
- fallback_model = "mistralai/Mistral-7B-Instruct-v0.3"
386
- yield from respond(message, history, fallback_model, temperature, num_calls, use_web_search, selected_docs)
387
- else:
388
- yield f"An error occurred with the {model} model: {str(e)}. Please try again or select a different model."
389
-
390
- def get_context(message, use_web_search, selected_docs):
391
- if use_web_search:
392
- search_results = duckduckgo_search(message)
393
- return "\n".join(f"{result['title']}\n{result['body']}\nSource: {result['href']}\n"
394
- for result in search_results if 'body' in result)
395
- else:
396
- embed = get_embeddings()
397
- database = FAISS.load_local("faiss_database", embed, allow_dangerous_deserialization=True)
398
- retriever = database.as_retriever()
399
- relevant_docs = retriever.get_relevant_documents(message)
400
- filtered_docs = [doc for doc in relevant_docs if doc.metadata["source"] in selected_docs]
401
- return "\n".join([doc.page_content for doc in filtered_docs])
402
-
403
-
404
- def vote(data: gr.LikeData):
405
- if data.liked:
406
- print(f"You upvoted this response: {data.value}")
407
- else:
408
- print(f"You downvoted this response: {data.value}")
409
-
410
- css = """
411
- /* Add your custom CSS here */
412
- """
413
-
414
- uploaded_documents = []
415
-
416
- def display_documents():
417
- return gr.CheckboxGroup(
418
- choices=[doc["name"] for doc in uploaded_documents],
419
- value=[doc["name"] for doc in uploaded_documents if doc["selected"]],
420
- label="Select documents to query"
421
- )
422
-
423
- # Define the checkbox outside the demo block
424
- document_selector = gr.CheckboxGroup(label="Select documents to query")
425
-
426
- use_web_search = gr.Checkbox(label="Use Web Search", value=False)
427
-
428
  demo = gr.ChatInterface(
429
- chatbot_interface,
430
  additional_inputs=[
431
  gr.Dropdown(choices=MODELS, label="Select Model", value=MODELS[0]),
432
  gr.Slider(minimum=0.1, maximum=1.0, value=0.2, step=0.1, label="Temperature"),
@@ -435,7 +8,7 @@ demo = gr.ChatInterface(
435
  document_selector # Add the document selector to the chat interface
436
  ],
437
  title="AI-powered Web Search and PDF Chat Assistant",
438
- description="Chat with your PDFs or use web search to answer questions. Type 'continue' to generate more if a response seems incomplete.",
439
  theme=gr.themes.Soft(
440
  primary_hue="orange",
441
  secondary_hue="amber",
@@ -467,6 +40,7 @@ demo = gr.ChatInterface(
467
  # Add file upload functionality
468
  with demo:
469
  gr.Markdown("## Upload PDF Documents")
 
470
  with gr.Row():
471
  file_input = gr.Files(label="Upload your PDF documents", file_types=[".pdf"])
472
  parser_dropdown = gr.Dropdown(choices=["pypdf", "llamaparse"], label="Select PDF Parser", value="llamaparse")
@@ -489,7 +63,6 @@ with demo:
489
  5. Toggle "Use Web Search" to switch between PDF chat and web search.
490
  6. Adjust Temperature and Number of API Calls to fine-tune the response generation.
491
  7. Use the provided examples or ask your own questions.
492
- 8. If a response seems incomplete, type 'continue' to generate more.
493
  """
494
  )
495
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  demo = gr.ChatInterface(
2
+ respond,
3
  additional_inputs=[
4
  gr.Dropdown(choices=MODELS, label="Select Model", value=MODELS[0]),
5
  gr.Slider(minimum=0.1, maximum=1.0, value=0.2, step=0.1, label="Temperature"),
 
8
  document_selector # Add the document selector to the chat interface
9
  ],
10
  title="AI-powered Web Search and PDF Chat Assistant",
11
+ description="Chat with your PDFs or use web search to answer questions.",
12
  theme=gr.themes.Soft(
13
  primary_hue="orange",
14
  secondary_hue="amber",
 
40
  # Add file upload functionality
41
  with demo:
42
  gr.Markdown("## Upload PDF Documents")
43
+
44
  with gr.Row():
45
  file_input = gr.Files(label="Upload your PDF documents", file_types=[".pdf"])
46
  parser_dropdown = gr.Dropdown(choices=["pypdf", "llamaparse"], label="Select PDF Parser", value="llamaparse")
 
63
  5. Toggle "Use Web Search" to switch between PDF chat and web search.
64
  6. Adjust Temperature and Number of API Calls to fine-tune the response generation.
65
  7. Use the provided examples or ask your own questions.
 
66
  """
67
  )
68