| | 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 |
| |
|
| | |
| | API_KEY = os.getenv("gemini_api") |
| |
|
| | 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) |
| |
|
| | |
| | MAIN_DATA_FILE = "updated_risale_final.json" |
| | EMBEDDINGS_FILE = "temizlenmis.json" |
| |
|
| | |
| | main_data_cache = {} |
| | embeddings_data_cache = {} |
| |
|
| | |
| |
|
| | 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("```","") |
| |
|
| | |
| | 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', []) |
| | |
| | |
| | for path_part in path_hierarchy: |
| | if location_term.lower() in path_part.lower(): |
| | location_results.append({ |
| | 'path_key': path_key, |
| | 'similarity': 1.0, |
| | '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 |
| |
|
| | |
| |
|
| | 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 |
| | |
| | |
| | if main_data_cache and embeddings_data_cache: |
| | return main_data_cache, embeddings_data_cache |
| | |
| | try: |
| | |
| | with open(MAIN_DATA_FILE, 'r', encoding='utf-8') as f: |
| | main_data = json.load(f) |
| | |
| | |
| | with open(EMBEDDINGS_FILE, 'r', encoding='utf-8') as f: |
| | embeddings_data = json.load(f) |
| | |
| | |
| | main_data_indexed = {} |
| | for item in main_data: |
| | path_key = " > ".join(item.get("path_hierarchy", [])) |
| | main_data_indexed[path_key] = item |
| | |
| | |
| | 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_embedding = embedding_info.get('gaye_embedding', []) |
| | if gaye_embedding: |
| | gaye_similarity = cosine_similarity(query_embedding, gaye_embedding) |
| | else: |
| | gaye_similarity = 0.0 |
| | |
| | |
| | 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 |
| | |
| | |
| | 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: |
| | results.append({ |
| | 'path_key': path_key, |
| | 'similarity': best_similarity, |
| | 'match_type': match_type, |
| | 'match_content': match_content, |
| | 'main_data': main_data[path_key] |
| | }) |
| | |
| | |
| | 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_parts = [] |
| | source_counter = 1 |
| | |
| | |
| | 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 |
| | |
| | |
| | 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) |
| | |
| | |
| | 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', '') |
| | |
| | |
| | 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 |
| |
|
| | |
| |
|
| | 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.", "" |
| | |
| | |
| | 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.", "" |
| | |
| | |
| | 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 = get_query_embedding(query) |
| | |
| | if not query_embedding: |
| | return "Sorgu embedding'i oluşturulamadı.", "" |
| | |
| | |
| | exclude_paths = [result['path_key'] for result in location_results] |
| | |
| | |
| | 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ı.", "" |
| | |
| | |
| | answer = generate_answer_with_context(query, search_results, location_results) |
| | |
| | |
| | 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 |
| |
|
| | |
| |
|
| | def create_interface(): |
| | """Gradio arayüzü oluştur""" |
| | |
| | |
| | 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: |
| | |
| | |
| | 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): |
| | |
| | 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 |
| | ) |
| | |
| | |
| | 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ı" |
| | ) |
| | |
| | |
| | with gr.Row(): |
| | search_btn = gr.Button("🔍 Ara", variant="primary", size="lg") |
| | clear_btn = gr.Button("🗑️ Temizle", variant="secondary") |
| | |
| | with gr.Column(scale=1): |
| | |
| | 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 |
| | ) |
| | |
| | |
| | gr.HTML("<hr>") |
| | |
| |
|
| | with gr.Row(): |
| | with gr.Column(): |
| | |
| | sources_output = gr.Markdown( |
| | label="📚 Kaynaklar", |
| | value="Buraya kaynak bilgileri gelecek...", |
| | elem_classes=["example-box"] |
| | ) |
| |
|
| | with gr.Row(): |
| | with gr.Column(): |
| | |
| | answer_output = gr.Markdown( |
| | label="📖 Cevap", |
| | value="Buraya arama sonuçları gelecek...", |
| | elem_classes=["source-box"] |
| | ) |
| | |
| |
|
| | |
| | |
| | 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> |
| | """) |
| | |
| | |
| | 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] |
| | ) |
| | |
| | |
| | query_input.submit( |
| | fn=search_risale_gradio, |
| | inputs=[query_input, top_k_slider], |
| | outputs=[answer_output, sources_output] |
| | ) |
| | |
| | return iface |
| |
|
| | |
| |
|
| | if __name__ == "__main__": |
| | |
| | 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") |
| | |
| | |
| | iface = create_interface() |
| | iface.launch( |
| | server_name="0.0.0.0", |
| | server_port=7860, |
| | share=False, |
| | debug=True |
| | ) |