
1. Başlıyoruz
Önceki bölümde Vector Search’ün ne olduğunu, VECTOR veri tipini ve Azure OpenAI ile ilk embedding’imizi nasıl ürettiğimizi beraber yapmıştık. O bölümde çalıştırdığımız örnekler elle girdiğimiz üç-beş satıra dayanıyordu. Bu makalede ise senaryomuzu gerçek hayata yaklaştırıyoruz: elimizde bir dizi şirket IT dokümanı (PDF) var ve bunu “sorabileceğimiz” bir veritabanına dönüştürmek istiyoruz.
Bu bölümde üç şey öğreneceğiz:
(1) PDF’lerden metin çıkarıp anlamlı parçalara (chunk) nasıl bölünür
(2) Python ile toplu embedding pipeline’ı nasıl kurulur ve SQL Server’a veri nasıl yüklenir
(3) DiskANN indeksini ne zaman ve nasıl kurarsınız, index’li ve index’siz performans farkı nedir.
Workshop tadını sürdürüyorum. Elinizde önceki makaleden kalan VectorDemo veritabanı varsa onu kullanabilirsiniz; yeni başlıyorsanız o bölüme dönmenizi tavsiye ederim.
2. Senaryo: IT Dokümantasyon Asistanı
Hayali bir şirketimiz var. Bu şirketin IT departmanında yıllar içinde birikmiş 200 civarı PDF dokümanı var — SOP’lar, runbook’lar, incident post-mortem’leri, best practice rehberleri. Mevcut durumda biri “son yedek başarısız olursa ne yapmalıyım?” diye sorduğunda, cevap bu 200 PDF’in içinde bir yerde saklı ama kimse tam olarak nerede olduğunu bilmiyor.
Hedefimiz: bir DBA oturup T-SQL ile bir soru sorduğunda, SQL Server’ın ilgili doküman parçalarını anlamca en yakın olanlardan çekip listelemesi. Yani klasik bir enterprise knowledge base, ama arka ucu olarak ayrı bir vektör veritabanı değil, senin zaten yönettiğin, yedeklediğin SQL Server’ı kullanıyor.
3. PDF’leri Chunk’lama Stratejisi
Bir PDF’i olduğu gibi embedding’e vermek işe yaramaz. Çünkü embedding modelleri belirli bir token limitine sahip (text-embedding-3-small için 8191 token). Ayrıca bir doküman embedding’i tek başına 40 sayfayı temsil ettiğinde, benzerlik sorgusu “bu dokümanda bir yerlerde alakalı bir kısım var” der ama tam olarak nerede olduğunu söyleyemez.
Çözüm: dokümanı küçük parçalara (chunk) bölmek. Deneyimim şunu söylüyor:
- Chunk boyutu: 500-1000 token arası ideal. Çok küçük olursa bağlam kaybolur, çok büyük olursa doğruluk düşer.
- Overlap (örtüşme): Ardışık chunk’lar arasında 50-100 token örtüşme bırakmak, bölünme yerine denk gelen önemli bir cümlenin ikiye ayrılmasını engeller.
- Semantik sınır tercihi: Mümkünse paragraf veya başlık sınırlarına saygı göster. Cümlenin ortasında kesme.
Chunk’larken her parçaya dokümanın adını, bölümünü ve sayfa numarasını meta olarak eklemenizi öneririm — arama sonucunda kullanıcıya “Bu bilgi Backup_Runbook_2024.pdf, sayfa 14’te” diyebilmek için.
4. Genişletilmiş Tablomuz
Bölüm 1’deki tabloyu chunk-bazlı senaryoya genişletelim:
USE VectorDemo;GO-- Ana doküman kaydıCREATE TABLE dbo.Documents( DocumentId INT IDENTITY(1,1) PRIMARY KEY, FileName NVARCHAR(300) NOT NULL, Title NVARCHAR(300) NOT NULL, TotalPages INT NOT NULL, CreatedAt DATETIME2(0) NOT NULL DEFAULT SYSUTCDATETIME());-- Her chunk bir satırCREATE TABLE dbo.DocumentChunks( ChunkId BIGINT IDENTITY(1,1) PRIMARY KEY, DocumentId INT NOT NULL REFERENCES dbo.Documents(DocumentId), PageNumber INT NOT NULL, ChunkIndex INT NOT NULL, ChunkText NVARCHAR(MAX) NOT NULL, TokenCount INT NOT NULL, Embedding VECTOR(1536) NULL);CREATE INDEX IX_Chunks_DocumentId ON dbo.DocumentChunks(DocumentId);GO
5. Python ile Toplu Embedding Pipeline’ı
Embedding üretme işini bir Python scripti ile yapacağız. Tercihim Python, çünkü PDF parsing için pypdf, tokenization için tiktoken, batch embedding için openai kütüphaneleri hazır.
import osimport tiktokenimport pyodbcfrom openai import AzureOpenAIfrom pypdf import PdfReaderAZURE_ENDPOINT = "https://<resource>.openai.azure.com/"AZURE_API_KEY = os.environ["AZURE_OPENAI_KEY"]DEPLOYMENT = "embed-small"API_VERSION = "2024-10-21"SQL_CONN = ( "Driver={ODBC Driver 18 for SQL Server};" "Server=localhost;Database=VectorDemo;Trusted_Connection=yes;")CHUNK_SIZE = 800CHUNK_OVERLAP = 100client = AzureOpenAI( azure_endpoint=AZURE_ENDPOINT, api_key=AZURE_API_KEY, api_version=API_VERSION,)tokenizer = tiktoken.get_encoding("cl100k_base")def chunk_text(text, size=CHUNK_SIZE, overlap=CHUNK_OVERLAP): tokens = tokenizer.encode(text) step = size - overlap for i in range(0, len(tokens), step): chunk = tokens[i : i + size] yield tokenizer.decode(chunk), len(chunk)def embed_batch(texts): response = client.embeddings.create( model=DEPLOYMENT, input=texts, ) return [item.embedding for item in response.data]def ingest_pdf(path): conn = pyodbc.connect(SQL_CONN) cur = conn.cursor() reader = PdfReader(path) cur.execute(""" INSERT INTO dbo.Documents (FileName, Title, TotalPages) OUTPUT INSERTED.DocumentId VALUES (?, ?, ?) """, os.path.basename(path), os.path.basename(path), len(reader.pages)) doc_id = cur.fetchone()[0] buffered = [] for page_no, page in enumerate(reader.pages, start=1): text = page.extract_text() or "" for i, (chunk, tokens) in enumerate(chunk_text(text)): buffered.append((page_no, i, chunk, tokens)) # Toplu embedding — 16'lık batch'ler for start in range(0, len(buffered), 16): batch = buffered[start : start + 16] embeddings = embed_batch([b[2] for b in batch]) for (page_no, idx, text, tokens), emb in zip(batch, embeddings): emb_json = str(emb) cur.execute(""" INSERT INTO dbo.DocumentChunks (DocumentId, PageNumber, ChunkIndex, ChunkText, TokenCount, Embedding) VALUES (?, ?, ?, ?, ?, CAST(? AS VECTOR(1536))) """, doc_id, page_no, idx, text, tokens, emb_json) conn.commit() conn.close() print(f"{path}: {len(buffered)} chunk yüklendi")if __name__ == "__main__": for f in os.listdir("./pdfs"): if f.endswith(".pdf"): ingest_pdf(os.path.join("./pdfs", f))
200 PDF için bu pipeline ortalama 30-45 dakika sürer — en yavaş kısmı Azure OpenAI çağrıları. Rate limit’i aşmamak için batch boyutunu (16) ve paralellik seviyesini projende göze alabileceğin seviyede tut.
6. DiskANN İndeksini Oluşturma
Şimdi tabloda on binlerce chunk var. Bölüm 1’deki brute force yaklaşımını denersek her sorgu kabaca satır sayısıyla orantılı zaman alır. 100.000 satır için bile benim testimde tek sorgu 700-900 ms sürdü. Production için bu çok. İşte burada DiskANN devreye giriyor.
CREATE VECTOR INDEX IX_Chunks_Embedding ON dbo.DocumentChunks(Embedding) WITH ( metric = 'cosine', m = 16, -- eş bağlantı sayısı ef_construction = 200, -- index kurulumu sırasında taranacak eş bağlantı sayısı ef_search = 80 -- sorgu sırasında taranacak eş bağlantı sayısı );
Parametreler için pratik rehber:
- m (varsayılan 16): Graf bağlantı yoğunluğu. Yüksek m = daha doğru ama daha büyük index. Metin embedding’leri için 16 gayet yeterli.
- ef_construction (varsayılan 200): Index kurulurken ne kadar dikkatli çalışacağını belirler. Bir kereye mahsus maliyet — 200-400 arası güvenli.
- ef_search (varsayılan 80): Her sorguda ne kadar arayacağını belirler. Artırırsan doğruluk artar, hız düşer. 80-150 arası sweet spot.
İndex kurulumu 100.000 satır için yaklaşık 3-5 dakika sürer. Bu işlem boyunca tabloya yazma yapılmaması daha sağlıklı — büyük data yüklemesini tamamladıktan sonra index’i kurmak en iyi strateji.
7. Performans Karşılaştırma
Aynı soruyu hem index’li hem index’siz çalıştıralım. Benim 100K chunk’lı test tablomdaki sonuçlar:
-- Brute force (tüm satırları tarar)SELECT TOP (5) ChunkId, VECTOR_DISTANCE('cosine', Embedding, @q) dFROM dbo.DocumentChunksORDER BY d ASC;-- ~780 ms, logical reads: 42,311-- DiskANN index kullanarakSELECT TOP (5) ChunkId, VECTOR_DISTANCE('cosine', Embedding, @q) dFROM dbo.DocumentChunksWITH (INDEX = IX_Chunks_Embedding)ORDER BY d ASC;-- ~28 ms, logical reads: 1,847
Yaklaşık 28 kat hız kazancı. Index’in approximate olduğunu unutmayın: brute force 5 sonucun ilk 4’ü aynıydı, 5. sonuç biraz farklıydı. Çoğu kullanıcı senaryosunda bu fark hiç hissedilmez; ama sağlıkta veya hukukta tam doğruluk şartsa brute force tercih edin veya ef_search değerini 300-500’e çıkartın.
SET STATISTICS IO ve SET STATISTICS TIME komutlarıyla kendi ortamınızda bu testi çalıştırmanızı ve sonuçlarınızı yorumlara yazmanızı öneririm.
8. Sırada Ne Var?
Bu bölümde uçtan uca bir pipeline kurduk: PDF → chunk → embedding → SQL Server → DiskANN index. Sırada duruyor:
- RAG mimarisi: Bu chunk’ları bir LLM’in prompt’una gömüp “şirket dokümanlarına cevap veren asistan” kurmak
- Güvenlik: Azure OpenAI API key’i Database Scoped Credential içinde tutmak, Private Endpoint ile SQL Server’dan Azure OpenAI’a giden trafiği public internet dışında tutmak
- Production checklist: Rate limit yönetimi, embedding versiyonlama (model değişirse ne olur?), maliyet takibi
Kendi kütüphanenize bir-iki PDF koyup script’i çalıştırdığınızda karşılaştığınız hataları veya süre gözlemlerini yorumlara yazabilirseniz, sonraki bölümde sorularınıza göre eklemeler yapabilirim.
