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("""
|