RnkGPT / app.py
JosephH's picture
Update app.py
03d4c6b verified
import gradio as gr
from google import genai
from google.genai import types
import json
import numpy as np
from typing import List, Dict, Tuple
import os
# --- 1. Yapılandırma ---
API_KEY = os.getenv("gemini_api") # Buraya API key'ini koy
if not API_KEY:
print("UYARI: API_KEY boş. Lütfen API key'inizi girin.")
gemini_client = None
if API_KEY:
gemini_client = genai.Client(api_key=API_KEY)
# Dosya yolları
MAIN_DATA_FILE = "updated_risale_final.json"
EMBEDDINGS_FILE = "temizlenmis.json"
# Global veri cache
main_data_cache = {}
embeddings_data_cache = {}
# --- 2. Yer Tespiti ve Arama Fonksiyonları ---
def extract_location_references_with_llm(query: str) -> List[str]:
"""LLM kullanarak sorgudaki yer referanslarını tespit et ve arama terimlerini oluştur"""
if not gemini_client:
return []
location_prompt = f"""
Aşağıdaki kullanıcı sorgusunu analiz et ve Risale-i Nur'dan belirli bir "yer" (Söz, Mektup, Şua, Lem'a, Mesele vb.) referansı var mı kontrol et.
Kullanıcı Sorgusu: "{query}"
Eğer sorguda yer referansı varsa, o yerin farklı yazılış şekillerini oluştur. Örneğin:
- "26. söz" için: ["Yirmialtıncı Söz", "Yirmi Altıncı Söz"] (Kesinlikle hem ayrı hem birleşik şekilde her zaman oluştur)
- "3. mektup" için: ["Üçüncü Mektup"]
- "birinci şua" için: ["Birinci Şua"]
Sadece JSON formatında cevap ver:
{{"has_location": true/false, "search_terms": ["terim1", "terim2"]}}
Eğer yer referansı yoksa:
{{"has_location": false, "search_terms": []}}
"""
try:
response = gemini_client.models.generate_content(
model="gemini-2.5-flash",
contents=location_prompt,
config=types.GenerateContentConfig(
thinking_config=types.ThinkingConfig(thinking_budget=0)
),
)
response_text = response.text
response_text = response_text.replace("```json","").replace("```","")
# JSON response'u parse et
result = json.loads(response_text)
if result.get('has_location', False):
return result.get('search_terms', [])
else:
return []
except Exception as e:
print(f"Yer tespiti hatası: {e}")
return []
def search_by_location(location_terms: List[str], main_data: Dict) -> List[Dict]:
"""Belirtilen yerlerde arama yap"""
location_results = []
for location_term in location_terms:
for path_key, item_data in main_data.items():
path_hierarchy = item_data.get('path_hierarchy', [])
# Path hierarchy'de bu terimi ara
for path_part in path_hierarchy:
if location_term.lower() in path_part.lower():
location_results.append({
'path_key': path_key,
'similarity': 1.0, # Tam eşleşme
'match_type': 'location_match',
'match_content': f"Yer referansı: {location_term}",
'main_data': item_data,
'matched_location': location_term
})
break
return location_results[:20] if len(location_results) > 20 else location_results
# --- 3. Mevcut Yardımcı Fonksiyonlar ---
def cosine_similarity(vec1: List[float], vec2: List[float]) -> float:
"""İki vektör arasındaki cosine similarity hesapla"""
if not vec1 or not vec2:
return 0.0
vec1 = np.array(vec1)
vec2 = np.array(vec2)
dot_product = np.dot(vec1, vec2)
norm1 = np.linalg.norm(vec1)
norm2 = np.linalg.norm(vec2)
if norm1 == 0 or norm2 == 0:
return 0.0
return dot_product / (norm1 * norm2)
def get_query_embedding(query: str) -> List[float]:
"""Sorgu için embedding oluştur"""
if not gemini_client:
return []
try:
result = gemini_client.models.embed_content(
model="gemini-embedding-001",
contents=[query],
config=types.EmbedContentConfig(task_type="RETRIEVAL_QUERY")
)
return result.embeddings[0].values
except Exception as e:
print(f"Query embedding hatası: {e}")
return []
def load_data_files() -> Tuple[Dict, Dict]:
"""Ana veri ve embedding dosyalarını yükle"""
global main_data_cache, embeddings_data_cache
# Cache'den döndür
if main_data_cache and embeddings_data_cache:
return main_data_cache, embeddings_data_cache
try:
# Ana veri dosyasını yükle
with open(MAIN_DATA_FILE, 'r', encoding='utf-8') as f:
main_data = json.load(f)
# Embedding dosyasını yükle
with open(EMBEDDINGS_FILE, 'r', encoding='utf-8') as f:
embeddings_data = json.load(f)
# Ana veriyi path_key ile indexle
main_data_indexed = {}
for item in main_data:
path_key = " > ".join(item.get("path_hierarchy", []))
main_data_indexed[path_key] = item
# Cache'e kaydet
main_data_cache = main_data_indexed
embeddings_data_cache = embeddings_data
return main_data_indexed, embeddings_data
except FileNotFoundError as e:
print(f"Dosya bulunamadı: {e}")
return {}, {}
except Exception as e:
print(f"Dosya yükleme hatası: {e}")
return {}, {}
def search_in_embeddings(query_embedding: List[float], embeddings_data: Dict, main_data: Dict, top_k: int = 5, exclude_paths: List[str] = None) -> List[Dict]:
"""Embedding'lerde arama yap"""
results = []
exclude_paths = exclude_paths or []
for path_key, embedding_info in embeddings_data.items():
if path_key not in main_data or path_key in exclude_paths:
continue
# Gaye ile karşılaştır
gaye_embedding = embedding_info.get('gaye_embedding', [])
if gaye_embedding:
gaye_similarity = cosine_similarity(query_embedding, gaye_embedding)
else:
gaye_similarity = 0.0
# Konular ile karşılaştır (en yüksek similarity'yi al)
konular_embeddings = embedding_info.get('konular_embeddings', [])
max_konu_similarity = 0.0
best_konu_index = -1
for i, konu_embedding in enumerate(konular_embeddings):
if konu_embedding:
similarity = cosine_similarity(query_embedding, konu_embedding)
if similarity > max_konu_similarity:
max_konu_similarity = similarity
best_konu_index = i
# En yüksek similarity'yi al (gaye vs konular)
if gaye_similarity > max_konu_similarity:
best_similarity = gaye_similarity
match_type = "gaye"
match_content = main_data[path_key].get('gaye', '')
else:
best_similarity = max_konu_similarity
match_type = "konu"
konular = main_data[path_key].get('konular', [])
match_content = konular[best_konu_index] if best_konu_index < len(konular) else ''
if best_similarity > 0.1: # Minimum threshold
results.append({
'path_key': path_key,
'similarity': best_similarity,
'match_type': match_type,
'match_content': match_content,
'main_data': main_data[path_key]
})
# Similarity'ye göre sırala ve top_k'yı al
results.sort(key=lambda x: x['similarity'], reverse=True)
return results[:top_k]
def generate_answer_with_context(query: str, search_results: List[Dict], location_results: List[Dict] = None) -> str:
"""Bulunan sonuçları kullanarak LLM ile cevap oluştur"""
if not gemini_client:
return "API anahtarı ayarlanmamış. Lütfen API_KEY değişkenini doldurun."
if not search_results and not location_results:
return "Sorgunuzla ilgili bir sonuç bulunamadı."
# Context oluştur
context_parts = []
source_counter = 1
# Önce yer tabanlı sonuçları ekle
if location_results:
context_parts.append("=== YER TABANLI SONUÇLAR ===")
for result in location_results:
main_data = result['main_data']
path = " > ".join(main_data.get('path_hierarchy', []))
content = main_data.get('content', '')
context_parts.append(f"""
KAYNAK {source_counter} (YER EŞLEŞMESI):
Konum: {path}\n
Eşleşen Yer: {result.get('matched_location', '')}\n
İçerik: {content}...
""")
source_counter += 1
# Sonra semantic arama sonuçlarını ekle
if search_results:
if location_results:
context_parts.append("\n=== SEMANTİK ARAMA SONUÇLARI ===")
for result in search_results:
main_data = result['main_data']
path = " > ".join(main_data.get('path_hierarchy', []))
content = main_data.get('content', '')
context_parts.append(f"""
KAYNAK {source_counter}:
Konum: {path}\n
Eşleşen: {result['match_content']} ({result['match_type']})\n
İçerik: {content}...
""")
source_counter += 1
context = "\n".join(context_parts)
# LLM prompt'u
location_context = ""
if location_results:
locations = [r.get('matched_location', '') for r in location_results]
location_context = f"\n\nKullanıcı özellikle şu yerlerden bahsetti: {', '.join(locations)}. Bu yerlerdeki bilgileri öncelikli olarak kullan."
system_prompt = f"""
Sen Risale-i Nur külliyatı konusunda uzman bir rehbersin. Kullanıcının sorusunu, verilen kaynaklardan yararlanarak kapsamlı ve doğru bir şekilde cevapla.
KULLANICI SORUSU: {query}{location_context}
BULUNAN KAYNAKLAR:
{context}
Kurallar:
1. Sadece verilen kaynaklardan yararlan
2. Hangi kaynaktan bilgi aldığını belirt (Kaynak 1, Kaynak 2 vb.)
3. Eğer yer tabanlı sonuçlar varsa, bunları öncelikli olarak kullan
4. Risale-i Nur'un ruhuna uygun, hikmetli bir cevap ver
5. Eğer kaynaklarda tam cevap yoksa, mevcut bilgilerle ne kadar cevap verebileceğini belirt
"""
try:
response = gemini_client.models.generate_content(
model="gemini-2.5-flash",
contents=system_prompt,
config=types.GenerateContentConfig(
thinking_config=types.ThinkingConfig(thinking_budget=0)
),
)
return response.text
except Exception as e:
print(f"LLM cevap oluşturma hatası: {e}")
return f"Cevap oluşturulurken bir hata oluştu: {str(e)}"
def format_sources(search_results: List[Dict], has_location_results: bool = False) -> str:
"""Kaynakları formatla"""
if not search_results:
return "Kaynak bulunamadı."
sources_text = "## 📚 Bulunan Kaynaklar\n\n"
location_count = 0
semantic_count = 0
for i, result in enumerate(search_results, 1):
main_data = result['main_data']
path = " > ".join(main_data.get('path_hierarchy', []))
title = main_data.get('title', 'Başlıksız')
content = main_data.get('content', '')
# Kaynak tipini belirle
if result.get('match_type') == 'location_match':
location_count += 1
source_type = f"🎯 YER EŞLEŞMESI {location_count}"
match_info = f"**Eşleşen Yer:** {result.get('matched_location', '')}"
else:
semantic_count += 1
source_type = f"🔍 SEMANTİK ARAMA {semantic_count}"
match_info = f"**Eşleşen:** {result['match_content']} ({result['match_type']}) - Benzerlik: {result['similarity']:.3f}"
sources_text += f"""### Kaynak {i} - {source_type}
**Risale:** {path}\n
{match_info}\n
**İçerik:**
{content[:200]}{'...' if len(content) > 200 else ''}
---
"""
return sources_text
# --- 3. Gradio Fonksiyonları ---
def search_risale_gradio(query: str, top_k: int = 5) -> Tuple[str, str]:
"""Gradio için ana arama fonksiyonu"""
if not query.strip():
return "Lütfen bir soru girin.", ""
if not API_KEY:
return "API anahtarı ayarlanmamış. Lütfen kodu düzenleyerek API_KEY değişkenini doldurun.", ""
# Veri dosyalarını yükle
main_data, embeddings_data = load_data_files()
if not main_data or not embeddings_data:
return "Veri dosyaları yüklenemedi. Lütfen 'updated_risale_final.json' ve 'temizlenmis.json' dosyalarının mevcut olduğundan emin olun.", ""
# Önce LLM ile yer referanslarını tespit et
location_terms = extract_location_references_with_llm(query)
location_results = []
if location_terms:
print(f"🎯 Tespit edilen yerler: {location_terms}")
location_results = search_by_location(location_terms, main_data)
print(f"📍 Yer tabanlı {len(location_results)} sonuç bulundu")
# Query embedding'i oluştur
query_embedding = get_query_embedding(query)
if not query_embedding:
return "Sorgu embedding'i oluşturulamadı.", ""
# Yer tabanlı sonuçların path'lerini exclude et
exclude_paths = [result['path_key'] for result in location_results]
# Semantic search yap (yer tabanlı sonuçları hariç tut)
remaining_slots = max(1, top_k - len(location_results))
search_results = search_in_embeddings(query_embedding, embeddings_data, main_data, remaining_slots, exclude_paths)
if not search_results and not location_results:
return "Sorgunuzla ilgili bir sonuç bulunamadı.", ""
# LLM ile cevap oluştur
answer = generate_answer_with_context(query, search_results, location_results)
# Kaynakları formatla
all_results = location_results + search_results
sources = format_sources(all_results, has_location_results=bool(location_results))
return answer, sources
def get_sample_questions():
"""Örnek sorular"""
return [
"İmanın faydaları nelerdir?",
"26. söz'de bahsedildiği gibi kader gerçekten bize bağlı mıdır?",
"Dünya hayatının önemi nedir?",
"Namaz kılmanın hikmeti nedir?",
"İnsanın kainattaki yeri nasıldır?",
"Tevhid nedir?",
"Kur'an-ı Kerim'in i'cazı nedir?",
"Ölümden sonra hayatın varlığını ispatla",
"İbadet etmenin bana ne faydası var?"
]
def load_sample_question(question):
"""Örnek soruyu yükle"""
return question
# --- 4. Gradio Arayüzü ---
def create_interface():
"""Gradio arayüzü oluştur"""
# CSS stilleri
css = """
.gradio-container {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.main-header {
text-align: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
border-radius: 10px;
margin-bottom: 2rem;
}
.example-box {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
}
.source-box {
background-color: #fff8e1;
border-left: 4px solid #ffc107;
padding: 1rem;
margin: 1rem 0;
border-radius: 0 8px 8px 0;
}
"""
with gr.Blocks(css=css, title="Risale-i Nur Semantic Search") as iface:
# Başlık
gr.HTML("""
<div class="main-header">
<h1>🌟 Risale-i Nur Semantic Search Sistemi</h1>
<p>Risale-i Nur külliyatında semantic arama yapın ve detaylı cevaplar alın</p>
<p><small>🎯 Yer referansları otomatik tespit edilir (26. söz, üçüncü mektup vb.)</small></p>
</div>
""")
with gr.Row():
with gr.Column(scale=2):
# Ana arama alanı
query_input = gr.Textbox(
label="📝 Sorunuzu yazın",
placeholder="Örnek: 26. söz'de bahsedildiği gibi kader gerçekten bize bağlı mıdır?",
lines=2,
max_lines=5
)
# Ayarlar
with gr.Row():
top_k_slider = gr.Slider(
minimum=3,
maximum=10,
value=5,
step=1,
label="🔍 Kaç kaynak gösterilsin?",
info="Arama sonuçlarında gösterilecek kaynak sayısı"
)
# Butonlar
with gr.Row():
search_btn = gr.Button("🔍 Ara", variant="primary", size="lg")
clear_btn = gr.Button("🗑️ Temizle", variant="secondary")
with gr.Column(scale=1):
# Örnek sorular
gr.HTML("<h3>💡 Örnek Sorular</h3>")
sample_questions = get_sample_questions()
for question in sample_questions:
sample_btn = gr.Button(
question,
variant="outline",
size="sm"
)
sample_btn.click(
fn=lambda q=question: q,
outputs=query_input
)
# Sonuçlar
gr.HTML("<hr>")
with gr.Row():
with gr.Column():
# Kaynaklar
sources_output = gr.Markdown(
label="📚 Kaynaklar",
value="Buraya kaynak bilgileri gelecek...",
elem_classes=["example-box"]
)
with gr.Row():
with gr.Column():
# Ana cevap
answer_output = gr.Markdown(
label="📖 Cevap",
value="Buraya arama sonuçları gelecek...",
elem_classes=["source-box"]
)
# Footer
gr.HTML("""
<div style="text-align: center; margin-top: 2rem; padding: 1rem; background-color: #f8f9fa; border-radius: 8px;">
<p><small>💡 <strong>İpucu:</strong> Daha iyi sonuçlar için sorularınızı açık ve net bir şekilde sorun.</small></p>
<p><small>🎯 <strong>Yeni özellik:</strong> "26. söz", "üçüncü mektup" gibi yer referansları otomatik tespit edilir!</small></p>
<p><small>🔧 Bu sistem Google Gemini AI ve semantic search teknolojisi kullanmaktadır.</small></p>
</div>
""")
# Event handlers
search_btn.click(
fn=search_risale_gradio,
inputs=[query_input, top_k_slider],
outputs=[answer_output, sources_output]
)
clear_btn.click(
fn=lambda: ("", "Buraya arama sonuçları gelecek...", "Buraya kaynak bilgileri gelecek..."),
outputs=[query_input, answer_output, sources_output]
)
# Enter tuşu ile arama
query_input.submit(
fn=search_risale_gradio,
inputs=[query_input, top_k_slider],
outputs=[answer_output, sources_output]
)
return iface
# --- 5. Ana Fonksiyon ---
if __name__ == "__main__":
# Başlangıçta veri dosyalarını yükle
print("📂 Veri dosyaları yükleniyor...")
main_data, embeddings_data = load_data_files()
if main_data and embeddings_data:
print(f"✅ {len(main_data)} ana veri ve {len(embeddings_data)} embedding yüklendi")
else:
print("❌ Veri dosyaları yüklenemedi")
if not API_KEY:
print("\n⚠️ UYARI: API_KEY boş!")
print("Lütfen kodun başındaki API_KEY değişkenine Google AI API anahtarınızı girin.")
print("API anahtarı almak için: https://makersuite.google.com/app/apikey\n")
# Arayüzü başlat
iface = create_interface()
iface.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
debug=True
)