Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -8,82 +8,112 @@ from transformers import pipeline, AutoTokenizer, GPT2LMHeadModel, BitsAndBytesC
|
|
| 8 |
import os
|
| 9 |
import torch
|
| 10 |
import re
|
|
|
|
| 11 |
|
| 12 |
# --- 0. Global Değişkenler ve Ayarlar ---
|
| 13 |
PDF_PATH = "mevzuat.pdf"
|
| 14 |
-
|
| 15 |
-
|
|
|
|
| 16 |
EMBEDDING_MODEL_NAME = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
|
| 17 |
LLM_MODEL_NAME = "ytu-ce-cosmos/turkish-gpt2-medium"
|
| 18 |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 19 |
|
| 20 |
MODEL_MAX_LENGTH = 1024
|
| 21 |
-
MAX_NEW_TOKENS =
|
| 22 |
-
|
| 23 |
-
# --- Yardımcı Metin Temizleme Fonksiyonu (
|
| 24 |
-
def
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
#
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
return text
|
| 31 |
|
| 32 |
# --- 1. PDF'i İşle ve Vektör Veritabanı Oluştur ---
|
| 33 |
def create_vector_db_from_pdf(pdf_path):
|
| 34 |
-
print(f"PDF okunuyor: {pdf_path}")
|
| 35 |
|
| 36 |
chunks_with_metadata = []
|
| 37 |
|
| 38 |
try:
|
| 39 |
with open(pdf_path, "rb") as file:
|
| 40 |
reader = PyPDF2.PdfReader(file)
|
|
|
|
|
|
|
|
|
|
| 41 |
for page_num, page in enumerate(reader.pages):
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
except Exception as e:
|
| 60 |
-
print(f"PDF okunurken hata oluştu: {e}")
|
| 61 |
return None
|
| 62 |
|
| 63 |
if not chunks_with_metadata:
|
| 64 |
-
print("PDF'ten metin çıkarılamadı veya parçalanamadı.")
|
| 65 |
return None
|
| 66 |
|
| 67 |
-
print(f"Metin {len(chunks_with_metadata)} parçaya ayrıldı (metadata dahil).")
|
| 68 |
|
| 69 |
-
print(f"Embedding modeli yükleniyor: {EMBEDDING_MODEL_NAME}")
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
texts = [item["content"] for item in chunks_with_metadata]
|
| 73 |
metadatas = [{"source_page": item["source_page"]} for item in chunks_with_metadata]
|
| 74 |
|
| 75 |
-
print("FAISS vektör veritabanı oluşturuluyor...")
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
|
|
|
|
| 80 |
vector_db, embeddings_model = None, None
|
| 81 |
if os.path.exists(PDF_PATH):
|
| 82 |
vector_db, embeddings_model = create_vector_db_from_pdf(PDF_PATH)
|
| 83 |
else:
|
| 84 |
-
print(f"Hata: {PDF_PATH} bulunamadı. Lütfen PDF dosyasını projenizin kök dizinine yükleyin.")
|
| 85 |
|
| 86 |
-
|
|
|
|
| 87 |
tokenizer = None
|
| 88 |
llm = None
|
| 89 |
try:
|
|
@@ -96,7 +126,7 @@ try:
|
|
| 96 |
quantization_config = BitsAndBytesConfig(
|
| 97 |
load_in_4bit=True,
|
| 98 |
bnb_4bit_quant_type="nf4",
|
| 99 |
-
bnb_4bit_compute_dtype=
|
| 100 |
bnb_4bit_use_double_quant=True,
|
| 101 |
)
|
| 102 |
model = GPT2LMHeadModel.from_pretrained(LLM_MODEL_NAME,
|
|
@@ -110,47 +140,82 @@ try:
|
|
| 110 |
model=model,
|
| 111 |
tokenizer=tokenizer,
|
| 112 |
max_new_tokens=MAX_NEW_TOKENS,
|
| 113 |
-
temperature=0.
|
| 114 |
do_sample=True,
|
| 115 |
-
top_k=
|
| 116 |
num_return_sequences=1,
|
| 117 |
device=0 if DEVICE == "cuda" else -1,
|
| 118 |
pad_token_id=tokenizer.pad_token_id,
|
| 119 |
-
return_full_text=False
|
| 120 |
)
|
| 121 |
llm = HuggingFacePipeline(pipeline=text_generation_pipeline)
|
| 122 |
-
print("LLM modeli başarıyla yüklendi.")
|
| 123 |
except Exception as e:
|
| 124 |
-
print(f"LLM modeli yüklenirken hata oluştu: {e}")
|
| 125 |
-
llm = None
|
| 126 |
|
| 127 |
# --- 2. Gradio Arayüzü İçin Soru Cevaplama Fonksiyonu ---
|
| 128 |
def answer_question(question, chat_history):
|
| 129 |
global tokenizer
|
| 130 |
if vector_db is None or llm is None or tokenizer is None:
|
| 131 |
-
|
|
|
|
|
|
|
| 132 |
|
| 133 |
-
print(f"Gelen soru: {question}")
|
| 134 |
-
|
| 135 |
-
# Soruyu da küçük harfe çevirerek normalizasyonu sağla
|
| 136 |
-
normalized_question = clean_and_normalize_text(question)
|
| 137 |
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
context_parts = []
|
| 141 |
-
source_pages = set()
|
| 142 |
|
| 143 |
-
for doc, score in retrieved_docs_with_scores:
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
context = "\n\n".join(context_parts)
|
| 149 |
|
| 150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
|
| 152 |
-
# Geliştirilmiş Prompt
|
| 153 |
-
prompt = f"""Sen bir Fırat Üniversitesi mevzuat uzmanısın.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
Bağlam:
|
| 156 |
{context}
|
|
@@ -159,21 +224,23 @@ Soru: {question}
|
|
| 159 |
|
| 160 |
Cevap:
|
| 161 |
"""
|
| 162 |
-
print("LLM'e gönderilen prompt'ın başlangıcı:
|
| 163 |
|
|
|
|
| 164 |
max_input_tokens = MODEL_MAX_LENGTH - MAX_NEW_TOKENS
|
| 165 |
|
| 166 |
prompt_tokens = tokenizer.encode(prompt, return_tensors='pt')[0]
|
| 167 |
tokenized_prompt_length = prompt_tokens.shape[0]
|
| 168 |
|
| 169 |
-
print(f"Oluşturulan prompt'un token uzunluğu: {tokenized_prompt_length}")
|
| 170 |
|
| 171 |
if tokenized_prompt_length > max_input_tokens:
|
| 172 |
-
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...")
|
|
|
|
| 173 |
prompt_tokens = prompt_tokens[:max_input_tokens]
|
| 174 |
prompt = tokenizer.decode(prompt_tokens, skip_special_tokens=True)
|
| 175 |
-
print(f"Prompt kısaltıldı. Yeni token uzunluğu: {len(tokenizer.encode(prompt))}")
|
| 176 |
-
print("Kısaltılmış prompt'ın başlangıcı:
|
| 177 |
|
| 178 |
final_answer = ""
|
| 179 |
try:
|
|
@@ -184,25 +251,41 @@ Cevap:
|
|
| 184 |
else:
|
| 185 |
final_answer = str(response_data).strip()
|
| 186 |
|
|
|
|
|
|
|
| 187 |
if final_answer.lower().startswith(prompt.lower()):
|
| 188 |
final_answer = final_answer[len(prompt):].strip()
|
| 189 |
|
|
|
|
| 190 |
if "Cevap:" in final_answer:
|
| 191 |
final_answer = final_answer.split("Cevap:", 1)[-1].strip()
|
| 192 |
|
| 193 |
-
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
-
|
|
|
|
| 197 |
final_answer = "Üzgünüm, bu soruya bağlamda yeterli bilgi bulunmamaktadır."
|
| 198 |
|
|
|
|
| 199 |
if source_pages and final_answer != "Üzgünüm, bu soruya bağlamda yeterli bilgi bulunmamaktadır.":
|
| 200 |
sorted_pages = sorted(list(source_pages))
|
| 201 |
final_answer += f"\n\n**Kaynak:** Sayfa {', '.join(map(str, sorted_pages))}"
|
|
|
|
|
|
|
| 202 |
|
| 203 |
except Exception as e:
|
| 204 |
-
final_answer = f"Cevap üretilirken bir hata oluştu: {e}"
|
| 205 |
-
print(
|
| 206 |
|
| 207 |
chat_history.append((question, final_answer))
|
| 208 |
return "", chat_history
|
|
@@ -214,7 +297,7 @@ with gr.Blocks() as demo:
|
|
| 214 |
# Fırat Üniversitesi Mini RAG Botu
|
| 215 |
Fırat Üniversitesi mevzuat.firat.edu.tr adresinden alınan PDF dosyası üzerinde soru-cevap yapın.
|
| 216 |
Sorularınıza belgedeki bilgilere dayanarak cevaplar verilecektir.
|
| 217 |
-
Bağlamda bilgi yoksa, bot uydurma yapmayacaktır.
|
| 218 |
**Cevaplar artık kaynak sayfa numarasını da içermektedir.**
|
| 219 |
"""
|
| 220 |
)
|
|
|
|
| 8 |
import os
|
| 9 |
import torch
|
| 10 |
import re
|
| 11 |
+
import unicodedata # Unicode normalizasyonu için
|
| 12 |
|
| 13 |
# --- 0. Global Değişkenler ve Ayarlar ---
|
| 14 |
PDF_PATH = "mevzuat.pdf"
|
| 15 |
+
# Mevzuat için daha uygun Chunk boyutları
|
| 16 |
+
CHUNK_SIZE = 700 # Daha da artırıldı
|
| 17 |
+
CHUNK_OVERLAP = 150 # Daha da artırıldı
|
| 18 |
EMBEDDING_MODEL_NAME = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
|
| 19 |
LLM_MODEL_NAME = "ytu-ce-cosmos/turkish-gpt2-medium"
|
| 20 |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 21 |
|
| 22 |
MODEL_MAX_LENGTH = 1024
|
| 23 |
+
MAX_NEW_TOKENS = 350 # Daha detaylı cevaplar için artırıldı
|
| 24 |
+
|
| 25 |
+
# --- Yardımcı Metin Temizleme ve Normalizasyon Fonksiyonu (Daha Kapsamlı) ---
|
| 26 |
+
def normalize_text(text):
|
| 27 |
+
if not isinstance(text, str):
|
| 28 |
+
return ""
|
| 29 |
+
|
| 30 |
+
# Unicode normalizasyonu (aksansız karakterlere çevirme, vb.)
|
| 31 |
+
text = unicodedata.normalize('NFKD', text).encode('ascii', 'ignore').decode('utf-8', 'ignore')
|
| 32 |
+
|
| 33 |
+
# Küçük harfe çevir
|
| 34 |
+
text = text.lower()
|
| 35 |
+
|
| 36 |
+
# Noktalama işaretlerini kaldır (kelime bazlı arama için önemli)
|
| 37 |
+
text = re.sub(r'[^\w\s]', '', text)
|
| 38 |
+
|
| 39 |
+
# Birden fazla boşluğu tek boşluğa çevir ve baştaki/sondaki boşlukları kaldır
|
| 40 |
+
text = re.sub(r'\s+', ' ', text).strip()
|
| 41 |
+
|
| 42 |
return text
|
| 43 |
|
| 44 |
# --- 1. PDF'i İşle ve Vektör Veritabanı Oluştur ---
|
| 45 |
def create_vector_db_from_pdf(pdf_path):
|
| 46 |
+
print(f"[{os.path.basename(__file__)}] PDF okunuyor: {pdf_path}")
|
| 47 |
|
| 48 |
chunks_with_metadata = []
|
| 49 |
|
| 50 |
try:
|
| 51 |
with open(pdf_path, "rb") as file:
|
| 52 |
reader = PyPDF2.PdfReader(file)
|
| 53 |
+
total_pages = len(reader.pages)
|
| 54 |
+
print(f"[{os.path.basename(__file__)}] Toplam {total_pages} sayfa bulundu.")
|
| 55 |
+
|
| 56 |
for page_num, page in enumerate(reader.pages):
|
| 57 |
+
try:
|
| 58 |
+
page_text = page.extract_text()
|
| 59 |
+
if page_text:
|
| 60 |
+
# Metin temizliği ve normalizasyonunu uygula
|
| 61 |
+
cleaned_and_normalized_page_text = normalize_text(page_text)
|
| 62 |
+
|
| 63 |
+
text_splitter = RecursiveCharacterTextSplitter(
|
| 64 |
+
chunk_size=CHUNK_SIZE,
|
| 65 |
+
chunk_overlap=CHUNK_OVERLAP,
|
| 66 |
+
length_function=len,
|
| 67 |
+
)
|
| 68 |
+
page_chunks = text_splitter.split_text(cleaned_and_normalized_page_text)
|
| 69 |
+
|
| 70 |
+
for chunk in page_chunks:
|
| 71 |
+
chunks_with_metadata.append({
|
| 72 |
+
"content": chunk,
|
| 73 |
+
"source_page": page_num + 1
|
| 74 |
+
})
|
| 75 |
+
except Exception as page_e:
|
| 76 |
+
print(f"[{os.path.basename(__file__)}] Sayfa {page_num + 1} okunurken veya işlenirken hata oluştu: {page_e}")
|
| 77 |
+
continue # Bir sonraki sayfaya geç
|
| 78 |
+
|
| 79 |
except Exception as e:
|
| 80 |
+
print(f"[{os.path.basename(__file__)}] PDF dosyası okunurken genel hata oluştu: {e}")
|
| 81 |
return None
|
| 82 |
|
| 83 |
if not chunks_with_metadata:
|
| 84 |
+
print(f"[{os.path.basename(__file__)}] PDF'ten metin çıkarılamadı veya parçalanamadı. Lütfen PDF içeriğini kontrol edin.")
|
| 85 |
return None
|
| 86 |
|
| 87 |
+
print(f"[{os.path.basename(__file__)}] Metin {len(chunks_with_metadata)} parçaya ayrıldı (metadata dahil).")
|
| 88 |
|
| 89 |
+
print(f"[{os.path.basename(__file__)}] Embedding modeli yükleniyor: {EMBEDDING_MODEL_NAME}")
|
| 90 |
+
try:
|
| 91 |
+
embeddings = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL_NAME)
|
| 92 |
+
except Exception as e:
|
| 93 |
+
print(f"[{os.path.basename(__file__)}] Embedding modeli yüklenirken hata oluştu: {e}")
|
| 94 |
+
return None
|
| 95 |
|
| 96 |
texts = [item["content"] for item in chunks_with_metadata]
|
| 97 |
metadatas = [{"source_page": item["source_page"]} for item in chunks_with_metadata]
|
| 98 |
|
| 99 |
+
print(f"[{os.path.basename(__file__)}] FAISS vektör veritabanı oluşturuluyor...")
|
| 100 |
+
try:
|
| 101 |
+
db = FAISS.from_texts(texts, embeddings, metadatas=metadatas)
|
| 102 |
+
print(f"[{os.path.basename(__file__)}] Vektör veritabanı başarıyla oluşturuldu.")
|
| 103 |
+
return db, embeddings
|
| 104 |
+
except Exception as e:
|
| 105 |
+
print(f"[{os.path.basename(__file__)}] FAISS veritabanı oluşturulurken hata oluştu: {e}")
|
| 106 |
+
return None
|
| 107 |
|
| 108 |
+
# --- Vektör veritabanını başlatma ---
|
| 109 |
vector_db, embeddings_model = None, None
|
| 110 |
if os.path.exists(PDF_PATH):
|
| 111 |
vector_db, embeddings_model = create_vector_db_from_pdf(PDF_PATH)
|
| 112 |
else:
|
| 113 |
+
print(f"[{os.path.basename(__file__)}] Hata: {PDF_PATH} bulunamadı. Lütfen PDF dosyasını projenizin kök dizinine yükleyin ve doğru yolda olduğundan emin olun.")
|
| 114 |
|
| 115 |
+
# --- LLM modelini yükleme ---
|
| 116 |
+
print(f"[{os.path.basename(__file__)}] LLM modeli yükleniyor: {LLM_MODEL_NAME} - Cihaz: {DEVICE}")
|
| 117 |
tokenizer = None
|
| 118 |
llm = None
|
| 119 |
try:
|
|
|
|
| 126 |
quantization_config = BitsAndBytesConfig(
|
| 127 |
load_in_4bit=True,
|
| 128 |
bnb_4bit_quant_type="nf4",
|
| 129 |
+
bnb_4bit_compute_dtype=torch.float16, # Doğru format "torch.float16" veya torch.float16
|
| 130 |
bnb_4bit_use_double_quant=True,
|
| 131 |
)
|
| 132 |
model = GPT2LMHeadModel.from_pretrained(LLM_MODEL_NAME,
|
|
|
|
| 140 |
model=model,
|
| 141 |
tokenizer=tokenizer,
|
| 142 |
max_new_tokens=MAX_NEW_TOKENS,
|
| 143 |
+
temperature=0.3, # Daha kesin ve tutarlı cevaplar için biraz daha düşürüldü
|
| 144 |
do_sample=True,
|
| 145 |
+
top_k=30, # Daha odaklı cevaplar için biraz daha düşürüldü
|
| 146 |
num_return_sequences=1,
|
| 147 |
device=0 if DEVICE == "cuda" else -1,
|
| 148 |
pad_token_id=tokenizer.pad_token_id,
|
| 149 |
+
return_full_text=False # Sadece üretilen metni döndür
|
| 150 |
)
|
| 151 |
llm = HuggingFacePipeline(pipeline=text_generation_pipeline)
|
| 152 |
+
print(f"[{os.path.basename(__file__)}] LLM modeli başarıyla yüklendi.")
|
| 153 |
except Exception as e:
|
| 154 |
+
print(f"[{os.path.basename(__file__)}] LLM modeli yüklenirken kritik hata oluştu: {e}")
|
| 155 |
+
llm = None # Hata durumunda LLM'i None olarak bırak
|
| 156 |
|
| 157 |
# --- 2. Gradio Arayüzü İçin Soru Cevaplama Fonksiyonu ---
|
| 158 |
def answer_question(question, chat_history):
|
| 159 |
global tokenizer
|
| 160 |
if vector_db is None or llm is None or tokenizer is None:
|
| 161 |
+
error_msg = "Üzgünüm, sistem başlatılamadı (PDF, LLM veya Tokenizer yüklenemedi). Lütfen logları kontrol edin ve yöneticinizle iletişime geçin."
|
| 162 |
+
print(f"[{os.path.basename(__file__)}] Hata: {error_msg}")
|
| 163 |
+
return "", chat_history + [[question, error_msg]]
|
| 164 |
|
| 165 |
+
print(f"[{os.path.basename(__file__)}] Gelen soru: '{question}'")
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
+
# Soruyu da veritabanındaki metinlerle aynı şekilde normalize et
|
| 168 |
+
normalized_question = normalize_text(question)
|
| 169 |
+
print(f"[{os.path.basename(__file__)}] Normalize edilmiş soru: '{normalized_question}'")
|
| 170 |
+
|
| 171 |
+
if not normalized_question.strip():
|
| 172 |
+
return "", chat_history + [[question, "Lütfen geçerli bir soru girin."]]
|
| 173 |
+
|
| 174 |
+
# Daha fazla potansiyel ilgili belge getir
|
| 175 |
+
retrieved_docs_with_scores = vector_db.similarity_search_with_score(normalized_question, k=6) # k değeri 4'ten 6'ya çıkarıldı
|
| 176 |
|
| 177 |
+
if not retrieved_docs_with_scores:
|
| 178 |
+
print(f"[{os.path.basename(__file__)}] Sorunuza uygun ilgili belge bulunamadı.")
|
| 179 |
+
return "", chat_history + [[question, "Üzgünüm, sorunuzla ilgili bağlamda herhangi bir bilgi bulunamadı. Lütfen sorunuzu farklı bir şekilde ifade etmeyi deneyin veya daha spesifik olun."]]
|
| 180 |
+
|
| 181 |
context_parts = []
|
| 182 |
+
source_pages = set() # Kullanılan sayfa numaralarını tutmak için
|
| 183 |
|
| 184 |
+
for i, (doc, score) in enumerate(retrieved_docs_with_scores):
|
| 185 |
+
# Yüksek benzerlik skoruna sahip (düşük mesafe) belgeleri tercih et
|
| 186 |
+
# Çok alakasız görünenleri eleyebiliriz, eşik değeri deneyerek ayarlanmalı
|
| 187 |
+
# Örneğin, mesafe 0.5'ten büyükse ele (FAISS'te daha küçük mesafe daha iyi benzerliktir)
|
| 188 |
+
# print(f"Doc {i+1} Score: {score}, Content: {doc.page_content[:100]}...") # Debug için
|
| 189 |
+
|
| 190 |
+
# Eğer embedding modeli cosine similarity döndürüyorsa, 0'a yakınlık yüksek benzerlik demektir.
|
| 191 |
+
# Eğer L2 mesafesi döndürüyorsa, 0'a yakınlık yüksek benzerlik demektir.
|
| 192 |
+
# sentence-transformers genellikle cosine similarity döndürür, bu yüzden 1'e yakın değerler daha iyi.
|
| 193 |
+
# Ancak FAISS genellikle L2 mesafesi kullanır (veya cosine distance'ı 1-cos(theta) olarak temsil eder).
|
| 194 |
+
# Genellikle 0'a yakın değerler daha iyi (daha küçük mesafe).
|
| 195 |
+
# Eğer score çok yüksek (büyük bir sayı) ise, bu doküman çok alakasız olabilir.
|
| 196 |
+
|
| 197 |
+
# Basit bir filtreleme: İlk 3 dokümanı kesin al, diğerlerini skorlarına göre değerlendir
|
| 198 |
+
if i < 3 or score < 0.6: # Bu eşik değeri denemelerle ayarlanmalı
|
| 199 |
+
context_parts.append(doc.page_content)
|
| 200 |
+
if 'source_page' in doc.metadata:
|
| 201 |
+
source_pages.add(doc.metadata['source_page'])
|
| 202 |
+
else:
|
| 203 |
+
print(f"[{os.path.basename(__file__)}] Doküman {i+1} çok alakasız olduğu için bağlama eklenmedi (Skor: {score:.4f}).")
|
| 204 |
|
| 205 |
context = "\n\n".join(context_parts)
|
| 206 |
|
| 207 |
+
if not context.strip():
|
| 208 |
+
print(f"[{os.path.basename(__file__)}] Filtreleme sonrası bağlam boş kaldı.")
|
| 209 |
+
return "", chat_history + [[question, "Üzgünüm, sorunuzla ilgili yeterince alakalı bilgi bulunamadı. Lütfen sorunuzu farklı bir şekilde ifade etmeyi deneyin veya daha spesifik olun."]]
|
| 210 |
+
|
| 211 |
+
print(f"[{os.path.basename(__file__)}] Bağlam için {len(context_parts)} belge parçası kullanıldı. İlk 200 karakter:\n'{context[:200]}...'")
|
| 212 |
|
| 213 |
+
# Geliştirilmiş, daha detaylı ve kısıtlayıcı Prompt
|
| 214 |
+
prompt = f"""Sen bir Fırat Üniversitesi mevzuat uzmanısın. Görevin, yalnızca ve kesinlikle aşağıdaki 'Bağlam' bölümünde verilen bilgilere dayanarak 'Soru'yu eksiksiz, net, anlaşılır ve madde madde olacak şekilde cevaplamaktır.
|
| 215 |
+
|
| 216 |
+
Eğer 'Bağlam'da soruya cevap verecek yeterli ve doğrudan bilgi bulunmuyorsa, kesinlikle kendi bilginizden uydurma yapmayın. Bu durumda, 'Üzgünüm, bu soruya bağlamda yeterli bilgi bulunmamaktadır.' şeklinde açıkça belirtin.
|
| 217 |
+
|
| 218 |
+
Cevabın sonunda, bilginin alındığı sayfa numaralarını 'Kaynak: Sayfa X, Y, Z' formatında açıkça ve sadece bağlamda belirtilen sayfa numaralarını kullanarak listeleyin. Bağlamda sayfa bilgisi yoksa (ki bu olmamalı), kaynak belirtme.
|
| 219 |
|
| 220 |
Bağlam:
|
| 221 |
{context}
|
|
|
|
| 224 |
|
| 225 |
Cevap:
|
| 226 |
"""
|
| 227 |
+
print(f"[{os.path.basename(__file__)}] LLM'e gönderilen prompt'ın başlangıcı: '{prompt[:500]}...'")
|
| 228 |
|
| 229 |
+
# --- Prompt uzunluğunu MODEL_MAX_LENGTH - MAX_NEW_TOKENS ile sınırlayalım ---
|
| 230 |
max_input_tokens = MODEL_MAX_LENGTH - MAX_NEW_TOKENS
|
| 231 |
|
| 232 |
prompt_tokens = tokenizer.encode(prompt, return_tensors='pt')[0]
|
| 233 |
tokenized_prompt_length = prompt_tokens.shape[0]
|
| 234 |
|
| 235 |
+
print(f"[{os.path.basename(__file__)}] Oluşturulan prompt'un token uzunluğu: {tokenized_prompt_length}")
|
| 236 |
|
| 237 |
if tokenized_prompt_length > max_input_tokens:
|
| 238 |
+
print(f"[{os.path.basename(__file__)}] 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...")
|
| 239 |
+
# Token bazında kısaltma
|
| 240 |
prompt_tokens = prompt_tokens[:max_input_tokens]
|
| 241 |
prompt = tokenizer.decode(prompt_tokens, skip_special_tokens=True)
|
| 242 |
+
print(f"[{os.path.basename(__file__)}] Prompt kısaltıldı. Yeni token uzunluğu: {len(tokenizer.encode(prompt))}")
|
| 243 |
+
print(f"[{os.path.basename(__file__)}] Kısaltılmış prompt'ın başlangıcı: '{prompt[:500]}...'")
|
| 244 |
|
| 245 |
final_answer = ""
|
| 246 |
try:
|
|
|
|
| 251 |
else:
|
| 252 |
final_answer = str(response_data).strip()
|
| 253 |
|
| 254 |
+
# LLM'in prompt'u tekrar etme veya gereksiz kısımları kaldırma
|
| 255 |
+
# Çoklu kontrol noktası
|
| 256 |
if final_answer.lower().startswith(prompt.lower()):
|
| 257 |
final_answer = final_answer[len(prompt):].strip()
|
| 258 |
|
| 259 |
+
# "Cevap:" kısmı LLM tarafından tekrar edildiyse temizle
|
| 260 |
if "Cevap:" in final_answer:
|
| 261 |
final_answer = final_answer.split("Cevap:", 1)[-1].strip()
|
| 262 |
|
| 263 |
+
# Prompt'un başlangıcından kalıntı kaldıysa temizle
|
| 264 |
+
if final_answer.lower().startswith("sen bir fırat üniversitesi mevzuat uzmanısın.") or \
|
| 265 |
+
final_answer.lower().startswith("aşağıdaki bağlamı kullanarak"):
|
| 266 |
+
# Daha sofistike bir temizleme: "Cevap:" ifadesini arayarak o kısımdan sonrasını al
|
| 267 |
+
match = re.search(r"cevap:\s*(.*)", final_answer, re.IGNORECASE | re.DOTALL)
|
| 268 |
+
if match:
|
| 269 |
+
final_answer = match.group(1).strip()
|
| 270 |
+
else: # Bulamazsa yine de bir kısmı sil
|
| 271 |
+
final_answer = final_answer.split("Soru:", 1)[-1].strip() if "Soru:" in final_answer else final_answer.strip()
|
| 272 |
+
final_answer = final_answer.split("Cevap:", 1)[-1].strip() if "Cevap:" in final_answer else final_answer.strip()
|
| 273 |
+
|
| 274 |
|
| 275 |
+
# Cevabın boş veya anlamsız olup olmadığını kontrol et
|
| 276 |
+
if not final_answer or final_answer.strip().lower() in ["üzgünüm, bu soruya bağlamda yeterli bilgi bulunmamaktadır.", "üzgünüm, bu soruya bağlamda yeterli bilgi bulunmamaktadır", "bilgi bulunamadı.", "bilgi bulunamadı"]:
|
| 277 |
final_answer = "Üzgünüm, bu soruya bağlamda yeterli bilgi bulunmamaktadır."
|
| 278 |
|
| 279 |
+
# Kaynakları cevaba ekle
|
| 280 |
if source_pages and final_answer != "Üzgünüm, bu soruya bağlamda yeterli bilgi bulunmamaktadır.":
|
| 281 |
sorted_pages = sorted(list(source_pages))
|
| 282 |
final_answer += f"\n\n**Kaynak:** Sayfa {', '.join(map(str, sorted_pages))}"
|
| 283 |
+
|
| 284 |
+
print(f"[{os.path.basename(__file__)}] Üretilen son cevap: '{final_answer[:500]}...'")
|
| 285 |
|
| 286 |
except Exception as e:
|
| 287 |
+
final_answer = f"Cevap üretilirken kritik bir hata oluştu: {e}. Lütfen daha sonra tekrar deneyin veya farklı bir soru sorun."
|
| 288 |
+
print(f"[{os.path.basename(__file__)}] Kritik cevap üretim hatası: {e}")
|
| 289 |
|
| 290 |
chat_history.append((question, final_answer))
|
| 291 |
return "", chat_history
|
|
|
|
| 297 |
# Fırat Üniversitesi Mini RAG Botu
|
| 298 |
Fırat Üniversitesi mevzuat.firat.edu.tr adresinden alınan PDF dosyası üzerinde soru-cevap yapın.
|
| 299 |
Sorularınıza belgedeki bilgilere dayanarak cevaplar verilecektir.
|
| 300 |
+
Bağlamda bilgi yoksa, bot kesinlikle uydurma yapmayacaktır ve bunu açıkça belirtecektir.
|
| 301 |
**Cevaplar artık kaynak sayfa numarasını da içermektedir.**
|
| 302 |
"""
|
| 303 |
)
|