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

🌟 Risale-i Nur Semantic Search Sistemi

Risale-i Nur külliyatında semantic arama yapın ve detaylı cevaplar alın

🎯 Yer referansları otomatik tespit edilir (26. söz, üçüncü mektup vb.)

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

💡 Örnek Sorular

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

💡 İpucu: Daha iyi sonuçlar için sorularınızı açık ve net bir şekilde sorun.

🎯 Yeni özellik: "26. söz", "üçüncü mektup" gibi yer referansları otomatik tespit edilir!

🔧 Bu sistem Google Gemini AI ve semantic search teknolojisi kullanmaktadır.

""") # 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 )