wylum commited on
Commit
5d8959c
·
verified ·
1 Parent(s): c547a4e

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +472 -0
app.py ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This code uses the PyMuPDF package.
3
+
4
+ PyMuPDF is AGPL licensed, please refer to:
5
+ https://pymupdf.readthedocs.io/en/latest/about.html#license-and-copyright
6
+ """
7
+
8
+ """
9
+ Code below is based on an implementation by Sunil Kumar Dash:
10
+
11
+ MIT License
12
+
13
+ Copyright (c) 2023 Sunil Kumar Dash
14
+
15
+ Permission is hereby granted, free of charge, to any person obtaining a copy
16
+ of this software and associated documentation files (the "Software"), to deal
17
+ in the Software without restriction, including without limitation the rights
18
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19
+ copies of the Software, and to permit persons to whom the Software is
20
+ furnished to do so, subject to the following conditions:
21
+
22
+ The above copyright notice and this permission notice shall be included in all
23
+ copies or substantial portions of the Software.
24
+
25
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
+ SOFTWARE.
32
+ """
33
+
34
+ from huggingface_hub import InferenceClient
35
+ from langchain_openai import AzureOpenAIEmbeddings
36
+ #from langchain_community.chat_models import AzureChatOpenAI
37
+ from langchain_openai import AzureChatOpenAI
38
+
39
+
40
+
41
+
42
+
43
+ from typing import Any
44
+ import gradio as gr
45
+ from langchain_openai import OpenAIEmbeddings
46
+ from langchain_community.vectorstores import Chroma
47
+ import chromadb
48
+ #to handle the tenant issue
49
+ chromadb.api.client.SharedSystemClient.clear_system_cache()
50
+
51
+ from langchain.chains import ConversationalRetrievalChain
52
+ from langchain_openai import ChatOpenAI
53
+
54
+ from langchain_community.document_loaders import PyMuPDFLoader
55
+ from langchain.schema.document import Document
56
+
57
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
58
+ from langchain.text_splitter import CharacterTextSplitter
59
+ from langchain.memory import ConversationBufferMemory
60
+ from langchain_community.llms import HuggingFaceEndpoint
61
+ from langchain_community.embeddings import HuggingFaceEmbeddings
62
+
63
+
64
+ # for hugging face llm
65
+ from transformers import AutoTokenizer
66
+ import transformers
67
+ import torch
68
+ import tqdm
69
+ import accelerate
70
+
71
+
72
+ import pymupdf
73
+ from PIL import Image
74
+ import os
75
+ import re
76
+ import uuid
77
+
78
+ import os
79
+ import wget
80
+ import subprocess
81
+ import urllib.request
82
+ import requests
83
+
84
+ from pathlib import Path
85
+ from unidecode import unidecode
86
+
87
+ api_key = os.getenv("OPENAI_API_KEY")
88
+ user_agent = os.getenv("USER_AGENT")
89
+
90
+ dr_link_url1 = os.getenv("DR_LINK_1")
91
+ dr_link_url2 = os.getenv("DR_LINK_2")
92
+ azure_endpt = os.getenv("AZURE_ENDPT")
93
+
94
+
95
+ list_llm = ["mistralai/Mistral-7B-Instruct-v0.2", "mistralai/Mixtral-8x7B-Instruct-v0.1", "mistralai/Mistral-7B-Instruct-v0.1", \
96
+ "google/gemma-7b-it","google/gemma-2b-it", \
97
+ "HuggingFaceH4/zephyr-7b-beta", "HuggingFaceH4/zephyr-7b-gemma-v0.1", \
98
+ "meta-llama/Llama-2-7b-chat-hf", "microsoft/phi-2", \
99
+ "TinyLlama/TinyLlama-1.1B-Chat-v1.0", "mosaicml/mpt-7b-instruct", "tiiuae/falcon-7b-instruct", \
100
+ "google/flan-t5-xxl"
101
+ ]
102
+ list_llm_simple = [os.path.basename(llm) for llm in list_llm]
103
+
104
+
105
+ """
106
+ enable_box = gr.Textbox(
107
+ value=None, placeholder="Upload your OpenAI API key", interactive=True
108
+ )
109
+ disable_box = gr.Textbox(value="OpenAI API key is set", interactive=False)
110
+ """
111
+
112
+ def set_apikey(api_key: str):
113
+ print("API Key set")
114
+ app.OPENAI_API_KEY = api_key
115
+ #return disable_box
116
+
117
+ """
118
+ def enable_api_box():
119
+ return enable_box
120
+ """
121
+
122
+ def add_text(history, text: str):
123
+ if not text:
124
+ raise gr.Error("enter text")
125
+ print("in add_text history="+str(history))
126
+ print("in add_text text="+str(text))
127
+
128
+ history = history + [(text, "")]
129
+ return history
130
+
131
+
132
+ class my_app:
133
+ def __init__(self, OPENAI_API_KEY: str = None) -> None:
134
+ print("init")
135
+ self.OPENAI_API_KEY: str = api_key
136
+ #self.chain = None
137
+ #self.chat_history: list = []
138
+ #self.N: int = 0
139
+ self.count: int = 0
140
+
141
+ def __call__(self, file: str) -> Any:
142
+ print("call")
143
+ #if self.count == 0:
144
+ #vincent added
145
+ #self.chain = None
146
+ #self.chat_history: list = []
147
+ #self.N: int = 0
148
+ #self.count: int = 0
149
+
150
+ #self.chain = self.build_chain(file)
151
+ #self.count += 1
152
+
153
+ #vincent added
154
+
155
+ #else:
156
+ #self.chain = self.build_chain(file)
157
+ #self.count += 1
158
+
159
+ #return self.chain
160
+
161
+
162
+ def process_file2(file: str):
163
+
164
+ loader = PyMuPDFLoader(file.name)
165
+ documents = loader.load()
166
+ pattern = r"/([^/]+)$"
167
+ match = re.search(pattern, file.name)
168
+ try:
169
+ file_name = match.group(1)
170
+ except:
171
+ file_name = os.path.basename(file)
172
+
173
+ return documents, file_name
174
+
175
+ def create_collection_name(filepath):
176
+ # Extract filename without extension
177
+ collection_name = Path(filepath).stem
178
+ # Fix potential issues from naming convention
179
+ ## Remove space
180
+ collection_name = collection_name.replace(" ","-")
181
+ ## ASCII transliterations of Unicode text
182
+ collection_name = unidecode(collection_name)
183
+ ## Remove special characters
184
+ #collection_name = re.findall("[\dA-Za-z]*", collection_name)[0]
185
+ collection_name = re.sub('[^A-Za-z0-9]+', '-', collection_name)
186
+ ## Limit length to 50 characters
187
+ collection_name = collection_name[:50]
188
+ ## Minimum length of 3 characters
189
+ if len(collection_name) < 3:
190
+ collection_name = collection_name + 'xyz'
191
+ ## Enforce start and end as alphanumeric character
192
+ if not collection_name[0].isalnum():
193
+ collection_name = 'A' + collection_name[1:]
194
+ if not collection_name[-1].isalnum():
195
+ collection_name = collection_name[:-1] + 'Z'
196
+ print('Filepath: ', filepath)
197
+ print('Collection name: ', collection_name)
198
+ return collection_name
199
+
200
+ def build_qa_chain(collection_name, vector_db, file: str):
201
+ print("in build_qa_chain="+file.name)
202
+ documents, file_name = process_file2(file)
203
+ # Load embeddings model
204
+ #embeddings = OpenAIEmbeddings(openai_api_key=self.OPENAI_API_KEY)
205
+
206
+ #vincent for old LLM
207
+ """
208
+ embeddings = AzureOpenAIEmbeddings(
209
+ model="text-embedding-ada-002",
210
+ # dimensions: Optional[int] = None, # Can specify dimensions with new text-embedding-3 models
211
+ azure_endpoint=azure_endpt , # If not provided, will read env variable AZURE_OPENAI_ENDPOINT
212
+ openai_api_key=api_key, # Can provide an API key directly. If missing read env variable AZURE_OPENAI_API_KEY
213
+ #openai_api_version="2023-05-15", # If not provided, will read env variable AZURE_OPENAI_API_VERSION
214
+ openai_api_version="2023-05-15", # If not provided, will read env variable AZURE_OPENAI_API_VERSION
215
+ )
216
+ """
217
+
218
+ #vincent for new LLM
219
+ embeddings = HuggingFaceEmbeddings()
220
+
221
+
222
+ #vincent added to handle the tenant problem 20250211
223
+ chromadb.api.client.SharedSystemClient.clear_system_cache()
224
+ new_client = chromadb.EphemeralClient()
225
+ memory = ConversationBufferMemory(
226
+ memory_key="chat_history",
227
+ output_key='answer',
228
+ return_messages=True
229
+ )
230
+
231
+ # added by vincent
232
+ text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=10)
233
+ chunked_documents = text_splitter.split_documents(documents)
234
+
235
+ #list_file_path = [x.name for x in list_file_obj if x is not None]
236
+ list_file_path = file.name
237
+ # Create collection_name for vector database
238
+ # vincent fix InvalidCollectionException 20250212
239
+ #collection_name = create_collection_name(list_file_path[0])
240
+ collection_name = "pdf_docs_l_"+file.name[-10:]
241
+
242
+ vector_db = Chroma.from_documents(
243
+ documents=chunked_documents,
244
+ embedding=embeddings,
245
+ client=new_client,
246
+ #collection_name=file_name,
247
+ #persist_directory = "db_" + file_name,
248
+ collection_name=collection_name,
249
+ )
250
+ """
251
+ chain = ConversationalRetrievalChain.from_llm(
252
+ ChatOpenAI(temperature=0.0, openai_api_key=self.OPENAI_API_KEY),
253
+ retriever=pdfsearch.as_retriever(search_kwargs={"k": 1}),
254
+ return_source_documents=True,
255
+ )
256
+ """
257
+
258
+ #vincent added for old LLM
259
+ """
260
+ chain = ConversationalRetrievalChain.from_llm(
261
+ #ChatOpenAI(temperature=0.0, openai_api_key=self.OPENAI_API_KEY),
262
+
263
+ AzureChatOpenAI(
264
+ temperature=0.0, openai_api_key=api_key, api_version="2024-08-01-preview",
265
+ model_name="gpt-4o", azure_endpoint=azure_endpt),
266
+ #vincent modified
267
+ retriever=vector_db.as_retriever(),
268
+ #retriever=pdfsearch.as_retriever(search_kwargs={"k": 1}),
269
+ return_source_documents=True,
270
+ chain_type="stuff",
271
+ memory=memory,
272
+ )
273
+
274
+ """
275
+ #vincent for new LLM
276
+ #llm_model = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
277
+ #llm_model = "meta-llama/Llama-2-7b-chat-hf"
278
+ llm = HuggingFaceEndpoint(
279
+ repo_id=llm_model,
280
+ task="text-generation", # Explicitly specify task
281
+ # model_kwargs={"temperature": temperature, "max_new_tokens": 250, "top_k": top_k}
282
+ temperature = 0.01,
283
+ max_new_tokens = 250,
284
+ top_k = 3,
285
+ )
286
+
287
+ chain = ConversationalRetrievalChain.from_llm(
288
+ llm,
289
+ retriever=vector_db.as_retriever(),
290
+ chain_type="stuff",
291
+ memory=memory,
292
+ # combine_docs_chain_kwargs={"prompt": your_prompt})
293
+ return_source_documents=True,
294
+ #return_generated_question=False,
295
+ verbose=False,
296
+ )
297
+
298
+ #vincent added 20250211
299
+ app.count += 1
300
+ return collection_name, vector_db, chain, "Complete!"
301
+
302
+
303
+ def get_response(collection_name, vector_db, qa_chain, history, query, file):
304
+ #vincent added
305
+ set_apikey(api_key)
306
+ #print("in get_response count=" + str(app.count))
307
+ if not file:
308
+ raise gr.Error(message="Upload a PDF")
309
+
310
+ formatted_chat_history = list(history)
311
+ formatted_chat_history = formatted_chat_history[:len(formatted_chat_history)-1]
312
+ print("in get_response query="+ query)
313
+ #print("in get_response chat_history="+ str(app.chat_history))
314
+ print("in get_response formatted_chat_history="+ str(formatted_chat_history))
315
+ #print("in get_response history="+ str(history))
316
+
317
+ chat_history_tuples = []
318
+ for message in formatted_chat_history:
319
+ chat_history_tuples.append((message[0], message[1]))
320
+
321
+ #vincent added 20250211
322
+ if app.count == 0:
323
+ collection_name, vector_db, qa_chain = build_qa_chain(collection_name, vector_db, file)
324
+ result = qa_chain.invoke(
325
+ {"question": query, "chat_history": chat_history_tuples}, return_only_outputs=True
326
+ #{"question": query, "chat_history": format_chat_history(query, history)}, return_only_outputs=True
327
+ )
328
+
329
+
330
+ #app.chat_history += [(query, result["answer"])]
331
+ ##app.N = list(result["source_documents"][0])[1][1]["page"]
332
+ for char in result["answer"]:
333
+ history[-1][-1] += char
334
+ yield collection_name, vector_db, qa_chain, history, ""
335
+
336
+ #print("answer:"+ result["answer"])
337
+
338
+
339
+ def render_file(file):
340
+ #print("in render_file="+file.name+" count="+str(app.count))
341
+ doc = pymupdf.open(file.name)
342
+ # vincent: issue in N
343
+ page = doc[N]
344
+ # Render the page as a PNG image with a resolution of 150 DPI
345
+ pix = page.get_pixmap(dpi=150)
346
+ image = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
347
+ return image
348
+
349
+
350
+ def purge_chat_and_render_first(file):
351
+ print("purge_chat_and_render_first")
352
+ # Purges the previous chat session so that the bot has no concept of previous documents
353
+ chat_history = []
354
+ history = []
355
+ #count = 0
356
+
357
+ #vincent added 20250211
358
+ #count = count + 1
359
+ app.count = 0
360
+
361
+ # Use PyMuPDF to render the first page of the uploaded document
362
+ doc = pymupdf.open(file.name)
363
+ page = doc[0]
364
+ # Render the page as a PNG image with a resolution of 150 DPI
365
+ pix = page.get_pixmap(dpi=150)
366
+ image = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
367
+ return image, []
368
+
369
+ app = my_app()
370
+
371
+ with gr.Blocks() as demo:
372
+
373
+ vector_db = gr.State()
374
+ qa_chain = gr.State()
375
+ collection_name = gr.State()
376
+ #N = gr.Number()
377
+ #count = gr.Number()
378
+
379
+ N = 0
380
+ count = 0
381
+
382
+ #chat_history = gr.State()
383
+ #chat_history: list = []
384
+ #chat_history = []
385
+
386
+ with gr.Column():
387
+ """
388
+ with gr.Row():
389
+
390
+ with gr.Column(scale=1):
391
+ api_key = gr.Textbox(
392
+ placeholder="Enter OpenAI API key and hit <RETURN>",
393
+ show_label=False,
394
+ interactive=True
395
+ )
396
+ """
397
+ with gr.Row():
398
+ llm_btn = gr.Radio(list_llm_simple, \
399
+ label="LLM models", value = list_llm_simple[0], type="index", info="Choose your LLM model")
400
+
401
+ with gr.Accordion("Advanced options - LLM model", open=False):
402
+ with gr.Row():
403
+ slider_temperature = gr.Slider(minimum = 0.01, maximum = 1.0, value=0.7, step=0.1, label="Temperature", info="Model temperature", interactive=True)
404
+ with gr.Row():
405
+ slider_maxtokens = gr.Slider(minimum = 224, maximum = 4096, value=1024, step=32, label="Max Tokens", info="Model max tokens", interactive=True)
406
+ with gr.Row():
407
+ slider_topk = gr.Slider(minimum = 1, maximum = 10, value=3, step=1, label="top-k samples", info="Model top-k samples", interactive=True)
408
+
409
+ with gr.Row():
410
+ llm_progress = gr.Textbox(value="None",label="QA chain initialization")
411
+ with gr.Row():
412
+ qachain_btn = gr.Button("Initialize Question Answering chain")
413
+
414
+ with gr.Row():
415
+ with gr.Column(scale=2):
416
+ with gr.Row():
417
+ chatbot = gr.Chatbot(value=[], elem_id="chatbot")
418
+ with gr.Row():
419
+ txt = gr.Textbox(
420
+ show_label=False,
421
+ placeholder="Enter text and press submit",
422
+ scale=2
423
+ )
424
+ submit_btn = gr.Button("submit", scale=1)
425
+
426
+ with gr.Column(scale=1):
427
+ with gr.Row():
428
+ show_img = gr.Image(label="Upload PDF")
429
+ with gr.Row():
430
+ btn = gr.UploadButton("📁 upload a PDF", file_types=[".pdf"])
431
+
432
+ """
433
+ api_key.submit(
434
+ fn=set_apikey,
435
+ inputs=[api_key],
436
+ outputs=[
437
+ api_key,
438
+ ],
439
+ )
440
+ """
441
+
442
+ btn.upload(
443
+ fn=purge_chat_and_render_first,
444
+ inputs=[btn],
445
+ outputs=[show_img, chatbot],
446
+ )
447
+
448
+ qachain_btn.click(build_qa_chain, \
449
+ inputs=[collection_name, vector_db, btn, llm_btn, slider_temperature, slider_maxtokens, slider_topk], \
450
+ outputs=[collection_name, vector_db, qa_chain, llm_progress]).then(lambda:[None], \
451
+ inputs=None, \
452
+ outputs=[chatbot], \
453
+ queue=False)
454
+
455
+ submit_btn.click(
456
+ fn=add_text,
457
+ inputs=[chatbot, txt],
458
+ outputs=[
459
+ chatbot,
460
+ ],
461
+ queue=False,
462
+ ).success(
463
+ fn=get_response, inputs=[collection_name,vector_db, qa_chain, chatbot, txt, btn], outputs=[collection_name,vector_db, qa_chain, chatbot, txt]
464
+ ).success(
465
+ fn=render_file, inputs=[btn], outputs=[show_img]
466
+ )
467
+
468
+ #demo.queue()
469
+ #demo.launch(share=True, ssr_mode=False)
470
+ #demo.launch()
471
+ demo.queue().launch(share=True)
472
+ #demo.launch(share=True)