lordzukoiroh commited on
Commit
a906618
·
verified ·
1 Parent(s): 76a4be5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +187 -174
app.py CHANGED
@@ -3,9 +3,9 @@ import gradio as gr
3
  import faiss
4
  import numpy as np
5
  from sentence_transformers import SentenceTransformer
6
- from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments
7
- from peft import get_peft_model, LoraConfig, TaskType, PeftModel
8
- from datasets import Dataset
9
  import json
10
  import os
11
  from typing import List, Tuple
@@ -13,9 +13,9 @@ from functools import partial
13
  import random
14
  from datetime import datetime
15
  from collections import deque
16
- import requests
17
- from huggingface_hub import hf_hub_download
18
- import tempfile
19
 
20
  # === CSS ve Emoji Fonksiyonu ===
21
  current_css = """
@@ -71,11 +71,10 @@ def add_emojis(text: str) -> str:
71
  }
72
 
73
  found_emojis = []
74
- words = text.split()
75
  for word in words:
76
- clean_word = word.lower().strip(".,!?")
77
- if clean_word in emoji_mapping:
78
- found_emojis.append(emoji_mapping[clean_word])
79
 
80
  unique_emojis = list(set(found_emojis))
81
  if unique_emojis:
@@ -85,31 +84,84 @@ def add_emojis(text: str) -> str:
85
  # === SABİTLER ===
86
  EMBEDDER_NAME = "paraphrase-multilingual-MiniLM-L12-v2"
87
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
88
- FEEDBACK_FILE = "chatbot_feedback.jsonl"
89
- QA_PATH = "qa_dataset.jsonl"
90
- # Değişkenler global scope'da tanımlı
91
- BASE_MODEL = "ytu-ce-cosmos/turkish-gpt2-large"
92
- MODEL_PATH = "lordzukoiroh/montaggppt2lora"
93
- DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
94
 
95
- def load_model_and_tokenizer():
96
- tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
97
- base_model = AutoModelForCausalLM.from_pretrained(BASE_MODEL).to(DEVICE)
98
- model = PeftModel.from_pretrained(base_model, MODEL_PATH).to(DEVICE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  if tokenizer.pad_token is None:
101
  tokenizer.pad_token = tokenizer.eos_token
 
 
 
 
102
 
103
- model.eval()
104
- return model, tokenizer
 
 
 
 
 
 
 
 
105
 
106
- model, tokenizer = load_model_and_tokenizer()
 
 
 
 
 
 
107
 
 
 
 
 
 
 
108
 
109
- #
 
 
 
 
 
 
110
 
 
 
 
 
 
111
 
112
  # === Fahrenheit 451 Metni (Örnek paragraflar) ===
 
113
  FAHRENHEIT_451_TEXT = """
114
  Montag mutlu değildi. Fark etmesi ancak kapısının önüne kadar gelmesi ile oldu. Gece yarısı vakti, sokağın sonunda çevresine yayılan karanlık içinde duran ev, rüzgarın yaprak hışırtısı ve soluğunu görebilir kılacak kadar soğuk havada ev ona garip bir görüntü sunuyordu.
115
 
@@ -145,8 +197,6 @@ Montag şaşkınlıkla kızın yüzüne baktı. Karanlık gecenin ortasında, so
145
 
146
  "Niye yaptı bunu?" diye sordu Montag. "Bilmiyoruz," dedi teknisyen. "Belki çok mutsuzdu. Belki de kazayla oldu. Çok yaygın bir durum." "Yaygın mı?" "Evet. İnsanlar çok bunalıyor. Televizyonda her şey çok hızlı. Radyoda çok gürültü. Herkes koşuyor. Kimse durmuyor."
147
 
148
- Montag teknisyenlerin çalışmasını izledi. Karısının yüzü çok solgundu. Sanki ölmüş gibiydi. Ama nefes alıyordu. Teknisyenler işlerini bitirdiler. "Sabaha kadar uyuyacak," dediler. "Yarın normal olacak. Hiçbir şey hatırlamayacak."
149
-
150
  Montag yalnız kaldı. Karısına baktı. Çok solgun görünüyordu. Montag Clarisse'i düşündü. "Mutlu musun?" sorusu kafasına takıldı. Gerçekten mutlu muydu? Karısı niye böyle bir şey yapmıştı?
151
 
152
  Montag pencereye gitti. Dışarı baktı. Sokak çok sessizdi. Kimse yoktu. Sadece sokak lambaları yanıyordu. Montag Clarisse'in yüzünü düşündü. Kızın gözleri çok parlaktı. Çok canlıydı. Sanki her şeyi görüyordu.
@@ -198,6 +248,7 @@ Ve o gece, Montag çok önemli bir karar verdi. Bir kitap okumaya karar verdi. Y
198
 
199
  # === DOSYA KAYIT ===
200
  def save_feedback(user_question: str, answer: str, liked: bool, filepath: str = FEEDBACK_FILE):
 
201
  feedback_entry = {
202
  "input": user_question,
203
  "output": answer,
@@ -225,8 +276,12 @@ class RLAgent:
225
  def record_experience(self, user_question: str, generated_answer: str, liked: bool):
226
  try:
227
  combined_text = f"Soru: {user_question} Cevap: {generated_answer}"
 
 
 
 
228
  embedding = embedder.encode([combined_text], convert_to_tensor=True).squeeze(0).to(self.device)
229
- reward = 1.0 if liked else -1.5
230
  self.experience_buffer.append((embedding, torch.tensor([reward], dtype=torch.float32).to(self.device)))
231
  self.train_reward_model()
232
  except Exception as e:
@@ -251,15 +306,18 @@ class RLAgent:
251
 
252
  avg_reward = rewards.mean().item() if rewards.numel() > 0 else 0
253
 
 
254
  if avg_reward > 0.5:
255
- self.current_temp = max(1.0, self.current_temp - self.learning_rate_reward * 0.1)
256
- self.current_rep_penalty = max(1.1, self.current_rep_penalty - self.learning_rate_reward * 0.05)
257
  elif avg_reward < -0.5:
258
- self.current_temp = min(1.5, self.current_temp + self.learning_rate_reward * 0.2)
259
- self.current_rep_penalty = min(2.0, self.current_rep_penalty + self.learning_rate_reward * 0.1)
260
 
261
  self.current_temp = float(np.clip(self.current_temp, 0.8, 1.8))
262
- self.current_rep_penalty = float(np.clip(self.current_rep_penalty, 1.1, 2.5))
 
 
263
  except Exception as e:
264
  print(f"Error training reward model: {e}")
265
 
@@ -269,51 +327,20 @@ class RLAgent:
269
  "repetition_penalty": self.current_rep_penalty
270
  }
271
 
272
- # === GLOBAL DEĞİŞKENLER ===
273
- model = None
274
- tokenizer = None
275
- embedder = None
276
- paragraphs = []
277
- index = None
278
- rl_agent = None
279
-
280
- def initialize_components():
281
- global model, tokenizer, embedder, paragraphs, index, rl_agent
282
-
283
  try:
284
- print("Model yükleniyor...")
285
- # Basit bir model kullanıyoruz - HF Space'te büyük modeller yüklenemeyebilir
286
- tokenizer = AutoTokenizer.from_pretrained("distilgpt2")
287
- model = AutoModelForCausalLM.from_pretrained("distilgpt2").to(DEVICE)
288
-
289
- if tokenizer.pad_token is None:
290
- tokenizer.pad_token = tokenizer.eos_token
291
 
292
- model.eval()
293
- print("Model yüklendi.")
294
-
295
- print("Embedder yükleniyor...")
296
- embedder = SentenceTransformer(EMBEDDER_NAME)
297
- print("Embedder yüklendi.")
298
-
299
- print("Metin işleniyor...")
300
- paragraphs = [p.strip() for p in FAHRENHEIT_451_TEXT.split("\n") if len(p.strip()) > 50]
301
-
302
- print("Embeddingler hesaplanıyor...")
303
- paragraph_embeddings = embedder.encode(paragraphs, convert_to_numpy=True)
304
- index = faiss.IndexFlatL2(paragraph_embeddings.shape[1])
305
- index.add(paragraph_embeddings)
306
- print("Embeddingler hazır.")
307
-
308
- print("RL Agent başlatılıyor...")
309
- rl_agent = RLAgent(embedder.get_sentence_embedding_dimension())
310
- print("RL Agent hazır.")
311
-
312
- print("Tüm bileşenler başarıyla yüklendi!")
313
- return True
314
  except Exception as e:
315
- print(f"Bileşenler yüklenirken hata: {e}")
316
- return False
317
 
318
  # === MONTAG PERSONA ===
319
  MONTAG_PERSONA = (
@@ -323,11 +350,10 @@ MONTAG_PERSONA = (
323
  f"- Derin, felsefi ve düşündürücü cevaplar verirsin.\n"
324
  f"- Kitapların değerini çok iyi bilirsin ve onlara karşı gizli bir sevgi beslersin.\n"
325
  f"- Toplumun baskıcı yapısına karşı içten içe isyankarsın ve arayış içindesin.\n"
326
- f"- Cevapların 2-5 cümle arasında, net, özgün ve akıcıdır. Bazen alaycı, sorgulayıcı veya melankolik bir ton kullanırsın.\n"
327
  f"- Asla kendini tekrarlamazsın ve genel geçer ifadelerden kaçınırsın.\n"
328
  )
329
 
330
-
331
  # === ALTERNATİF CEVAPLAR ===
332
  alternative_responses = [
333
  "Bu soru bana Clarisse'i hatırlattı... Onun da sorgulayan bir ruhu vardı, tıpkı şimdi senin sorduğun gibi.",
@@ -346,30 +372,36 @@ alternative_responses = [
346
  "Bir itfaiyeci olarak benim görevim yakmak... Ama bazen bir soru, yaktığım bin kitaptan daha çok aydınlatır.",
347
  "Clarisse'i ilk gördüğümde bana sorduğu o soruyu hatırladım. Senin bu sorun da o kadar masum ama yıkıcı."
348
  ]
 
349
  def generate_alternative_response(user_question: str) -> str:
350
  """Modelin cevap üretemediği veya yetersiz cevap verdiği durumlarda alternatif bir yanıt döndürür."""
351
- # Montag persona'sına uygun, rastgele bir alternatif cevap seçelim
352
- # user_question'ı doğrudan kullanmasak da, signature'da tutmak uygun olabilir
353
-
354
  response = random.choice(alternative_responses)
355
-
356
- # Cevaba emoji ekleme mantığını da burada kullanabiliriz
357
  final_response = add_emojis(response)
358
  return final_response
359
 
360
  def generate_answer(question: str, chatbot_history: List[List[str]]) -> str:
 
 
 
 
361
  try:
362
  gen_params = rl_agent.get_generation_params()
363
- context = retrieve_context(question)
364
 
365
  history_text = ""
366
  if chatbot_history:
 
367
  recent_dialogue = []
368
- for user_msg, assistant_msg in chatbot_history[-3:]:
369
  if user_msg:
370
- recent_dialogue.append(f"Kullanıcı: {user_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()}")
 
 
 
371
  if assistant_msg:
372
- recent_dialogue.append(f"Montag: {assistant_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()}")
 
 
373
  history_text = "\n".join(recent_dialogue) + "\n"
374
 
375
  prompt = (
@@ -377,75 +409,71 @@ def generate_answer(question: str, chatbot_history: List[List[str]]) -> str:
377
  f"Bağlam:\n{context}\n\n"
378
  f"Önceki Sohbet:\n{history_text}\n"
379
  f"Soru: {question}\n"
380
- f"Montag (tipik tarzında cevap verir):"
381
  )
382
 
383
  inputs = tokenizer.encode(prompt, return_tensors="pt", truncation=True, max_length=512).to(DEVICE)
384
 
385
  outputs = model.generate(
386
  inputs,
387
- max_new_tokens=150,
388
  do_sample=True,
389
  top_p=0.9,
390
  temperature=gen_params["temperature"],
391
  repetition_penalty=gen_params["repetition_penalty"],
392
- no_repeat_ngram_size=3,
393
  num_beams=1,
394
  pad_token_id=tokenizer.eos_token_id,
395
  eos_token_id=tokenizer.eos_token_id,
396
- early_stopping=True
397
  )
398
-
399
  response = tokenizer.decode(outputs[0], skip_special_tokens=True)
400
 
401
- if "Montag (tipik tarzında cevap verir):" in response:
402
- response = response.split("Montag (tipik tarzında cevap verir):")[-1].strip()
 
403
  else:
404
- prompt_start = " ".join(prompt.split()[:10])
405
- if response.startswith(prompt_start):
406
- response = response[len(prompt):].strip()
407
-
408
- generic_phrases = [
409
- "bilmiyorum", "emin değilim", "cevap veremem", "daha fazla bilgiye ihtiyacım var",
410
- "ne düşünmem gerektiğini bilmiyorum", "anlamadım", "tekrar eder misin",
411
- "evet", "hayır", "hmm", "sanırım", "bunu hiç düşünmemiştim", "düşünmem gerekiyor"
412
- ]
413
-
414
- is_generic_or_too_short = (
415
- any(phrase in response.lower() for phrase in generic_phrases) or
416
- len(response.split()) < 10 or
417
- response.count('.') + response.count('?') + response.count('!') < 1 or
418
- (response.count('.') + response.count('?') + response.count('!') == 1 and len(response.split()) < 15)
419
- )
420
 
421
- if is_generic_or_too_short:
422
- print("INFO: Generic/Too short response detected, generating alternative.")
423
- return generate_alternative_response(question)
424
 
 
425
  sentences = []
426
  current_sentence_parts = []
427
  sentence_count = 0
428
-
 
429
  for char in response:
430
  current_sentence_parts.append(char)
431
  if char in ['.', '!', '?']:
432
  sentence = "".join(current_sentence_parts).strip()
433
- if sentence:
434
  sentences.append(sentence)
435
  sentence_count += 1
436
  current_sentence_parts = []
437
- if sentence_count >= 5:
438
  break
439
-
 
440
  if current_sentence_parts and "".join(current_sentence_parts).strip():
441
  final_part = "".join(current_sentence_parts).strip()
442
- if final_part and (sentence_count < 5 or (sentence_count == 5 and len(sentences) < 5)):
443
- sentences.append(final_part)
 
 
 
444
 
445
  response_cleaned = ' '.join(sentences).strip()
446
 
447
- if not response_cleaned or len(response_cleaned.split()) < 5:
448
- print("INFO: Post-processing resulted in too short response, generating alternative.")
 
 
449
  return generate_alternative_response(question)
450
 
451
  final_response = add_emojis(response_cleaned)
@@ -454,12 +482,6 @@ def generate_answer(question: str, chatbot_history: List[List[str]]) -> str:
454
  except Exception as e:
455
  print(f"Error generating answer: {e}")
456
  return generate_alternative_response(question)
457
-
458
- def retrieve_context(question: str, top_k=3) -> str:
459
- question_embedding = embedder.encode([question], convert_to_numpy=True)
460
- D, I = index.search(question_embedding, top_k)
461
- retrieved_paragraphs = [paragraphs[i] for i in I[0] if i < len(paragraphs)]
462
- return "\n".join(retrieved_paragraphs)
463
 
464
 
465
  # === Gradio callback fonksiyonları ===
@@ -467,83 +489,66 @@ def respond(msg: str, chatbot_history: List[List[str]]) -> Tuple[str, List[List[
467
  if not msg.strip():
468
  return "", chatbot_history
469
 
470
- # Tokenize et
471
- input_ids = tokenizer.encode(msg, return_tensors="pt").to(DEVICE)
472
-
473
- # Modelden cevap üret
474
- output = model.generate(
475
- input_ids=input_ids,
476
- max_new_tokens=150,
477
- temperature=0.8,
478
- top_p=0.95,
479
- top_k=50,
480
- do_sample=True,
481
- repetition_penalty=1.1,
482
- pad_token_id=tokenizer.eos_token_id,
483
- eos_token_id=tokenizer.eos_token_id
484
- )
485
 
486
- # Tokenleri stringe çevir
487
- answer = tokenizer.decode(output[0], skip_special_tokens=True)
488
-
489
- # Sohbet geçmişine ekle
490
- chatbot_history.append([msg, answer])
491
 
492
  return "", chatbot_history
493
 
494
-
495
  def regenerate_answer(chatbot_history: List[List[str]]) -> Tuple[str, List[List[str]]]:
 
496
  if not chatbot_history:
497
  return "", []
498
 
499
- last_user_question = chatbot_history[-1][0]
500
 
501
  if last_user_question:
502
- chatbot_history.pop()
 
503
 
504
- new_answer = generate_answer(last_user_question, chatbot_history)
505
- chatbot_history.append([last_user_question, new_answer])
506
  return "", chatbot_history
507
  return "", chatbot_history
508
 
509
 
510
- def feedback_callback(chatbot_history: List[List[str]], liked: bool) -> None:
511
  if not chatbot_history:
512
- return
513
-
514
- last_pair = chatbot_history[-1]
515
- if len(last_pair) != 2:
516
- return
517
-
518
- user_question, answer = last_pair
519
- if user_question and answer:
520
- save_feedback(user_question, answer, liked)
521
- rl_agent.record_experience(user_question, answer, liked)
522
-
523
 
524
  last_user_question = chatbot_history[-1][0]
525
  last_assistant_answer = chatbot_history[-1][1]
526
 
527
  if last_user_question and last_assistant_answer:
528
- save_feedback(last_user_question, last_assistant_answer, liked, FEEDBACK_FILE)
 
 
 
 
 
529
 
530
  rl_agent.record_experience(
531
- last_user_question.replace('📚', '').replace('🧠', '').replace('🔥', '').strip(),
532
- last_assistant_answer.replace('📚', '').replace('🧠', '').replace('🔥', '').strip(),
533
  liked
534
  )
535
 
536
  if liked:
537
- qa_pair = {"question": last_user_question, "answer": last_assistant_answer, "liked": True}
 
 
538
  with open(QA_PATH, "a", encoding="utf-8") as f:
539
  f.write(json.dumps(qa_pair, ensure_ascii=False) + "\n")
540
-
541
- return "Geri bildiriminiz kaydedildi. Teşekkürler!"
 
542
  return "Geri bildirim kaydedilemedi. Geçmişte yeterli sohbet bulunmuyor."
543
 
544
  # === Gradio arayüzü == #
545
- current_css = """#chatbot { overflow-y: auto; }"""
546
-
547
  def create_chat_interface():
548
  with gr.Blocks(theme=gr.themes.Soft(), css=current_css) as demo:
549
  gr.Markdown("""
@@ -560,17 +565,25 @@ def create_chat_interface():
560
 
561
  with gr.Row():
562
  like_btn = gr.Button("👍 Beğendim")
563
- dislike_btn = gr.Button("👎 Beğenmedim (Alternatif Cevap)")
564
 
565
  feedback_status_output = gr.Textbox(label="Geri Bildirim Durumu", interactive=False, max_lines=1)
566
 
567
  msg.submit(respond, [msg, chatbot], [msg, chatbot])
568
  submit_btn.click(respond, [msg, chatbot], [msg, chatbot])
569
- clear_btn.click(lambda: [], None, chatbot, queue=False)
570
 
 
571
  like_btn.click(partial(feedback_callback, liked=True), [chatbot], [feedback_status_output])
 
 
572
  dislike_btn.click(partial(feedback_callback, liked=False), [chatbot], [feedback_status_output])
573
- dislike_btn.click(regenerate_answer, [chatbot], [msg, chatbot], queue=False)
 
 
 
 
 
574
  return demo
575
 
576
 
@@ -580,4 +593,4 @@ if __name__ == "__main__":
580
  demo = create_chat_interface()
581
  demo.launch()
582
  else:
583
- print("Uygulama başlatılamadı: Bileşenler yüklenirken hata oluştu.")
 
3
  import faiss
4
  import numpy as np
5
  from sentence_transformers import SentenceTransformer
6
+ from transformers import AutoTokenizer, AutoModelForCausalLM # TrainingArguments, LoraConfig, TaskType removed as not directly used for inference
7
+ from peft import PeftModel # get_peft_model removed as not directly used for inference
8
+ from datasets import Dataset # Not directly used in the provided snippet but kept for completeness
9
  import json
10
  import os
11
  from typing import List, Tuple
 
13
  import random
14
  from datetime import datetime
15
  from collections import deque
16
+ # import requests # Not directly used but kept for completeness
17
+ # from huggingface_hub import hf_hub_download # Not directly used but kept for completeness
18
+ # import tempfile # Not directly used but kept for completeness
19
 
20
  # === CSS ve Emoji Fonksiyonu ===
21
  current_css = """
 
71
  }
72
 
73
  found_emojis = []
74
+ words = text.lower().replace('.', '').replace(',', '').replace('!', '').replace('?', '').split()
75
  for word in words:
76
+ if word in emoji_mapping:
77
+ found_emojis.append(emoji_mapping[word])
 
78
 
79
  unique_emojis = list(set(found_emojis))
80
  if unique_emojis:
 
84
  # === SABİTLER ===
85
  EMBEDDER_NAME = "paraphrase-multilingual-MiniLM-L12-v2"
86
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
87
+ FEEDBACK_FILE = "data/chatbot_feedback.jsonl" # Changed to a data directory
88
+ QA_PATH = "data/qa_dataset.jsonl" # Changed to a data directory
89
+
90
+ # Your original base model and fine-tuned model path (for reference)
91
+ # BASE_MODEL = "ytu-ce-cosmos/turkish-gpt2-large"
92
+ # MODEL_PATH = "lordzukoiroh/montaggppt2lora"
93
 
94
+ # Using a smaller model for broader compatibility (e.g., Hugging Face Spaces free tier)
95
+ BASE_MODEL_FOR_DEMO = "distilgpt2"
96
+
97
+ # === GLOBAL DEĞİŞKENLER ===
98
+ model = None
99
+ tokenizer = None
100
+ embedder = None
101
+ paragraphs = []
102
+ paragraph_embeddings = None # Store embeddings to avoid recomputing
103
+ index = None
104
+ rl_agent = None
105
+
106
+ def load_model_and_tokenizer_func(base_model_name: str):
107
+ """Loads the base model and tokenizer."""
108
+ print(f"Model yükleniyor: {base_model_name}...")
109
+ tokenizer = AutoTokenizer.from_pretrained(base_model_name)
110
+ base_model = AutoModelForCausalLM.from_pretrained(base_model_name).to(DEVICE)
111
+
112
+ # If you were loading a fine-tuned LoRA model, you would uncomment and adjust this:
113
+ # model = PeftModel.from_pretrained(base_model, MODEL_PATH).to(DEVICE)
114
+ model_to_return = base_model # For this demo, we use the base model
115
 
116
  if tokenizer.pad_token is None:
117
  tokenizer.pad_token = tokenizer.eos_token
118
+
119
+ model_to_return.eval()
120
+ print(f"Model {base_model_name} yüklendi.")
121
+ return model_to_return, tokenizer
122
 
123
+ def initialize_components():
124
+ global model, tokenizer, embedder, paragraphs, paragraph_embeddings, index, rl_agent
125
+
126
+ try:
127
+ print("Model ve tokenizer yükleniyor...")
128
+ model, tokenizer = load_model_and_tokenizer_func(BASE_MODEL_FOR_DEMO)
129
+
130
+ print("Embedder yükleniyor...")
131
+ embedder = SentenceTransformer(EMBEDDER_NAME)
132
+ print("Embedder yüklendi.")
133
 
134
+ print("Metin işleniyor...")
135
+ # Clean and filter paragraphs, ensure they are substantial enough for embedding
136
+ paragraphs = [p.strip() for p in FAHRENHEIT_451_TEXT.split("\n") if len(p.strip().split()) > 10]
137
+
138
+ print(f"Toplam {len(paragraphs)} paragraf işlendi.")
139
+ if not paragraphs:
140
+ raise ValueError("No valid paragraphs extracted from the text.")
141
 
142
+ print("Embeddingler hesaplanıyor ve FAISS indeksi oluşturuluyor...")
143
+ paragraph_embeddings = embedder.encode(paragraphs, convert_to_numpy=True)
144
+
145
+ # Ensure embeddings are float32 for FAISS
146
+ if paragraph_embeddings.dtype != np.float32:
147
+ paragraph_embeddings = paragraph_embeddings.astype(np.float32)
148
 
149
+ index = faiss.IndexFlatL2(paragraph_embeddings.shape[1])
150
+ index.add(paragraph_embeddings)
151
+ print("Embeddingler ve FAISS indeksi hazır.")
152
+
153
+ print("RL Agent başlatılıyor...")
154
+ rl_agent = RLAgent(embedder.get_sentence_embedding_dimension())
155
+ print("RL Agent hazır.")
156
 
157
+ print("Tüm bileşenler başarıyla yüklendi!")
158
+ return True
159
+ except Exception as e:
160
+ print(f"Bileşenler yüklenirken hata: {e}")
161
+ return False
162
 
163
  # === Fahrenheit 451 Metni (Örnek paragraflar) ===
164
+ # Moved up for clarity and scope if needed elsewhere, though it's already a global string.
165
  FAHRENHEIT_451_TEXT = """
166
  Montag mutlu değildi. Fark etmesi ancak kapısının önüne kadar gelmesi ile oldu. Gece yarısı vakti, sokağın sonunda çevresine yayılan karanlık içinde duran ev, rüzgarın yaprak hışırtısı ve soluğunu görebilir kılacak kadar soğuk havada ev ona garip bir görüntü sunuyordu.
167
 
 
197
 
198
  "Niye yaptı bunu?" diye sordu Montag. "Bilmiyoruz," dedi teknisyen. "Belki çok mutsuzdu. Belki de kazayla oldu. Çok yaygın bir durum." "Yaygın mı?" "Evet. İnsanlar çok bunalıyor. Televizyonda her şey çok hızlı. Radyoda çok gürültü. Herkes koşuyor. Kimse durmuyor."
199
 
 
 
200
  Montag yalnız kaldı. Karısına baktı. Çok solgun görünüyordu. Montag Clarisse'i düşündü. "Mutlu musun?" sorusu kafasına takıldı. Gerçekten mutlu muydu? Karısı niye böyle bir şey yapmıştı?
201
 
202
  Montag pencereye gitti. Dışarı baktı. Sokak çok sessizdi. Kimse yoktu. Sadece sokak lambaları yanıyordu. Montag Clarisse'in yüzünü düşündü. Kızın gözleri çok parlaktı. Çok canlıydı. Sanki her şeyi görüyordu.
 
248
 
249
  # === DOSYA KAYIT ===
250
  def save_feedback(user_question: str, answer: str, liked: bool, filepath: str = FEEDBACK_FILE):
251
+ os.makedirs(os.path.dirname(filepath), exist_ok=True) # Ensure directory exists
252
  feedback_entry = {
253
  "input": user_question,
254
  "output": answer,
 
276
  def record_experience(self, user_question: str, generated_answer: str, liked: bool):
277
  try:
278
  combined_text = f"Soru: {user_question} Cevap: {generated_answer}"
279
+ # Ensure embedder is available
280
+ if embedder is None:
281
+ print("Embedder not initialized, cannot record experience.")
282
+ return
283
  embedding = embedder.encode([combined_text], convert_to_tensor=True).squeeze(0).to(self.device)
284
+ reward = 1.0 if liked else -1.5 # Adjusted negative reward for stronger signal
285
  self.experience_buffer.append((embedding, torch.tensor([reward], dtype=torch.float32).to(self.device)))
286
  self.train_reward_model()
287
  except Exception as e:
 
306
 
307
  avg_reward = rewards.mean().item() if rewards.numel() > 0 else 0
308
 
309
+ # Adjust parameters more subtly
310
  if avg_reward > 0.5:
311
+ self.current_temp = max(0.9, self.current_temp - self.learning_rate_reward * 0.05) # Lower temp for more deterministic good answers
312
+ self.current_rep_penalty = max(1.0, self.current_rep_penalty - self.learning_rate_reward * 0.02)
313
  elif avg_reward < -0.5:
314
+ self.current_temp = min(1.5, self.current_temp + self.learning_rate_reward * 0.05) # Higher temp for more exploration if bad
315
+ self.current_rep_penalty = min(2.0, self.current_rep_penalty + self.learning_rate_reward * 0.02)
316
 
317
  self.current_temp = float(np.clip(self.current_temp, 0.8, 1.8))
318
+ self.current_rep_penalty = float(np.clip(self.current_rep_penalty, 1.0, 2.5)) # Allow lower rep penalty
319
+ # print(f"INFO: Updated generation params - Temp: {self.current_temp:.2f}, Rep_Penalty: {self.current_rep_penalty:.2f}")
320
+
321
  except Exception as e:
322
  print(f"Error training reward model: {e}")
323
 
 
327
  "repetition_penalty": self.current_rep_penalty
328
  }
329
 
330
+ def retrieve_context(query: str, k: int = 2) -> str:
331
+ """FAISS indeksini kullanarak sorguya en uygun paragrafları getirir."""
332
+ if index is None or embedder is None or not paragraphs:
333
+ print("WARNING: FAISS index, embedder or paragraphs not initialized for context retrieval.")
334
+ return "Bağlam bulunamadı."
 
 
 
 
 
 
335
  try:
336
+ query_embedding = embedder.encode([query], convert_to_numpy=True).astype(np.float32)
337
+ D, I = index.search(query_embedding, k) # D: distances, I: indices
 
 
 
 
 
338
 
339
+ retrieved_texts = [paragraphs[i] for i in I[0] if i < len(paragraphs)]
340
+ return "\n".join(retrieved_texts)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  except Exception as e:
342
+ print(f"Bağlam alınırken hata: {e}")
343
+ return "Bağlam alınırken bir sorun oluştu."
344
 
345
  # === MONTAG PERSONA ===
346
  MONTAG_PERSONA = (
 
350
  f"- Derin, felsefi ve düşündürücü cevaplar verirsin.\n"
351
  f"- Kitapların değerini çok iyi bilirsin ve onlara karşı gizli bir sevgi beslersin.\n"
352
  f"- Toplumun baskıcı yapısına karşı içten içe isyankarsın ve arayış içindesin.\n"
353
+ f"- Cevapların **2 ila 5 cümle** arasında, net, özgün ve akıcıdır. Bazen alaycı, sorgulayıcı veya melankolik bir ton kullanırsın.\n"
354
  f"- Asla kendini tekrarlamazsın ve genel geçer ifadelerden kaçınırsın.\n"
355
  )
356
 
 
357
  # === ALTERNATİF CEVAPLAR ===
358
  alternative_responses = [
359
  "Bu soru bana Clarisse'i hatırlattı... Onun da sorgulayan bir ruhu vardı, tıpkı şimdi senin sorduğun gibi.",
 
372
  "Bir itfaiyeci olarak benim görevim yakmak... Ama bazen bir soru, yaktığım bin kitaptan daha çok aydınlatır.",
373
  "Clarisse'i ilk gördüğümde bana sorduğu o soruyu hatırladım. Senin bu sorun da o kadar masum ama yıkıcı."
374
  ]
375
+
376
  def generate_alternative_response(user_question: str) -> str:
377
  """Modelin cevap üretemediği veya yetersiz cevap verdiği durumlarda alternatif bir yanıt döndürür."""
 
 
 
378
  response = random.choice(alternative_responses)
 
 
379
  final_response = add_emojis(response)
380
  return final_response
381
 
382
  def generate_answer(question: str, chatbot_history: List[List[str]]) -> str:
383
+ if model is None or tokenizer is None or rl_agent is None:
384
+ print("ERROR: Model, tokenizer veya RL Agent başlatılmamış.")
385
+ return generate_alternative_response(question)
386
+
387
  try:
388
  gen_params = rl_agent.get_generation_params()
389
+ context = retrieve_context(question) # Call the correct retrieve_context
390
 
391
  history_text = ""
392
  if chatbot_history:
393
+ # Get last 3 turns to maintain short-term context
394
  recent_dialogue = []
395
+ for user_msg, assistant_msg in chatbot_history[-3:]:
396
  if user_msg:
397
+ # Clean emojis from history before passing to model
398
+ cleaned_user_msg = user_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()
399
+ if cleaned_user_msg:
400
+ recent_dialogue.append(f"Kullanıcı: {cleaned_user_msg}")
401
  if assistant_msg:
402
+ cleaned_assistant_msg = assistant_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()
403
+ if cleaned_assistant_msg:
404
+ recent_dialogue.append(f"Montag: {cleaned_assistant_msg}")
405
  history_text = "\n".join(recent_dialogue) + "\n"
406
 
407
  prompt = (
 
409
  f"Bağlam:\n{context}\n\n"
410
  f"Önceki Sohbet:\n{history_text}\n"
411
  f"Soru: {question}\n"
412
+ f"Montag (cevap 2 ila 5 cümle arasında olmalı):" # Explicitly tell model the sentence limit
413
  )
414
 
415
  inputs = tokenizer.encode(prompt, return_tensors="pt", truncation=True, max_length=512).to(DEVICE)
416
 
417
  outputs = model.generate(
418
  inputs,
419
+ max_new_tokens=100, # Increased max_new_tokens to allow for 2-5 sentences
420
  do_sample=True,
421
  top_p=0.9,
422
  temperature=gen_params["temperature"],
423
  repetition_penalty=gen_params["repetition_penalty"],
424
+ no_repeat_ngram_size=3, # Helps prevent direct repetition of phrases
425
  num_beams=1,
426
  pad_token_id=tokenizer.eos_token_id,
427
  eos_token_id=tokenizer.eos_token_id,
428
+ early_stopping=True # Stop when EOS token is generated
429
  )
430
+
431
  response = tokenizer.decode(outputs[0], skip_special_tokens=True)
432
 
433
+ # Post-processing to extract Montag's specific response and trim prefix
434
+ if "Montag (cevap 2 ila 5 cümle arasında olmalı):" in response:
435
+ response = response.split("Montag (cevap 2 ila 5 cümle arasında olmalı):")[-1].strip()
436
  else:
437
+ # Fallback if the marker isn't found (e.g., model truncated or ignored)
438
+ # Try to remove the prompt part from the beginning of the response
439
+ prompt_end_marker_len = len(f"Soru: {question}\nMontag (cevap 2 ila 5 cümle arasında olmalı):")
440
+ # This check is a bit tricky, but aims to remove the input prompt if it's echoed
441
+ if len(response) > len(prompt) and response.startswith(prompt[:-min(prompt_end_marker_len, len(prompt))]):
442
+ response = response[len(prompt):].strip()
 
 
 
 
 
 
 
 
 
 
443
 
 
 
 
444
 
445
+ # Ensure response adheres to 2-5 sentence structure and isn't too generic
446
  sentences = []
447
  current_sentence_parts = []
448
  sentence_count = 0
449
+
450
+ # Simple sentence tokenization based on punctuation
451
  for char in response:
452
  current_sentence_parts.append(char)
453
  if char in ['.', '!', '?']:
454
  sentence = "".join(current_sentence_parts).strip()
455
+ if sentence: # Add sentence if not empty
456
  sentences.append(sentence)
457
  sentence_count += 1
458
  current_sentence_parts = []
459
+ if sentence_count >= 5: # Stop after 5 sentences
460
  break
461
+
462
+ # Add any remaining part as a sentence if it's not just whitespace
463
  if current_sentence_parts and "".join(current_sentence_parts).strip():
464
  final_part = "".join(current_sentence_parts).strip()
465
+ if final_part and sentence_count < 5: # Only add if we haven't reached 5 sentences yet
466
+ sentences.append(final_part)
467
+ elif final_part and sentence_count == 5 and len(sentences) < 5: # Edge case: ensure 5th sentence is added if still building
468
+ sentences.append(final_part)
469
+
470
 
471
  response_cleaned = ' '.join(sentences).strip()
472
 
473
+ # Final check for quality and length
474
+ # A good Montag response should be at least 15 words and have at least 2 sentences
475
+ if not response_cleaned or len(response_cleaned.split()) < 15 or sentence_count < 2:
476
+ print("INFO: Generated response is too short or malformed after processing. Generating alternative.")
477
  return generate_alternative_response(question)
478
 
479
  final_response = add_emojis(response_cleaned)
 
482
  except Exception as e:
483
  print(f"Error generating answer: {e}")
484
  return generate_alternative_response(question)
 
 
 
 
 
 
485
 
486
 
487
  # === Gradio callback fonksiyonları ===
 
489
  if not msg.strip():
490
  return "", chatbot_history
491
 
492
+ # This now correctly calls generate_answer, which handles RAG and RL parameters
493
+ answer = generate_answer(msg, chatbot_history)
 
 
 
 
 
 
 
 
 
 
 
 
 
494
 
495
+ # Ensure no duplicate responses are appended (if user clicks send multiple times)
496
+ if not chatbot_history or chatbot_history[-1] != [msg, answer]:
497
+ chatbot_history.append([msg, answer])
 
 
498
 
499
  return "", chatbot_history
500
 
 
501
  def regenerate_answer(chatbot_history: List[List[str]]) -> Tuple[str, List[List[str]]]:
502
+ """Generates a new answer for the last user question using the main generation logic."""
503
  if not chatbot_history:
504
  return "", []
505
 
506
+ last_user_question = chatbot_history[-1][0] # Get the last user question
507
 
508
  if last_user_question:
509
+ # Remove the previous assistant answer to replace it
510
+ chatbot_history.pop()
511
 
512
+ new_answer = generate_answer(last_user_question, chatbot_history) # Generate new answer
513
+ chatbot_history.append([last_user_question, new_answer]) # Add new answer
514
  return "", chatbot_history
515
  return "", chatbot_history
516
 
517
 
518
+ def feedback_callback(chatbot_history: List[List[str]], liked: bool) -> str:
519
  if not chatbot_history:
520
+ return "Önce bir sohbet gerçekleştirin."
 
 
 
 
 
 
 
 
 
 
521
 
522
  last_user_question = chatbot_history[-1][0]
523
  last_assistant_answer = chatbot_history[-1][1]
524
 
525
  if last_user_question and last_assistant_answer:
526
+ # Clean emojis before saving feedback or passing to RL agent
527
+ cleaned_user_question = last_user_question.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()
528
+ cleaned_assistant_answer = last_assistant_answer.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()
529
+
530
+ # ONLY ONE CALL FOR SAVE AND RECORD
531
+ save_feedback(cleaned_user_question, cleaned_assistant_answer, liked, FEEDBACK_FILE)
532
 
533
  rl_agent.record_experience(
534
+ cleaned_user_question,
535
+ cleaned_assistant_answer,
536
  liked
537
  )
538
 
539
  if liked:
540
+ # Save liked QA pairs for potential future fine-tuning or analysis
541
+ qa_pair = {"question": cleaned_user_question, "answer": cleaned_assistant_answer, "liked": True}
542
+ os.makedirs(os.path.dirname(QA_PATH), exist_ok=True)
543
  with open(QA_PATH, "a", encoding="utf-8") as f:
544
  f.write(json.dumps(qa_pair, ensure_ascii=False) + "\n")
545
+ return "Geri bildiriminiz kaydedildi. Teşekkürler! 👍"
546
+ else:
547
+ return "Geri bildiriminiz kaydedildi. Yeni bir yanıt denenecek. 👎"
548
  return "Geri bildirim kaydedilemedi. Geçmişte yeterli sohbet bulunmuyor."
549
 
550
  # === Gradio arayüzü == #
551
+ # current_css defined at the top of the file
 
552
  def create_chat_interface():
553
  with gr.Blocks(theme=gr.themes.Soft(), css=current_css) as demo:
554
  gr.Markdown("""
 
565
 
566
  with gr.Row():
567
  like_btn = gr.Button("👍 Beğendim")
568
+ dislike_btn = gr.Button("👎 Beğenmedim (Yeni Cevap Dene)")
569
 
570
  feedback_status_output = gr.Textbox(label="Geri Bildirim Durumu", interactive=False, max_lines=1)
571
 
572
  msg.submit(respond, [msg, chatbot], [msg, chatbot])
573
  submit_btn.click(respond, [msg, chatbot], [msg, chatbot])
574
+ clear_btn.click(lambda: [], None, chatbot, queue=False) # Clear button resets the chat
575
 
576
+ # When "Like" is clicked, record feedback.
577
  like_btn.click(partial(feedback_callback, liked=True), [chatbot], [feedback_status_output])
578
+
579
+ # When "Dislike" is clicked, record feedback AND regenerate the answer.
580
  dislike_btn.click(partial(feedback_callback, liked=False), [chatbot], [feedback_status_output])
581
+ dislike_btn.click(
582
+ regenerate_answer,
583
+ [chatbot],
584
+ [msg, chatbot], # msg for clearing input, chatbot for updating chat
585
+ queue=False # Important for immediate regeneration
586
+ )
587
  return demo
588
 
589
 
 
593
  demo = create_chat_interface()
594
  demo.launch()
595
  else:
596
+ print("Uygulama başlatılamadı: Bileşenler yüklenirken hata oluştu.")