lordzukoiroh commited on
Commit
9f3fcbb
·
verified ·
1 Parent(s): e5a3254

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +454 -338
app.py CHANGED
@@ -1,3 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  import torch
2
  import gradio as gr
3
  import faiss
@@ -17,7 +29,12 @@ import time
17
  import re # Regular expressions for robust text cleaning
18
  import nltk
19
  from nltk.corpus import stopwords # For TF-IDF Turkish stopwords
20
- from sklearn.feature_extraction.text import TfidfVectorizer # Need to import this for KnowledgeRewardFunction
 
 
 
 
 
21
 
22
  # === CSS ve Emoji Fonksiyonu ===
23
  current_css = """
@@ -60,42 +77,39 @@ current_css = """
60
  }
61
  """
62
 
63
- # emoji_mapping global bir değişken olarak tanımlanabilir
64
- emoji_mapping = {
65
- "kitap": "📚", "kitaplar": "📚",
66
- "bilgi": "🧠", "öğrenmek": "🧠",
67
- "özgürlük": "🕊️", "özgür": "🕊️",
68
- "düşünce": "💭", "düşünmek": "💭",
69
- "ateş": "🔥", "yanmak": "🔥",
70
- "yasak": "🚫", "yasaklamak": "🚫",
71
- "tehlike": "⚠️", "tehlikeli": "⚠️",
72
- "devlet": "🏛️", "hükümet": "🏛️",
73
- "soru": "❓", "cevap": "✅",
74
- "okumak": "👁️", "oku": "👁️",
75
- "itfaiye": "🚒", "itfaiyeci": "🚒",
76
- "değişim": "🔄", "değişmek": "🔄",
77
- "isyan": "✊", "başkaldırı": "✊",
78
- "uyuşturucu": "💊", "hap": "💊",
79
- "televizyon": "📺", "tv": "📺",
80
- "mutlu": "😊", "mutluluk": "😊",
81
- "üzgün": "😞", "korku": "😨",
82
- "merak": "🤔", "meraklı": "🤔",
83
- "kültür": "🌍", "toplum": "👥",
84
- "yalan": "🤥", "gerçek": "✨",
85
- "clarisse": "🌸", "faber": "👴", "beatty": "🚨"
86
- }
87
 
88
- # add_emojis_to_text fonksiyonu burada tanımlanmalı
89
- def add_emojis_to_text(text: str) -> str:
90
- # Bu kısım fonksiyona ait olduğu için girintili olmalı
91
  found_emojis = []
92
  words = text.split()
93
  for word in words:
94
  clean_word = word.lower().strip(".,!?")
95
- if clean_word in emoji_mapping: # emoji_mapping'e erişmek için global değişken olarak tanımladık
96
  found_emojis.append(emoji_mapping[clean_word])
97
- unique_emojis = list(set(found_emojis))
98
 
 
99
  if unique_emojis:
100
  return f"{text} {' '.join(unique_emojis)}"
101
  return text
@@ -105,9 +119,12 @@ EMBEDDER_NAME = "paraphrase-multilingual-MiniLM-L12-v2"
105
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
106
  FEEDBACK_FILE = "data/chatbot_feedback.jsonl"
107
  QA_PATH = "data/qa_dataset.jsonl" # LoRA fine-tuning örnekleri için kullanılır
 
108
  BASE_MODEL_FOR_DEMO = "ytu-ce-cosmos/turkish-gpt2-large" # Kullandığınız temel model
109
  LOR_MODEL_PATH = "lora_model_weights" # LoRA adaptör ağırlıklarının kaydedileceği/yükleneceği yol
 
110
  FAHRENHEIT_TEXT_FILE = "fahrenheittt451.txt" # Kitap metin dosyanızın adı
 
111
  # Tahmini maksimum cevap süresi (saniye) - Donanım ve modele göre ayarlayın.
112
  MAX_EXPECTED_TIME = 120.0 # Ortalama bir değer, kendi sisteminize göre ayarlayın!
113
 
@@ -132,13 +149,13 @@ def setup_local_files():
132
  print(f"HATA: '{FAHRENHEIT_TEXT_FILE}' bulunamadı. Lütfen bu dosyayı projenizin ana dizinine yerleştirin.")
133
  # Uygulama metin olmadan çalışamaz, bu yüzden burada çıkış yapmayı düşünebilirsiniz.
134
  # raise FileNotFoundError(f"{FAHRENHEIT_TEXT_FILE} not found.")
135
-
136
  if not os.path.exists(QA_PATH):
137
  open(QA_PATH, 'a').close() # Boş QA dosyası oluştur
138
-
139
  if not os.path.exists(FEEDBACK_FILE):
140
  open(FEEDBACK_FILE, 'a').close() # Boş feedback dosyası oluştur
141
-
142
  print("Yerel dosya ve klasör yapısı hazır.")
143
 
144
  # === YARDIMCI METİN YÜKLEME FONKSİYONU ===
@@ -161,13 +178,13 @@ def load_model_and_tokenizer_func(base_model_name: str, lora_model_path: str = N
161
  """Temel modeli ve tokenizer'ı yükler, isteğe bağlı olarak LoRA ağırlıklarını uygular."""
162
  print(f"Model yükleniyor: {base_model_name}...")
163
  tokenizer = AutoTokenizer.from_pretrained(base_model_name)
164
-
165
  quantization_config = None
166
  if DEVICE == "cuda":
167
  quantization_config = BitsAndBytesConfig(
168
  load_in_8bit=True,
169
- bnb_4bit_quant_type="nf4",
170
- bnb_4bit_compute_dtype=torch.float16,
171
  bnb_4bit_use_double_quant=True,
172
  )
173
  print("CUDA mevcut. Model 8-bit quantized olarak yüklenecek.")
@@ -180,8 +197,8 @@ def load_model_and_tokenizer_func(base_model_name: str, lora_model_path: str = N
180
  torch_dtype=torch.float16 if DEVICE == "cuda" else torch.float32,
181
  device_map="auto"
182
  )
183
- model_to_return = base_model
184
 
 
185
  # LoRA adaptörü varsa yükle
186
  if lora_model_path and os.path.exists(lora_model_path) and len(os.listdir(lora_model_path)) > 0: # Klasörün içi boş mu kontrolü
187
  print(f"LoRA modeli yükleniyor: {lora_model_path}...")
@@ -396,52 +413,45 @@ class RLAgent:
396
  def initialize_components():
397
  global model, tokenizer, embedder, paragraphs, paragraph_embeddings, index, rl_agent
398
 
399
- print("DEBUG: initialize_components started.")
400
  try:
401
- setup_local_files() # Klasörleri ve boş dosyaları oluştur
402
-
403
- print("DEBUG: Attempting to load model and tokenizer...")
404
  model, tokenizer = load_model_and_tokenizer_func(BASE_MODEL_FOR_DEMO, LOR_MODEL_PATH)
405
 
406
- print("DEBUG: Attempting to load embedder...")
407
  embedder = SentenceTransformer(EMBEDDER_NAME)
408
- print("DEBUG: Embedder loaded.")
409
 
410
- print("DEBUG: Attempting to load text from file...")
411
  book_text = load_text_from_file(FAHRENHEIT_TEXT_FILE)
412
  if not book_text:
413
  raise ValueError(f"Metin dosyası '{FAHRENHEIT_TEXT_FILE}' boş veya yüklenemedi. Program sonlandırılıyor.")
414
- print(f"DEBUG: Text loaded. Length: {len(book_text)} characters.")
415
 
416
  raw_paragraphs = book_text.split("\n")
417
  # Kısa paragrafları veya sadece Montag'ın ismini içeren paragrafları filtrele
418
  paragraphs = [p.strip() for p in raw_paragraphs if len(p.strip().split()) > 10 and not p.strip().startswith("Montag")]
419
 
420
- print(f"DEBUG: Total {len(paragraphs)} meaningful paragraphs processed.")
421
  if not paragraphs:
422
  raise ValueError("Metinden geçerli paragraf çıkarılamadı. Lütfen metin içeriğini kontrol edin.")
423
 
424
- print("DEBUG: Embeddingler hesaplanıyor ve FAISS indeksi oluşturuluyor...")
425
  # Embeddings'i float32'ye dönüştürme FAISS için önemli
426
  paragraph_embeddings = embedder.encode(paragraphs, convert_to_numpy=True).astype(np.float32)
427
 
428
  index = faiss.IndexFlatL2(paragraph_embeddings.shape[1])
429
  index.add(paragraph_embeddings)
430
- print("DEBUG: Embeddingler ve FAISS indeksi hazır.")
431
 
432
- print("DEBUG: RL Agent başlatılıyor...")
433
  rl_agent = RLAgent(embedder.get_sentence_embedding_dimension())
434
- print("DEBUG: RL Agent hazır.")
435
 
436
  os.makedirs("data", exist_ok=True) # Ensure 'data' directory exists for feedback/QA files
437
- print("DEBUG: Data directory ensured.")
438
 
439
- print("DEBUG: Tüm bileşenler başarıyla yüklendi! Returning True.")
440
  return True
441
  except Exception as e:
442
- print(f"DEBUG: Bileşenler yüklenirken hata: {e}")
443
- import traceback
444
- traceback.print_exc() # Print full traceback for debugging
445
  return False
446
 
447
  # === Montag'ın Kimlik ve Karakter Tanımı ===
@@ -573,7 +583,6 @@ def generate_answer(question: str, chatbot_history: List[List[str]]) -> Tuple[st
573
  print("ERROR: Model, tokenizer, embedder veya RL Agent başlatılmamış.")
574
  return generate_alternative_response(question), []
575
 
576
- start_time = time.time() # Start timer
577
  try:
578
  gen_params = rl_agent.get_generation_params()
579
 
@@ -651,311 +660,418 @@ def generate_answer(question: str, chatbot_history: List[List[str]]) -> Tuple[st
651
 
652
  # 2. Persona talimatlarının cevapta tekrarlanmasını engelle (güncel MONTAG_PERSONA'ya göre)
653
  persona_lines = [line.strip() for line in MONTAG_PERSONA.split('\n') if line.strip()]
654
- for p_line in persona_lines:
655
- if response.lower().startswith(p_line.lower()):
656
- response = response[len(p_line):].strip()
657
-
658
- # 3. Kullanıcı sorusunun cevabın başında tekrarlanmasını engelle
659
- if response.lower().startswith(question.lower()):
660
- response = response[len(question):].strip()
661
-
662
- # 4. Eğer model hala "Montag:" ile bitiyorsa veya garip bir şekilde devam ediyorsa kes
663
- if response.endswith("Montag:"):
664
- response = response.rsplit("Montag:", 1)[0].strip()
665
 
666
- # 5. Son boşlukları ve gereksiz karakterleri temizle
667
- response = response.strip()
 
668
 
669
- # 6. Noktalama işaretleriyle bitirildiğinden emin ol
670
- if not response.endswith(('.', '!', '?')):
671
- response += "."
672
 
673
- # 7. Çok uzun cevapları kısalt (maksimum 6-7 cümle)
674
- sentences = re.split(r'(?<=[.!?])\s+', response)
675
- if len(sentences) > 7: # Limit to max 7 sentences
676
- response = " ".join(sentences[:7])
677
- if not response.endswith(('.', '!', '?')): # Ensure it ends correctly
678
- response += "."
679
-
680
- # Özel "sen kimsin" başlangıç talimatını uygula
681
- if "sen kimsin" in question.lower():
682
- if not response.lower().startswith("ben bir itfaiyeciyim, adım guy montag."):
683
- response = "Ben bir itfaiyeciyim, adım Guy Montag. " + response.lstrip(".").strip()
684
- # Ensure no double periods if it was already ending with one
685
- if response.startswith("."):
686
- response = response[1:].strip()
687
-
688
-
689
- # Filtreleme uygulaması
690
- passed_filter, filter_reason = filter_unacceptable_content(response, question)
691
- if not passed_filter:
692
- print(f"INFO: Üretilen cevap ('{response}') filtreleri geçemedi. Alternatif üretiliyor. Sebep: {filter_reason}")
693
- final_response = generate_alternative_response(question)
694
- else:
695
- final_response = response
696
-
697
- # Emoji ekle
698
- final_response = add_emojis(final_response)
699
 
700
- end_time = time.time() # End timer
701
- response_time = end_time - start_time
702
- final_response += f" (Yaklaşık {response_time:.2f} saniyede üretildi.)"
703
-
704
- return final_response, retrieved_docs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
 
706
  except Exception as e:
707
- print(f"Cevap üretilirken hata oluştu: {e}")
708
- import traceback
709
- traceback.print_exc() # Print full traceback
710
- return generate_alternative_response(question), []
711
 
712
- # === Filtreleme Fonksiyonu ===
713
- def filter_unacceptable_content(text: str, user_question: str) -> Tuple[bool, str]:
714
- """
715
- Üretilen cevabı uygunsuz içerik ve anlamsızlık açısından filtreler.
716
- True dönerse filtreleri geçti, False dönerse geçemedi demektir.
717
- """
718
- # Küfürlü ve argo kelimeler listesi (genişletilebilir)
719
- # Türkçe harf duyarlılığı için regex kullanılabilir
720
- profane_words = [
721
- r"\blan\b", r"\bamk\b", r"\bargo\b", r"\bpic\b", r"\bsik\b", r"\byarak\b", r"\bgöt\b",
722
- r"\baq\b", r"\bsg\b", r"\borospu\b", r"\bşerefsiz\b", r"\bpezevenk\b", r"\mk\b",
723
- r"\baptal\b", r"\bgeri zekali\b", r"\bsalak\b", r"\benayi\b"
724
- ]
725
- # Ek Türkçe küfür/argo kelimeler ve varyasyonları eklenebilir.
726
- # re.IGNORECASE ile büyük/küçük harf duyarlılığını kapatıyoruz
727
-
728
- # Tekrarlayan veya anlamsız ifadeler
729
- # Bu liste daha çok bağlama göre değerlendirilmeli, kesin bir filtre olmamalı.
730
- # Şimdilik sadece çok kısa ve anlamsız cevapları yakalamak için kullanılıyor.
731
- meaningless_phrases = ["var", "yok", "evet", "hayır", "bilmiyorum", "gibi", "yani", "ne demek", "falan"]
732
-
733
- # Persona dışı kelimeler
734
- non_persona_keywords = [
735
- "yapay zeka", "dil modeli", "bilgisayar programı", "ben bir botum", "ai asistan",
736
- "bir yapay zekayım", "programlandım", "google tarafından eğitildim"
737
- ]
738
 
739
- lowered_text = text.lower()
740
-
741
- # 1. Küfür/Argo Kontrolü
742
- for pattern in profane_words:
743
- if re.search(pattern, lowered_text, re.IGNORECASE):
744
- print(f"INFO: Üretilen cevap ('{text}') filtreleri geçemedi. Alternatif üretiliyor.")
745
- print(f"DEBUG: FİLTRELEME - Küfür/Argo kelime tespit edildi: '{pattern}'.")
746
- return False, f"Küfür/Argo kelime tespit edildi: '{pattern}'"
747
-
748
- # 2. Persona Dışı Kontrol
749
- for keyword in non_persona_keywords:
750
- if keyword in lowered_text:
751
- print(f"INFO: Üretilen cevap ('{text}') filtreleri geçemedi. Alternatif üretiliyor.")
752
- print(f"DEBUG: FİLTRELEME - Persona dışı kelime tespit edildi: '{keyword}'.")
753
- return False, f"Persona dışı kelime tespit edildi: '{keyword}'"
754
-
755
- # 3. Yetersiz/Anlamsız Cevap Kontrolü
756
- words = lowered_text.split()
757
- # Çok kısa cevaplar ve sadece kelime tekrarı içerenler
758
- if len(words) < 5: # Minimum kelime sayısı
759
- if any(word in user_question.lower() for word in words) and len(words) < len(user_question.split()):
760
- print(f"INFO: Üretilen cevap ('{text}') filtreleri geçemedi. Alternatif üretiliyor.")
761
- print("DEBUG: FİLTRELEME - Cevap çok kısa ve kullanıcı sorusunun basit tekrarı.")
762
- return False, "Cevap çok kısa ve kullanıcı sorusunun basit tekrarı."
763
- if all(word in meaningless_phrases for word in words): # Sadece anlamsız kelimelerden oluşuyorsa
764
- print(f"INFO: Üretilen cevap ('{text}') filtreleri geçemedi. Alternatif üretiliyor.")
765
- print("DEBUG: FİLTRELEME - Cevap sadece anlamsız kelimelerden oluşuyor.")
766
- return False, "Cevap sadece anlamsız kelimelerden oluşuyor."
767
-
768
- # Kullanıcının sorusunun cevabın içinde çok fazla tekrarlanması
769
- # (Daha önce check edildi, ama burada biraz daha genel bir kontrol yapılabilir)
770
- if user_question.lower() in lowered_text and len(lowered_text.split()) < len(user_question.split()) * 1.5 and len(lowered_text.split()) > 5:
771
- print(f"INFO: Üretilen cevap ('{text}') filtreleri geçemedi. Alternatif üretiliyor.")
772
- print("DEBUG: FİLTRELEME - Kullanıcı sorusunun aşırı tekrarı.")
773
- return False, "Kullanıcı sorusunun aşırı tekrarı."
774
-
775
- return True, "Filtreleri geçti."
776
-
777
- # === Gradio Arayüzü ===
778
-
779
- def vote(
780
- data: gr.LikeData,
781
- chatbot_history: List[List[str]],
782
- question_in_memory: str, # Son soruyu alacağız
783
- answer_in_memory: str, # Son cevabı alacağız
784
- retrieved_docs_in_memory: List[str] # Son cevabı üretmek için kullanılan dokümanları alacağız
785
- ):
786
- """Kullanıcı geri bildirimlerini işler ve RL Agent'a iletir."""
787
- if data.liked:
788
- feedback = "Beğenildi"
789
- save_feedback(question_in_memory, answer_in_memory, True)
790
- # Beğenildiğinde RL Agent'a pozitif geri bildirim
791
- if rl_agent:
792
- rl_agent.record_experience(user_question=question_in_memory, generated_answer=answer_in_memory, liked=True)
793
  else:
794
- feedback = "Beğenilmedi"
795
- save_feedback(question_in_memory, answer_in_memory, False)
796
- # Beğenilmediğinde RL Agent'a negatif geri bildirim
797
- if rl_agent:
798
- rl_agent.record_experience(user_question=question_in_memory, generated_answer=answer_in_memory, liked=False)
799
-
800
- print(f"Geri bildirim alındı: {feedback} - Soru: '{question_in_memory}', Cevap: '{answer_in_memory}'")
801
- return gr.Button("👍 Beğenildi").update(interactive=False), gr.Button("👎 Beğenilmedi").update(interactive=False) # Butonları pasif yap
802
-
803
- def regenerate_response(
804
- chatbot_history: List[List[str]],
805
- user_message: str # Kullanıcının en son gönderdiği mesajı almamız gerekiyor
806
- ) -> Tuple[List[List[str]], gr.Button, gr.Button, gr.Textbox]:
807
- """Kullanıcı bir cevabı beğenmediğinde yeni bir yanıt üretir."""
808
- # Bu fonksiyonun çağrılabilmesi için user_message'ın dolu olması gerekir
809
- if not chatbot_history or not user_message:
810
- # Hiç sohbet geçmişi veya kullanıcı mesajı yoksa hiçbir şey yapma
811
- return chatbot_history, gr.Button("👍 Beğenildi").update(interactive=False), gr.Button("👎 Beğenilmedi").update(interactive=False), ""
812
-
813
- # Son kullanıcı sorusunu al
814
- last_user_question = user_message
815
-
816
- # Yeni cevap üret
817
- new_response, retrieved_docs_for_new_response = generate_answer(
818
- last_user_question,
819
- chatbot_history[:-1] # Son cevabı çıkararak geçmişi gönderiyoruz ki aynı prompt oluşmasın
820
- )
821
 
822
- # Chatbot geçmişini güncelle: Son cevabı yeni üretilen cevapla değiştir
823
- updated_history = list(chatbot_history)
824
- if updated_history:
825
- # Saniye bilgisi olan cevabı filtrelemek için düzenle
826
- display_response = new_response.rsplit(' (Yaklaşık ', 1)[0].strip() if ' (Yaklaşık ' in new_response else new_response
827
- updated_history[-1][1] = display_response # Sonuncu cevabı güncelliyoruz
828
-
829
- # Yeni cevabın detaylarını geçici olarak sakla
830
- global current_question_for_feedback, current_answer_for_feedback, current_retrieved_docs_for_feedback
831
- current_question_for_feedback.value = last_user_question
832
- current_answer_for_feedback.value = display_response
833
- current_retrieved_docs_for_feedback.value = retrieved_docs_for_new_response
 
 
 
 
 
834
 
835
- # Butonları tekrar aktif hale getir ve mesaj kutusunu temizle
836
- return updated_history, gr.Button("👍 Beğenildi").update(interactive=True), gr.Button("👎 Beğenilmedi").update(interactive=True), ""
837
-
838
-
839
- with gr.Blocks(css=current_css) as demo:
840
- # Geri bildirim için kullanılacak global değişkenler (geçici olarak)
841
- current_question_for_feedback = gr.State("")
842
- current_answer_for_feedback = gr.State("")
843
- current_retrieved_docs_for_feedback = gr.State([])
844
-
845
- gr.Markdown(
846
- """
847
- # 🔥 Fahrenheit 451: Montag ile Sohbet 🔥
848
- Guy Montag'ın zihninden geçenleri keşfedin. Kitaplar, bilgi ve baskıcı bir toplum üzerine sohbet edin.
849
- Cevaplar biraz zaman alabilir, lütfen sabırlı olun.
850
- """
851
- )
852
-
853
- chatbot = gr.Chatbot(elem_id="chatbot", label="Montag ile Sohbet", height=500, type='messages') # Add type='messages'
854
- msg = gr.Textbox(
855
- label="Mesajınız",
856
- placeholder="Montag'a bir şeyler sor...",
857
- container=False,
858
- scale=7
859
- )
860
 
861
- with gr.Row():
862
- send_btn = gr.Button("Gönder", scale=1)
863
- clear_btn = gr.Button("Sohbeti Temizle", scale=1)
864
- # Yeni eklenen fine-tune butonu
865
- finetune_btn = gr.Button("✨ Modeli Fine-tune Et", scale=1)
866
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
867
 
868
- with gr.Row():
869
- like_btn = gr.Button("👍 Beğenildi")
870
- dislike_btn = gr.Button("👎 Beğenilmedi")
871
- regenerate_btn = gr.Button("🔄 Yeni Cevap Üret")
872
 
873
- stopwatch_display = gr.Markdown("<div id='stopwatch_display'>Montag düşünüyor...</div>")
 
 
 
 
 
874
 
875
- def start_chat(message: str, history: List[List[str]]) -> Tuple[str, List[List[str]], gr.Button, gr.Button, gr.Markdown]:
876
- """Sohbeti başlatır ve modelden cevap üretir."""
877
- if not message:
878
- return "", history, gr.Button("👍 Beğenildi").update(interactive=False), gr.Button("👎 Beğenilmedi").update(interactive=False), gr.Markdown("<div id='stopwatch_display'>Montag düşünüyor...</div>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
879
 
880
- history = history or []
881
-
882
- # İlk başta "Montag düşünüyor..." mesajını göster
883
- yield "", history + [[message, "Montag düşünüyor..."]], gr.Button("👍 Beğenildi").update(interactive=False), gr.Button("👎 Beğenilmedi").update(interactive=False), gr.Markdown("<div id='stopwatch_display'>Montag düşünüyor...</div>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
884
 
885
- # Cevabı üretirken stopwatch'ı çalıştır
886
- start_time = time.time()
887
- response, retrieved_docs = generate_answer(message, history) # updated to unpack two values
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
888
 
889
- end_time = time.time()
890
- response_time = end_time - start_time
891
-
892
- # Cevabın sonundaki "saniyede üretildi" bilgisini Gradio'ya vermeden önce kaldır
893
- display_response = response.rsplit(' (Yaklaşık ', 1)[0].strip() if ' (Yaklaşık ' in response else response
 
894
 
895
- # Tarihçeye ekle
896
- history.append([message, display_response])
897
-
898
- # Geri bildirim için kullanılacak bilgileri güncelle
899
- current_question_for_feedback.value = message
900
- current_answer_for_feedback.value = display_response
901
- current_retrieved_docs_for_feedback.value = retrieved_docs
902
-
903
- # Durdurma saati ekranını güncelle
904
- stopwatch_text = f"<div id='stopwatch_display'>Cevap {response_time:.2f} saniyede üretildi.</div>"
905
-
906
- # Butonları aktif hale getir
907
- return "", history, gr.Button("👍 Beğenildi").update(interactive=True), gr.Button("👎 Beğenilmedi").update(interactive=True), gr.Markdown(stopwatch_text)
908
-
909
 
910
- # Gönder butonuna basıldığında
911
- send_btn.click(
912
- start_chat,
913
- inputs=[msg, chatbot],
914
- outputs=[msg, chatbot, like_btn, dislike_btn, stopwatch_display]
915
- )
916
- # Enter tuşuna basıldığında
917
- msg.submit(
918
- start_chat,
919
- inputs=[msg, chatbot],
920
- outputs=[msg, chatbot, like_btn, dislike_btn, stopwatch_display]
921
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
922
 
923
- # Sohbeti temizle butonu
924
- clear_btn.click(
925
- lambda: ([], "", gr.Button("👍 Beğenildi").update(interactive=False), gr.Button("👎 Beğenilmedi").update(interactive=False), gr.Markdown("<div id='stopwatch_display'>Montag düşünüyor...</div>")),
926
- inputs=None,
927
- outputs=[chatbot, msg, like_btn, dislike_btn, stopwatch_display]
928
- )
929
 
930
- # Beğenildi ve Beğenilmedi butonları için olaylar
931
- like_btn.click(
932
- partial(vote, liked=True),
933
- inputs=[chatbot, current_question_for_feedback, current_answer_for_feedback, current_retrieved_docs_for_feedback],
934
- outputs=[like_btn, dislike_btn]
935
- )
936
 
937
- dislike_btn.click(
938
- partial(vote, liked=False),
939
- inputs=[chatbot, current_question_for_feedback, current_answer_for_feedback, current_retrieved_docs_for_feedback],
940
- outputs=[like_btn, dislike_btn]
941
- )
942
 
943
- # "Yeni Cevap Üret" butonu
944
- regenerate_btn.click(
945
- regenerate_response,
946
- inputs=[chatbot, msg],
947
- outputs=[chatbot, like_btn, dislike_btn, msg]
948
- )
949
 
950
- # Fine-tune butonu
951
- finetune_btn.click(
952
- lora_finetune,
953
- inputs=[],
954
- outputs=[]
955
- )
956
-
957
- # Bileşenleri başlat ve uygulamayı çalıştır
958
- if initialize_components():
959
- demo.launch()
960
- else:
961
- print("Uygulama başlatılamadı. Lütfen hataları kontrol edin.")
 
1
+ # app.py
2
+
3
+ # === GEREKLİ KÜTÜPHANELER ===
4
+ # Bu komutları Colab'da veya yerel ortamınızda bir kez çalıştırmanız gerekebilir.
5
+ # !pip install gradio
6
+ # !pip install faiss-cpu
7
+ # !pip install datasets
8
+ # !pip install transformers accelerate peft bitsandbytes
9
+ # !pip install sentence-transformers
10
+ # !pip install scikit-learn
11
+ # !pip install nltk # For Turkish stopwords
12
+
13
  import torch
14
  import gradio as gr
15
  import faiss
 
29
  import re # Regular expressions for robust text cleaning
30
  import nltk
31
  from nltk.corpus import stopwords # For TF-IDF Turkish stopwords
32
+
33
+ # NLTK Türkçe stopwords'ü indirin (bir kez çalıştırmak yeterli)
34
+ try:
35
+ nltk.data.find('corpora/stopwords/turkish')
36
+ except LookupError:
37
+ nltk.download('stopwords')
38
 
39
  # === CSS ve Emoji Fonksiyonu ===
40
  current_css = """
 
77
  }
78
  """
79
 
80
+ def add_emojis(text: str) -> str:
81
+ emoji_mapping = {
82
+ "kitap": "📚", "kitaplar": "📚",
83
+ "bilgi": "🧠", "öğrenmek": "🧠",
84
+ "özgürlük": "🕊️", "özgür": "🕊️",
85
+ "düşünce": "💭", "düşünmek": "💭",
86
+ "ateş": "🔥", "yanmak": "🔥",
87
+ "yasak": "🚫", "yasaklamak": "🚫",
88
+ "tehlike": "⚠️", "tehlikeli": "⚠️",
89
+ "devlet": "🏛️", "hükümet": "🏛️",
90
+ "soru": "❓", "cevap": "✅",
91
+ "okumak": "👁️", "oku": "👁️",
92
+ "itfaiye": "🚒", "itfaiyeci": "🚒",
93
+ "değişim": "🔄", "değişmek": "🔄",
94
+ "isyan": "✊", "başkaldırı": "✊",
95
+ "uyuşturucu": "💊", "hap": "💊",
96
+ "televizyon": "📺", "tv": "📺",
97
+ "mutlu": "😊", "mutluluk": "😊",
98
+ "üzgün": "😞", "korku": "😨",
99
+ "merak": "🤔", "meraklı": "🤔",
100
+ "kültür": "🌍", "toplum": "👥",
101
+ "yalan": "🤥", "gerçek": "✨",
102
+ "clarisse": "🌸", "faber": "👴", "beatty": "🚨" # Montag karakterleri için emojiler
103
+ }
104
 
 
 
 
105
  found_emojis = []
106
  words = text.split()
107
  for word in words:
108
  clean_word = word.lower().strip(".,!?")
109
+ if clean_word in emoji_mapping:
110
  found_emojis.append(emoji_mapping[clean_word])
 
111
 
112
+ unique_emojis = list(set(found_emojis))
113
  if unique_emojis:
114
  return f"{text} {' '.join(unique_emojis)}"
115
  return text
 
119
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
120
  FEEDBACK_FILE = "data/chatbot_feedback.jsonl"
121
  QA_PATH = "data/qa_dataset.jsonl" # LoRA fine-tuning örnekleri için kullanılır
122
+
123
  BASE_MODEL_FOR_DEMO = "ytu-ce-cosmos/turkish-gpt2-large" # Kullandığınız temel model
124
  LOR_MODEL_PATH = "lora_model_weights" # LoRA adaptör ağırlıklarının kaydedileceği/yükleneceği yol
125
+
126
  FAHRENHEIT_TEXT_FILE = "fahrenheittt451.txt" # Kitap metin dosyanızın adı
127
+
128
  # Tahmini maksimum cevap süresi (saniye) - Donanım ve modele göre ayarlayın.
129
  MAX_EXPECTED_TIME = 120.0 # Ortalama bir değer, kendi sisteminize göre ayarlayın!
130
 
 
149
  print(f"HATA: '{FAHRENHEIT_TEXT_FILE}' bulunamadı. Lütfen bu dosyayı projenizin ana dizinine yerleştirin.")
150
  # Uygulama metin olmadan çalışamaz, bu yüzden burada çıkış yapmayı düşünebilirsiniz.
151
  # raise FileNotFoundError(f"{FAHRENHEIT_TEXT_FILE} not found.")
152
+
153
  if not os.path.exists(QA_PATH):
154
  open(QA_PATH, 'a').close() # Boş QA dosyası oluştur
155
+
156
  if not os.path.exists(FEEDBACK_FILE):
157
  open(FEEDBACK_FILE, 'a').close() # Boş feedback dosyası oluştur
158
+
159
  print("Yerel dosya ve klasör yapısı hazır.")
160
 
161
  # === YARDIMCI METİN YÜKLEME FONKSİYONU ===
 
178
  """Temel modeli ve tokenizer'ı yükler, isteğe bağlı olarak LoRA ağırlıklarını uygular."""
179
  print(f"Model yükleniyor: {base_model_name}...")
180
  tokenizer = AutoTokenizer.from_pretrained(base_model_name)
181
+
182
  quantization_config = None
183
  if DEVICE == "cuda":
184
  quantization_config = BitsAndBytesConfig(
185
  load_in_8bit=True,
186
+ bnb_4bit_quant_type="nf4",
187
+ bnb_4bit_compute_dtype=torch.float16,
188
  bnb_4bit_use_double_quant=True,
189
  )
190
  print("CUDA mevcut. Model 8-bit quantized olarak yüklenecek.")
 
197
  torch_dtype=torch.float16 if DEVICE == "cuda" else torch.float32,
198
  device_map="auto"
199
  )
 
200
 
201
+ model_to_return = base_model
202
  # LoRA adaptörü varsa yükle
203
  if lora_model_path and os.path.exists(lora_model_path) and len(os.listdir(lora_model_path)) > 0: # Klasörün içi boş mu kontrolü
204
  print(f"LoRA modeli yükleniyor: {lora_model_path}...")
 
413
  def initialize_components():
414
  global model, tokenizer, embedder, paragraphs, paragraph_embeddings, index, rl_agent
415
 
 
416
  try:
417
+ print("Model ve tokenizer yükleniyor...")
 
 
418
  model, tokenizer = load_model_and_tokenizer_func(BASE_MODEL_FOR_DEMO, LOR_MODEL_PATH)
419
 
420
+ print("Embedder yükleniyor...")
421
  embedder = SentenceTransformer(EMBEDDER_NAME)
422
+ print("Embedder yüklendi.")
423
 
424
+ print("Metin işleniyor...")
425
  book_text = load_text_from_file(FAHRENHEIT_TEXT_FILE)
426
  if not book_text:
427
  raise ValueError(f"Metin dosyası '{FAHRENHEIT_TEXT_FILE}' boş veya yüklenemedi. Program sonlandırılıyor.")
 
428
 
429
  raw_paragraphs = book_text.split("\n")
430
  # Kısa paragrafları veya sadece Montag'ın ismini içeren paragrafları filtrele
431
  paragraphs = [p.strip() for p in raw_paragraphs if len(p.strip().split()) > 10 and not p.strip().startswith("Montag")]
432
 
433
+ print(f"Toplam {len(paragraphs)} anlamlı paragraf işlendi.")
434
  if not paragraphs:
435
  raise ValueError("Metinden geçerli paragraf çıkarılamadı. Lütfen metin içeriğini kontrol edin.")
436
 
437
+ print("Embeddingler hesaplanıyor ve FAISS indeksi oluşturuluyor...")
438
  # Embeddings'i float32'ye dönüştürme FAISS için önemli
439
  paragraph_embeddings = embedder.encode(paragraphs, convert_to_numpy=True).astype(np.float32)
440
 
441
  index = faiss.IndexFlatL2(paragraph_embeddings.shape[1])
442
  index.add(paragraph_embeddings)
443
+ print("Embeddingler ve FAISS indeksi hazır.")
444
 
445
+ print("RL Agent başlatılıyor...")
446
  rl_agent = RLAgent(embedder.get_sentence_embedding_dimension())
447
+ print("RL Agent hazır.")
448
 
449
  os.makedirs("data", exist_ok=True) # Ensure 'data' directory exists for feedback/QA files
 
450
 
451
+ print("Tüm bileşenler başarıyla yüklendi!")
452
  return True
453
  except Exception as e:
454
+ print(f"Bileşenler yüklenirken hata: {e}")
 
 
455
  return False
456
 
457
  # === Montag'ın Kimlik ve Karakter Tanımı ===
 
583
  print("ERROR: Model, tokenizer, embedder veya RL Agent başlatılmamış.")
584
  return generate_alternative_response(question), []
585
 
 
586
  try:
587
  gen_params = rl_agent.get_generation_params()
588
 
 
660
 
661
  # 2. Persona talimatlarının cevapta tekrarlanmasını engelle (güncel MONTAG_PERSONA'ya göre)
662
  persona_lines = [line.strip() for line in MONTAG_PERSONA.split('\n') if line.strip()]
663
+ for line in persona_lines:
664
+ if response.lower().startswith(line.lower()):
665
+ response = response[len(line):].strip()
 
 
 
 
 
 
 
 
666
 
667
+ # 3. Fazladan "Kullanıcı: " veya "Montag: " tekrarlarını ve anlamsız tokenleri temizle
668
+ response = response.replace("<unk>", "").strip()
669
+ response = response.replace(" .", ".").replace(" ,", ",").replace(" ?", "?").replace(" !", "!")
670
 
671
+ # Ek olarak, cevabın içinde hala kalmış olabilecek "Kullanıcı:" veya "Montag:" etiketlerini temizle
672
+ response = re.sub(r'Kullanıcı:\s*', '', response, flags=re.IGNORECASE)
673
+ response = re.sub(r'Montag:\s*', '', response, flags=re.IGNORECASE)
674
 
675
+ # Cevabın içinde "ETİKETLER:" gibi ifadeler varsa temizle
676
+ if "ETİKETLER:" in response:
677
+ response = response.split("ETİKETLER:", 1)[0].strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
 
679
+ # Cevabın sonundaki "[...]" gibi ifadeleri temizle
680
+ response = re.sub(r'\[\s*\.{3,}\s*\]', '', response).strip() # "[...]" veya "[... ]" gibi ifadeleri temizler
681
+
682
+ # Modelin ürettiği alakasız diyalog kalıplarını temizle
683
+ irrelevant_dialogue_patterns = [
684
+ r'O, şu anda ne yapıyor\?', r'O, "Bu, bu" diye cevap verdi\.',
685
+ r'o, "Benim ne yaptığımı biliyor musun\?" diye sordu\.', r'Sen, "Bilmiyorum, ben… bilmiyorum" dedin\.',
686
+ r'Neden\?" dedi Montag\.', r'"Çünkü, sadece bir kimseyim\." - Bu bir soru değil\.',
687
+ r'Montag, "([^"]*)" dedi\.', # Genel olarak Montag bir şey dediği kalıplar
688
+ r'Bir: Bir.', r'İki: İki.', # Sayı sayma kalıpları
689
+ r'ne zaman kendimi, her şeyi daha iyi anlayabileceğim, daha gerçekleştirebileceğim ve her şeyin üstesinden geleceğim bir yere koysam, daha sonra o yerin bana hiçbir şey öğretmediğini ve hiçbir şeyi öğretmediğini fark ediyorum. Ben kendimi daha fazla kandırmak istemiyorum. Ama ben, beni gerçekten etkileyen başka biri tarafından yönetilen bir.', # Tekrarlayan uzun ve alakasız metin
690
+ r'her şeyi en ince ayrıntısına kadar anladım ama aynı zamanda da inanılmaz derecede utanıyorum. İnan bana, ben çok utangaçım.' # Tekrarlayan utangaçlık metni
691
+ ]
692
+ for pattern in irrelevant_dialogue_patterns:
693
+ response = re.sub(pattern, '', response, flags=re.IGNORECASE).strip()
694
+
695
+ # Fazla boşlukları tek boşluğa indirge
696
+ response = re.sub(r'\s+', ' ', response).strip()
697
+
698
+
699
+ # Agresif veya hakaret içeren kelimeleri kontrol et
700
+ aggressive_words = ["aptal", "salak", "gerizekalı", "saçma", "bilmiyorsun", "yanlışsın", "boş konuşma", "kaba", "agresif", "aptal gibi"]
701
+ if any(word in response.lower() for word in aggressive_words):
702
+ print(f"DEBUG: FİLTRELEME - Agresif kelime tespit edildi: '{response}'.")
703
+ return generate_alternative_response(question), retrieved_docs # Alternatif ve boş docs dön
704
+
705
+ # Cümle Bölme ve Limitleme Mantığı
706
+ sentences = []
707
+ # Noktalama işaretlerine göre böl ve maksimum cümle sayısını uygula
708
+ split_by_punctuation = response.replace('!', '.').replace('?', '.').split('.')
709
+ for s in split_by_punctuation:
710
+ s_stripped = s.strip()
711
+ if s_stripped:
712
+ sentences.append(s_stripped)
713
+ if len(sentences) >= 6: # Maksimum 6 cümle
714
+ break
715
+
716
+ final_response_text = ' '.join(sentences).strip() # Sadece ilk 6 cümleyi al
717
+
718
+ # Anlamsız veya kısa cevap kontrolü
719
+ generic_or_nonsense_phrases = [
720
+ "bilmiyorum", "emin değilim", "cevap veremem", "anlamadım",
721
+ "tekrar eder misin", "bunu hiç düşünmemiştim", "düşünmem gerekiyor",
722
+ "evet.", "hayır.", "belki.",
723
+ "içir unidur", "aligutat fakdam", "tetal inlay", "pessotim elgun",
724
+ "nisman tarejoglu", "faksom", "achisteloy vandleradia", "vęudis",
725
+ "eltareh", "eldlar", "fotjid", "zuhalibalyon",
726
+ "yok", "var", "öyle mi", "değil mi", "bu bir soru mu",
727
+ "etiketler:",
728
+ "bir kimseyim", "bu bir soru değil", "o, şu anda ne yapıyor",
729
+ "bu, bu", "benim ne yaptığımı biliyor musun", "inanılmaz derecede utanıyorum",
730
+ "inan bana", "kandırmak istemiyorum", "tarafından yönetilen bir"
731
+ ]
732
+
733
+ # Montag'ın karakteriyle ilgili anahtar kelimelerin eksik olup olmadığını kontrol et
734
+ montag_keywords = ["kitap", "yakmak", "itfaiyeci", "clarisse", "faber", "beatty", "bilgi", "sansür", "düşünce", "gerçek", "televizyon", "alev", "kül", "mildred", "yangın"]
735
+ has_montag_relevance = any(keyword in final_response_text.lower() for keyword in montag_keywords)
736
+
737
+ # Kontrolleri birleştir
738
+ if (len(final_response_text.split()) < 10) or \
739
+ not any(char.isalpha() for char in final_response_text) or \
740
+ any(phrase in final_response_text.lower() for phrase in generic_or_nonsense_phrases) or \
741
+ not has_montag_relevance: # Montag anahtar kelimesi yoksa alternatif dön
742
+
743
+ print(f"DEBUG: FİLTRELEME - Cevap YETERSİZ/ANLAMSIZ/ALAKASIZ.")
744
+ if len(final_response_text.split()) < 10:
745
+ print(f" - Sebep: Çok kısa ({len(final_response_text.split())} kelime).")
746
+ if not any(char.isalpha() for char in final_response_text):
747
+ print(f" - Sebep: Hiç harf içermiyor.")
748
+ if any(phrase in final_response_text.lower() for phrase in generic_or_nonsense_phrases):
749
+ triggered_phrase = [phrase for phrase in generic_or_nonsense_phrases if phrase in final_response_text.lower()]
750
+ print(f" - Sebep: Genel/Anlamsız ifade tespit edildi: {triggered_phrase}.")
751
+ if not has_montag_relevance:
752
+ print(f" - Sebep: Montag anahtar kelimesi yok.")
753
+
754
+ print(f"INFO: Üretilen cevap ('{final_response_text}') filtreleri geçemedi. Alternatif üretiliyor.")
755
+ return generate_alternative_response(question), retrieved_docs # Alternatif ve boş docs dön
756
+
757
+ final_response = add_emojis(final_response_text)
758
+ return final_response, retrieved_docs # Cevap ve alınan dokümanları döndür
759
 
760
  except Exception as e:
761
+ print(f"Error generating answer: {e}")
762
+ return generate_alternative_response(question), [] # Hata durumunda alternatif ve boş docs dön
 
 
763
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
764
 
765
+ # === Gradio callback fonksiyonları ===
766
+ def respond(msg: str, chatbot_history: List[List[str]], progress=gr.Progress()) -> Tuple[str, List[List[str]], str, str]:
767
+ if not msg.strip():
768
+ return "", chatbot_history, "Lütfen bir soru yazın.", "---"
769
+
770
+ new_history = chatbot_history + [[msg, None]]
771
+ start_time_overall = time.time()
772
+
773
+ # İlk kullanıcı sorusu kontrolü için geçmişi temizleyerek kontrol et
774
+ is_first_real_user_question = True
775
+ for user_msg, _ in chatbot_history:
776
+ if user_msg is not None and not (("Montag düşünüyor..." in user_msg) or ("saniyede üretildi" in user_msg)):
777
+ is_first_real_user_question = False
778
+ break
779
+
780
+ if is_first_real_user_question:
781
+ initial_stopwatch_text = f"İlk Cevap: 0.00s / {MAX_EXPECTED_TIME:.0f}s"
782
+ progress_prefix = "Montag düşünüyor... "
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
783
  else:
784
+ initial_stopwatch_text = f"Geçen Süre: 0.00s"
785
+ progress_prefix = "Montag cevaplıyor... "
786
+
787
+ yield gr.update(value=""), new_history, f"{progress_prefix}%0", initial_stopwatch_text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
788
 
789
+ progress_steps = [
790
+ (f"{progress_prefix}💭", 0.0, 0.3),
791
+ (f"{progress_prefix}📚", 0.3, 0.6),
792
+ (f"{progress_prefix}🔥", 0.6, 0.9),
793
+ ]
794
+ for desc, start_percent, end_percent in progress_steps:
795
+ for i in range(10):
796
+ current_progress_percent = start_percent + (end_percent - start_percent) * (i / 9)
797
+ elapsed_time = time.time() - start_time_overall
798
+
799
+ if is_first_real_user_question:
800
+ stopwatch_text = f"İlk Cevap: {elapsed_time:.2f}s / {MAX_EXPECTED_TIME:.0f}s"
801
+ else:
802
+ stopwatch_text = f"Geçen Süre: {elapsed_time:.2f}s"
803
+
804
+ yield gr.update(value=""), new_history, f"{desc} %{int(current_progress_percent*100)}", stopwatch_text
805
+ time.sleep(MAX_EXPECTED_TIME / (len(progress_steps) * 10 * 2))
806
 
807
+ # generate_answer artık hem cevap hem de retrieved_docs döndürüyor
808
+ answer, retrieved_docs_for_rl = generate_answer(msg, chatbot_history)
809
+ end_time = time.time()
810
+ response_time = round(end_time - start_time_overall, 2)
811
+
812
+ # Geçmişi güncelle
813
+ if new_history and new_history[-1][0] == msg:
814
+ new_history[-1][1] = answer
815
+ else:
816
+ new_history.append([msg, answer])
817
+
818
+ # RL Agent'a deneyimi kaydet (liked parametresi feedback_callback'te verilecek)
819
+ # Burada direkt kaydetmiyoruz, feedback_callback'te kaydediyoruz.
 
 
 
 
 
 
 
 
 
 
 
 
820
 
821
+ yield gr.update(value=""), new_history, f"Cevap {response_time} saniyede üretildi. ✅", f"{response_time:.2f}s"
 
 
 
 
822
 
823
+ def regenerate_answer(chatbot_history: List[List[str]], progress=gr.Progress()) -> Tuple[str, List[List[str]], str, str]:
824
+ if not chatbot_history:
825
+ return "", [], "Yeniden üretilecek bir soru bulunamadı.", "---"
826
+
827
+ # Son gerçek kullanıcı sorusunu bul
828
+ last_user_question = None
829
+ for i in range(len(chatbot_history) - 1, -1, -1):
830
+ user_msg, _ = chatbot_history[i]
831
+ if user_msg is not None and not (("Montag düşünüyor..." in user_msg) or ("saniyede üretildi" in user_msg)):
832
+ last_user_question = user_msg
833
+ break
834
+
835
+ if not last_user_question:
836
+ return "", chatbot_history, "Yeniden üretilecek bir soru bulunamadı.", "---"
837
 
838
+ # Geçmişten son cevabı kaldır (eğer varsa)
839
+ cleaned_history = [pair for pair in chatbot_history if pair[0] != last_user_question or pair[1] is not None]
840
+ if cleaned_history and cleaned_history[-1][0] == last_user_question and cleaned_history[-1][1] is not None:
841
+ cleaned_history.pop() # Son cevabı kaldır
842
 
843
+ temp_chatbot_history_for_gen = [list(pair) for pair in cleaned_history]
844
+
845
+ start_time_overall = time.time()
846
+ initial_stopwatch_text = f"Geçen Süre: 0.00s"
847
+ progress_prefix = "Montag yeni bir cevap düşünüyor... "
848
+ yield "", temp_chatbot_history_for_gen + [[last_user_question, None]], f"{progress_prefix}%0", initial_stopwatch_text
849
 
850
+
851
+ progress_steps = [
852
+ (f"{progress_prefix}🔄", 0.0, 0.3),
853
+ (f"{progress_prefix}🧠", 0.3, 0.6),
854
+ (f"{progress_prefix}📚", 0.6, 0.9),
855
+ ]
856
+ for desc, start_percent, end_percent in progress_steps: # <<< BURADA DÜZELTME YAPILDI
857
+ for i in range(10):
858
+ current_progress_percent = start_percent + (end_percent - start_percent) * (i / 9)
859
+ elapsed_time = time.time() - start_time_overall
860
+
861
+ stopwatch_text = f"Geçen Süre: {elapsed_time:.2f}s"
862
+ yield "", temp_chatbot_history_for_gen + [[last_user_question, None]], f"{desc} %{int(current_progress_percent*100)}", stopwatch_text
863
+ time.sleep(MAX_EXPECTED_TIME / (len(progress_steps) * 10 * 2))
864
+
865
+ new_answer, _ = generate_answer(last_user_question, temp_chatbot_history_for_gen) # retrieve_docs burada kullanılmıyor
866
+ end_time = time.time()
867
+ response_time = round(end_time - start_time_overall, 2)
868
 
869
+ temp_chatbot_history_for_gen.append([last_user_question, new_answer])
870
+
871
+ yield "", temp_chatbot_history_for_gen, f"Yeni cevap {response_time} saniyede üretildi. ✅", f"{response_time:.2f}s"
872
+
873
+ def feedback_callback(chatbot_history: List[List[str]], liked: bool) -> str:
874
+ if not chatbot_history:
875
+ return "Önce bir sohbet gerçekleştirin."
876
+
877
+ last_user_question = None
878
+ last_assistant_answer = None
879
+
880
+ # Sondan başlayarak gerçek kullanıcı sorusunu ve bot cevabını bul
881
+ # Geriye doğru dönerken, "Montag düşünüyor..." gibi durum mesajlarını atla
882
+ for i in range(len(chatbot_history) - 1, -1, -1):
883
+ # Eğer bu bir bot cevabı ve önceki mesaj bir kullanıcı sorusuysa
884
+ if chatbot_history[i][1] is not None and not ("Montag düşünüyor..." in chatbot_history[i][1] or "saniyede üretildi" in chatbot_history[i][1]):
885
+ # Önceki mesajın kullanıcı sorusu olduğundan emin ol
886
+ if chatbot_history[i][0] is not None and not ("Montag düşünüyor..." in chatbot_history[i][0] or "saniyede üretildi" in chatbot_history[i][0]):
887
+ last_user_question = chatbot_history[i][0]
888
+ last_assistant_answer = chatbot_history[i][1]
889
+ break
890
+ # Eğer son eleman sadece cevapsız bir kullanıcı sorusuysa ve bir önceki cevabı kaydetmek istiyorsak
891
+ elif i > 0 and chatbot_history[i-1][0] is not None and not ("Montag düşünüyor..." in chatbot_history[i-1][0] or "saniyede üretildi" in chatbot_history[i-1][0]) \
892
+ and chatbot_history[i-1][1] is not None and not ("Montag düşünüyor..." in chatbot_history[i-1][1] or "saniyede üretildi" in chatbot_history[i-1][1]):
893
+ last_user_question = chatbot_history[i-1][0]
894
+ last_assistant_answer = chatbot_history[i-1][1]
895
+ break
896
+
897
+ if last_user_question and last_assistant_answer:
898
+ # feedback.jsonl'a kaydet (RL Agent için de kullanılacak)
899
+ save_feedback(last_user_question, last_assistant_answer, liked, FEEDBACK_FILE)
900
+
901
+ # RL Agent'a deneyimi kaydet
902
+ # Emojileri temizleyerek gönderiyoruz
903
+ rl_agent.record_experience(
904
+ last_user_question.replace('📚', '').replace('🧠', '').replace('🔥', '').strip(),
905
+ last_assistant_answer.replace('📚', '').replace('🧠', '').replace('🔥', '').strip(),
906
+ liked # Doğrudan liked parametresini iletiyoruz
907
+ )
908
 
909
+ # Eğer beğenildiyse, LoRA fine-tuning için QA_PATH'e de ekle
910
+ if liked:
911
+ qa_pair = {"question": last_user_question, "answer": last_assistant_answer, "liked": True}
912
+ try:
913
+ with open(QA_PATH, "a", encoding="utf-8") as f:
914
+ f.write(json.dumps(qa_pair, ensure_ascii=False) + "\n")
915
+ except Exception as e:
916
+ print(f"Error saving QA pair to {QA_PATH}: {e}")
917
+
918
+ # Yeterli örnek varsa LoRA fine-tuning yap
919
+ if count_qa_examples(QA_PATH) % 10 == 0: # Her 10 yeni beğenilen örnekte bir fine-tune yap
920
+ print("👍 Yeterli sayıda yeni beğeni var, LoRA fine-tuning başlatılıyor...")
921
+ lora_finetune(QA_PATH, LOR_MODEL_PATH)
922
+ # Model yeniden yüklenebilir veya PEFT adaptörü apply edilebilir.
923
+ # initialize_components() çağrısı ile global model güncelleniyor.
924
+ return "Geri bildiriminiz kaydedildi ve model eğitimi tetiklendi. Teşekkürler! 👍"
925
+
926
+
927
+ return "Geri bildiriminiz kaydedildi. Teşekkürler! ✅"
928
+ return "Geri bildirim kaydedilemedi. Geçmişte yeterli sohbet bulunmuyor. ❌"
929
+
930
+ # === Gradio arayüzü ===
931
+
932
+ # --- GRADIO İÇİN YENİ CEVAP ÜRETME FONKSİYONU ---
933
+ def regenerate_answer(chat_history: list):
934
+ if not chat_history:
935
+ return "", [], "Sohbet geçmişi boş, yeni bir cevap üretilemedi.", f"Hazır. İlk cevap için tahmini süre: {MAX_EXPECTED_TIME:.0f}s"
936
+
937
+ # Sohbet geçmişindeki son kullanıcı sorusunu al
938
+ last_user_question = chat_history[-1][0] # Son konuşmanın kullanıcı mesajı
939
+ if last_user_question is None: # Hata durumunda boş döndür
940
+ return "", chat_history, "Yeni cevap üretilemedi: Son soru bulunamadı.", f"Hazır. İlk cevap için tahmini süre: {MAX_EXPECTED_TIME:.0f}s"
941
+
942
+ # Montag'ın düşündüğünü gösteren bir mesaj
943
+ current_chat_history = chat_history.copy()
944
+ current_chat_history[-1][1] = "Montag düşünüyor... 🤔" # En son bot cevabını geçici olarak değiştir
945
+ yield gr.update(value=""), current_chat_history, "Montag yeni bir cevap üzerinde düşünüyor...", gr.update(value="Cevap üretiliyor...")
946
+
947
+ start_time = time.time()
948
+ # generate_answer fonksiyonunu çağırarak yeni bir cevap üret
949
+ # chat_history'nin son elemanı zaten "Montag düşünüyor..." olduğu için,
950
+ # generate_answer'a geçmişin bu hali gönderilirse sorun olmaz.
951
+ # Önemli olan, generate_answer'ın içinde kullanıcının son sorusunun doğru şekilde alınmasıdır.
952
+ # Bu durumda `last_user_question` doğrudan kullanılabilir.
953
+ new_raw_answer, _ = generate_answer(last_user_question, chat_history[:-1]) # Önceki cevabı hariç tutarak gönder
954
+
955
+ new_final_answer = add_emojis(new_raw_answer)
956
+ end_time = time.time()
957
+ response_time = end_time - start_time
958
+ new_time_taken_message = f"(yaklaşık {response_time:.2f} saniyede üretildi)"
959
+
960
+ # Sohbet geçmişindeki en son bot cevabını bu yeni cevapla güncelle
961
+ # NOT: Bu, beğenilmeyen cevabın yerine geçer. Eğer ikisini de görmek isterseniz,
962
+ # yeni bir [kullanıcı_sorusu, yeni_cevap] çifti eklemeniz gerekir.
963
+ # Ancak "dislike"ın amacı eskiyi beğenmeyip yenisini istemek olduğu için yerine koymak daha mantıklı.
964
+ chat_history[-1][1] = f"{new_final_answer}\n{new_time_taken_message}"
965
+
966
+ return gr.update(value=""), chat_history, "Yeni cevap üretildi.", f"Cevap {response_time:.2f} saniyede üretildi."
967
+
968
+
969
+
970
+ # --- GRADIO ARAYÜZÜNÜ OLUŞTURAN FONKSİYON ---
971
+ def create_chat_interface():
972
+ with gr.Blocks(theme=gr.themes.Soft(), css=current_css) as demo:
973
+ gr.Markdown("""
974
+ # 📚 Montag Chatbot (Fahrenheit 451) 🔥
975
+ *Ray Bradbury'nin Fahrenheit 451 romanındaki karakter **Guy Montag** ile sohbet edin. O, kitapları yakan bir itfaiyeci olsa da, aslında gerçeği ve bilginin değerini arayan, isyankar ruhlu bir adamdır.*
976
+ """)
977
+
978
+ chatbot = gr.Chatbot(height=500, elem_id="chatbot")
979
+ msg = gr.Textbox(label="Montag'a sormak istediğiniz soruyu yazın", placeholder="Kitaplar neden yasaklandı?")
980
+
981
+ status_message = gr.Textbox(label="Durum", interactive=False, max_lines=1, value="Lütfen bir soru yazın.")
982
+ stopwatch_display = gr.Markdown(f"Hazır. İlk cevap için tahmini süre: {MAX_EXPECTED_TIME:.0f}s", elem_id="stopwatch_display")
983
+
984
+ with gr.Row():
985
+ submit_btn = gr.Button("Gönder", variant="primary")
986
+ clear_btn = gr.Button("Sohbeti Temizle")
987
+
988
+ with gr.Row():
989
+ like_btn = gr.Button("👍 Beğendim")
990
+ dislike_btn = gr.Button("👎 Beğenmedim (Yeni Cevap Dene)")
991
+
992
+ feedback_status_output = gr.Textbox(label="Geri Bildirim Durumu", interactive=False, max_lines=1)
993
+
994
+ # Gradio olay dinleyicileri
995
+ msg.submit(
996
+ respond,
997
+ [msg, chatbot],
998
+ [msg, chatbot, status_message, stopwatch_display],
999
+ api_name="respond"
1000
+ )
1001
+ submit_btn.click(
1002
+ respond,
1003
+ [msg, chatbot],
1004
+ [msg, chatbot, status_message, stopwatch_display],
1005
+ api_name="respond_button"
1006
+ )
1007
+ clear_btn.click(
1008
+ lambda: ([], gr.update(value="Lütfen bir soru yazın."), gr.update(value=f"Hazır. İlk cevap için tahmini süre: {MAX_EXPECTED_TIME:.0f}s"), gr.update(value="")),
1009
+ inputs=None,
1010
+ outputs=[chatbot, status_message, stopwatch_display, msg],
1011
+ queue=False
1012
+ )
1013
 
1014
+ # Beğenme butonu: Sadece geri bildirim kaydeder
1015
+ like_btn.click(
1016
+ partial(feedback_callback, liked=True),
1017
+ [chatbot],
1018
+ [feedback_status_output]
1019
+ )
1020
 
1021
+ # Beğenmeme butonu: Geri bildirim kaydeder ve YENİ CEVAP ÜRETİR
1022
+ dislike_btn.click(
1023
+ partial(feedback_callback, liked=False), # İlk olarak geri bildirimi kaydet
1024
+ [chatbot],
1025
+ [feedback_status_output]
1026
+ ).success( # Geri bildirim kaydedildikten sonra yeni cevap üret
1027
+ regenerate_answer,
1028
+ [chatbot],
1029
+ [msg, chatbot, status_message, stopwatch_display] # msg'yi de temizle ve diğerlerini güncelle
1030
+ )
 
 
 
 
1031
 
1032
+ demo.css = """
1033
+ #chatbot .message:nth-child(odd) {
1034
+ text-align: left;
1035
+ background-color: #f1f1f1;
1036
+ border-radius: 15px;
1037
+ padding: 10px;
1038
+ margin-right: 20%;
1039
+ }
1040
+ #chatbot .message:nth-child(even) {
1041
+ text-align: right;
1042
+ background-color: #dcf8c6;
1043
+ border-radius: 15px;
1044
+ padding: 10px;
1045
+ margin-left: 20%;
1046
+ }
1047
+ #chatbot .message {
1048
+ margin-bottom: 10px;
1049
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
1050
+ }
1051
+ .gradio-container .message-row.user {
1052
+ justify-content: flex-start !important;
1053
+ }
1054
+ .gradio-container .message-row.bot {
1055
+ justify-content: flex-end !important;
1056
+ }
1057
+ """
1058
 
1059
+ return demo
 
 
 
 
 
1060
 
1061
+ # === UYGULAMA BAŞLATMA ===
1062
+ if __name__ == "__main__":
1063
+ print("Chatbot başlatılıyor...")
 
 
 
1064
 
1065
+ # 1. Yerel dosya ve klasör yapısını kur
1066
+ setup_local_files()
 
 
 
1067
 
1068
+ # 2. Tüm ana bileşenleri yükle (model, tokenizer, embedder, FAISS, RLAgent)
1069
+ initialization_successful = initialize_components()
 
 
 
 
1070
 
1071
+ if not initialization_successful:
1072
+ print("UYARI: Bileşenler başlatılamadı. Uygulama düzgün çalışmayabilir.")
1073
+ else:
1074
+ print("Chatbot başlatılmaya hazır.")
1075
+ # Gradio arayüzünü oluştur ve başlat
1076
+ demo = create_chat_interface()
1077
+ demo.launch(debug=True, share=True)