Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import PyPDF2 | |
| from langchain.text_splitter import RecursiveCharacterTextSplitter | |
| from langchain.embeddings import HuggingFaceEmbeddings | |
| from langchain.vectorstores import FAISS | |
| from langchain.llms import HuggingFacePipeline | |
| from transformers import pipeline, AutoTokenizer, GPT2LMHeadModel, BitsAndBytesConfig | |
| import os | |
| import torch | |
| # --- 0. Global Değişkenler ve Ayarlar --- | |
| PDF_PATH = "mevzuat.pdf" | |
| CHUNK_SIZE = 400 | |
| CHUNK_OVERLAP = 100 | |
| EMBEDDING_MODEL_NAME = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2" | |
| LLM_MODEL_NAME = "ytu-ce-cosmos/turkish-gpt2-medium" | |
| DEVICE = "cuda" if torch.cuda.is_available() else "cpu" | |
| # GPT-2'nin maksimum token uzunluğu (genellikle 1024) | |
| MODEL_MAX_LENGTH = 1024 | |
| # Üretilecek maksimum yeni token sayısı | |
| MAX_NEW_TOKENS = 200 # Global olarak tanımlandı | |
| # --- 1. PDF'i İşle ve Vektör Veritabanı Oluştur --- | |
| def create_vector_db_from_pdf(pdf_path): | |
| print(f"PDF okunuyor: {pdf_path}") | |
| text = "" | |
| try: | |
| with open(pdf_path, "rb") as file: | |
| reader = PyPDF2.PdfReader(file) | |
| for page_num, page in enumerate(reader.pages): | |
| page_text = page.extract_text() | |
| if page_text: | |
| text += f"Sayfa {page_num + 1}:\n" + page_text + "\n\n" | |
| except Exception as e: | |
| print(f"PDF okunurken hata oluştu: {e}") | |
| return None | |
| if not text: | |
| print("PDF'ten metin çıkarılamadı.") | |
| return None | |
| print(f"Metin toplam {len(text)} karakter.") | |
| text_splitter = RecursiveCharacterTextSplitter( | |
| chunk_size=CHUNK_SIZE, | |
| chunk_overlap=CHUNK_OVERLAP, | |
| length_function=len, | |
| ) | |
| chunks = text_splitter.split_text(text) | |
| print(f"Metin {len(chunks)} parçaya ayrıldı.") | |
| print(f"Embedding modeli yükleniyor: {EMBEDDING_MODEL_NAME}") | |
| embeddings = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL_NAME) | |
| print("FAISS vektör veritabanı oluşturuluyor...") | |
| db = FAISS.from_texts(chunks, embeddings) | |
| print("Vektör veritabanı başarıyla oluşturuldu.") | |
| return db, embeddings | |
| vector_db, embeddings_model = None, None | |
| if os.path.exists(PDF_PATH): | |
| vector_db, embeddings_model = create_vector_db_from_pdf(PDF_PATH) | |
| else: | |
| print(f"Hata: {PDF_PATH} bulunamadı. Lütfen PDF dosyasını projenizin kök dizinine yükleyin.") | |
| print(f"LLM modeli yükleniyor: {LLM_MODEL_NAME} - Cihaz: {DEVICE}") | |
| tokenizer = None | |
| llm = None | |
| try: | |
| tokenizer = AutoTokenizer.from_pretrained(LLM_MODEL_NAME) | |
| if tokenizer.pad_token is None: | |
| tokenizer.pad_token = tokenizer.eos_token | |
| if DEVICE == "cuda": | |
| quantization_config = BitsAndBytesConfig( | |
| load_in_4bit=True, | |
| bnb_4bit_quant_type="nf4", | |
| bnb_4bit_compute_dtype=torch.float16, | |
| bnb_4bit_use_double_quant=True, | |
| ) | |
| model = GPT2LMHeadModel.from_pretrained(LLM_MODEL_NAME, | |
| quantization_config=quantization_config, | |
| device_map="auto") | |
| else: | |
| model = GPT2LMHeadModel.from_pretrained(LLM_MODEL_NAME).to(DEVICE) | |
| text_generation_pipeline = pipeline( | |
| "text-generation", | |
| model=model, | |
| tokenizer=tokenizer, | |
| max_new_tokens=MAX_NEW_TOKENS, # Global MAX_NEW_TOKENS kullanıldı | |
| temperature=0.7, | |
| do_sample=True, | |
| top_k=50, | |
| num_return_sequences=1, | |
| device=0 if DEVICE == "cuda" else -1, | |
| pad_token_id=tokenizer.pad_token_id, | |
| return_full_text=False | |
| ) | |
| llm = HuggingFacePipeline(pipeline=text_generation_pipeline) | |
| print("LLM modeli başarıyla yüklendi.") | |
| except Exception as e: | |
| print(f"LLM modeli yüklenirken hata oluştu: {e}") | |
| llm = None | |
| # --- 2. Gradio Arayüzü İçin Soru Cevaplama Fonksiyonu --- | |
| def answer_question(question, chat_history): | |
| global tokenizer | |
| if vector_db is None or llm is None or tokenizer is None: | |
| return "", chat_history + [[question, "Üzgünüm, PDF, LLM veya Tokenizer yüklenemediği için şu an sorularınızı cevaplayamıyorum. Lütfen logları kontrol edin."]] | |
| print(f"Gelen soru: {question}") | |
| retrieved_docs = vector_db.similarity_search(question, k=2) | |
| context = "\n\n".join([doc.page_content for doc in retrieved_docs]) | |
| print(f"Bağlam için {len(retrieved_docs)} belge bulundu. İlk 200 karakter:\n{context[:200]}...") | |
| prompt = f"""Aşağıdaki bağlamı kullanarak soruyu cevaplayın. Bağlamda yoksa uydurma yapmayın. Cevabı madde madde yazın ve en sonda kullandığınız kaynakları (varsa 'Sayfa X' gibi belirtin) listeleyin. | |
| Bağlam: | |
| {context} | |
| Soru: {question} | |
| Cevap: | |
| """ | |
| print("LLM'e gönderilen prompt'ın başlangıcı:", prompt[:500]) | |
| # --- Prompt uzunluğunu MODEL_MAX_LENGTH - MAX_NEW_TOKENS ile sınırlayalım --- | |
| # Modelin içine girecek token sayısı (MODEL_MAX_LENGTH - MAX_NEW_TOKENS) olmalı | |
| max_input_tokens = MODEL_MAX_LENGTH - MAX_NEW_TOKENS | |
| # Prompt'u token'lara ayır ve uzunluğunu kontrol et | |
| prompt_tokens = tokenizer.encode(prompt, return_tensors='pt')[0] | |
| tokenized_prompt_length = prompt_tokens.shape[0] | |
| print(f"Oluşturulan prompt'un token uzunluğu: {tokenized_prompt_length}") | |
| if tokenized_prompt_length > max_input_tokens: | |
| print(f"UYARI: Prompt'un token uzunluğu ({tokenized_prompt_length}) modelin kabul edebileceği maksimum girdi uzunluğunu ({max_input_tokens}) aşıyor! Kısaltılıyor...") | |
| prompt_tokens = prompt_tokens[:max_input_tokens] | |
| prompt = tokenizer.decode(prompt_tokens, skip_special_tokens=True) | |
| print(f"Prompt kısaltıldı. Yeni token uzunluğu: {len(tokenizer.encode(prompt))}") | |
| print("Kısaltılmış prompt'ın başlangıcı:", prompt[:500]) | |
| try: | |
| response_data = llm(prompt) | |
| if isinstance(response_data, list) and len(response_data) > 0 and 'generated_text' in response_data[0]: | |
| final_answer = response_data[0]['generated_text'].strip() | |
| else: | |
| final_answer = str(response_data).strip() | |
| if not final_answer: | |
| final_answer = "Sorunuzla ilgili bağlamda yeterli bilgi bulunamadı." | |
| if final_answer.lower().startswith(prompt.lower()): | |
| final_answer = final_answer[len(prompt):].strip() | |
| if "Cevap:" in final_answer: | |
| final_answer = final_answer.split("Cevap:", 1)[-1].strip() | |
| if final_answer.lower().startswith("aşağıdaki bağlamı kullanarak"): | |
| final_answer = final_answer.split("Cevap:", 1)[-1].strip() if "Cevap:" in final_answer else final_answer.strip() | |
| final_answer = "..." + final_answer | |
| except Exception as e: | |
| final_answer = f"Cevap üretilirken bir hata oluştu: {e}" | |
| print(final_answer) | |
| chat_history.append((question, final_answer)) | |
| return "", chat_history | |
| # --- 3. Gradio Arayüz Tanımı --- | |
| with gr.Blocks() as demo: | |
| gr.Markdown( | |
| """ | |
| # Fırat Üniversitesi Mini RAG Botu | |
| Fırat Üniversitesi mevzuat.firat.edu.tr adresinden alınan PDF dosyası üzerinde soru-cevap yapın. | |
| Sorularınıza belgedeki bilgilere dayanarak cevaplar verilecektir. | |
| Bağlamda bilgi yoksa, bot uydurma yapmayacaktır. | |
| """ | |
| ) | |
| chatbot = gr.Chatbot(height=400, label="Sohbet") | |
| msg = gr.Textbox(label="Sorunuzu buraya yazın:", placeholder="Örn: 'Belgede tez jürisi kuralı ne?'") | |
| with gr.Row(): | |
| submit_btn = gr.Button("Gönder") | |
| clear_btn = gr.Button("Sohbeti Temizle") | |
| msg.submit(answer_question, [msg, chatbot], [msg, chatbot]) | |
| submit_btn.click(answer_question, [msg, chatbot], [msg, chatbot]) | |
| clear_btn.click(lambda: (None, []), outputs=[msg, chatbot]) | |
| if __name__ == "__main__": | |
| demo.launch() |