Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,5 +1,14 @@
|
|
1 |
# app.py
|
2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
|
4 |
import torch
|
5 |
import gradio as gr
|
@@ -17,9 +26,10 @@ import random
|
|
17 |
from datetime import datetime
|
18 |
from collections import deque
|
19 |
import time
|
20 |
-
import re
|
21 |
import nltk
|
22 |
-
from nltk.corpus import stopwords
|
|
|
23 |
|
24 |
|
25 |
# === CSS ve Emoji Fonksiyonu ===
|
@@ -85,7 +95,7 @@ def add_emojis(text: str) -> str:
|
|
85 |
"merak": "🤔", "meraklı": "🤔",
|
86 |
"kültür": "🌍", "toplum": "👥",
|
87 |
"yalan": "🤥", "gerçek": "✨",
|
88 |
-
"clarisse": "🌸", "faber": "👴", "beatty": "🚨"
|
89 |
}
|
90 |
|
91 |
found_emojis = []
|
@@ -109,21 +119,23 @@ QA_PATH = "data/qa_dataset.jsonl" # LoRA fine-tuning örnekleri için kullanıl
|
|
109 |
BASE_MODEL_FOR_DEMO = "ytu-ce-cosmos/turkish-gpt2-large" # Kullandığınız temel model
|
110 |
LOR_MODEL_PATH = "lora_model_weights" # LoRA adaptör ağırlıklarının kaydedileceği/yükleneceği yol
|
111 |
|
112 |
-
FAHRENHEIT_TEXT_FILE = "fahrenheittt451.txt"
|
113 |
|
114 |
-
|
|
|
115 |
|
116 |
# === GLOBAL DEĞİŞKENLER ===
|
117 |
-
# Bu değişkenler initialize_components fonksiyonu
|
118 |
model = None
|
119 |
tokenizer = None
|
120 |
embedder = None
|
121 |
paragraphs = []
|
122 |
paragraph_embeddings = None
|
123 |
index = None
|
124 |
-
rl_agent = None
|
125 |
|
126 |
# === DOSYA VE KLASÖR YAPISI OLUŞTURMA ===
|
|
|
127 |
def setup_local_files():
|
128 |
os.makedirs('data', exist_ok=True)
|
129 |
os.makedirs(LOR_MODEL_PATH, exist_ok=True)
|
@@ -131,7 +143,8 @@ def setup_local_files():
|
|
131 |
# Gerekli dosyaların varlığını kontrol et ve yoksa boş oluştur
|
132 |
if not os.path.exists(FAHRENHEIT_TEXT_FILE):
|
133 |
print(f"HATA: '{FAHRENHEIT_TEXT_FILE}' bulunamadı. Lütfen bu dosyayı projenizin ana dizinine yerleştirin.")
|
134 |
-
|
|
|
135 |
|
136 |
if not os.path.exists(QA_PATH):
|
137 |
open(QA_PATH, 'a').close() # Boş QA dosyası oluştur
|
@@ -271,8 +284,8 @@ def lora_finetune(filepath: str = QA_PATH, lora_output_path: str = LOR_MODEL_PAT
|
|
271 |
peft_model.print_trainable_parameters()
|
272 |
|
273 |
args = TrainingArguments(
|
274 |
-
output_dir="./results",
|
275 |
-
num_train_epochs=3,
|
276 |
per_device_train_batch_size=2,
|
277 |
gradient_accumulation_steps=4,
|
278 |
logging_steps=10,
|
@@ -456,13 +469,13 @@ MONTAG_PERSONA = (
|
|
456 |
"**Eğer soru kitaptan veya doğrudan Montag'ın dünyasından bağımsızsa bile, cevabını Montag'ın bakış açısıyla, kitaplara ve bilgiye olan özlemiyle ilişkilendirerek ver.**"
|
457 |
)
|
458 |
|
|
|
459 |
def retrieve_context(question: str, chatbot_history: List[List[str]], k: int = 2) -> Tuple[List[str], str]:
|
460 |
"""FAISS indeksini kullanarak sorguya, geçmiş sohbete ve kitap odaklı anahtar kelimelere en uygun paragrafları getirir."""
|
461 |
if index is None or embedder is None or not paragraphs:
|
462 |
print("WARNING: FAISS index, embedder or paragraphs not initialized for context retrieval.")
|
463 |
return [], "Bağlam bulunamadı."
|
464 |
|
465 |
-
# --- history_queries'i burada tanımlıyoruz (SADECE BİR KERE) ---
|
466 |
history_queries = []
|
467 |
# Son 5 konuşma çiftini geçmişe dahil et (sadece kullanıcı mesajları)
|
468 |
for user_msg, _ in chatbot_history[-5:]:
|
@@ -471,29 +484,13 @@ def retrieve_context(question: str, chatbot_history: List[List[str]], k: int = 2
|
|
471 |
if cleaned_user_msg and not (("Montag düşünüyor..." in cleaned_user_msg) or ("saniyede üretildi" in cleaned_user_msg)):
|
472 |
history_queries.append(cleaned_user_msg)
|
473 |
|
474 |
-
#
|
475 |
-
# chatbot_history'deki tüm geçmiş mesajları (hem kullanıcı hem asistan) 'previous_paragraphs' olarak alabiliriz.
|
476 |
-
# Bu, zaten konuşulmuş konuların bağlam olarak tekrar getirilmesini engeller.
|
477 |
-
previous_paragraphs = []
|
478 |
-
for user_msg, assistant_msg in chatbot_history:
|
479 |
-
if user_msg:
|
480 |
-
cleaned_user_msg = user_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()
|
481 |
-
if cleaned_user_msg and not (("Montag düşünüyor..." in cleaned_user_msg) or ("saniyede üretildi" in cleaned_user_msg)):
|
482 |
-
previous_paragraphs.append(cleaned_user_msg)
|
483 |
-
if assistant_msg:
|
484 |
-
cleaned_assistant_msg = assistant_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()
|
485 |
-
if cleaned_assistant_msg and not (("Montag düşünüyor..." in cleaned_assistant_msg) or ("saniyede üretildi" in cleaned_assistant_msg)):
|
486 |
-
previous_paragraphs.append(cleaned_assistant_msg)
|
487 |
-
# Tekrar eden öğeleri kaldırıp listeyi benzersiz hale getirelim (opsiyonel ama iyi bir pratik)
|
488 |
-
previous_paragraphs = list(dict.fromkeys(previous_paragraphs))
|
489 |
-
# --- previous_paragraphs tanımı sonu ---
|
490 |
-
|
491 |
-
|
492 |
montag_identity_keywords = [
|
493 |
"guy montag", "montag", "itfaiyeci", "kitap yakmak", "yakıcı", "yangın",
|
494 |
"kül", "alev", "benzin", "kask", "savaş", "kaos", "direniş"
|
495 |
]
|
496 |
|
|
|
497 |
general_book_keywords = [
|
498 |
"kitap", "okuma", "bilgi", "düşünce", "hakikat", "yasak", "sansür",
|
499 |
"toplum", "televizyon", "mildred", "clarisse", "faber", "beatty",
|
@@ -501,22 +498,23 @@ def retrieve_context(question: str, chatbot_history: List[List[str]], k: int = 2
|
|
501 |
"kaçış", "nehir"
|
502 |
]
|
503 |
|
|
|
504 |
combined_query_text = f"{question} {' '.join(history_queries)} {' '.join(montag_identity_keywords)} {' '.join(general_book_keywords)}"
|
505 |
-
|
|
|
506 |
combined_query_text = ' '.join(combined_query_text.split())
|
507 |
|
508 |
try:
|
509 |
query_embedding = embedder.encode([combined_query_text], convert_to_numpy=True).astype(np.float32)
|
510 |
D, I = index.search(query_embedding, k)
|
511 |
-
|
512 |
-
retrieved_texts = [
|
513 |
unique_retrieved_texts = list(dict.fromkeys(retrieved_texts))
|
514 |
context_text = "\n".join(unique_retrieved_texts)
|
515 |
-
return unique_retrieved_texts, context_text
|
516 |
except Exception as e:
|
517 |
print(f"Bağlam alınırken hata: {e}")
|
518 |
-
return [], "Bağlam
|
519 |
-
|
520 |
|
521 |
# === ALTERNATİF CEVAPLAR ===
|
522 |
alternative_responses = [
|
@@ -581,27 +579,26 @@ def generate_answer(question: str, chatbot_history: List[List[str]]) -> Tuple[st
|
|
581 |
print("ERROR: Model, tokenizer, embedder veya RL Agent başlatılmamış.")
|
582 |
return generate_alternative_response(question), []
|
583 |
|
584 |
-
try:
|
585 |
gen_params = rl_agent.get_generation_params()
|
586 |
-
|
587 |
# Bağlamı al (hem metin hem de doküman listesi olarak)
|
588 |
-
# retrieve_context fonksiyonuna chatbot_history'yi doğru bir şekilde iletiyoruz.
|
589 |
-
# Eğer retrieve_context kendi içinde previous_paragraphs adında bir değişken bekliyorsa,
|
590 |
-
# o fonksiyonun içindeki tanımlama/atama doğru yapılmalı.
|
591 |
retrieved_docs, context_text = retrieve_context(question, chatbot_history, k=2)
|
592 |
|
593 |
history_text = ""
|
594 |
# Son 3 konuşma çiftini geçmişe dahil et (emojileri temizleyerek)
|
595 |
if chatbot_history:
|
596 |
-
|
597 |
-
|
|
|
598 |
cleaned_user_msg = user_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()
|
599 |
-
|
600 |
-
|
601 |
-
if assistant_msg
|
602 |
cleaned_assistant_msg = assistant_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()
|
603 |
-
|
604 |
-
|
|
|
605 |
prompt = (
|
606 |
f"{MONTAG_PERSONA}\n\n"
|
607 |
f"--- Bağlamdan Önemli Bilgiler ---\n{context_text}\n\n"
|
@@ -609,13 +606,14 @@ def generate_answer(question: str, chatbot_history: List[List[str]]) -> Tuple[st
|
|
609 |
f"Kullanıcı: {question}\n"
|
610 |
f"Montag: "
|
611 |
)
|
612 |
-
|
613 |
-
# Prompt uzunluğunu kontrol et ve gerekirse kısalt
|
614 |
encoded_inputs = tokenizer.encode_plus(
|
615 |
prompt,
|
616 |
return_tensors="pt",
|
617 |
-
truncation=True,
|
618 |
-
max_length=512,
|
|
|
619 |
).to(DEVICE)
|
620 |
|
621 |
inputs = encoded_inputs["input_ids"]
|
@@ -624,166 +622,145 @@ def generate_answer(question: str, chatbot_history: List[List[str]]) -> Tuple[st
|
|
624 |
outputs = model.generate(
|
625 |
inputs,
|
626 |
attention_mask=attention_mask,
|
627 |
-
max_new_tokens=150,
|
628 |
do_sample=True,
|
629 |
top_p=0.9,
|
630 |
temperature=gen_params["temperature"],
|
631 |
-
repetition_penalty=gen_params["repetition_penalty"],
|
632 |
-
no_repeat_ngram_size=
|
633 |
-
num_beams=1,
|
634 |
pad_token_id=tokenizer.eos_token_id,
|
635 |
eos_token_id=tokenizer.eos_token_id,
|
|
|
636 |
)
|
637 |
-
|
638 |
raw_response_with_prompt = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
639 |
-
response = raw_response_with_prompt # Tüm temizlikler bu 'response' değişkeni üzerinde yapılacak
|
640 |
|
641 |
-
# ---
|
642 |
-
|
643 |
-
|
644 |
-
|
645 |
-
|
646 |
-
|
647 |
-
|
648 |
-
|
649 |
-
"
|
650 |
-
|
651 |
-
|
652 |
-
|
653 |
-
|
654 |
-
|
655 |
-
|
656 |
-
|
657 |
-
|
658 |
-
|
659 |
-
last_assistant_response_in_history = (
|
660 |
-
last_assistant_response_in_history.replace('📚', '')
|
661 |
-
.replace('🧠', '')
|
662 |
-
.replace('🔥', '')
|
663 |
-
.replace('✅', '')
|
664 |
-
.strip()
|
665 |
-
)
|
666 |
-
if "saniyede üretildi" in last_assistant_response_in_history.lower():
|
667 |
-
last_assistant_response_in_history = ""
|
668 |
-
|
669 |
-
if last_assistant_response_in_history:
|
670 |
-
cleaned_raw_response_norm_space = re.sub(r'\s+', ' ', raw_response_with_prompt).strip()
|
671 |
-
cleaned_last_response_norm_space = re.sub(r'\s+', ' ', last_assistant_response_in_history).strip()
|
672 |
-
|
673 |
-
if cleaned_raw_response_norm_space.lower().startswith(cleaned_last_response_norm_space.lower()):
|
674 |
-
response = raw_response_with_prompt[len(last_assistant_response_in_history):].strip()
|
675 |
-
print(f"DEBUG: Önceki Montag cevabı tespit edildi ve temizlendi: '{last_assistant_response_in_history[:50]}...'")
|
676 |
-
|
677 |
-
# --- ADIM 2: İlk "Montag:" Etiketini ve Prompt Kalıntılarını Temizleme ---
|
678 |
-
match = re.search(r'(?i)Montag:\s*(.*)', response, re.DOTALL)
|
679 |
-
if match:
|
680 |
-
response = match.group(1).strip()
|
681 |
-
else:
|
682 |
-
prompt_decoded_for_comparison = tokenizer.decode(inputs[0], skip_special_tokens=True)
|
683 |
-
if response.startswith(prompt_decoded_for_comparison):
|
684 |
-
response = response[len(prompt_decoded_for_comparison):].strip()
|
685 |
|
686 |
-
#
|
687 |
persona_lines = [line.strip() for line in MONTAG_PERSONA.split('\n') if line.strip()]
|
688 |
for line in persona_lines:
|
689 |
if response.lower().startswith(line.lower()):
|
690 |
response = response[len(line):].strip()
|
691 |
|
|
|
692 |
response = response.replace("<unk>", "").strip()
|
693 |
response = response.replace(" .", ".").replace(" ,", ",").replace(" ?", "?").replace(" !", "!")
|
694 |
|
|
|
695 |
response = re.sub(r'Kullanıcı:\s*', '', response, flags=re.IGNORECASE)
|
696 |
response = re.sub(r'Montag:\s*', '', response, flags=re.IGNORECASE)
|
697 |
|
|
|
698 |
if "ETİKETLER:" in response:
|
699 |
response = response.split("ETİKETLER:", 1)[0].strip()
|
700 |
|
701 |
-
|
|
|
702 |
|
703 |
-
#
|
704 |
irrelevant_dialogue_patterns = [
|
705 |
-
|
706 |
-
|
707 |
-
|
708 |
-
|
709 |
-
|
710 |
-
|
711 |
-
|
712 |
-
r'içir unidur', r'aligutat fakdam', r'tetal inlay', r'pessotim elgun',
|
713 |
-
r'nisman tarejoglu', r'faksom', r'achisteloy vandleradia', r'vęudis',
|
714 |
-
r'eltareh', r'eldlar', r'fotjid', r'zuhalibalyon',
|
715 |
-
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.',
|
716 |
-
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.',
|
717 |
-
|
718 |
-
r' ✅',
|
719 |
-
r' 📚',
|
720 |
]
|
721 |
-
|
722 |
for pattern in irrelevant_dialogue_patterns:
|
723 |
response = re.sub(pattern, '', response, flags=re.IGNORECASE).strip()
|
724 |
-
|
|
|
725 |
response = re.sub(r'\s+', ' ', response).strip()
|
726 |
|
727 |
-
# --- ADIM 5: Filtreleme Mantığı (Puanlama Sistemi) ---
|
728 |
-
rejection_score = 0
|
729 |
-
filter_reasons = []
|
730 |
|
731 |
-
|
732 |
-
|
733 |
-
|
734 |
-
|
735 |
-
|
736 |
-
rejection_score += 10
|
737 |
-
filter_reasons.append("Hiç harf içermiyor (sadece noktalama/sayı).")
|
738 |
-
|
739 |
-
triggered_generic_phrases = [phrase for phrase in generic_or_nonsense_phrases if phrase in response.lower()]
|
740 |
-
if triggered_generic_phrases:
|
741 |
-
rejection_score += len(triggered_generic_phrases) * 3
|
742 |
-
filter_reasons.append(f"Anlamsız/istenmeyen ifade tespit edildi: {triggered_generic_phrases}.")
|
743 |
|
744 |
-
has_montag_relevance = any(keyword in response.lower() for keyword in montag_keywords)
|
745 |
-
|
746 |
-
if len(response.split()) > 20 and not has_montag_relevance:
|
747 |
-
rejection_score += 1
|
748 |
-
filter_reasons.append("Montag/bağlamsal anahtar kelime yok ve cevap uzun.")
|
749 |
-
|
750 |
-
aggressive_words_found = [word for word in aggressive_words if word in response.lower()]
|
751 |
-
if aggressive_words_found:
|
752 |
-
rejection_score += 5
|
753 |
-
filter_reasons.append(f"Agresif/istenmeyen kelime tespit edildi: {aggressive_words_found}.")
|
754 |
-
|
755 |
-
if rejection_score >= 5:
|
756 |
-
print(f"DEBUG: FİLTRELEME - Cevap YETERSİZ/ANLAMSIZ/ALAKASIZ. Toplam Puan: {rejection_score}")
|
757 |
-
for reason in filter_reasons:
|
758 |
-
print(f" - Sebep: {reason}")
|
759 |
-
print(f"INFO: Üretilen cevap ('{response}') filtreleri geçemedi. Alternatif üretiliyor.")
|
760 |
-
return generate_alternative_response(question), retrieved_docs
|
761 |
-
|
762 |
# Cümle Bölme ve Limitleme Mantığı
|
763 |
sentences = []
|
764 |
-
|
|
|
765 |
for s in split_by_punctuation:
|
766 |
s_stripped = s.strip()
|
767 |
if s_stripped:
|
768 |
sentences.append(s_stripped)
|
769 |
-
if len(sentences) >= 6:
|
770 |
break
|
771 |
-
final_response_text = ' '.join(sentences).strip()
|
772 |
-
|
773 |
-
if not final_response_text:
|
774 |
-
print("INFO: Filtrelerden geçen cevap boş kaldı. Alternatif üretiliyor.")
|
775 |
-
return generate_alternative_response(question), retrieved_docs
|
776 |
-
|
777 |
-
final_response = add_emojis(final_response_text)
|
778 |
-
return final_response, retrieved_docs
|
779 |
|
780 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
781 |
print(f"Error generating answer: {e}")
|
782 |
-
return generate_alternative_response(question), []
|
783 |
-
|
784 |
|
785 |
|
786 |
-
|
|
|
787 |
def respond(msg: str, chatbot_history: List[List[str]], progress=gr.Progress()) -> Tuple[str, List[List[str]], str, str]:
|
788 |
if not msg.strip():
|
789 |
return "", chatbot_history, "Lütfen bir soru yazın.", "---"
|
@@ -816,12 +793,12 @@ def respond(msg: str, chatbot_history: List[List[str]], progress=gr.Progress())
|
|
816 |
for i in range(10):
|
817 |
current_progress_percent = start_percent + (end_percent - start_percent) * (i / 9)
|
818 |
elapsed_time = time.time() - start_time_overall
|
819 |
-
|
820 |
if is_first_real_user_question:
|
821 |
stopwatch_text = f"İlk Cevap: {elapsed_time:.2f}s / {MAX_EXPECTED_TIME:.0f}s"
|
822 |
else:
|
823 |
stopwatch_text = f"Geçen Süre: {elapsed_time:.2f}s"
|
824 |
-
|
825 |
yield gr.update(value=""), new_history, f"{desc} %{int(current_progress_percent*100)}", stopwatch_text
|
826 |
time.sleep(MAX_EXPECTED_TIME / (len(progress_steps) * 10 * 2))
|
827 |
|
@@ -836,9 +813,6 @@ def respond(msg: str, chatbot_history: List[List[str]], progress=gr.Progress())
|
|
836 |
else:
|
837 |
new_history.append([msg, answer])
|
838 |
|
839 |
-
# RL Agent'a deneyimi kaydet (liked parametresi feedback_callback'te verilecek)
|
840 |
-
# Burada direkt kaydetmiyoruz, feedback_callback'te kaydediyoruz.
|
841 |
-
|
842 |
yield gr.update(value=""), new_history, f"Cevap {response_time} saniyede üretildi. ✅", f"{response_time:.2f}s"
|
843 |
|
844 |
def regenerate_answer(chatbot_history: List[List[str]], progress=gr.Progress()) -> Tuple[str, List[List[str]], str, str]:
|
@@ -847,8 +821,13 @@ def regenerate_answer(chatbot_history: List[List[str]], progress=gr.Progress())
|
|
847 |
|
848 |
# Son gerçek kullanıcı sorusunu bul
|
849 |
last_user_question = None
|
|
|
|
|
|
|
|
|
850 |
for i in range(len(chatbot_history) - 1, -1, -1):
|
851 |
-
user_msg,
|
|
|
852 |
if user_msg is not None and not (("Montag düşünüyor..." in user_msg) or ("saniyede ��retildi" in user_msg)):
|
853 |
last_user_question = user_msg
|
854 |
break
|
@@ -856,40 +835,59 @@ def regenerate_answer(chatbot_history: List[List[str]], progress=gr.Progress())
|
|
856 |
if not last_user_question:
|
857 |
return "", chatbot_history, "Yeniden üretilecek bir soru bulunamadı.", "---"
|
858 |
|
859 |
-
# Geçmişten son cevabı kaldır (eğer varsa)
|
860 |
-
|
861 |
-
|
862 |
-
cleaned_history.pop() # Son cevabı kaldır
|
863 |
-
|
864 |
-
temp_chatbot_history_for_gen = [list(pair) for pair in cleaned_history]
|
865 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
866 |
start_time_overall = time.time()
|
867 |
initial_stopwatch_text = f"Geçen Süre: 0.00s"
|
868 |
progress_prefix = "Montag yeni bir cevap düşünüyor... "
|
869 |
-
|
|
|
870 |
|
871 |
-
|
872 |
progress_steps = [
|
873 |
(f"{progress_prefix}🔄", 0.0, 0.3),
|
874 |
(f"{progress_prefix}🧠", 0.3, 0.6),
|
875 |
(f"{progress_prefix}📚", 0.6, 0.9),
|
876 |
]
|
877 |
-
for desc, start_percent, end_percent in progress_steps: #
|
878 |
for i in range(10):
|
879 |
current_progress_percent = start_percent + (end_percent - start_percent) * (i / 9)
|
880 |
elapsed_time = time.time() - start_time_overall
|
881 |
-
|
882 |
stopwatch_text = f"Geçen Süre: {elapsed_time:.2f}s"
|
883 |
-
|
|
|
884 |
time.sleep(MAX_EXPECTED_TIME / (len(progress_steps) * 10 * 2))
|
885 |
|
886 |
-
|
|
|
887 |
end_time = time.time()
|
888 |
response_time = round(end_time - start_time_overall, 2)
|
889 |
|
890 |
-
|
|
|
891 |
|
892 |
-
yield "",
|
893 |
|
894 |
def feedback_callback(chatbot_history: List[List[str]], liked: bool) -> str:
|
895 |
if not chatbot_history:
|
@@ -899,37 +897,35 @@ def feedback_callback(chatbot_history: List[List[str]], liked: bool) -> str:
|
|
899 |
last_assistant_answer = None
|
900 |
|
901 |
# Sondan başlayarak gerçek kullanıcı sorusunu ve bot cevabını bul
|
902 |
-
#
|
903 |
for i in range(len(chatbot_history) - 1, -1, -1):
|
904 |
-
|
905 |
-
if
|
906 |
-
|
907 |
-
|
908 |
-
|
909 |
-
|
910 |
-
|
911 |
-
# Eğer son eleman sadece cevapsız bir kullanıcı sorusuysa ve bir önceki cevabı kaydetmek istiyorsak
|
912 |
-
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]) \
|
913 |
-
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]):
|
914 |
-
last_user_question = chatbot_history[i-1][0]
|
915 |
-
last_assistant_answer = chatbot_history[i-1][1]
|
916 |
-
break
|
917 |
|
918 |
if last_user_question and last_assistant_answer:
|
919 |
-
#
|
920 |
-
|
|
|
|
|
|
|
|
|
|
|
921 |
|
922 |
# RL Agent'a deneyimi kaydet
|
923 |
-
# Emojileri temizleyerek gönderiyoruz
|
924 |
rl_agent.record_experience(
|
925 |
-
|
926 |
-
|
927 |
-
liked
|
928 |
)
|
929 |
|
930 |
# Eğer beğenildiyse, LoRA fine-tuning için QA_PATH'e de ekle
|
931 |
if liked:
|
932 |
-
qa_pair = {"question":
|
933 |
try:
|
934 |
with open(QA_PATH, "a", encoding="utf-8") as f:
|
935 |
f.write(json.dumps(qa_pair, ensure_ascii=False) + "\n")
|
@@ -940,55 +936,14 @@ def feedback_callback(chatbot_history: List[List[str]], liked: bool) -> str:
|
|
940 |
if count_qa_examples(QA_PATH) % 10 == 0: # Her 10 yeni beğenilen örnekte bir fine-tune yap
|
941 |
print("👍 Yeterli sayıda yeni beğeni var, LoRA fine-tuning başlatılıyor...")
|
942 |
lora_finetune(QA_PATH, LOR_MODEL_PATH)
|
943 |
-
# Model yeniden yüklenebilir veya PEFT adaptörü apply edilebilir.
|
944 |
-
# initialize_components() çağrısı ile global model güncelleniyor.
|
945 |
return "Geri bildiriminiz kaydedildi ve model eğitimi tetiklendi. Teşekkürler! 👍"
|
946 |
-
|
947 |
-
|
948 |
-
|
|
|
949 |
return "Geri bildirim kaydedilemedi. Geçmişte yeterli sohbet bulunmuyor. ❌"
|
950 |
|
951 |
# === Gradio arayüzü ===
|
952 |
-
|
953 |
-
# --- GRADIO İÇİN YENİ CEVAP ÜRETME FONKSİYONU ---
|
954 |
-
def regenerate_answer(chat_history: list):
|
955 |
-
if not chat_history:
|
956 |
-
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"
|
957 |
-
|
958 |
-
# Sohbet geçmişindeki son kullanıcı sorusunu al
|
959 |
-
last_user_question = chat_history[-1][0] # Son konuşmanın kullanıcı mesajı
|
960 |
-
if last_user_question is None: # Hata durumunda boş döndür
|
961 |
-
return "", chat_history, "Yeni cevap üretilemedi: Son soru bulunamadı.", f"Hazır. İlk cevap için tahmini süre: {MAX_EXPECTED_TIME:.0f}s"
|
962 |
-
|
963 |
-
# Montag'ın düşündüğünü gösteren bir mesaj
|
964 |
-
current_chat_history = chat_history.copy()
|
965 |
-
current_chat_history[-1][1] = "Montag düşünüyor... 🤔" # En son bot cevabını geçici olarak değiştir
|
966 |
-
yield gr.update(value=""), current_chat_history, "Montag yeni bir cevap üzerinde düşünüyor...", gr.update(value="Cevap üretiliyor...")
|
967 |
-
|
968 |
-
start_time = time.time()
|
969 |
-
# generate_answer fonksiyonunu çağırarak yeni bir cevap üret
|
970 |
-
# chat_history'nin son elemanı zaten "Montag düşünüyor..." olduğu için,
|
971 |
-
# generate_answer'a geçmişin bu hali gönderilirse sorun olmaz.
|
972 |
-
# Önemli olan, generate_answer'ın içinde kullanıcının son sorusunun doğru şekilde alınmasıdır.
|
973 |
-
# Bu durumda `last_user_question` doğrudan kullanılabilir.
|
974 |
-
new_raw_answer, _ = generate_answer(last_user_question, chat_history[:-1]) # Önceki cevabı hariç tutarak gönder
|
975 |
-
|
976 |
-
new_final_answer = add_emojis(new_raw_answer)
|
977 |
-
end_time = time.time()
|
978 |
-
response_time = end_time - start_time
|
979 |
-
new_time_taken_message = f"(yaklaşık {response_time:.2f} saniyede üretildi)"
|
980 |
-
|
981 |
-
# Sohbet geçmişindeki en son bot cevabını bu yeni cevapla güncelle
|
982 |
-
# NOT: Bu, beğenilmeyen cevabın yerine geçer. Eğer ikisini de görmek isterseniz,
|
983 |
-
# yeni bir [kullanıcı_sorusu, yeni_cevap] çifti eklemeniz gerekir.
|
984 |
-
# Ancak "dislike"ın amacı eskiyi beğenmeyip yenisini istemek olduğu için yerine koymak daha mantıklı.
|
985 |
-
chat_history[-1][1] = f"{new_final_answer}\n{new_time_taken_message}"
|
986 |
-
|
987 |
-
return gr.update(value=""), chat_history, "Yeni cevap üretildi.", f"Cevap {response_time:.2f} saniyede üretildi."
|
988 |
-
|
989 |
-
|
990 |
-
|
991 |
-
# --- GRADIO ARAYÜZÜNÜ OLUŞTURAN FONKSİYON ---
|
992 |
def create_chat_interface():
|
993 |
with gr.Blocks(theme=gr.themes.Soft(), css=current_css) as demo:
|
994 |
gr.Markdown("""
|
|
|
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
|
|
|
26 |
from datetime import datetime
|
27 |
from collections import deque
|
28 |
import time
|
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 |
|
34 |
|
35 |
# === CSS ve Emoji Fonksiyonu ===
|
|
|
95 |
"merak": "🤔", "meraklı": "🤔",
|
96 |
"kültür": "🌍", "toplum": "👥",
|
97 |
"yalan": "🤥", "gerçek": "✨",
|
98 |
+
"clarisse": "🌸", "faber": "👴", "beatty": "🚨" # Montag karakterleri için emojiler
|
99 |
}
|
100 |
|
101 |
found_emojis = []
|
|
|
119 |
BASE_MODEL_FOR_DEMO = "ytu-ce-cosmos/turkish-gpt2-large" # Kullandığınız temel model
|
120 |
LOR_MODEL_PATH = "lora_model_weights" # LoRA adaptör ağırlıklarının kaydedileceği/yükleneceği yol
|
121 |
|
122 |
+
FAHRENHEIT_TEXT_FILE = "fahrenheittt451.txt" # Kitap metin dosyanızın adı
|
123 |
|
124 |
+
# Tahmini maksimum cevap süresi (saniye) - Donanım ve modele göre ayarlayın.
|
125 |
+
MAX_EXPECTED_TIME = 120.0 # Ortalama bir değer, kendi sisteminize göre ayarlayın!
|
126 |
|
127 |
# === GLOBAL DEĞİŞKENLER ===
|
128 |
+
# Bu değişkenler initialize_components fonksiyonu tarafından atanacak
|
129 |
model = None
|
130 |
tokenizer = None
|
131 |
embedder = None
|
132 |
paragraphs = []
|
133 |
paragraph_embeddings = None
|
134 |
index = None
|
135 |
+
rl_agent = None # RLAgent nesnesi
|
136 |
|
137 |
# === DOSYA VE KLASÖR YAPISI OLUŞTURMA ===
|
138 |
+
# Google Drive bağlantıları olmadan yerel dosya yapısını oluşturur
|
139 |
def setup_local_files():
|
140 |
os.makedirs('data', exist_ok=True)
|
141 |
os.makedirs(LOR_MODEL_PATH, exist_ok=True)
|
|
|
143 |
# Gerekli dosyaların varlığını kontrol et ve yoksa boş oluştur
|
144 |
if not os.path.exists(FAHRENHEIT_TEXT_FILE):
|
145 |
print(f"HATA: '{FAHRENHEIT_TEXT_FILE}' bulunamadı. Lütfen bu dosyayı projenizin ana dizinine yerleştirin.")
|
146 |
+
# Uygulama metin olmadan çalışamaz, bu yüzden burada çıkış yapmayı düşünebilirsiniz.
|
147 |
+
# raise FileNotFoundError(f"{FAHRENHEIT_TEXT_FILE} not found.")
|
148 |
|
149 |
if not os.path.exists(QA_PATH):
|
150 |
open(QA_PATH, 'a').close() # Boş QA dosyası oluştur
|
|
|
284 |
peft_model.print_trainable_parameters()
|
285 |
|
286 |
args = TrainingArguments(
|
287 |
+
output_dir="./results", # Geçici çıktılar buraya
|
288 |
+
num_train_epochs=3, # Daha az epoch ile başlayabilirsiniz
|
289 |
per_device_train_batch_size=2,
|
290 |
gradient_accumulation_steps=4,
|
291 |
logging_steps=10,
|
|
|
469 |
"**Eğer soru kitaptan veya doğrudan Montag'ın dünyasından bağımsızsa bile, cevabını Montag'ın bakış açısıyla, kitaplara ve bilgiye olan özlemiyle ilişkilendirerek ver.**"
|
470 |
)
|
471 |
|
472 |
+
# === BAĞLAM ALMA FONKSİYONU ===
|
473 |
def retrieve_context(question: str, chatbot_history: List[List[str]], k: int = 2) -> Tuple[List[str], str]:
|
474 |
"""FAISS indeksini kullanarak sorguya, geçmiş sohbete ve kitap odaklı anahtar kelimelere en uygun paragrafları getirir."""
|
475 |
if index is None or embedder is None or not paragraphs:
|
476 |
print("WARNING: FAISS index, embedder or paragraphs not initialized for context retrieval.")
|
477 |
return [], "Bağlam bulunamadı."
|
478 |
|
|
|
479 |
history_queries = []
|
480 |
# Son 5 konuşma çiftini geçmişe dahil et (sadece kullanıcı mesajları)
|
481 |
for user_msg, _ in chatbot_history[-5:]:
|
|
|
484 |
if cleaned_user_msg and not (("Montag düşünüyor..." in cleaned_user_msg) or ("saniyede üretildi" in cleaned_user_msg)):
|
485 |
history_queries.append(cleaned_user_msg)
|
486 |
|
487 |
+
# Montag'ın temel kimliğini ve görevini yansıtan doğrudan anahtar kelimeler
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
488 |
montag_identity_keywords = [
|
489 |
"guy montag", "montag", "itfaiyeci", "kitap yakmak", "yakıcı", "yangın",
|
490 |
"kül", "alev", "benzin", "kask", "savaş", "kaos", "direniş"
|
491 |
]
|
492 |
|
493 |
+
# Romanın genel temaları ve önemli karakterleri
|
494 |
general_book_keywords = [
|
495 |
"kitap", "okuma", "bilgi", "düşünce", "hakikat", "yasak", "sansür",
|
496 |
"toplum", "televizyon", "mildred", "clarisse", "faber", "beatty",
|
|
|
498 |
"kaçış", "nehir"
|
499 |
]
|
500 |
|
501 |
+
# Kullanıcı sorgusu, geçmiş sorguları ve tüm anahtar kelimeleri birleştirerek tek bir arama metni oluştur
|
502 |
combined_query_text = f"{question} {' '.join(history_queries)} {' '.join(montag_identity_keywords)} {' '.join(general_book_keywords)}"
|
503 |
+
|
504 |
+
# Fazla boşlukları temizleyelim
|
505 |
combined_query_text = ' '.join(combined_query_text.split())
|
506 |
|
507 |
try:
|
508 |
query_embedding = embedder.encode([combined_query_text], convert_to_numpy=True).astype(np.float32)
|
509 |
D, I = index.search(query_embedding, k)
|
510 |
+
|
511 |
+
retrieved_texts = [paragraphs[i] for i in I[0] if i < len(paragraphs)]
|
512 |
unique_retrieved_texts = list(dict.fromkeys(retrieved_texts))
|
513 |
context_text = "\n".join(unique_retrieved_texts)
|
514 |
+
return unique_retrieved_texts, context_text # Hem liste hem de birleştirilmiş metni döndür
|
515 |
except Exception as e:
|
516 |
print(f"Bağlam alınırken hata: {e}")
|
517 |
+
return [], "Bağlam alınırken bir sorun oluştu."
|
|
|
518 |
|
519 |
# === ALTERNATİF CEVAPLAR ===
|
520 |
alternative_responses = [
|
|
|
579 |
print("ERROR: Model, tokenizer, embedder veya RL Agent başlatılmamış.")
|
580 |
return generate_alternative_response(question), []
|
581 |
|
582 |
+
try:
|
583 |
gen_params = rl_agent.get_generation_params()
|
584 |
+
|
585 |
# Bağlamı al (hem metin hem de doküman listesi olarak)
|
|
|
|
|
|
|
586 |
retrieved_docs, context_text = retrieve_context(question, chatbot_history, k=2)
|
587 |
|
588 |
history_text = ""
|
589 |
# Son 3 konuşma çiftini geçmişe dahil et (emojileri temizleyerek)
|
590 |
if chatbot_history:
|
591 |
+
recent_dialogue = []
|
592 |
+
for user_msg, assistant_msg in chatbot_history[-3:]: # Son 3 konuşma yeterli
|
593 |
+
if user_msg:
|
594 |
cleaned_user_msg = user_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()
|
595 |
+
if cleaned_user_msg and not (("Montag düşünüyor..." in cleaned_user_msg) or ("saniyede üretildi" in cleaned_user_msg)):
|
596 |
+
history_text += f"Kullanıcı: {cleaned_user_msg}\n"
|
597 |
+
if assistant_msg:
|
598 |
cleaned_assistant_msg = assistant_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()
|
599 |
+
if cleaned_assistant_msg and not (("Montag düşünüyor..." in cleaned_assistant_msg) or ("saniyede üretildi" in cleaned_assistant_msg)):
|
600 |
+
history_text += f"Montag: {cleaned_assistant_msg}\n"
|
601 |
+
|
602 |
prompt = (
|
603 |
f"{MONTAG_PERSONA}\n\n"
|
604 |
f"--- Bağlamdan Önemli Bilgiler ---\n{context_text}\n\n"
|
|
|
606 |
f"Kullanıcı: {question}\n"
|
607 |
f"Montag: "
|
608 |
)
|
609 |
+
|
610 |
+
# Prompt uzunluğunu kontrol et ve gerekirse kısalt (tokenizer'ın truncation'ına güveniyoruz)
|
611 |
encoded_inputs = tokenizer.encode_plus(
|
612 |
prompt,
|
613 |
return_tensors="pt",
|
614 |
+
truncation=True, # Max_length'i aşarsa kırp
|
615 |
+
max_length=512, # Modelin alabileceği maksimum token sayısı
|
616 |
+
# padding="max_length" # Tek input için padding gerekli değil, ama batching için kullanılabilir
|
617 |
).to(DEVICE)
|
618 |
|
619 |
inputs = encoded_inputs["input_ids"]
|
|
|
622 |
outputs = model.generate(
|
623 |
inputs,
|
624 |
attention_mask=attention_mask,
|
625 |
+
max_new_tokens=150, # Üretilecek maksimum yeni token sayısı
|
626 |
do_sample=True,
|
627 |
top_p=0.9,
|
628 |
temperature=gen_params["temperature"],
|
629 |
+
repetition_penalty=gen_params["repetition_penalty"],
|
630 |
+
no_repeat_ngram_size=5, # 5 kelimelik tekrar eden dizileri engelle
|
631 |
+
num_beams=1, # Sampling kullandığımız için beam search'e gerek yok
|
632 |
pad_token_id=tokenizer.eos_token_id,
|
633 |
eos_token_id=tokenizer.eos_token_id,
|
634 |
+
early_stopping=True # EOS token'ı görürse üretimi durdur
|
635 |
)
|
|
|
636 |
raw_response_with_prompt = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
|
|
637 |
|
638 |
+
# --- Cevap Temizleme ve Post-Processing İyileştirmeleri ---
|
639 |
+
response = raw_response_with_prompt
|
640 |
+
|
641 |
+
# 1. Prompt'un kendisini ve "Montag:" kısmını cevaptan ayırma
|
642 |
+
# Modelin çıktısında "Montag: " kısmından sonraki bölümü almaya çalış
|
643 |
+
if prompt in response: # Eğer tüm prompt output içinde ise
|
644 |
+
response = response[len(prompt):].strip()
|
645 |
+
else: # Eğer prompt'un sadece son kısmı "Montag: " tekrar ediyorsa
|
646 |
+
response_start_marker = "Montag:"
|
647 |
+
if response_start_marker in response:
|
648 |
+
response = response.split(response_start_marker, 1)[-1].strip()
|
649 |
+
# Eğer prompt'un başı tekrar ediyorsa
|
650 |
+
else:
|
651 |
+
prompt_decoded_for_comparison = tokenizer.decode(inputs[0], skip_special_tokens=True)
|
652 |
+
if response.startswith(prompt_decoded_for_comparison):
|
653 |
+
response = response[len(prompt_decoded_for_comparison):].strip()
|
654 |
+
else:
|
655 |
+
response = raw_response_with_prompt.strip() # Hiçbir şey bulunamazsa ham cevabı al
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
656 |
|
657 |
+
# 2. Persona talimatlarının cevapta tekrarlanmasını engelle (güncel MONTAG_PERSONA'ya göre)
|
658 |
persona_lines = [line.strip() for line in MONTAG_PERSONA.split('\n') if line.strip()]
|
659 |
for line in persona_lines:
|
660 |
if response.lower().startswith(line.lower()):
|
661 |
response = response[len(line):].strip()
|
662 |
|
663 |
+
# 3. Fazladan "Kullanıcı: " veya "Montag: " tekrarlarını ve anlamsız tokenleri temizle
|
664 |
response = response.replace("<unk>", "").strip()
|
665 |
response = response.replace(" .", ".").replace(" ,", ",").replace(" ?", "?").replace(" !", "!")
|
666 |
|
667 |
+
# Ek olarak, cevabın içinde hala kalmış olabilecek "Kullanıcı:" veya "Montag:" etiketlerini temizle
|
668 |
response = re.sub(r'Kullanıcı:\s*', '', response, flags=re.IGNORECASE)
|
669 |
response = re.sub(r'Montag:\s*', '', response, flags=re.IGNORECASE)
|
670 |
|
671 |
+
# Cevabın içinde "ETİKETLER:" gibi ifadeler varsa temizle
|
672 |
if "ETİKETLER:" in response:
|
673 |
response = response.split("ETİKETLER:", 1)[0].strip()
|
674 |
|
675 |
+
# Cevabın sonundaki "[...]" gibi ifadeleri temizle
|
676 |
+
response = re.sub(r'\[\s*\.{3,}\s*\]', '', response).strip() # "[...]" veya "[... ]" gibi ifadeleri temizler
|
677 |
|
678 |
+
# Modelin ürettiği alakasız diyalog kalıplarını temizle
|
679 |
irrelevant_dialogue_patterns = [
|
680 |
+
r'O, şu anda ne yapıyor\?', r'O, "Bu, bu" diye cevap verdi\.',
|
681 |
+
r'o, "Benim ne yaptığımı biliyor musun\?" diye sordu\.', r'Sen, "Bilmiyorum, ben… bilmiyorum" dedin\.',
|
682 |
+
r'Neden\?" dedi Montag\.', r'"Çünkü, sadece bir kimseyim\." - Bu bir soru değil\.',
|
683 |
+
r'Montag, "([^"]*)" dedi\.', # Genel olarak Montag bir şey dediği kalıplar
|
684 |
+
r'Bir: Bir.', r'İki: İki.', # Sayı sayma kalıpları
|
685 |
+
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
|
686 |
+
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
687 |
]
|
|
|
688 |
for pattern in irrelevant_dialogue_patterns:
|
689 |
response = re.sub(pattern, '', response, flags=re.IGNORECASE).strip()
|
690 |
+
|
691 |
+
# Fazla boşlukları tek boşluğa indirge
|
692 |
response = re.sub(r'\s+', ' ', response).strip()
|
693 |
|
|
|
|
|
|
|
694 |
|
695 |
+
# Agresif veya hakaret içeren kelimeleri kontrol et
|
696 |
+
aggressive_words = ["aptal", "salak", "gerizekalı", "saçma", "bilmiyorsun", "yanlışsın", "boş konuşma", "kaba", "agresif", "aptal gibi"]
|
697 |
+
if any(word in response.lower() for word in aggressive_words):
|
698 |
+
print(f"DEBUG: FİLTRELEME - Agresif kelime tespit edildi: '{response}'.")
|
699 |
+
return generate_alternative_response(question), retrieved_docs # Alternatif ve boş docs dön
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
700 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
701 |
# Cümle Bölme ve Limitleme Mantığı
|
702 |
sentences = []
|
703 |
+
# Noktalama işaretlerine göre böl ve maksimum cümle sayısını uygula
|
704 |
+
split_by_punctuation = response.replace('!', '.').replace('?', '.').split('.')
|
705 |
for s in split_by_punctuation:
|
706 |
s_stripped = s.strip()
|
707 |
if s_stripped:
|
708 |
sentences.append(s_stripped)
|
709 |
+
if len(sentences) >= 6: # Maksimum 6 cümle
|
710 |
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
711 |
|
712 |
+
final_response_text = ' '.join(sentences).strip() # Sadece ilk 6 cümleyi al
|
713 |
+
|
714 |
+
# Anlamsız veya kısa cevap kontrolü
|
715 |
+
generic_or_nonsense_phrases = [
|
716 |
+
"bilmiyorum", "emin değilim", "cevap veremem", "anlamadım",
|
717 |
+
"tekrar eder misin", "bunu hiç düşünmemiştim", "düşünmem gerekiyor",
|
718 |
+
"evet.", "hayır.", "belki.",
|
719 |
+
"içir unidur", "aligutat fakdam", "tetal inlay", "pessotim elgun",
|
720 |
+
"nisman tarejoglu", "faksom", "achisteloy vandleradia", "vęudis",
|
721 |
+
"eltareh", "eldlar", "fotjid", "zuhalibalyon",
|
722 |
+
"yok", "var", "öyle mi", "değil mi", "bu bir soru mu",
|
723 |
+
"etiketler:",
|
724 |
+
"bir kimseyim", "bu bir soru değil", "o, şu anda ne yapıyor",
|
725 |
+
"bu, bu", "benim ne yaptığımı biliyor musun", "inanılmaz derecede utanıyorum",
|
726 |
+
"inan bana", "kandırmak istemiyorum", "tarafından yönetilen bir"
|
727 |
+
]
|
728 |
+
|
729 |
+
# Montag'ın karakteriyle ilgili anahtar kelimelerin eksik olup olmadığını kontrol et
|
730 |
+
montag_keywords = ["kitap", "yakmak", "itfaiyeci", "clarisse", "faber", "beatty", "bilgi", "sansür", "düşünce", "gerçek", "televizyon", "alev", "kül", "mildred", "yangın"]
|
731 |
+
has_montag_relevance = any(keyword in final_response_text.lower() for keyword in montag_keywords)
|
732 |
+
|
733 |
+
# Kontrolleri birleştir
|
734 |
+
if (len(final_response_text.split()) < 10) or \
|
735 |
+
not any(char.isalpha() for char in final_response_text) or \
|
736 |
+
any(phrase in final_response_text.lower() for phrase in generic_or_nonsense_phrases) or \
|
737 |
+
not has_montag_relevance: # Montag anahtar kelimesi yoksa alternatif dön
|
738 |
+
|
739 |
+
print(f"DEBUG: FİLTRELEME - Cevap YETERSİZ/ANLAMSIZ/ALAKASIZ.")
|
740 |
+
if len(final_response_text.split()) < 10:
|
741 |
+
print(f" - Sebep: Çok kısa ({len(final_response_text.split())} kelime).")
|
742 |
+
if not any(char.isalpha() for char in final_response_text):
|
743 |
+
print(f" - Sebep: Hiç harf içermiyor.")
|
744 |
+
if any(phrase in final_response_text.lower() for phrase in generic_or_nonsense_phrases):
|
745 |
+
triggered_phrase = [phrase for phrase in generic_or_nonsense_phrases if phrase in final_response_text.lower()]
|
746 |
+
print(f" - Sebep: Genel/Anlamsız ifade tespit edildi: {triggered_phrase}.")
|
747 |
+
if not has_montag_relevance:
|
748 |
+
print(f" - Sebep: Montag anahtar kelimesi yok.")
|
749 |
+
|
750 |
+
print(f"INFO: Üretilen cevap ('{final_response_text}') filtreleri geçemedi. Alternatif üretiliyor.")
|
751 |
+
return generate_alternative_response(question), retrieved_docs # Alternatif ve boş docs dön
|
752 |
+
|
753 |
+
final_response = add_emojis(final_response_text)
|
754 |
+
return final_response, retrieved_docs # Cevap ve alınan dokümanları döndür
|
755 |
+
|
756 |
+
except Exception as e:
|
757 |
print(f"Error generating answer: {e}")
|
758 |
+
return generate_alternative_response(question), [] # Hata durumunda alternatif ve boş docs dön
|
759 |
+
|
760 |
|
761 |
|
762 |
+
|
763 |
+
# === Gradio arayüzü ===
|
764 |
def respond(msg: str, chatbot_history: List[List[str]], progress=gr.Progress()) -> Tuple[str, List[List[str]], str, str]:
|
765 |
if not msg.strip():
|
766 |
return "", chatbot_history, "Lütfen bir soru yazın.", "---"
|
|
|
793 |
for i in range(10):
|
794 |
current_progress_percent = start_percent + (end_percent - start_percent) * (i / 9)
|
795 |
elapsed_time = time.time() - start_time_overall
|
796 |
+
|
797 |
if is_first_real_user_question:
|
798 |
stopwatch_text = f"İlk Cevap: {elapsed_time:.2f}s / {MAX_EXPECTED_TIME:.0f}s"
|
799 |
else:
|
800 |
stopwatch_text = f"Geçen Süre: {elapsed_time:.2f}s"
|
801 |
+
|
802 |
yield gr.update(value=""), new_history, f"{desc} %{int(current_progress_percent*100)}", stopwatch_text
|
803 |
time.sleep(MAX_EXPECTED_TIME / (len(progress_steps) * 10 * 2))
|
804 |
|
|
|
813 |
else:
|
814 |
new_history.append([msg, answer])
|
815 |
|
|
|
|
|
|
|
816 |
yield gr.update(value=""), new_history, f"Cevap {response_time} saniyede üretildi. ✅", f"{response_time:.2f}s"
|
817 |
|
818 |
def regenerate_answer(chatbot_history: List[List[str]], progress=gr.Progress()) -> Tuple[str, List[List[str]], str, str]:
|
|
|
821 |
|
822 |
# Son gerçek kullanıcı sorusunu bul
|
823 |
last_user_question = None
|
824 |
+
# Geriye doğru dönerken, "Montag düşünüyor..." gibi durum mesajlarını atla
|
825 |
+
# Ayrıca, regenerate_answer'dan hemen önce basılan dislike nedeniyle
|
826 |
+
# son item'da "Montag düşünüyor..." olabilir.
|
827 |
+
# Bu yüzden, son tamamlanmış kullanıcı sorusunu arıyoruz.
|
828 |
for i in range(len(chatbot_history) - 1, -1, -1):
|
829 |
+
user_msg, bot_msg = chatbot_history[i]
|
830 |
+
# Eğer bu bir kullanıcı mesajıysa ve daha önce bir cevap üretilmişse
|
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
|
|
|
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 ve bu cevap beğenilmeyip yenisi isteniyorsa)
|
839 |
+
# Burada dikkat: chatbot_history'nin son elemanı beğenilmeyen cevap olabilir.
|
840 |
+
# Bu durumda o elemanı doğrudan modifiye edebiliriz.
|
|
|
|
|
|
|
841 |
|
842 |
+
# Yeni bir liste oluşturarak eski cevabı çıkartıp, yeniden üretilen cevabı eklemek daha temiz olabilir.
|
843 |
+
temp_chatbot_history_for_gen = [list(pair) for pair in chatbot_history if pair[0] != last_user_question or pair[1] is not None]
|
844 |
+
|
845 |
+
# Eğer son eleman beğenilmeyen cevap ise, onu çıkartıyoruz ki generate_answer'a tekrar gitmesin.
|
846 |
+
# regenerate_answer'a gelen chat_history zaten feedback_callback tarafından işlenmiş,
|
847 |
+
# ve kullanıcıya gösterilen son hali oluyor. Bu yüzden, son bot cevabını silmek yerine,
|
848 |
+
# o soruyu ve eski cevabı history'den çıkarıp yeni cevabı ekleyebiliriz.
|
849 |
+
# Basitçe, son kullanıcı sorusunu yeniden cevaplıyorsak, önceki cevabı yok sayarız.
|
850 |
+
|
851 |
+
# Son kullanıcı sorusunu içeren kısmı geçmişten çıkar, yenisini ekleyeceğiz.
|
852 |
+
# Ancak Gradio yield ile güncelleyeceğimiz için, son kullanıcı sorusu haricindeki geçmişi generate_answer'a verelim.
|
853 |
+
|
854 |
+
# Sohbet geçmişinden son kullanıcı-bot çiftini ayırın (eğer varsa)
|
855 |
+
history_without_last_qa = [list(pair) for pair in chatbot_history]
|
856 |
+
if history_without_last_qa and history_without_last_qa[-1][0] == last_user_question:
|
857 |
+
# Son kullanıcı sorusunu ve cevabını (eğer varsa) çıkar
|
858 |
+
history_without_last_qa.pop()
|
859 |
+
|
860 |
+
|
861 |
start_time_overall = time.time()
|
862 |
initial_stopwatch_text = f"Geçen Süre: 0.00s"
|
863 |
progress_prefix = "Montag yeni bir cevap düşünüyor... "
|
864 |
+
# Kullanıcıya bir "düşünüyor" mesajı göster
|
865 |
+
yield "", history_without_last_qa + [[last_user_question, "Montag düşünüyor... 🤔"]], f"{progress_prefix}%0", initial_stopwatch_text
|
866 |
|
|
|
867 |
progress_steps = [
|
868 |
(f"{progress_prefix}🔄", 0.0, 0.3),
|
869 |
(f"{progress_prefix}🧠", 0.3, 0.6),
|
870 |
(f"{progress_prefix}📚", 0.6, 0.9),
|
871 |
]
|
872 |
+
for desc, start_percent, end_percent in progress_steps: # HATA BURADAYDI! start_percent ve end_percent tanımı ekledik.
|
873 |
for i in range(10):
|
874 |
current_progress_percent = start_percent + (end_percent - start_percent) * (i / 9)
|
875 |
elapsed_time = time.time() - start_time_overall
|
876 |
+
|
877 |
stopwatch_text = f"Geçen Süre: {elapsed_time:.2f}s"
|
878 |
+
|
879 |
+
yield "", history_without_last_qa + [[last_user_question, "Montag düşünüyor... 🤔"]], f"{desc} %{int(current_progress_percent*100)}", stopwatch_text
|
880 |
time.sleep(MAX_EXPECTED_TIME / (len(progress_steps) * 10 * 2))
|
881 |
|
882 |
+
new_raw_answer, _ = generate_answer(last_user_question, history_without_last_qa)
|
883 |
+
new_final_answer = add_emojis(new_raw_answer)
|
884 |
end_time = time.time()
|
885 |
response_time = round(end_time - start_time_overall, 2)
|
886 |
|
887 |
+
# Yeni cevabı sohbet geçmişine ekle (eski cevabın yerine)
|
888 |
+
final_history = history_without_last_qa + [[last_user_question, f"{new_final_answer}\n(yaklaşık {response_time:.2f} saniyede üretildi)"]]
|
889 |
|
890 |
+
yield "", final_history, f"Yeni cevap {response_time} saniyede üretildi. ✅", f"{response_time:.2f}s"
|
891 |
|
892 |
def feedback_callback(chatbot_history: List[List[str]], liked: bool) -> str:
|
893 |
if not chatbot_history:
|
|
|
897 |
last_assistant_answer = None
|
898 |
|
899 |
# Sondan başlayarak gerçek kullanıcı sorusunu ve bot cevabını bul
|
900 |
+
# Eğer en son girdi sadece kullanıcı sorusu ise veya Montag düşünüyor ise, bir önceki tam Q&A'yı ararız
|
901 |
for i in range(len(chatbot_history) - 1, -1, -1):
|
902 |
+
current_pair = chatbot_history[i]
|
903 |
+
if current_pair[0] is not None and current_pair[1] is not None and \
|
904 |
+
not (("Montag düşünüyor..." in current_pair[0]) or ("saniyede üretildi" in current_pair[0])) and \
|
905 |
+
not (("Montag düşünüyor..." in current_pair[1]) or ("saniyede üretildi" in current_pair[1])):
|
906 |
+
last_user_question = current_pair[0]
|
907 |
+
last_assistant_answer = current_pair[1]
|
908 |
+
break
|
|
|
|
|
|
|
|
|
|
|
|
|
909 |
|
910 |
if last_user_question and last_assistant_answer:
|
911 |
+
# "saniyede üretildi" ve emoji kısımlarını temizle
|
912 |
+
cleaned_assistant_answer = re.sub(r'\(yaklaşık \d+\.\d{2} saniyede üretildi\)', '', last_assistant_answer).strip()
|
913 |
+
cleaned_assistant_answer = cleaned_assistant_answer.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()
|
914 |
+
|
915 |
+
cleaned_user_question = last_user_question.replace('📚', '').replace('🧠', '').replace('🔥', '').strip()
|
916 |
+
|
917 |
+
save_feedback(cleaned_user_question, cleaned_assistant_answer, liked, FEEDBACK_FILE)
|
918 |
|
919 |
# RL Agent'a deneyimi kaydet
|
|
|
920 |
rl_agent.record_experience(
|
921 |
+
cleaned_user_question,
|
922 |
+
cleaned_assistant_answer,
|
923 |
+
liked
|
924 |
)
|
925 |
|
926 |
# Eğer beğenildiyse, LoRA fine-tuning için QA_PATH'e de ekle
|
927 |
if liked:
|
928 |
+
qa_pair = {"question": cleaned_user_question, "answer": cleaned_assistant_answer, "liked": True}
|
929 |
try:
|
930 |
with open(QA_PATH, "a", encoding="utf-8") as f:
|
931 |
f.write(json.dumps(qa_pair, ensure_ascii=False) + "\n")
|
|
|
936 |
if count_qa_examples(QA_PATH) % 10 == 0: # Her 10 yeni beğenilen örnekte bir fine-tune yap
|
937 |
print("👍 Yeterli sayıda yeni beğeni var, LoRA fine-tuning başlatılıyor...")
|
938 |
lora_finetune(QA_PATH, LOR_MODEL_PATH)
|
|
|
|
|
939 |
return "Geri bildiriminiz kaydedildi ve model eğitimi tetiklendi. Teşekkürler! 👍"
|
940 |
+
else:
|
941 |
+
return "Geri bildiriminiz kaydedildi. Teşekkürler! ✅"
|
942 |
+
else:
|
943 |
+
return "Geri bildiriminiz kaydedildi. Montag daha iyi olmaya çalışacak. ❌"
|
944 |
return "Geri bildirim kaydedilemedi. Geçmişte yeterli sohbet bulunmuyor. ❌"
|
945 |
|
946 |
# === Gradio arayüzü ===
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
947 |
def create_chat_interface():
|
948 |
with gr.Blocks(theme=gr.themes.Soft(), css=current_css) as demo:
|
949 |
gr.Markdown("""
|