hmrizal commited on
Commit
6b32859
·
verified ·
1 Parent(s): bc3e7d7

add performance_tracker, pure llm w/o pandas bypass, full english

Browse files
Files changed (1) hide show
  1. app.py +158 -142
app.py CHANGED
@@ -30,6 +30,7 @@ MODEL_CACHE = {
30
 
31
  # Create directories for user data
32
  os.makedirs("user_data", exist_ok=True)
 
33
 
34
  # Model configuration dictionary
35
  MODEL_CONFIG = {
@@ -40,7 +41,7 @@ MODEL_CONFIG = {
40
  },
41
  "TinyLlama Chat": {
42
  "name": "TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF",
43
- "description": "Model ringan dengan 1.1B parameter, cepat dan ringan",
44
  "dtype": torch.float16 if torch.cuda.is_available() else torch.float32
45
  },
46
  "Mistral Instruct": {
@@ -50,12 +51,12 @@ MODEL_CONFIG = {
50
  },
51
  "Phi-4 Mini Instruct": {
52
  "name": "microsoft/Phi-4-mini-instruct",
53
- "description": "Model yang ringan dari Microsoft cocok untuk tugas instruksional",
54
  "dtype": torch.float16 if torch.cuda.is_available() else torch.float32
55
  },
56
  "DeepSeek Coder Instruct": {
57
  "name": "deepseek-ai/deepseek-coder-1.3b-instruct",
58
- "description": "1.3B model untuk kode dan analisis data",
59
  "dtype": torch.float16 if torch.cuda.is_available() else torch.float32
60
  },
61
  "DeepSeek Lite Chat": {
@@ -81,6 +82,28 @@ MODEL_CONFIG = {
81
  }
82
  }
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  def initialize_model_once(model_key):
85
  with MODEL_CACHE["init_lock"]:
86
  current_model = MODEL_CACHE["model_name"]
@@ -99,20 +122,20 @@ def initialize_model_once(model_key):
99
  try:
100
  print(f"Loading model: {model_name}")
101
 
102
- # Periksa apakah ini model GGUF
103
  if "GGUF" in model_name:
104
- # Download model file terlebih dahulu jika belum ada
105
  from huggingface_hub import hf_hub_download
106
  try:
107
- # Coba temukan file GGUF di repo
108
  repo_id = model_name
109
  model_path = hf_hub_download(
110
  repo_id=repo_id,
111
- filename="model.gguf" # Nama file dapat berbeda
112
  )
113
  except Exception as e:
114
  print(f"Couldn't find model.gguf, trying other filenames: {str(e)}")
115
- # Coba cari file GGUF dengan nama lain
116
  import requests
117
  from huggingface_hub import list_repo_files
118
 
@@ -122,17 +145,17 @@ def initialize_model_once(model_key):
122
  if not gguf_files:
123
  raise ValueError(f"No GGUF files found in {repo_id}")
124
 
125
- # Gunakan file GGUF pertama yang ditemukan
126
  model_path = hf_hub_download(repo_id=repo_id, filename=gguf_files[0])
127
 
128
- # Load model GGUF dengan llama-cpp-python
129
  MODEL_CACHE["model"] = Llama(
130
  model_path=model_path,
131
- n_ctx=2048, # Konteks yang lebih kecil untuk penghematan memori
132
  n_batch=512,
133
- n_threads=2 # Sesuaikan dengan 2 vCPU
134
  )
135
- MODEL_CACHE["tokenizer"] = None # GGUF tidak membutuhkan tokenizer terpisah
136
  MODEL_CACHE["is_gguf"] = True
137
 
138
  # Handle T5 models
@@ -148,21 +171,34 @@ def initialize_model_once(model_key):
148
 
149
  # Handle standard HF models
150
  else:
151
- quantization_config = BitsAndBytesConfig(
152
- load_in_4bit=True,
153
- bnb_4bit_compute_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
154
- bnb_4bit_quant_type="nf4",
155
- bnb_4bit_use_double_quant=True
156
- )
157
- MODEL_CACHE["tokenizer"] = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
158
- MODEL_CACHE["model"] = AutoModelForCausalLM.from_pretrained(
159
- model_name,
160
- quantization_config=quantization_config,
161
- torch_dtype=model_info["dtype"],
162
- device_map="auto" if torch.cuda.is_available() else None,
163
- low_cpu_mem_usage=True,
164
- trust_remote_code=True
165
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  MODEL_CACHE["is_gguf"] = False
167
 
168
  print(f"Model {model_name} loaded successfully")
@@ -180,17 +216,20 @@ def create_llm_pipeline(model_key):
180
  print(f"Creating pipeline for model: {model_key}")
181
  tokenizer, model, is_gguf = initialize_model_once(model_key)
182
 
 
 
 
183
  if model is None:
184
  raise ValueError(f"Model is None for {model_key}")
185
 
186
  # For GGUF models from llama-cpp-python
187
  if is_gguf:
188
- # Buat adaptor untuk menggunakan model GGUF seperti HF pipeline
189
  from langchain.llms import LlamaCpp
190
  llm = LlamaCpp(
191
  model_path=model.model_path,
192
  temperature=0.3,
193
- max_tokens=128,
194
  top_p=0.9,
195
  n_ctx=2048,
196
  streaming=False
@@ -198,13 +237,13 @@ def create_llm_pipeline(model_key):
198
  return llm
199
 
200
  # Create appropriate pipeline for HF models
201
- elif getattr(model_info, "is_t5", False):
202
  print("Creating T5 pipeline")
203
  pipe = pipeline(
204
  "text2text-generation",
205
  model=model,
206
  tokenizer=tokenizer,
207
- max_new_tokens=128,
208
  temperature=0.3,
209
  top_p=0.9,
210
  return_full_text=False,
@@ -215,7 +254,7 @@ def create_llm_pipeline(model_key):
215
  "text-generation",
216
  model=model,
217
  tokenizer=tokenizer,
218
- max_new_tokens=128,
219
  temperature=0.3,
220
  top_p=0.9,
221
  top_k=30,
@@ -229,6 +268,7 @@ def create_llm_pipeline(model_key):
229
  import traceback
230
  print(f"Error creating pipeline: {str(e)}")
231
  print(traceback.format_exc())
 
232
 
233
  def handle_model_loading_error(model_key, session_id):
234
  """Handle model loading errors by providing alternative model suggestions"""
@@ -244,113 +284,73 @@ def handle_model_loading_error(model_key, session_id):
244
  suggested_models.remove(model_key)
245
 
246
  suggestions = ", ".join(suggested_models[:3]) # Only show top 3 suggestions
247
- return None, f"Tidak dapat memuat model {model_key}. Silakan coba model lain seperti: {suggestions}"
248
 
249
  def create_conversational_chain(db, file_path, model_key):
250
  llm = create_llm_pipeline(model_key)
251
 
252
- # Load the file into pandas to enable code execution for data analysis
253
  df = pd.read_csv(file_path)
254
 
255
- # Create improved prompt template that focuses on direct answers, not code
256
  template = """
257
- Berikut ini adalah informasi tentang file CSV:
258
-
259
- Kolom-kolom dalam file: {columns}
260
-
261
- Beberapa baris pertama:
 
 
 
262
  {sample_data}
263
-
264
- Konteks tambahan dari vector database:
265
  {context}
266
-
267
- Pertanyaan: {question}
268
-
269
- INSTRUKSI PENTING:
270
- 1. Jangan tampilkan kode Python, berikan jawaban langsung dalam Bahasa Indonesia.
271
- 2. Jika pertanyaan terkait statistik data (rata-rata, maksimum dll), lakukan perhitungan dan berikan hasilnya.
272
- 3. Jawaban harus singkat, jelas dan akurat berdasarkan data yang ada.
273
- 4. Gunakan format yang sesuai untuk angka (desimal 2 digit untuk nilai non-integer).
274
- 5. Jangan menyebutkan proses perhitungan, fokus pada hasil akhir.
275
-
276
- Jawaban:
 
 
 
277
  """
278
 
279
  PROMPT = PromptTemplate(
280
  template=template,
281
- input_variables=["columns", "sample_data", "context", "question"]
282
  )
283
 
284
  # Create retriever
285
- retriever = db.as_retriever(search_kwargs={"k": 3}) # Reduced k for better performance
286
 
287
  # Process query with better error handling
288
  def process_query(query, chat_history):
289
  try:
 
 
290
  # Get information from dataframe for context
291
- columns_str = ", ".join(df.columns.tolist())
292
- sample_data = df.head(2).to_string() # Reduced to 2 rows for performance
 
 
293
 
294
  # Get context from vector database
295
  docs = retriever.get_relevant_documents(query)
296
  context = "\n\n".join([doc.page_content for doc in docs])
297
 
298
- # Dynamically calculate answers for common statistical queries
299
- def preprocess_query():
300
- query_lower = query.lower()
301
- result = None
302
-
303
- # Handle statistical queries directly
304
- if "rata-rata" in query_lower or "mean" in query_lower or "average" in query_lower:
305
- for col in df.columns:
306
- if col.lower() in query_lower and pd.api.types.is_numeric_dtype(df[col]):
307
- try:
308
- result = f"Rata-rata {col} adalah {df[col].mean():.2f}"
309
- except:
310
- pass
311
-
312
- elif "maksimum" in query_lower or "max" in query_lower or "tertinggi" in query_lower:
313
- for col in df.columns:
314
- if col.lower() in query_lower and pd.api.types.is_numeric_dtype(df[col]):
315
- try:
316
- result = f"Nilai maksimum {col} adalah {df[col].max():.2f}"
317
- except:
318
- pass
319
-
320
- elif "minimum" in query_lower or "min" in query_lower or "terendah" in query_lower:
321
- for col in df.columns:
322
- if col.lower() in query_lower and pd.api.types.is_numeric_dtype(df[col]):
323
- try:
324
- result = f"Nilai minimum {col} adalah {df[col].min():.2f}"
325
- except:
326
- pass
327
-
328
- elif "total" in query_lower or "jumlah" in query_lower or "sum" in query_lower:
329
- for col in df.columns:
330
- if col.lower() in query_lower and pd.api.types.is_numeric_dtype(df[col]):
331
- try:
332
- result = f"Total {col} adalah {df[col].sum():.2f}"
333
- except:
334
- pass
335
-
336
- elif "baris" in query_lower or "jumlah data" in query_lower or "row" in query_lower:
337
- result = f"Jumlah baris data adalah {len(df)}"
338
-
339
- elif "kolom" in query_lower or "field" in query_lower:
340
- if "nama" in query_lower or "list" in query_lower or "sebutkan" in query_lower:
341
- result = f"Kolom dalam data: {', '.join(df.columns.tolist())}"
342
-
343
- return result
344
-
345
- # Try direct calculation first
346
- direct_answer = preprocess_query()
347
- if direct_answer:
348
- return {"answer": direct_answer}
349
-
350
- # If no direct calculation, use the LLM
351
  chain = LLMChain(llm=llm, prompt=PROMPT)
352
  raw_result = chain.run(
353
- columns=columns_str,
 
 
354
  sample_data=sample_data,
355
  context=context,
356
  question=query
@@ -361,14 +361,28 @@ def create_conversational_chain(db, file_path, model_key):
361
 
362
  # If result is empty after cleaning, use a fallback
363
  if not cleaned_result:
364
- return {"answer": "Tidak dapat memproses jawaban. Silakan coba pertanyaan lain."}
 
 
 
 
 
 
 
 
 
 
 
 
 
365
 
366
- return {"answer": cleaned_result}
 
367
  except Exception as e:
368
  import traceback
369
  print(f"Error in process_query: {str(e)}")
370
  print(traceback.format_exc())
371
- return {"answer": f"Terjadi kesalahan saat memproses pertanyaan: {str(e)}"}
372
 
373
  return process_query
374
 
@@ -387,7 +401,7 @@ class ChatBot:
387
  self.model_key = model_key
388
 
389
  if file is None:
390
- return "Mohon upload file CSV terlebih dahulu."
391
 
392
  try:
393
  print(f"Processing file using model: {self.model_key}")
@@ -410,7 +424,7 @@ class ChatBot:
410
  print(f"CSV saved to {user_file_path}")
411
  except Exception as e:
412
  print(f"Error reading CSV: {str(e)}")
413
- return f"Error membaca CSV: {str(e)}"
414
 
415
  # Load document with reduced chunk size for better memory usage
416
  try:
@@ -451,20 +465,20 @@ class ChatBot:
451
  return f"Error creating chain: {str(e)}"
452
 
453
  # Add basic file info to chat history for context
454
- file_info = f"CSV berhasil dimuat dengan {df.shape[0]} baris dan {len(df.columns)} kolom menggunakan model {self.model_key}. Kolom: {', '.join(df.columns.tolist())}"
455
  self.chat_history.append(("System", file_info))
456
 
457
- return f"File CSV berhasil diproses dengan model {self.model_key}! Anda dapat mulai chat dengan model untuk analisis data."
458
  except Exception as e:
459
  import traceback
460
  print(traceback.format_exc())
461
- return f"Error pemrosesan file: {str(e)}"
462
 
463
  def change_model(self, model_key):
464
  """Change the model being used and recreate the chain if necessary"""
465
  try:
466
  if model_key == self.model_key:
467
- return f"Model {model_key} sudah digunakan."
468
 
469
  print(f"Changing model from {self.model_key} to {model_key}")
470
  self.model_key = model_key
@@ -475,7 +489,7 @@ class ChatBot:
475
  # Load existing database
476
  db_path = f"{self.user_dir}/db_faiss"
477
  if not os.path.exists(db_path):
478
- return f"Error: Database tidak ditemukan. Silakan upload file CSV kembali."
479
 
480
  print(f"Loading embeddings from {db_path}")
481
  embeddings = HuggingFaceEmbeddings(
@@ -483,7 +497,7 @@ class ChatBot:
483
  model_kwargs={'device': 'cpu'}
484
  )
485
 
486
- # Tambahkan flag allow_dangerous_deserialization=True
487
  db = FAISS.load_local(db_path, embeddings, allow_dangerous_deserialization=True)
488
  print(f"FAISS database loaded successfully")
489
 
@@ -493,38 +507,38 @@ class ChatBot:
493
  print(f"Chain created successfully")
494
 
495
  # Add notification to chat history
496
- self.chat_history.append(("System", f"Model berhasil diubah ke {model_key}."))
497
 
498
- return f"Model berhasil diubah ke {model_key}."
499
  except Exception as e:
500
  import traceback
501
  error_trace = traceback.format_exc()
502
  print(f"Detailed error in change_model: {error_trace}")
503
- return f"Error mengubah model: {str(e)}"
504
  else:
505
  # Just update the model key if no file is loaded yet
506
  print(f"No CSV file loaded yet, just updating model preference to {model_key}")
507
- return f"Model diubah ke {model_key}. Silakan upload file CSV untuk memulai."
508
  except Exception as e:
509
  import traceback
510
  error_trace = traceback.format_exc()
511
  print(f"Unexpected error in change_model: {error_trace}")
512
- return f"Error tidak terduga saat mengubah model: {str(e)}"
513
 
514
  def chat(self, message, history):
515
  if self.chain is None:
516
- return "Mohon upload file CSV terlebih dahulu."
517
 
518
  try:
519
  # Process the question with the chain
520
  result = self.chain(message, self.chat_history)
521
 
522
  # Get the answer with fallback
523
- answer = result.get("answer", "Maaf, tidak dapat menghasilkan jawaban. Silakan coba pertanyaan lain.")
524
 
525
  # Ensure we never return empty
526
  if not answer or answer.strip() == "":
527
- answer = "Maaf, tidak dapat menghasilkan jawaban yang sesuai. Silakan coba pertanyaan lain."
528
 
529
  # Update internal chat history
530
  self.chat_history.append((message, answer))
@@ -553,7 +567,7 @@ def create_gradio_interface():
553
  with gr.Row():
554
  with gr.Column(scale=1):
555
  with gr.Group():
556
- gr.Markdown("### Langkah 1: Pilih Model AI")
557
  model_dropdown = gr.Dropdown(
558
  label="Model",
559
  choices=model_choices,
@@ -565,28 +579,28 @@ def create_gradio_interface():
565
  )
566
 
567
  with gr.Group():
568
- gr.Markdown("### Langkah 2: Unggah dan Proses CSV")
569
  file_input = gr.File(
570
  label="Upload CSV Anda",
571
  file_types=[".csv"]
572
  )
573
- process_button = gr.Button("Proses CSV")
574
 
575
- reset_button = gr.Button("Reset Sesi (Untuk Ganti Model)")
576
 
577
  with gr.Column(scale=2):
578
  chatbot_interface = gr.Chatbot(
579
- label="Riwayat Chat",
580
  # type="messages",
581
  height=400
582
  )
583
  message_input = gr.Textbox(
584
- label="Ketik pesan Anda",
585
- placeholder="Tanyakan tentang data CSV Anda...",
586
  lines=2
587
  )
588
- submit_button = gr.Button("Kirim")
589
- clear_button = gr.Button("Bersihkan Chat")
590
 
591
  # Update model info when selection changes
592
  def update_model_info(model_key):
@@ -601,7 +615,7 @@ def create_gradio_interface():
601
  # Process file handler - disables model selection after file is processed
602
  def handle_process_file(file, model_key, sess_id):
603
  if file is None:
604
- return None, None, False, "Mohon upload file CSV terlebih dahulu."
605
 
606
  try:
607
  chatbot = ChatBot(sess_id, model_key)
@@ -611,6 +625,8 @@ def create_gradio_interface():
611
  import traceback
612
  print(f"Error processing file with {model_key}: {str(e)}")
613
  print(traceback.format_exc())
 
 
614
 
615
  process_button.click(
616
  fn=handle_process_file,
@@ -641,7 +657,7 @@ def create_gradio_interface():
641
  def bot_response(history, chatbot, sess_id):
642
  if chatbot is None:
643
  chatbot = ChatBot(sess_id)
644
- history[-1] = (history[-1][0], "Mohon upload file CSV terlebih dahulu.")
645
  return chatbot, history
646
 
647
  user_message = history[-1][0]
 
30
 
31
  # Create directories for user data
32
  os.makedirs("user_data", exist_ok=True)
33
+ os.makedirs("performance_metrics", exist_ok=True)
34
 
35
  # Model configuration dictionary
36
  MODEL_CONFIG = {
 
41
  },
42
  "TinyLlama Chat": {
43
  "name": "TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF",
44
+ "description": "Lightweight model with 1.1B parameters, fast and efficient",
45
  "dtype": torch.float16 if torch.cuda.is_available() else torch.float32
46
  },
47
  "Mistral Instruct": {
 
51
  },
52
  "Phi-4 Mini Instruct": {
53
  "name": "microsoft/Phi-4-mini-instruct",
54
+ "description": "Lightweight model from Microsoft suitable for instructional tasks",
55
  "dtype": torch.float16 if torch.cuda.is_available() else torch.float32
56
  },
57
  "DeepSeek Coder Instruct": {
58
  "name": "deepseek-ai/deepseek-coder-1.3b-instruct",
59
+ "description": "1.3B model for code and data analysis",
60
  "dtype": torch.float16 if torch.cuda.is_available() else torch.float32
61
  },
62
  "DeepSeek Lite Chat": {
 
82
  }
83
  }
84
 
85
+ # Performance metrics tracking
86
+ class PerformanceTracker:
87
+ def __init__(self):
88
+ self.metrics_file = "performance_metrics/model_performance.csv"
89
+
90
+ # Create metrics file if it doesn't exist
91
+ if not os.path.exists(self.metrics_file):
92
+ with open(self.metrics_file, "w") as f:
93
+ f.write("timestamp,model,question,processing_time,response_length\n")
94
+
95
+ def log_performance(self, model_name, question, processing_time, response):
96
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
97
+ response_length = len(response)
98
+
99
+ with open(self.metrics_file, "a") as f:
100
+ f.write(f'"{timestamp}","{model_name}","{question}",{processing_time},{response_length}\n')
101
+
102
+ print(f"Logged performance for {model_name}: {processing_time:.2f}s")
103
+
104
+ # Initialize performance tracker
105
+ performance_tracker = PerformanceTracker()
106
+
107
  def initialize_model_once(model_key):
108
  with MODEL_CACHE["init_lock"]:
109
  current_model = MODEL_CACHE["model_name"]
 
122
  try:
123
  print(f"Loading model: {model_name}")
124
 
125
+ # Check if this is a GGUF model
126
  if "GGUF" in model_name:
127
+ # Download the model file first if it doesn't exist
128
  from huggingface_hub import hf_hub_download
129
  try:
130
+ # Try to find the GGUF file in the repo
131
  repo_id = model_name
132
  model_path = hf_hub_download(
133
  repo_id=repo_id,
134
+ filename="model.gguf" # File name may differ
135
  )
136
  except Exception as e:
137
  print(f"Couldn't find model.gguf, trying other filenames: {str(e)}")
138
+ # Try to find GGUF file with other names
139
  import requests
140
  from huggingface_hub import list_repo_files
141
 
 
145
  if not gguf_files:
146
  raise ValueError(f"No GGUF files found in {repo_id}")
147
 
148
+ # Use first GGUF file found
149
  model_path = hf_hub_download(repo_id=repo_id, filename=gguf_files[0])
150
 
151
+ # Load GGUF model with llama-cpp-python
152
  MODEL_CACHE["model"] = Llama(
153
  model_path=model_path,
154
+ n_ctx=2048, # Smaller context for memory savings
155
  n_batch=512,
156
+ n_threads=2 # Adjust for 2 vCPU
157
  )
158
+ MODEL_CACHE["tokenizer"] = None # GGUF doesn't need separate tokenizer
159
  MODEL_CACHE["is_gguf"] = True
160
 
161
  # Handle T5 models
 
171
 
172
  # Handle standard HF models
173
  else:
174
+ # Only use quantization if CUDA is available
175
+ if torch.cuda.is_available():
176
+ quantization_config = BitsAndBytesConfig(
177
+ load_in_4bit=True,
178
+ bnb_4bit_compute_dtype=torch.float16,
179
+ bnb_4bit_quant_type="nf4",
180
+ bnb_4bit_use_double_quant=True
181
+ )
182
+
183
+ MODEL_CACHE["tokenizer"] = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
184
+ MODEL_CACHE["model"] = AutoModelForCausalLM.from_pretrained(
185
+ model_name,
186
+ quantization_config=quantization_config,
187
+ torch_dtype=model_info["dtype"],
188
+ device_map="auto",
189
+ low_cpu_mem_usage=True,
190
+ trust_remote_code=True
191
+ )
192
+ else:
193
+ # For CPU-only environments, load without quantization
194
+ MODEL_CACHE["tokenizer"] = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
195
+ MODEL_CACHE["model"] = AutoModelForCausalLM.from_pretrained(
196
+ model_name,
197
+ torch_dtype=torch.float32, # Use float32 for CPU
198
+ device_map=None,
199
+ low_cpu_mem_usage=True,
200
+ trust_remote_code=True
201
+ )
202
  MODEL_CACHE["is_gguf"] = False
203
 
204
  print(f"Model {model_name} loaded successfully")
 
216
  print(f"Creating pipeline for model: {model_key}")
217
  tokenizer, model, is_gguf = initialize_model_once(model_key)
218
 
219
+ # Get the model info for reference
220
+ model_info = MODEL_CONFIG[model_key]
221
+
222
  if model is None:
223
  raise ValueError(f"Model is None for {model_key}")
224
 
225
  # For GGUF models from llama-cpp-python
226
  if is_gguf:
227
+ # Create adapter to use GGUF model like HF pipeline
228
  from langchain.llms import LlamaCpp
229
  llm = LlamaCpp(
230
  model_path=model.model_path,
231
  temperature=0.3,
232
+ max_tokens=256, # Increased for more comprehensive answers
233
  top_p=0.9,
234
  n_ctx=2048,
235
  streaming=False
 
237
  return llm
238
 
239
  # Create appropriate pipeline for HF models
240
+ elif model_info.get("is_t5", False):
241
  print("Creating T5 pipeline")
242
  pipe = pipeline(
243
  "text2text-generation",
244
  model=model,
245
  tokenizer=tokenizer,
246
+ max_new_tokens=256, # Increased for more comprehensive answers
247
  temperature=0.3,
248
  top_p=0.9,
249
  return_full_text=False,
 
254
  "text-generation",
255
  model=model,
256
  tokenizer=tokenizer,
257
+ max_new_tokens=256, # Increased for more comprehensive answers
258
  temperature=0.3,
259
  top_p=0.9,
260
  top_k=30,
 
268
  import traceback
269
  print(f"Error creating pipeline: {str(e)}")
270
  print(traceback.format_exc())
271
+ raise RuntimeError(f"Failed to create pipeline: {str(e)}")
272
 
273
  def handle_model_loading_error(model_key, session_id):
274
  """Handle model loading errors by providing alternative model suggestions"""
 
284
  suggested_models.remove(model_key)
285
 
286
  suggestions = ", ".join(suggested_models[:3]) # Only show top 3 suggestions
287
+ return None, f"Unable to load model {model_key}. Please try another model such as: {suggestions}"
288
 
289
  def create_conversational_chain(db, file_path, model_key):
290
  llm = create_llm_pipeline(model_key)
291
 
292
+ # Load the file into pandas to get metadata about the CSV
293
  df = pd.read_csv(file_path)
294
 
295
+ # Create improved prompt template that focuses on pure LLM analysis
296
  template = """
297
+ You are an expert data analyst tasked with answering questions about a CSV file. The file has been analyzed, and its structure is provided below.
298
+
299
+ CSV File Structure:
300
+ - Total rows: {row_count}
301
+ - Total columns: {column_count}
302
+ - Columns: {columns_list}
303
+
304
+ Sample data (first few rows):
305
  {sample_data}
306
+
307
+ Additional context from the document:
308
  {context}
309
+
310
+ User Question: {question}
311
+
312
+ IMPORTANT INSTRUCTIONS:
313
+ 1. Answer the question directly about the CSV data with accurate information.
314
+ 2. If asked for basic statistics (mean, sum, max, min, count, etc.), perform the calculation mentally and provide the result. Include up to 2 decimal places for non-integer values.
315
+ 3. If asked about patterns or trends, analyze the data thoughtfully.
316
+ 4. Keep answers concise but informative. Respond in the same language as the question.
317
+ 5. If you are not certain of a precise answer, explain what you can determine from the available data.
318
+ 6. You can perform simple calculations including: counts, sums, averages, minimums, maximums, and basic filtering.
319
+ 7. For questions about specific values in the data, reference the sample data and available context.
320
+ 8. Do not mention any programming language or how you would code the solution.
321
+
322
+ Your analysis:
323
  """
324
 
325
  PROMPT = PromptTemplate(
326
  template=template,
327
+ input_variables=["row_count", "column_count", "columns_list", "sample_data", "context", "question"]
328
  )
329
 
330
  # Create retriever
331
+ retriever = db.as_retriever(search_kwargs={"k": 5}) # Increase k for better context
332
 
333
  # Process query with better error handling
334
  def process_query(query, chat_history):
335
  try:
336
+ start_time = time.time()
337
+
338
  # Get information from dataframe for context
339
+ columns_list = ", ".join(df.columns.tolist())
340
+ sample_data = df.head(5).to_string() # Show 5 rows for better context
341
+ row_count = len(df)
342
+ column_count = len(df.columns)
343
 
344
  # Get context from vector database
345
  docs = retriever.get_relevant_documents(query)
346
  context = "\n\n".join([doc.page_content for doc in docs])
347
 
348
+ # Run the chain
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  chain = LLMChain(llm=llm, prompt=PROMPT)
350
  raw_result = chain.run(
351
+ row_count=row_count,
352
+ column_count=column_count,
353
+ columns_list=columns_list,
354
  sample_data=sample_data,
355
  context=context,
356
  question=query
 
361
 
362
  # If result is empty after cleaning, use a fallback
363
  if not cleaned_result:
364
+ cleaned_result = "I couldn't process a complete answer to your question. Please try asking in a different way or provide more specific details about what you'd like to know about the data."
365
+
366
+ processing_time = time.time() - start_time
367
+
368
+ # Log performance metrics
369
+ performance_tracker.log_performance(
370
+ model_key,
371
+ query,
372
+ processing_time,
373
+ cleaned_result
374
+ )
375
+
376
+ # Add processing time to the response for comparison purposes
377
+ result_with_metrics = f"{cleaned_result}\n\n[Processing time: {processing_time:.2f} seconds]"
378
 
379
+ return {"answer": result_with_metrics}
380
+
381
  except Exception as e:
382
  import traceback
383
  print(f"Error in process_query: {str(e)}")
384
  print(traceback.format_exc())
385
+ return {"answer": f"An error occurred while processing your question: {str(e)}"}
386
 
387
  return process_query
388
 
 
401
  self.model_key = model_key
402
 
403
  if file is None:
404
+ return "Please upload a CSV file first."
405
 
406
  try:
407
  print(f"Processing file using model: {self.model_key}")
 
424
  print(f"CSV saved to {user_file_path}")
425
  except Exception as e:
426
  print(f"Error reading CSV: {str(e)}")
427
+ return f"Error reading CSV: {str(e)}"
428
 
429
  # Load document with reduced chunk size for better memory usage
430
  try:
 
465
  return f"Error creating chain: {str(e)}"
466
 
467
  # Add basic file info to chat history for context
468
+ file_info = f"CSV successfully loaded with {df.shape[0]} rows and {len(df.columns)} columns using model {self.model_key}. Columns: {', '.join(df.columns.tolist())}"
469
  self.chat_history.append(("System", file_info))
470
 
471
+ return f"CSV file successfully processed with model {self.model_key}! You can now chat with the model to analyze the data."
472
  except Exception as e:
473
  import traceback
474
  print(traceback.format_exc())
475
+ return f"File processing error: {str(e)}"
476
 
477
  def change_model(self, model_key):
478
  """Change the model being used and recreate the chain if necessary"""
479
  try:
480
  if model_key == self.model_key:
481
+ return f"Model {model_key} is already in use."
482
 
483
  print(f"Changing model from {self.model_key} to {model_key}")
484
  self.model_key = model_key
 
489
  # Load existing database
490
  db_path = f"{self.user_dir}/db_faiss"
491
  if not os.path.exists(db_path):
492
+ return f"Error: Database not found. Please upload the CSV file again."
493
 
494
  print(f"Loading embeddings from {db_path}")
495
  embeddings = HuggingFaceEmbeddings(
 
497
  model_kwargs={'device': 'cpu'}
498
  )
499
 
500
+ # Add allow_dangerous_deserialization=True flag
501
  db = FAISS.load_local(db_path, embeddings, allow_dangerous_deserialization=True)
502
  print(f"FAISS database loaded successfully")
503
 
 
507
  print(f"Chain created successfully")
508
 
509
  # Add notification to chat history
510
+ self.chat_history.append(("System", f"Model successfully changed to {model_key}."))
511
 
512
+ return f"Model successfully changed to {model_key}."
513
  except Exception as e:
514
  import traceback
515
  error_trace = traceback.format_exc()
516
  print(f"Detailed error in change_model: {error_trace}")
517
+ return f"Error changing model: {str(e)}"
518
  else:
519
  # Just update the model key if no file is loaded yet
520
  print(f"No CSV file loaded yet, just updating model preference to {model_key}")
521
+ return f"Model changed to {model_key}. Please upload a CSV file to begin."
522
  except Exception as e:
523
  import traceback
524
  error_trace = traceback.format_exc()
525
  print(f"Unexpected error in change_model: {error_trace}")
526
+ return f"Unexpected error while changing model: {str(e)}"
527
 
528
  def chat(self, message, history):
529
  if self.chain is None:
530
+ return "Please upload a CSV file first."
531
 
532
  try:
533
  # Process the question with the chain
534
  result = self.chain(message, self.chat_history)
535
 
536
  # Get the answer with fallback
537
+ answer = result.get("answer", "Sorry, I couldn't generate an answer. Please try asking a different question.")
538
 
539
  # Ensure we never return empty
540
  if not answer or answer.strip() == "":
541
+ answer = "Sorry, I couldn't generate an appropriate answer. Please try asking the question differently."
542
 
543
  # Update internal chat history
544
  self.chat_history.append((message, answer))
 
567
  with gr.Row():
568
  with gr.Column(scale=1):
569
  with gr.Group():
570
+ gr.Markdown("### Step 1: Choose AI Model")
571
  model_dropdown = gr.Dropdown(
572
  label="Model",
573
  choices=model_choices,
 
579
  )
580
 
581
  with gr.Group():
582
+ gr.Markdown("### Step 2: Upload and Process CSV")
583
  file_input = gr.File(
584
  label="Upload CSV Anda",
585
  file_types=[".csv"]
586
  )
587
+ process_button = gr.Button("Process CSV")
588
 
589
+ reset_button = gr.Button("Reset Session (To Change Model)")
590
 
591
  with gr.Column(scale=2):
592
  chatbot_interface = gr.Chatbot(
593
+ label="Chat History",
594
  # type="messages",
595
  height=400
596
  )
597
  message_input = gr.Textbox(
598
+ label="Type your message",
599
+ placeholder="Ask questions about your CSV data...",
600
  lines=2
601
  )
602
+ submit_button = gr.Button("Send")
603
+ clear_button = gr.Button("Clear Chat")
604
 
605
  # Update model info when selection changes
606
  def update_model_info(model_key):
 
615
  # Process file handler - disables model selection after file is processed
616
  def handle_process_file(file, model_key, sess_id):
617
  if file is None:
618
+ return None, None, False, "Please upload a CSV file first."
619
 
620
  try:
621
  chatbot = ChatBot(sess_id, model_key)
 
625
  import traceback
626
  print(f"Error processing file with {model_key}: {str(e)}")
627
  print(traceback.format_exc())
628
+ error_msg = f"Error with model {model_key}: {str(e)}\n\nPlease try another model."
629
+ return None, False, [(None, error_msg)]
630
 
631
  process_button.click(
632
  fn=handle_process_file,
 
657
  def bot_response(history, chatbot, sess_id):
658
  if chatbot is None:
659
  chatbot = ChatBot(sess_id)
660
+ history[-1] = (history[-1][0], "Please upload a CSV file first.")
661
  return chatbot, history
662
 
663
  user_message = history[-1][0]