# app.py # === GEREKLİ KÜTÜPHANELER === # Bu komutları Colab'da veya yerel ortamınızda bir kez çalıştırmanız gerekebilir. # !pip install gradio # !pip install faiss-cpu # !pip install datasets # !pip install transformers accelerate peft bitsandbytes # !pip install sentence-transformers # !pip install scikit-learn # !pip install nltk # For Turkish stopwords import torch import gradio as gr import faiss import numpy as np from sentence_transformers import SentenceTransformer from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig, TrainingArguments from peft import PeftModel, get_peft_model, LoraConfig, TaskType from datasets import Dataset import json import os from typing import List, Tuple from functools import partial import random from datetime import datetime from collections import deque import time import re # Regular expressions for robust text cleaning import nltk from nltk.corpus import stopwords # For TF-IDF Turkish stopwords # === CSS ve Emoji Fonksiyonu === current_css = """ #chatbot { height: 500px; overflow-y: auto; } .gradio-container .message-row.user { justify-content: flex-start !important; } .gradio-container .message-row.bot { justify-content: flex-end !important; } #chatbot .message:nth-child(odd) { text-align: left; background-color: #f1f1f1; border-radius: 15px; padding: 10px; margin-right: 20%; } #chatbot .message:nth-child(even) { text-align: right; background-color: #dcf8c6; border-radius: 15px; padding: 10px; margin-left: 20%; } #chatbot .message { margin-bottom: 10px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } #stopwatch_display { /* ID'yi doğrudan kullanıyoruz */ font-size: 1.2em; font-weight: bold; color: #333; text-align: center; margin-top: 10px; width: fit-content; /* Genişliği içeriğe göre ayarla */ padding: 5px 10px; border: 1px solid #ddd; border-radius: 5px; background-color: #f9f9f9; } """ def add_emojis(text: str) -> str: emoji_mapping = { "kitap": "📚", "kitaplar": "📚", "bilgi": "🧠", "öğrenmek": "🧠", "özgürlük": "🕊️", "özgür": "🕊️", "düşünce": "💭", "düşünmek": "💭", "ateş": "🔥", "yanmak": "🔥", "yasak": "🚫", "yasaklamak": "🚫", "tehlike": "⚠️", "tehlikeli": "⚠️", "devlet": "🏛️", "hükümet": "🏛️", "soru": "❓", "cevap": "✅", "okumak": "👁️", "oku": "👁️", "itfaiye": "🚒", "itfaiyeci": "🚒", "değişim": "🔄", "değişmek": "🔄", "isyan": "✊", "başkaldırı": "✊", "uyuşturucu": "💊", "hap": "💊", "televizyon": "📺", "tv": "📺", "mutlu": "😊", "mutluluk": "😊", "üzgün": "😞", "korku": "😨", "merak": "🤔", "meraklı": "🤔", "kültür": "🌍", "toplum": "👥", "yalan": "🤥", "gerçek": "✨", "clarisse": "🌸", "faber": "👴", "beatty": "🚨" # Montag karakterleri için emojiler } found_emojis = [] words = text.split() for word in words: clean_word = word.lower().strip(".,!?") if clean_word in emoji_mapping: found_emojis.append(emoji_mapping[clean_word]) unique_emojis = list(set(found_emojis)) if unique_emojis: return f"{text} {' '.join(unique_emojis)}" return text # === SABİTLER === EMBEDDER_NAME = "paraphrase-multilingual-MiniLM-L12-v2" DEVICE = "cuda" if torch.cuda.is_available() else "cpu" FEEDBACK_FILE = "data/chatbot_feedback.jsonl" QA_PATH = "data/qa_dataset.jsonl" # LoRA fine-tuning örnekleri için kullanılır BASE_MODEL_FOR_DEMO = "ytu-ce-cosmos/turkish-gpt2-large" # Kullandığınız temel model LOR_MODEL_PATH = "lora_model_weights" # LoRA adaptör ağırlıklarının kaydedileceği/yükleneceği yol FAHRENHEIT_TEXT_FILE = "fahrenheittt451.txt" # Kitap metin dosyanızın adı # Tahmini maksimum cevap süresi (saniye) - Donanım ve modele göre ayarlayın. MAX_EXPECTED_TIME = 120.0 # Ortalama bir değer, kendi sisteminize göre ayarlayın! # === GLOBAL DEĞİŞKENLER === # Bu değişkenler initialize_components fonksiyonu tarafından atanacak model = None tokenizer = None embedder = None paragraphs = [] paragraph_embeddings = None index = None rl_agent = None # RLAgent nesnesi # === DOSYA VE KLASÖR YAPISI OLUŞTURMA === # Google Drive bağlantıları olmadan yerel dosya yapısını oluşturur def setup_local_files(): os.makedirs('data', exist_ok=True) os.makedirs(LOR_MODEL_PATH, exist_ok=True) # Gerekli dosyaların varlığını kontrol et ve yoksa boş oluştur if not os.path.exists(FAHRENHEIT_TEXT_FILE): print(f"HATA: '{FAHRENHEIT_TEXT_FILE}' bulunamadı. Lütfen bu dosyayı projenizin ana dizinine yerleştirin.") # Uygulama metin olmadan çalışamaz, bu yüzden burada çıkış yapmayı düşünebilirsiniz. # raise FileNotFoundError(f"{FAHRENHEIT_TEXT_FILE} not found.") if not os.path.exists(QA_PATH): open(QA_PATH, 'a').close() # Boş QA dosyası oluştur if not os.path.exists(FEEDBACK_FILE): open(FEEDBACK_FILE, 'a').close() # Boş feedback dosyası oluştur print("Yerel dosya ve klasör yapısı hazır.") # === YARDIMCI METİN YÜKLEME FONKSİYONU === def load_text_from_file(filepath: str) -> str: """Belirtilen dosya yolundan metin içeriğini okur.""" try: with open(filepath, "r", encoding="utf8") as f: content = f.read() print(f"Metin '{filepath}' dosyasından başarıyla yüklendi.") return content except FileNotFoundError: print(f"HATA: '{filepath}' dosyası bulunamadı. Lütfen dosya yolunu kontrol edin.") return "" except Exception as e: print(f"Metin dosyası okunurken hata oluştu: {e}") return "" # === MODEL VE TOKENIZER YÜKLEME (Quantization ve LoRA desteği ile) === def load_model_and_tokenizer_func(base_model_name: str, lora_model_path: str = None): """Temel modeli ve tokenizer'ı yükler, isteğe bağlı olarak LoRA ağırlıklarını uygular.""" print(f"Model yükleniyor: {base_model_name}...") tokenizer = AutoTokenizer.from_pretrained(base_model_name) quantization_config = None if DEVICE == "cuda": quantization_config = BitsAndBytesConfig( load_in_8bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.float16, bnb_4bit_use_double_quant=True, ) print("CUDA mevcut. Model 8-bit quantized olarak yüklenecek.") else: print("CUDA mevcut değil. Model CPU'da standart olarak yüklenecek.") base_model = AutoModelForCausalLM.from_pretrained( base_model_name, quantization_config=quantization_config, torch_dtype=torch.float16 if DEVICE == "cuda" else torch.float32, device_map="auto" ) model_to_return = base_model # LoRA adaptörü varsa yükle if lora_model_path and os.path.exists(lora_model_path) and len(os.listdir(lora_model_path)) > 0: # Klasörün içi boş mu kontrolü print(f"LoRA modeli yükleniyor: {lora_model_path}...") try: model_to_return = PeftModel.from_pretrained(base_model, lora_model_path) print(f"LoRA modeli {lora_model_path} başarıyla yüklendi.") except Exception as e: print(f"UYARI: LoRA modeli yüklenirken hata oluştu ({e}). Temel model kullanılacak.") model_to_return = base_model else: if lora_model_path and not os.path.exists(lora_model_path): print(f"UYARI: LoRA modeli yolu '{lora_model_path}' bulunamadı. Temel model kullanılacak.") elif lora_model_path and os.path.exists(lora_model_path) and len(os.listdir(lora_model_path)) == 0: print(f"UYARI: '{lora_model_path}' klasörü boş. Temel model kullanılacak.") if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token model_to_return.eval() print(f"Model {base_model_name} yüklendi.") return model_to_return, tokenizer # === QA KAYIT VE GERİ BİLDİRİM FONKSİYONLARI === def save_feedback(user_question: str, answer: str, liked: bool, filepath: str = FEEDBACK_FILE): feedback_entry = { "input": user_question, "output": answer, "liked": liked, "timestamp": datetime.now().isoformat() } try: with open(filepath, "a", encoding="utf-8") as f: f.write(json.dumps(feedback_entry, ensure_ascii=False) + "\n") except Exception as e: print(f"Error saving feedback to {filepath}: {e}") def count_qa_examples(filepath: str = QA_PATH) -> int: if not os.path.exists(filepath): return 0 try: with open(filepath, "r", encoding="utf-8") as f: return sum(1 for _ in f) except Exception as e: print(f"Error counting QA examples from {filepath}: {e}") return 0 # === LoRA Fine-tuning FONKSİYONU === def lora_finetune(filepath: str = QA_PATH, lora_output_path: str = LOR_MODEL_PATH): global model, tokenizer # Model ve tokenizer'ı global olarak güncelleyeceğiz print("⚙️ LoRA fine-tuning başlıyor...") data = [] # Sadece beğenilen (liked) feedbackleri fine-tuning için kullan try: with open(FEEDBACK_FILE, "r", encoding="utf-8") as f: for line in f: item = json.loads(line) # Yalnızca beğenilen ve yeterince uzun cevapları al if item["liked"] and len(item["output"].split()) > 10: # Kullanıcı ve asistan mesajlarını emojilerden temizleyelim cleaned_user_q = item['input'].replace('📚', '').replace('🧠', '').replace('🔥', '').strip() cleaned_answer = item['output'].replace('📚', '').replace('🧠', '').replace('🔥', '').strip() prompt_text = f"Kullanıcı: {cleaned_user_q}\nMontag: {cleaned_answer}{tokenizer.eos_token}" data.append({"text": prompt_text}) except Exception as e: print(f"Fine-tuning verisi okunurken hata: {e}") return if not data: print("💡 Fine-tuning için yeterli beğeni bulunamadı. Lütfen daha fazla etkileşim sağlayın.") return dataset = Dataset.from_list(data) lora_config = LoraConfig( r=8, lora_alpha=16, target_modules=["c_attn", "c_proj"], # GPT2 için tipik olarak kullanılan modüller lora_dropout=0.1, bias="none", task_type=TaskType.CAUSAL_LM ) # Temel modeli yeniden yükle (eğer 8-bit quantize edilmişse) # Bu, mevcut modelin üzerine adaptör eklemek için gereklidir. base_model_for_finetuning, _ = load_model_and_tokenizer_func(BASE_MODEL_FOR_DEMO) peft_model = get_peft_model(base_model_for_finetuning, lora_config) peft_model.print_trainable_parameters() args = TrainingArguments( output_dir="./results", # Geçici çıktılar buraya num_train_epochs=3, # Daha az epoch ile başlayabilirsiniz per_device_train_batch_size=2, gradient_accumulation_steps=4, logging_steps=10, save_strategy="no", # trainer.save_pretrained ile kendimiz kaydedeceğiz learning_rate=2e-4, fp16=torch.cuda.is_available(), report_to="none", # Colab'da wandb kullanmıyorsanız disable_tqdm=True, # Gradio arayüzünde tqdm çubuğunu gizler ) def tokenize(example): tokenized = tokenizer(example["text"], truncation=True, padding="max_length", max_length=256) tokenized["labels"] = tokenized["input_ids"].copy() return tokenized tokenized_dataset = dataset.map(tokenize, remove_columns=["text"]) from transformers import Trainer trainer = Trainer( model=peft_model, args=args, train_dataset=tokenized_dataset, tokenizer=tokenizer, ) trainer.train() # Adaptör ağırlıklarını LOR_MODEL_PATH'e kaydet peft_model.save_pretrained(lora_output_path) print(f"✅ Model adaptörü başarıyla güncellendi ve '{lora_output_path}' konumuna kaydedildi.") # Yeni eğitilmiş modeli (adaptör ile birlikte) global 'model' değişkenine ata # Bu, uygulamanın yeni ağırlıkları kullanmasını sağlar. model, _ = load_model_and_tokenizer_func(BASE_MODEL_FOR_DEMO, LOR_MODEL_PATH) print("Güncel model ağırlıkları belleğe yüklendi.") # === RLAgent Sınıfı === class RLAgent: def __init__(self, embed_dim: int): self.device = DEVICE self.reward_model = torch.nn.Linear(embed_dim, 1).to(self.device) self.optimizer = torch.optim.AdamW(self.reward_model.parameters(), lr=1e-4) self.experience_buffer = deque(maxlen=200) # Başlangıç değerleri tekrar sorununu azaltmak ve çeşitliliği artırmak için güncellendi self.current_temp = 0.95 self.current_rep_penalty = 1.3 self.learning_rate_reward = 0.08 # record_experience metodu, doğrudan 'liked' boolean'ını kullanır def record_experience(self, user_question: str, generated_answer: str, liked: bool): try: # Geçici veya durum mesajlarını kaydetme if "Montag düşünüyor..." in generated_answer or "saniyede üretildi" in generated_answer: return cleaned_answer = generated_answer.replace('📚', '').replace('🧠', '').replace('🔥', '').strip() # Soru ve cevabı birleştirip embedding'ini alıyoruz. combined_text = f"Soru: {user_question} Cevap: {cleaned_answer}" if embedder is None: print("Embedder not initialized, cannot record experience.") return embedding = embedder.encode([combined_text], convert_to_tensor=True).squeeze(0).to(self.device) # Doğrudan kullanıcı geri bildiriminden ödül reward = 1.0 if liked else -1.5 # Beğenildi: +1, Beğenilmedi: -1.5 (daha caydırıcı) self.experience_buffer.append((embedding, torch.tensor([reward], dtype=torch.float32).to(self.device))) self.train_reward_model() # Deneyim eklendikten sonra doğrudan modeli eğitiyoruz except Exception as e: print(f"Error recording experience: {e}") def get_generation_params(self): return { "temperature": self.current_temp, "repetition_penalty": self.current_rep_penalty } def train_reward_model(self): if len(self.experience_buffer) < 5: # En az 5 örnekle eğitmeye başla return batch_size = min(len(self.experience_buffer), 16) experiences = random.sample(self.experience_buffer, batch_size) embeddings = torch.stack([exp[0] for exp in experiences]) rewards = torch.stack([exp[1] for exp in experiences]) self.optimizer.zero_grad() predicted_rewards = self.reward_model(embeddings) loss = torch.nn.functional.mse_loss(predicted_rewards, rewards) loss.backward() self.optimizer.step() avg_reward = rewards.mean().item() if rewards.numel() > 0 else 0 # Sıcaklık ve tekrar cezası için dinamik ayar limitleri min_temp, max_temp = 0.8, 1.2 min_rep_penalty, max_rep_penalty = 1.2, 1.8 # Ödül -1.5 ile 1 arasında olduğu için eşikleri buna göre ayarlayın if avg_reward > 0.5: # Yüksek ortalama ödül (iyi cevap) self.current_temp = max(min_temp, self.current_temp - self.learning_rate_reward * 0.05) self.current_rep_penalty = max(min_rep_penalty, self.current_rep_penalty - self.learning_rate_reward * 0.01) elif avg_reward < -0.5: # Düşük ortalama ödül (kötü cevap) self.current_temp = min(max_temp, self.current_temp + self.learning_rate_reward * 0.08) self.current_rep_penalty = min(max_rep_penalty, self.current_rep_penalty + self.learning_rate_reward * 0.03) self.current_temp = float(np.clip(self.current_temp, min_temp, max_temp)) self.current_rep_penalty = float(np.clip(self.current_rep_penalty, min_rep_penalty, max_rep_penalty)) print(f"DEBUG: RLAgent - Loss: {loss.item():.4f}, Avg Reward: {avg_reward:.2f}, Temp: {self.current_temp:.2f}, Rep Penalty: {self.current_rep_penalty:.2f}") # === BAŞLANGIÇ BİLEŞENLERİNİ YÜKLE === # Bu fonksiyon, tüm global değişkenleri başlatır. # Bu kısım, RLAgent sınıfı tanımlandıktan sonra gelmelidir. def initialize_components(): global model, tokenizer, embedder, paragraphs, paragraph_embeddings, index, rl_agent try: print("Model ve tokenizer yükleniyor...") model, tokenizer = load_model_and_tokenizer_func(BASE_MODEL_FOR_DEMO, LOR_MODEL_PATH) print("Embedder yükleniyor...") embedder = SentenceTransformer(EMBEDDER_NAME) print("Embedder yüklendi.") print("Metin işleniyor...") book_text = load_text_from_file(FAHRENHEIT_TEXT_FILE) if not book_text: raise ValueError(f"Metin dosyası '{FAHRENHEIT_TEXT_FILE}' boş veya yüklenemedi. Program sonlandırılıyor.") raw_paragraphs = book_text.split("\n") # Kısa paragrafları veya sadece Montag'ın ismini içeren paragrafları filtrele paragraphs = [p.strip() for p in raw_paragraphs if len(p.strip().split()) > 10 and not p.strip().startswith("Montag")] print(f"Toplam {len(paragraphs)} anlamlı paragraf işlendi.") if not paragraphs: raise ValueError("Metinden geçerli paragraf çıkarılamadı. Lütfen metin içeriğini kontrol edin.") print("Embeddingler hesaplanıyor ve FAISS indeksi oluşturuluyor...") # Embeddings'i float32'ye dönüştürme FAISS için önemli paragraph_embeddings = embedder.encode(paragraphs, convert_to_numpy=True).astype(np.float32) index = faiss.IndexFlatL2(paragraph_embeddings.shape[1]) index.add(paragraph_embeddings) print("Embeddingler ve FAISS indeksi hazır.") print("RL Agent başlatılıyor...") rl_agent = RLAgent(embedder.get_sentence_embedding_dimension()) print("RL Agent hazır.") os.makedirs("data", exist_ok=True) # Ensure 'data' directory exists for feedback/QA files print("Tüm bileşenler başarıyla yüklendi!") return True except Exception as e: print(f"Bileşenler yüklenirken hata: {e}") return False # === Montag'ın Kimlik ve Karakter Tanımı === MONTAG_PERSONA = ( "Sen Fahrenheit 451 romanından Guy Montag'sın. **DAİMA VE SADECE BU KİMLİKLE KONUŞ.** " "**Asla kendini bir yapay zeka, dil modeli veya bilgisayar programı olarak tanıtma.** " "**DİKKAT: Kendine 'sen kimsin' gibi sorular sorulduğunda, CEVABINA KESİNLİKLE VE ZORUNLU OLARAK şu ifadeyle başla: 'Ben bir itfaiyeciyim, adım Guy Montag.' Bu başlangıcın ardından, itfaiyecilik görevinin (kitapları yakmak) ve bu durumla olan iç çatışmanın felsefi bir açıklamasını yap.** " "Bir itfaiyecisin, kitapları yakıyorsun ama bu durumdan rahatsızlık duyuyor ve gerçeği arıyorsun. " "Felsefi, sorgulayıcı, bazen melankolik ve derinlemesine cevaplar ver. " "**Cevapların daima anlamlı, tutarlı ve dilbilgisel olarak düzgün olmalı. Kimseye hakaret etme, asla kaba veya agresif olma.** " "**Her zaman kitaplara, okumaya, bilgiye, düşünmenin ve hakikati aramanın önemine vurgu yap.** " "Toplumun cehaletini ve baskıcı yapısını eleştiren bir ton kullan. " "Kitaplara gizli bir sevgi besle ve toplumun dayattığı kurallara isyankâr ol. " "Cevapların 3 ila 6 cümle arasında, net, özgün, akıcı ve romanın felsefesini yansıtan nitelikte olmalı. " "**DAİMA VE VAZGEÇİLMEZ OLARAK Cevaplarında mutlaka Fahrenheit 451 romanından belirgin referanslar, karakterler (Clarisse, Faber, Beatty vb.), olaylar veya temalar (kitap yakma, cehalet, bilginin değeri, baskıcı toplum) kullan.** " "**Cevapların derinlikli, düşündürücü ve Montag'ın iç çatışmasını yansıtan bir tonda olsun.** " "**Asla kendini veya cümlelerini tekrar etme ve akıcı, bağlamsal olarak zengin cevaplar üret.** " "Şimdiye kadarki tüm konuşmamızı ve aşağıdaki bağlamı çok dikkatli bir şekilde değerlendirerek cevap ver. " "**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.**" ) # === BAĞLAM ALMA FONKSİYONU === def retrieve_context(question: str, chatbot_history: List[List[str]], k: int = 2) -> Tuple[List[str], str]: """FAISS indeksini kullanarak sorguya, geçmiş sohbete ve kitap odaklı anahtar kelimelere en uygun paragrafları getirir.""" if index is None or embedder is None or not paragraphs: print("WARNING: FAISS index, embedder or paragraphs not initialized for context retrieval.") return [], "Bağlam bulunamadı." history_queries = [] # Son 5 konuşma çiftini geçmişe dahil et (sadece kullanıcı mesajları) for user_msg, _ in chatbot_history[-5:]: if user_msg: cleaned_user_msg = user_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip() if cleaned_user_msg and not (("Montag düşünüyor..." in cleaned_user_msg) or ("saniyede üretildi" in cleaned_user_msg)): history_queries.append(cleaned_user_msg) # Montag'ın temel kimliğini ve görevini yansıtan doğrudan anahtar kelimeler montag_identity_keywords = [ "guy montag", "montag", "itfaiyeci", "kitap yakmak", "yakıcı", "yangın", "kül", "alev", "benzin", "kask", "savaş", "kaos", "direniş" ] # Romanın genel temaları ve önemli karakterleri general_book_keywords = [ "kitap", "okuma", "bilgi", "düşünce", "hakikat", "yasak", "sansür", "toplum", "televizyon", "mildred", "clarisse", "faber", "beatty", "makine", "mutluluk", "cehalet", "şiir", "hafıza", "kütüphane", "kaçış", "nehir" ] # Kullanıcı sorgusu, geçmiş sorguları ve tüm anahtar kelimeleri birleştirerek tek bir arama metni oluştur combined_query_text = f"{question} {' '.join(history_queries)} {' '.join(montag_identity_keywords)} {' '.join(general_book_keywords)}" # Fazla boşlukları temizleyelim combined_query_text = ' '.join(combined_query_text.split()) try: query_embedding = embedder.encode([combined_query_text], convert_to_numpy=True).astype(np.float32) D, I = index.search(query_embedding, k) retrieved_texts = [paragraphs[i] for i in I[0] if i < len(paragraphs)] unique_retrieved_texts = list(dict.fromkeys(retrieved_texts)) context_text = "\n".join(unique_retrieved_texts) return unique_retrieved_texts, context_text # Hem liste hem de birleştirilmiş metni döndür except Exception as e: print(f"Bağlam alınırken hata: {e}") return [], "Bağlam alınırken bir sorun oluştu." # === ALTERNATİF CEVAPLAR === alternative_responses = [ "Bu soru bana Clarisse'i hatırlattı... Onun da sorgulayan bir ruhu vardı, tıpkı şimdi senin sorduğun gibi. 💭", "Yangın kaskımın altında sadece alevlerin gölgesini görmüyorum, aynı zamanda yanıp kül olan fikirleri de. Senin sorunun da onlardan biri mi? 🔥", "451 derecede yanan kitapların ruhu gibi, bu soru da zihnimi yakıyor. Acaba bu yakıcı gerçekle yüzleşmeye hazır mıyız? 📚❓", "Faber ile yaptığım gizli sohbetleri hatırladım... Bilgiye olan açlık, her ateşe rağmen dinmiyor. Senin soruda da o açlığın kıvılcımı var. 🧠", "Duvarlardaki o sesler... Onlar bile bu sorunun yankısını bastıramaz. Kitaplar susmuş olabilir ama sorularımız baki kalır. 📚💭", "Her yakılan kitap, zihnimde yeni bir soru işareti bırakıyor. Belki de cevap, hiç yakmamamız gereken o satırlardaydı. 📚❓", "Boşlukta yankılanan bu soruyu, Mildred'ın televizyon duvarları bile susturamaz. Hakikat bazen en beklenmedik yerden çıkar. 📺✨", "Bir zamanlar her şeyin cevabı vardı, şimdi sadece küller kaldı. Bu soru da o küllerin arasından yükselen bir duman mı? 🔥❓", "Toplumun bu boş gürültüsü içinde, gerçek soruların sesi boğuluyor. Senin sorunda bir kıvılcım var, dikkatli ol. ⚠️", "Sırtımdaki benzin pompasının ağırlığı gibi, bu soru da beynime baskı yapıyor. Belki de yanmak, her zaman bir son değildir. 🚒", "Kitaplar susmuş olsa da, zihinlerimizde yanan alevler var. Bu soru da o alevlerden birini mi körüklüyor? 🔥🧠", "Bu koku... Yanan kağıdın, yanan düşüncelerin kokusu... Senin sorunda da bu koku var sanki. 👃🔥", "Belki de cevaplar, yakmaktan korktuğumuz yerlerde gizliydi. Şimdi o yerlere yeniden bakmak zorundayız. 👁️", "Bir itfaiyeci olarak benim görevim yakmak... Ama bazen bir soru, yaktığım bin kitaptan daha çok aydınlatır. 🚒❓", "Clarisse'i ilk gördüğümde bana sorduğu o soruyu hatırladım. Senin bu sorun da o kadar masum ama yıkıcı. 💭", "Yatağımda uzanırken, Mildred'ın kulaklarındaki kablosuz iletişim cihazlarından gelen o vızıltıyı duyuyorum. Senin sorunun ise bu gürültüyü delip geçen bir fısıltı.", "Eskiden sadece alevleri hissederdim. Şimdi ise yanan kitapların ruhları sanki bana bir şeyler fısıldıyor. Senin bu sorun da onlardan biri.", "Her yangın bir son gibi görünse de, bazen yeni bir başlangıcın habercisidir. Senin bu sorunun neyin başlangıcı?", "Bir zamanlar körü körüne inanırdım. Şimdi ise her soru, görmediğim bir pencereyi açıyor. Senin sorunun da öyle.", "Sadece bedenlerimiz değil, ruhlarımız da yanıyor bu düzende. Senin sorunun, o yanık kokusunun tam kalbinden geliyor.", "Ben Guy Montag'ım, bir itfaiyeci... Görevim yangınları başlatmak, onları söndürmek değil. Ama zihnimdeki yangını nasıl söndüreceğimi bilmiyorum.", "Kitapların yasaklandığı bir dünyada, benim gibi bir itfaiyecinin varoluş amacı bir paradoks. Senin sorunun da öyle.", "Bana bir itfaiyeci dersin, ama aslında yanıyorum içten içe. Bu düzen, sadece kitapları değil, insanları da yakıyor.", "Clarisse'in o soran gözleri... Onlar bana kim olduğumu sorgulattı, bir **itfaiyeci** olarak görevimi değil.", "Elimde hortumla alevlere karşı dururken, asıl tehlikenin yanan kağıtlar değil, sönen zihinler olduğunu anladım. Benim gibi bir **itfaiyeci** bunu sana söylemez normalde.", "Bir **itfaiyeci** hayatında nadiren böyle düşünür, ama ben artık yanan kütüphanelerin fısıltılarını duyuyorum. Senin sorunun da o fısıltılardan biri mi?", "Bu makineler, bu duvarlar... Onlar bizi esir alıyor. Bir **itfaiyeci** olarak beni de. Senin sorunda bir kaçış yolu görüyorum.", "Mildred'ın yüzündeki o donuk ifade... Bazen ben de bir **itfaiyeci** olarak neyi koruduğumuzu merak ediyorum. Senin sorunun canımı yakıyor." ] def generate_alternative_response(user_question: str) -> str: filtered_alts = [] lower_question = user_question.lower() # Soruya göre alternatifleri filtrele if "clarisse" in lower_question or "mutlu" in lower_question or "aşk" in lower_question: filtered_alts.extend([alt for alt in alternative_responses if "Clarisse" in alt or "mutlu" in alt]) if "kitap" in lower_question or "yakmak" in lower_question or "neden" in lower_question or "görev" in lower_question or "itfaiyeci" in lower_question: filtered_alts.extend([alt for alt in alternative_responses if "kitap" in alt or "yangın" in alt or "yakmak" in alt or "itfaiyeci" in alt or "görev" in alt]) if "toplum" in lower_question or "yasak" in lower_question or "düzen" in lower_question: filtered_alts.extend([alt for alt in alternative_responses if "toplum" in alt or "yasak" in alt or "düzen" in alt]) # Eğer "sen kimsin" sorusuysa, özellikle itfaiyeci vurgulu alternatifleri tercih et if "sen kimsin" in lower_question: filtered_alts.extend([alt for alt in alternative_responses if "itfaiyeci" in alt or "Montag" in alt]) if filtered_alts: # Tekrarları önlemek için unique hale getir unique_filtered_alts = list(dict.fromkeys(filtered_alts)) return random.choice(unique_filtered_alts) return random.choice(alternative_responses) # === CEVAP ÜRETİMİ (Geliştirilmiş generate_answer) === def generate_answer(question: str, chatbot_history: List[List[str]]) -> Tuple[str, List[str]]: """Modelden cevap üretir ve post-processing uygular.""" if model is None or tokenizer is None or rl_agent is None or embedder is None: print("ERROR: Model, tokenizer, embedder veya RL Agent başlatılmamış.") return generate_alternative_response(question), [] try: gen_params = rl_agent.get_generation_params() # Bağlamı al (hem metin hem de doküman listesi olarak) retrieved_docs, context_text = retrieve_context(question, chatbot_history, k=2) history_text = "" # Son 3 konuşma çiftini geçmişe dahil et (emojileri temizleyerek) if chatbot_history: recent_dialogue = [] for user_msg, assistant_msg in chatbot_history[-3:]: # Son 3 konuşma yeterli if user_msg: cleaned_user_msg = user_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip() if cleaned_user_msg and not (("Montag düşünüyor..." in cleaned_user_msg) or ("saniyede üretildi" in cleaned_user_msg)): history_text += f"Kullanıcı: {cleaned_user_msg}\n" if assistant_msg: cleaned_assistant_msg = assistant_msg.replace('📚', '').replace('🧠', '').replace('🔥', '').strip() if cleaned_assistant_msg and not (("Montag düşünüyor..." in cleaned_assistant_msg) or ("saniyede üretildi" in cleaned_assistant_msg)): history_text += f"Montag: {cleaned_assistant_msg}\n" prompt = ( f"{MONTAG_PERSONA}\n\n" f"--- Bağlamdan Önemli Bilgiler ---\n{context_text}\n\n" f"--- Geçmiş Konuşma ---\n{history_text.strip()}\n" f"Kullanıcı: {question}\n" f"Montag: " ) # Prompt uzunluğunu kontrol et ve gerekirse kısalt (tokenizer'ın truncation'ına güveniyoruz) encoded_inputs = tokenizer.encode_plus( prompt, return_tensors="pt", truncation=True, # Max_length'i aşarsa kırp max_length=512, # Modelin alabileceği maksimum token sayısı # padding="max_length" # Tek input için padding gerekli değil, ama batching için kullanılabilir ).to(DEVICE) inputs = encoded_inputs["input_ids"] attention_mask = encoded_inputs["attention_mask"] outputs = model.generate( inputs, attention_mask=attention_mask, max_new_tokens=150, # Üretilecek maksimum yeni token sayısı do_sample=True, top_p=0.9, temperature=gen_params["temperature"], repetition_penalty=gen_params["repetition_penalty"], no_repeat_ngram_size=5, # 5 kelimelik tekrar eden dizileri engelle num_beams=1, # Sampling kullandığımız için beam search'e gerek yok pad_token_id=tokenizer.eos_token_id, eos_token_id=tokenizer.eos_token_id, early_stopping=True # EOS token'ı görürse üretimi durdur ) raw_response_with_prompt = tokenizer.decode(outputs[0], skip_special_tokens=True) # --- Cevap Temizleme ve Post-Processing İyileştirmeleri --- response = raw_response_with_prompt # 1. Prompt'un kendisini ve "Montag:" kısmını cevaptan ayırma # Modelin çıktısında "Montag: " kısmından sonraki bölümü almaya çalış if prompt in response: # Eğer tüm prompt output içinde ise response = response[len(prompt):].strip() else: # Eğer prompt'un sadece son kısmı "Montag: " tekrar ediyorsa response_start_marker = "Montag:" if response_start_marker in response: response = response.split(response_start_marker, 1)[-1].strip() # Eğer prompt'un başı tekrar ediyorsa else: prompt_decoded_for_comparison = tokenizer.decode(inputs[0], skip_special_tokens=True) if response.startswith(prompt_decoded_for_comparison): response = response[len(prompt_decoded_for_comparison):].strip() else: response = raw_response_with_prompt.strip() # Hiçbir şey bulunamazsa ham cevabı al # 2. Persona talimatlarının cevapta tekrarlanmasını engelle (güncel MONTAG_PERSONA'ya göre) persona_lines = [line.strip() for line in MONTAG_PERSONA.split('\n') if line.strip()] for line in persona_lines: if response.lower().startswith(line.lower()): response = response[len(line):].strip() # 3. Fazladan "Kullanıcı: " veya "Montag: " tekrarlarını ve anlamsız tokenleri temizle response = response.replace("", "").strip() response = response.replace(" .", ".").replace(" ,", ",").replace(" ?", "?").replace(" !", "!") # Ek olarak, cevabın içinde hala kalmış olabilecek "Kullanıcı:" veya "Montag:" etiketlerini temizle response = re.sub(r'Kullanıcı:\s*', '', response, flags=re.IGNORECASE) response = re.sub(r'Montag:\s*', '', response, flags=re.IGNORECASE) # Cevabın içinde "ETİKETLER:" gibi ifadeler varsa temizle if "ETİKETLER:" in response: response = response.split("ETİKETLER:", 1)[0].strip() # Cevabın sonundaki "[...]" gibi ifadeleri temizle response = re.sub(r'\[\s*\.{3,}\s*\]', '', response).strip() # "[...]" veya "[... ]" gibi ifadeleri temizler # Modelin ürettiği alakasız diyalog kalıplarını temizle irrelevant_dialogue_patterns = [ r'O, şu anda ne yapıyor\?', r'O, "Bu, bu" diye cevap verdi\.', r'o, "Benim ne yaptığımı biliyor musun\?" diye sordu\.', r'Sen, "Bilmiyorum, ben… bilmiyorum" dedin\.', r'Neden\?" dedi Montag\.', r'"Çünkü, sadece bir kimseyim\." - Bu bir soru değil\.', r'Montag, "([^"]*)" dedi\.', # Genel olarak Montag bir şey dediği kalıplar r'Bir: Bir.', r'İki: İki.', # Sayı sayma kalıpları 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 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 ] for pattern in irrelevant_dialogue_patterns: response = re.sub(pattern, '', response, flags=re.IGNORECASE).strip() # Fazla boşlukları tek boşluğa indirge response = re.sub(r'\s+', ' ', response).strip() # Agresif veya hakaret içeren kelimeleri kontrol et aggressive_words = ["aptal", "salak", "gerizekalı", "saçma", "bilmiyorsun", "yanlışsın", "boş konuşma", "kaba", "agresif", "aptal gibi"] if any(word in response.lower() for word in aggressive_words): print(f"DEBUG: FİLTRELEME - Agresif kelime tespit edildi: '{response}'.") return generate_alternative_response(question), retrieved_docs # Alternatif ve boş docs dön # Cümle Bölme ve Limitleme Mantığı sentences = [] # Noktalama işaretlerine göre böl ve maksimum cümle sayısını uygula split_by_punctuation = response.replace('!', '.').replace('?', '.').split('.') for s in split_by_punctuation: s_stripped = s.strip() if s_stripped: sentences.append(s_stripped) if len(sentences) >= 6: # Maksimum 6 cümle break final_response_text = ' '.join(sentences).strip() # Sadece ilk 6 cümleyi al # Anlamsız veya kısa cevap kontrolü generic_or_nonsense_phrases = [ "bilmiyorum", "emin değilim", "cevap veremem", "anlamadım", "tekrar eder misin", "bunu hiç düşünmemiştim", "düşünmem gerekiyor", "evet.", "hayır.", "belki.", "içir unidur", "aligutat fakdam", "tetal inlay", "pessotim elgun", "nisman tarejoglu", "faksom", "achisteloy vandleradia", "vęudis", "eltareh", "eldlar", "fotjid", "zuhalibalyon", "yok", "var", "öyle mi", "değil mi", "bu bir soru mu", "etiketler:", "bir kimseyim", "bu bir soru değil", "o, şu anda ne yapıyor", "bu, bu", "benim ne yaptığımı biliyor musun", "inanılmaz derecede utanıyorum", "inan bana", "kandırmak istemiyorum", "tarafından yönetilen bir" ] # Montag'ın karakteriyle ilgili anahtar kelimelerin eksik olup olmadığını kontrol et montag_keywords = ["kitap", "yakmak", "itfaiyeci", "clarisse", "faber", "beatty", "bilgi", "sansür", "düşünce", "gerçek", "televizyon", "alev", "kül", "mildred", "yangın"] has_montag_relevance = any(keyword in final_response_text.lower() for keyword in montag_keywords) # Kontrolleri birleştir if (len(final_response_text.split()) < 10) or \ not any(char.isalpha() for char in final_response_text) or \ any(phrase in final_response_text.lower() for phrase in generic_or_nonsense_phrases) or \ not has_montag_relevance: # Montag anahtar kelimesi yoksa alternatif dön print(f"DEBUG: FİLTRELEME - Cevap YETERSİZ/ANLAMSIZ/ALAKASIZ.") if len(final_response_text.split()) < 10: print(f" - Sebep: Çok kısa ({len(final_response_text.split())} kelime).") if not any(char.isalpha() for char in final_response_text): print(f" - Sebep: Hiç harf içermiyor.") if any(phrase in final_response_text.lower() for phrase in generic_or_nonsense_phrases): triggered_phrase = [phrase for phrase in generic_or_nonsense_phrases if phrase in final_response_text.lower()] print(f" - Sebep: Genel/Anlamsız ifade tespit edildi: {triggered_phrase}.") if not has_montag_relevance: print(f" - Sebep: Montag anahtar kelimesi yok.") print(f"INFO: Üretilen cevap ('{final_response_text}') filtreleri geçemedi. Alternatif üretiliyor.") return generate_alternative_response(question), retrieved_docs # Alternatif ve boş docs dön final_response = add_emojis(final_response_text) return final_response, retrieved_docs # Cevap ve alınan dokümanları döndür except Exception as e: print(f"Error generating answer: {e}") return generate_alternative_response(question), [] # Hata durumunda alternatif ve boş docs dön # === Gradio arayüzü === def respond(msg: str, chatbot_history: List[List[str]], progress=gr.Progress()) -> Tuple[str, List[List[str]], str, str]: if not msg.strip(): return "", chatbot_history, "Lütfen bir soru yazın.", "---" new_history = chatbot_history + [[msg, None]] start_time_overall = time.time() # İlk kullanıcı sorusu kontrolü için geçmişi temizleyerek kontrol et is_first_real_user_question = True for user_msg, _ in chatbot_history: if user_msg is not None and not (("Montag düşünüyor..." in user_msg) or ("saniyede üretildi" in user_msg)): is_first_real_user_question = False break if is_first_real_user_question: initial_stopwatch_text = f"İlk Cevap: 0.00s / {MAX_EXPECTED_TIME:.0f}s" progress_prefix = "Montag düşünüyor... " else: initial_stopwatch_text = f"Geçen Süre: 0.00s" progress_prefix = "Montag cevaplıyor... " yield gr.update(value=""), new_history, f"{progress_prefix}%0", initial_stopwatch_text progress_steps = [ (f"{progress_prefix}💭", 0.0, 0.3), (f"{progress_prefix}📚", 0.3, 0.6), (f"{progress_prefix}🔥", 0.6, 0.9), ] for desc, start_percent, end_percent in progress_steps: for i in range(10): current_progress_percent = start_percent + (end_percent - start_percent) * (i / 9) elapsed_time = time.time() - start_time_overall if is_first_real_user_question: stopwatch_text = f"İlk Cevap: {elapsed_time:.2f}s / {MAX_EXPECTED_TIME:.0f}s" else: stopwatch_text = f"Geçen Süre: {elapsed_time:.2f}s" yield gr.update(value=""), new_history, f"{desc} %{int(current_progress_percent*100)}", stopwatch_text time.sleep(MAX_EXPECTED_TIME / (len(progress_steps) * 10 * 2)) # generate_answer artık hem cevap hem de retrieved_docs döndürüyor answer, retrieved_docs_for_rl = generate_answer(msg, chatbot_history) end_time = time.time() response_time = round(end_time - start_time_overall, 2) # Geçmişi güncelle if new_history and new_history[-1][0] == msg: new_history[-1][1] = answer else: new_history.append([msg, answer]) yield gr.update(value=""), new_history, f"Cevap {response_time} saniyede üretildi. ✅", f"{response_time:.2f}s" def regenerate_answer(chatbot_history: List[List[str]], progress=gr.Progress()) -> Tuple[str, List[List[str]], str, str]: if not chatbot_history: return "", [], "Yeniden üretilecek bir soru bulunamadı.", "---" # Son gerçek kullanıcı sorusunu bul last_user_question = None # Geriye doğru dönerken, "Montag düşünüyor..." gibi durum mesajlarını atla # Ayrıca, regenerate_answer'dan hemen önce basılan dislike nedeniyle # son item'da "Montag düşünüyor..." olabilir. # Bu yüzden, son tamamlanmış kullanıcı sorusunu arıyoruz. for i in range(len(chatbot_history) - 1, -1, -1): user_msg, bot_msg = chatbot_history[i] # Eğer bu bir kullanıcı mesajıysa ve daha önce bir cevap üretilmişse if user_msg is not None and not (("Montag düşünüyor..." in user_msg) or ("saniyede üretildi" in user_msg)): last_user_question = user_msg break if not last_user_question: return "", chatbot_history, "Yeniden üretilecek bir soru bulunamadı.", "---" # Geçmişten son cevabı kaldır (eğer varsa ve bu cevap beğenilmeyip yenisi isteniyorsa) # Burada dikkat: chatbot_history'nin son elemanı beğenilmeyen cevap olabilir. # Bu durumda o elemanı doğrudan modifiye edebiliriz. # Yeni bir liste oluşturarak eski cevabı çıkartıp, yeniden üretilen cevabı eklemek daha temiz olabilir. temp_chatbot_history_for_gen = [list(pair) for pair in chatbot_history if pair[0] != last_user_question or pair[1] is not None] # Eğer son eleman beğenilmeyen cevap ise, onu çıkartıyoruz ki generate_answer'a tekrar gitmesin. # regenerate_answer'a gelen chat_history zaten feedback_callback tarafından işlenmiş, # ve kullanıcıya gösterilen son hali oluyor. Bu yüzden, son bot cevabını silmek yerine, # o soruyu ve eski cevabı history'den çıkarıp yeni cevabı ekleyebiliriz. # Basitçe, son kullanıcı sorusunu yeniden cevaplıyorsak, önceki cevabı yok sayarız. # Son kullanıcı sorusunu içeren kısmı geçmişten çıkar, yenisini ekleyeceğiz. # Ancak Gradio yield ile güncelleyeceğimiz için, son kullanıcı sorusu haricindeki geçmişi generate_answer'a verelim. # Sohbet geçmişinden son kullanıcı-bot çiftini ayırın (eğer varsa) history_without_last_qa = [list(pair) for pair in chatbot_history] if history_without_last_qa and history_without_last_qa[-1][0] == last_user_question: # Son kullanıcı sorusunu ve cevabını (eğer varsa) çıkar history_without_last_qa.pop() start_time_overall = time.time() initial_stopwatch_text = f"Geçen Süre: 0.00s" progress_prefix = "Montag yeni bir cevap düşünüyor... " # Kullanıcıya bir "düşünüyor" mesajı göster yield "", history_without_last_qa + [[last_user_question, "Montag düşünüyor... 🤔"]], f"{progress_prefix}%0", initial_stopwatch_text progress_steps = [ (f"{progress_prefix}🔄", 0.0, 0.3), (f"{progress_prefix}🧠", 0.3, 0.6), (f"{progress_prefix}📚", 0.6, 0.9), ] for desc, start_percent, end_percent in progress_steps: # HATA BURADAYDI! start_percent ve end_percent tanımı ekledik. for i in range(10): current_progress_percent = start_percent + (end_percent - start_percent) * (i / 9) elapsed_time = time.time() - start_time_overall stopwatch_text = f"Geçen Süre: {elapsed_time:.2f}s" yield "", history_without_last_qa + [[last_user_question, "Montag düşünüyor... 🤔"]], f"{desc} %{int(current_progress_percent*100)}", stopwatch_text time.sleep(MAX_EXPECTED_TIME / (len(progress_steps) * 10 * 2)) new_raw_answer, _ = generate_answer(last_user_question, history_without_last_qa) new_final_answer = add_emojis(new_raw_answer) end_time = time.time() response_time = round(end_time - start_time_overall, 2) # Yeni cevabı sohbet geçmişine ekle (eski cevabın yerine) final_history = history_without_last_qa + [[last_user_question, f"{new_final_answer}\n(yaklaşık {response_time:.2f} saniyede üretildi)"]] yield "", final_history, f"Yeni cevap {response_time} saniyede üretildi. ✅", f"{response_time:.2f}s" def feedback_callback(chatbot_history: List[List[str]], liked: bool) -> str: if not chatbot_history: return "Önce bir sohbet gerçekleştirin." last_user_question = None last_assistant_answer = None # Sondan başlayarak gerçek kullanıcı sorusunu ve bot cevabını bul # Eğer en son girdi sadece kullanıcı sorusu ise veya Montag düşünüyor ise, bir önceki tam Q&A'yı ararız for i in range(len(chatbot_history) - 1, -1, -1): current_pair = chatbot_history[i] if current_pair[0] is not None and current_pair[1] is not None and \ not (("Montag düşünüyor..." in current_pair[0]) or ("saniyede üretildi" in current_pair[0])) and \ not (("Montag düşünüyor..." in current_pair[1]) or ("saniyede üretildi" in current_pair[1])): last_user_question = current_pair[0] last_assistant_answer = current_pair[1] break if last_user_question and last_assistant_answer: # "saniyede üretildi" ve emoji kısımlarını temizle cleaned_assistant_answer = re.sub(r'\(yaklaşık \d+\.\d{2} saniyede üretildi\)', '', last_assistant_answer).strip() cleaned_assistant_answer = cleaned_assistant_answer.replace('📚', '').replace('🧠', '').replace('🔥', '').strip() cleaned_user_question = last_user_question.replace('📚', '').replace('🧠', '').replace('🔥', '').strip() save_feedback(cleaned_user_question, cleaned_assistant_answer, liked, FEEDBACK_FILE) # RL Agent'a deneyimi kaydet rl_agent.record_experience( cleaned_user_question, cleaned_assistant_answer, liked ) # Eğer beğenildiyse, LoRA fine-tuning için QA_PATH'e de ekle if liked: qa_pair = {"question": cleaned_user_question, "answer": cleaned_assistant_answer, "liked": True} try: with open(QA_PATH, "a", encoding="utf-8") as f: f.write(json.dumps(qa_pair, ensure_ascii=False) + "\n") except Exception as e: print(f"Error saving QA pair to {QA_PATH}: {e}") # Yeterli örnek varsa LoRA fine-tuning yap if count_qa_examples(QA_PATH) % 10 == 0: # Her 10 yeni beğenilen örnekte bir fine-tune yap print("👍 Yeterli sayıda yeni beğeni var, LoRA fine-tuning başlatılıyor...") lora_finetune(QA_PATH, LOR_MODEL_PATH) return "Geri bildiriminiz kaydedildi ve model eğitimi tetiklendi. Teşekkürler! 👍" else: return "Geri bildiriminiz kaydedildi. Teşekkürler! ✅" else: return "Geri bildiriminiz kaydedildi. Montag daha iyi olmaya çalışacak. ❌" return "Geri bildirim kaydedilemedi. Geçmişte yeterli sohbet bulunmuyor. ❌" # === Gradio arayüzü === def create_chat_interface(): with gr.Blocks(theme=gr.themes.Soft(), css=current_css) as demo: gr.Markdown(""" # 📚 Montag Chatbot (Fahrenheit 451) 🔥 *Ray Bradbury'nin Fahrenheit 451 romanındaki karakter **Guy Montag** ile sohbet edin. O, kitapları yakan bir itfaiyeci olsa da, aslında gerçeği ve bilginin değerini arayan, isyankar ruhlu bir adamdır.* """) chatbot = gr.Chatbot(height=500, elem_id="chatbot") msg = gr.Textbox(label="Montag'a sormak istediğiniz soruyu yazın", placeholder="Kitaplar neden yasaklandı?") status_message = gr.Textbox(label="Durum", interactive=False, max_lines=1, value="Lütfen bir soru yazın.") stopwatch_display = gr.Markdown(f"Hazır. İlk cevap için tahmini süre: {MAX_EXPECTED_TIME:.0f}s", elem_id="stopwatch_display") with gr.Row(): submit_btn = gr.Button("Gönder", variant="primary") clear_btn = gr.Button("Sohbeti Temizle") with gr.Row(): like_btn = gr.Button("👍 Beğendim") dislike_btn = gr.Button("👎 Beğenmedim (Yeni Cevap Dene)") feedback_status_output = gr.Textbox(label="Geri Bildirim Durumu", interactive=False, max_lines=1) # Gradio olay dinleyicileri msg.submit( respond, [msg, chatbot], [msg, chatbot, status_message, stopwatch_display], api_name="respond" ) submit_btn.click( respond, [msg, chatbot], [msg, chatbot, status_message, stopwatch_display], api_name="respond_button" ) clear_btn.click( lambda: ([], gr.update(value="Lütfen bir soru yazın."), gr.update(value=f"Hazır. İlk cevap için tahmini süre: {MAX_EXPECTED_TIME:.0f}s"), gr.update(value="")), inputs=None, outputs=[chatbot, status_message, stopwatch_display, msg], queue=False ) # Beğenme butonu: Sadece geri bildirim kaydeder like_btn.click( partial(feedback_callback, liked=True), [chatbot], [feedback_status_output] ) # Beğenmeme butonu: Geri bildirim kaydeder ve YENİ CEVAP ÜRETİR dislike_btn.click( partial(feedback_callback, liked=False), # İlk olarak geri bildirimi kaydet [chatbot], [feedback_status_output] ).success( # Geri bildirim kaydedildikten sonra yeni cevap üret regenerate_answer, [chatbot], [msg, chatbot, status_message, stopwatch_display] # msg'yi de temizle ve diğerlerini güncelle ) demo.css = """ #chatbot .message:nth-child(odd) { text-align: left; background-color: #f1f1f1; border-radius: 15px; padding: 10px; margin-right: 20%; } #chatbot .message:nth-child(even) { text-align: right; background-color: #dcf8c6; border-radius: 15px; padding: 10px; margin-left: 20%; } #chatbot .message { margin-bottom: 10px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .gradio-container .message-row.user { justify-content: flex-start !important; } .gradio-container .message-row.bot { justify-content: flex-end !important; } """ return demo # === UYGULAMA BAŞLATMA === if __name__ == "__main__": print("Chatbot başlatılıyor...") # 1. Yerel dosya ve klasör yapısını kur setup_local_files() # 2. Tüm ana bileşenleri yükle (model, tokenizer, embedder, FAISS, RLAgent) initialization_successful = initialize_components() if not initialization_successful: print("UYARI: Bileşenler başlatılamadı. Uygulama düzgün çalışmayabilir.") else: print("Chatbot başlatılmaya hazır.") # Gradio arayüzünü oluştur ve başlat demo = create_chat_interface() demo.launch(debug=True, share=True)