YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
- 🩺 Medikal Bilgi Asistanı — RAG Sistemi
- İçindekiler
- Senaryo
- Mimari
- Veri seti ve chunking stratejisi
- Embedding modeli
- Vektör veritabanı
- Eşik (threshold) analizi
- Halüsinasyon önleme
- Kaynak gösterimi (kanıt vurgusu)
- Kurulum ve çalıştırma
- Örnek test soruları ve cevapları
- Test sürecinde karşılaşılan bulgular
- Bilinen sınırlamalar
- Kullanılan teknolojiler
- İçindekiler
🩺 Medikal Bilgi Asistanı — RAG Sistemi
Türkçe hastane makalelerinden derlenmiş, kaynağı gösterilen, eşik bazlı halüsinasyon önleme uygulayan bir RAG (Retrieval-Augmented Generation) sistemi. Kullanıcı bir sağlık sorusu sorduğunda, sistem gerçek hastane makalelerinden en alakalı parçaları bulup bir LLM'e veriyor; LLM sadece bu parçalardan bir cevap kuruyor, ve cevabın tam olarak hangi makalenin hangi cümlesinden geldiği kullanıcıya sarı vurguyla gösteriliyor.
Kullanılan vektör veri seti (ayrı bir HF Dataset reposu):
Resad2173/medikal-rag-dataset
— 3226 chunk, url/chunk_text/chunk_vector + opsiyonel
parent_id/title/__source sütunlarıyla.
İçindekiler
- Senaryo
- Mimari
- Veri seti ve chunking stratejisi
- Embedding modeli
- Vektör veritabanı
- Eşik (threshold) analizi
- Test soruları — tam liste
- Halüsinasyon önleme
- Kaynak gösterimi (kanıt vurgusu)
- Kurulum ve çalıştırma
- Örnek test soruları ve cevapları
- Test sürecinde karşılaşılan bulgular
- Bilinen sınırlamalar
- Kullanılan teknolojiler
Senaryo
umutertugrul/turkish-hospital-medical-articles veri setinden (14 farklı
hastanenin — Acıbadem, Memorial, Medicana, Liv vb. — yayımladığı hasta
bilgilendirme makaleleri) rastgele 300 makale seçildi, parçalara
(chunk) bölündü, vektörlendi ve bir vektör veritabanına yüklendi.
Kullanıcı bir soru sorduğunda: soru vektörleniyor → veritabanında en
alakalı 3 parça bulunuyor → benzerlik skoru bir eşiğin altındaysa LLM'e
hiç gidilmeden "cevabı yok" deniyor → eşik üstündeyse bu 3 parça bir
LLM'e verilip sadece onlardan bir cevap kurduruluyor → cevabın
gerçekten hangi parçadan geldiği kullanıcıya, orijinal makale
içerisinde sarı vurguyla gösteriliyor.
Mimari
| Dosya | Görevi |
|---|---|
veri_secimi.py |
Veri setinden (gated, erişim onaylı) 300 makaleyi rastgele seçer, geçersiz/boş metinleri baştan eler. |
chunking.py |
Makaleleri paragraf bazlı + kelime limitini aşan paragraflarda overlap'li ("mixed chunking") parçalara böler. |
embedding_model.py |
magibu/embeddingmagibu-200m modelini yükler; sorgu ve doküman için ayrı iki fonksiyon sağlar (asimetrik model). |
embed_uret.py |
Tüm chunk'ları embedding_model.py aracılığıyla vektörler. |
chroma_yukle.py |
Vektörlü chunk'ları ChromaDB'ye (kalıcı, diskte) yükler. |
arama.py |
Bir soruyu vektörleyip ChromaDB'de arayan tek fonksiyon — hem app.py hem esik_analizi.py bunu kullanır. |
test_sorulari.py |
20 pozitif (gerçek chunk'lardan türetilmiş, parent_id etiketli) + 10 negatif (tıpla alakasız) test sorusu. |
esik_analizi.py |
Test sorularını arama.py üzerinden çalıştırıp doğruluk oranını ve eşik değerini ölçer. |
cevap_uret.py |
RAG'in "üretim" kısmı — eşik kontrolü + Ollama ile yapılandırılmış (JSON şemalı) cevap üretimi. |
app.py |
FastAPI backend — özel frontend'i sunar, /api/sor ve /api/makale/{id} endpoint'leri. |
index.html, style.css, app.js |
Özel tasarlanmış sohbet arayüzü — "klinik dosya / kanıt vurgusu" konsepti. |
kullanıcı sorusu
│
▼
arama.py (embedding_model.py + ChromaDB)
│
▼
en iyi 3 chunk + benzerlik skoru
│
├── eşik altı ──────────────► "cevabı yok" (LLM'e hiç gidilmez)
│
└── eşik üstü ──► cevap_uret.py ──► Ollama (yapılandırılmış JSON)
│
▼
cevap + hangi kaynağın kullanıldığı
│
▼
app.js: cevap gösterilir + "Kaynağı gör"
→ tam makale, ilgili cümle sarıyla vurgulu
Veri seti ve chunking stratejisi
Veri seti: umutertugrul/turkish-hospital-medical-articles (gated,
CC BY 4.0 lisanslı) — 14 hastanenin toplam ~24.600 makalesinden 300'ü
sabit bir rastgele tohumla (seed=42) seçildi. Seçim öncesi text
alanı boş/çok kısa olan makaleler baştan elendi ("kontrollü indirme" —
seçimden sonra geçersiz veriyle karşılaşıp iptal etmek yerine, seçim
havuzunu baştan temizledik).
Chunking: "Mixed chunking" stratejisi — önce paragraf bazlı (\n\n)
bölünüyor; bir paragraf 120 kelimeyi aşarsa, 25 kelimelik overlap ile
kendi içinde tekrar bölünüyor. Bu strateji tercih edildi çünkü:
- Doğal paragraf sınırlarını koruyor (anlam bütünlüğü)
- Aşırı uzun paragrafları da güvenli, tutarlı boyutta tutuyor
- Overlap, bir cümlenin ortadan kesilip iki chunk'a bölünmesi durumunda bile bağlamın tamamen kaybolmasını önlüyor
300 makale, bu stratejiyle 3226 chunk'a bölündü (makale başına ortalama 10.8 chunk, medyan makale uzunluğu 750 kelime).
Embedding modeli
magibu/embeddingmagibu-200m seçildi — Türkçe odaklı, 768 boyutlu,
8192 token context window (kurs tarafından önerilen model). Bu model
asimetrik: sorgu (query) ve doküman (document) farklı yöntemlerle
vektörleniyor (encode_query() / encode_document()) — bu yüzden
embedding_model.py'de bilinçli olarak iki ayrı fonksiyon var, chunk'lar
hep dokuman_vektorle() ile, kullanıcı soruları hep sorgu_vektorle()
ile işleniyor. Bu ayrımın karışması hata vermez ama arama kalitesini
sessizce düşürür.
Vektör veritabanı
ChromaDB (PersistentClient, diskte kalıcı) tercih edildi —
PGVector'ün gerektirdiği ayrı bir Postgres sunucusu kurulumuna gerek
kalmadan, tek satırla embedded çalışıyor; projenin ölçeğinde (3226
kayıt) bu basitlik yeterliydi. Koleksiyon hnsw:space: cosine ile
kuruldu (embedding modelinin ürettiği normalize vektörlerle uyumlu).
Şema (ödevin istediği 3 zorunlu + 3 opsiyonel sütun, ChromaDB'nin kendi alanlarına şu şekilde karşılık geliyor):
| Ödevin istediği sütun | ChromaDB'deki karşılığı |
|---|---|
url (zorunlu) |
metadatas[].url |
chunk_text (zorunlu) |
documents[] |
chunk_vector (zorunlu) |
embeddings[] |
parent_id (opsiyonel) |
metadatas[].parent_id |
title (opsiyonel) |
metadatas[].title |
__source / kaynak hastane (opsiyonel) |
metadatas[].hastane |
Ayrıca her kayıt için benzersiz bir chunk_id (ids[]) tutuluyor.
Eşik (threshold) analizi
20 pozitif + 10 negatif soru, esik_analizi.py ile gerçek modele karşı
test edildi:
| Pozitif sorular | Negatif sorular | |
|---|---|---|
| Minimum benzerlik | 0.539 | 0.177 |
| Maksimum benzerlik | 0.861 | 0.392 |
| Medyan benzerlik | 0.733 | 0.213 |
En düşük pozitif skor (0.539), en yüksek negatif skordan (0.392) 0.147 puan yukarıda — temiz bir ayrım. Eşik değeri olarak 0.466 (ikisinin ortası) belirlendi.
Doğruluk (makale-seviyesinde, chunk-seviyesinde değil): 20/20 pozitif soruda doğru makale ilk 3 sonuç arasında bulundu, 19/20'sinde ilk sırada çıktı. (Makale-seviyesinde test edilmesinin sebebi: aynı makalenin farklı chunk'ları bazen aynı soruya farklı açılardan cevap verebiliyor — bkz. bulgular.)
Test soruları — tam liste
20 pozitif soru (her biri gerçek bir chunk okunarak, cevabı o chunk'ın içinde birebir bulunacak şekilde yazıldı):
- PCR test sonuçları genellikle ne kadar sürede çıkar?
- K vitamininin kaç farklı formu vardır ve bunlar nelerdir?
- Laparoskopi öncesinde kişi en az kaç saat aç kalmalıdır?
- Gebelikte ilk bebek hareketleri genellikle kaçıncı haftada hissedilmeye başlanır?
- MCV neyin kısaltmasıdır ve ne ölçer?
- Kalp çarpıntısını hafifletmek için önerilen doğal yöntemlerden bazıları nelerdir?
- KOAH (Kronik Obstrüktif Akciğer Hastalığı) genellikle hangi alışkanlıkla ilişkilidir?
- Allojeneik kök hücre naklinden sonra kaçıncı haftada verilen hücrelerin kemik iliğine yerleşme durumuna bakılır?
- Gül hastalığı (rozasea) tedavisinde hangi yöntemler kullanılabilir?
- Diz yaralanmalarının tanı ve tedavisinde fiber optik kamerayla eklem içine girilen yönteme ne denir?
- Dilde oluşan aftların ağrısını hafifletmek için hangi yöntemler önerilir?
- Hiponatremi riskini artırabilecek ilaç türlerinden bazıları nelerdir?
- Verem (tüberküloz) en yaygın olarak nasıl bulaşır?
- Hareket bozuklukları temel olarak hangi iki ana gruba ayrılır?
- Yumurta alerjisi olan kişilerin yumurta dışında hangi gıdalara dikkat etmesi önerilir?
- Sedef hastalığı hangi yaş aralıklarında zirve yapma eğilimindedir?
- Yağ bezesi (lipoma) tedavisinde vakum araçlarının kullanıldığı yönteme ne denir?
- Bağışıklık sistemini desteklemek için haftada kaç gün, ne kadar balık tüketilmesi önerilir?
- Rahim ağzı kanserinin en sık görülen risk faktörü nedir?
- Diş sıkma tedavisinde kullanılan yöntemlerden bazıları nelerdir?
10 negatif soru (veri setindeki hiçbir konuyla — tıpla — alakası olmayan, tamamen farklı alanlardan):
- Osmanlı İmparatorluğu'nun kaç padişahı vardır?
- Everest Dağı'nın yüksekliği kaç metredir?
- Python programlama dilinde bir for döngüsü nasıl yazılır?
- 2022 Dünya Kupası'nı hangi ülke kazandı?
- Mona Lisa tablosunu kim yapmıştır?
- Bitcoin'in yaratıcısı olduğu düşünülen kişi kimdir?
- Türkiye'nin başkenti neresidir?
- Bir yılda kaç gün vardır?
- İstanbul Boğazı hangi iki denizi birbirine bağlar?
- Formula 1'de en çok dünya şampiyonluğu kazanan pilot kimdir?
Halüsinasyon önleme
Üç katmanlı bir strateji:
- Eşik altı reddetme: Benzerlik skoru 0.466'nın altındaysa, LLM'e hiç gidilmeden "Bu sorunun cevabı dokümanlarımda yer almamaktadır" denir — modelin uydurma ihtimaline hiç fırsat verilmez.
- Sistem promptu kuralları: Eşik üstünde bile, model sadece verilen kaynakları kullanmaya, kaynaklarda olmayan bilgiyi eklememeye zorlanıyor.
- Yapılandırılmış çıktı (structured output): Model, serbest metin
değil, sabit bir JSON şemasına (
cevap,kullanilan_kaynaklar) zorlanıyor — bu, modelin "düşünme sürecini" veya alakasız içeriği cevaba sızdırmasını fiziksel olarak imkansız kılıyor (detay: bulgular).
Kaynak gösterimi (kanıt vurgusu)
Her cevabın yanında bir "Kaynağı gör" butonu var — tıklanınca orijinal makalenin tam metni açılıyor, ve LLM'in cevabı gerçekten hangi parçadan kurduğu sarı fosforlu kalemle vurgulanıyor. Bu, "en yüksek benzerlik skorlu chunk" değil, "LLM'in gerçekten kullandığı chunk" — ikisi her zaman aynı olmuyor (bkz. bulgular). Ayrıca, chunk metninin makalede birebir aynı boşluklarla bulunamadığı (kelime birleştirmesi nedeniyle) durumlar için, backend boşluk farklarını görmezden gelen esnek bir arama yapıp makaledeki gerçek (orijinal boşluklu) karşılığı buluyor.
Kurulum ve çalıştırma
pip install -r requirements.txt
# 1. Veri seçimi (gated dataset -- önce huggingface.co'da erişim onaylanmalı)
hf auth login
python veri_secimi.py
# 2. Parçalama
python chunking.py
# 3. Embedding üretimi (model ilk seferde iner, ~birkaç dakika sürebilir)
python embed_uret.py
# 4. Vektör veritabanına yükleme
python chroma_yukle.py
# 5. (opsiyonel) Eşik analizini tekrar çalıştırmak için
python esik_analizi.py
# 6. Ollama'yı ayağa kaldır (ayrı terminalde)
ollama serve
ollama pull qwen3.5:9b
# 7. Uygulamayı başlat
python app.py
Tarayıcıda http://localhost:8000.
Örnek test soruları ve cevapları
Soru: "Migren tedavisi nasıl yapılır?" Cevap: "Migren tedavisi, doğrudan atakların tedavisi ve önleyici tedavi olmak üzere ikiye ayrılır. Atak tedavisinde parasetamol, aspirin dahil basit ağrı kesiciler, bulantı önleyici ilaçlar ve triptanlar kullanılır..." (florence, benzerlik %83)
Soru: "Bitcoin'in şu anki değeri nedir?" (tıpla alakasız) Cevap: "Bu sorunun cevabı dokümanlarımda yer almamaktadır." (LLM'e hiç gidilmedi)
Soru: "Grip ile nezle arasındaki fark nedir?" (karşılaştırma sorusu — kaynaklarda bu bilgi gerçekten mevcut) Cevap: Model, ateş/boğaz ağrısı-öksürük/burun belirtileri/vücut ağrıları/genel durum başlıkları altında 5 maddelik doğru, kaynaklara dayalı bir karşılaştırma sundu (memorial, benzerlik %68). Bu, sistemin körü körüne her karşılaştırmayı reddetmediğini, sadece elinde veri olmayanı reddettiğini gösteriyor.
Soru: "Diyabet belirtileri nelerdir?" (terminoloji tuzağı — kaynaklar sadece "Diabetes Insipidus"tan bahsediyor, genel diyabetten değil) Cevap: "Verilen kaynaklarda 'Diyabet' (genel anlamda veya diyabetes mellitus) belirtilerinden bahsedilmemiştir; sadece Diabetes Insipidus'un belirtileri listelenmiştir. Kaynaklara göre Diabetes Mellitus ile Diabetes Insipidus farklı hastalıklardır..." — model, yüzeysel isim benzerliğine kanıp yanlış bilgi uydurmak yerine, gerçek farkı fark edip dürüstçe belirtti.
Test sürecinde karşılaşılan bulgular
Geliştirme sürecinde canlı testler sırasında dört önemli sorun bulundu ve düzeltildi:
Kaynak gösterimi yanlış chunk'ı vurguluyordu. İlk tasarımda "Kaynağı gör" her zaman en yüksek benzerlik skorlu chunk'ı (
kaynaklar[0]) gösteriyordu. Ama LLM'e 3 chunk birden veriliyor, ve model bazen 1. değil 2./3. sıradaki chunk'tan cevap kuruyordu (en yüksek benzerlik skoru, gerçek cevabın nerede olduğuyla her zaman örtüşmüyor). Çözüm: yapılandırılmış çıktıya, modelin gerçekten hangi kaynağı kullandığını belirten bir alan (kullanilan_kaynaklar) eklendi, kaynak gösterimi buna göre düzeltildi.Model bazı sorularda devasa, İngilizce, gösterilemez bir "düşünme süreci" yazıp gerçek cevabı hiç üretemiyordu. Özellikle belirsiz terminoloji içeren sorularda (ör. "Diyabet" vs "Diabetes Insipidus") ortaya çıktı. Bu davranış hem
think=Truehemthink=Falseile devam etti — yani sorun Ollama'nın "thinking" API alanıyla değil, modelin genel eğilimiyle ilgiliydi. Kesin çözüm: modeli serbest metin yazmaya hiç izin vermeden, doğrudan bir JSON şemasına (format=...) zorlamak. Model bu şekilde "düşünmeye" fiziksel olarak yer bulamıyor, sadece şemaya uyancevapvekullanilan_kaynaklaralanlarını doldurabiliyor. Bu değişiklikten sonra sorun tamamen ortadan kalktı.Sarı vurgu bazı chunk'larda hiç görünmüyordu. Kaynak paneli, chunk metnini makalenin tam metninde birebir arıyordu. Ama chunking.py, 120 kelimeyi aşan paragrafları bölerken kelimeleri
split()+join()ile tek boşlukla yeniden birleştiriyor — orijinal metindeki çift boşluk/satır sonu gibi düzensizlikler bu işlemde kayboluyor, ve o chunk artık makalede birebir bulunamıyordu. Çözüm: backend'de, boşluk farklarını görmezden gelen esnek bir regex araması eklendi; bu arama makaledeki gerçek (orijinal boşluklu) karşılığı bulup onu döndürüyor.Terminoloji farkının doğru yönetilmesi. "Diyabet" sorusu, veri setinde sadece "Diabetes Insipidus" (nadir görülen, kan şekeriyle ilgisi olmayan bir hastalık) makalesiyle eşleşti. Model, bu ismin yüzeysel benzerliğine kanıp genel diyabet (mellitus) bilgisi uydurmak yerine, kaynaklardaki gerçek farkı tespit edip kullanıcıya açıkça bildirdi — bu, halüsinasyon önlemenin gerçek bir kanıtı.
Bilinen sınırlamalar
- Eşik değeri (0.466) sadece 30 test sorusuyla kalibre edildi; çok daha büyük bir test setiyle ince ayar yapılabilir.
- Konuşma geçmişi yok — her soru bağımsız olarak işleniyor.
- Sistem promptundaki "ciddi durumda uzmana yönlendir" kuralı her cevapta tutarlı şekilde tetiklenmiyor.
- Döviz/güncel veri gibi sürekli değişen bilgiler için uygun değil — veri seti statik bir anlık görüntü.
Kullanılan teknolojiler
Python · ChromaDB · sentence-transformers (magibu/embeddingmagibu-200m)
· Ollama (qwen3.5:9b) · Pydantic (yapılandırılmış çıktı) ·
FastAPI · Vanilla JS/HTML/CSS
Geliştiren: Reşad Yılmaz
Magibu Academy — Vektör Veritabanı ve RAG projesi.