Semantic Kernel Memory ve Vector Store ile RAG Mimarisi
Büyük Dil Modelleri (LLM) eğitim verilerinin ötesindeki bilgileri bilmez. Retrieval Augmented Generation (RAG) mimarisi bu sorunu çözer: kullanıcının sorusuyla ilgili belgeleri vektör veritabanından çekip LLM'e bağlam olarak sunar. Semantic Kernel'in Memory ve Vector Store bileşenleri, bu süreci .NET'te kutudan çıkartır gibi hazır hale getirir.
Paket Kurulumu
dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Connectors.InMemory
dotnet add package Microsoft.SemanticKernel.Connectors.Qdrant
dotnet add package Microsoft.SemanticKernel.Plugins.Memory
In-Memory Vector Store (Geliştirme Ortamı)
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Connectors.InMemory;
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion("gpt-4o", apiKey)
.AddOpenAITextEmbeddingGeneration("text-embedding-3-small", apiKey)
.Build();
var memory = new MemoryBuilder()
.WithOpenAITextEmbeddingGeneration("text-embedding-3-small", apiKey)
.WithMemoryStore(new InMemoryMemoryStore())
.Build();
Belgeleri Vektör Mağazasına Kaydetme
public class DocumentIngestionService
{
private readonly ISemanticTextMemory _memory;
private const string CollectionName = "docs";
public async Task IngestDocumentAsync(Document doc)
{
// Büyük belgeleri chunk'lara böl
var chunks = SplitIntoChunks(doc.Content, maxTokens: 500);
for (int i = 0; i < chunks.Count; i++)
{
await _memory.SaveInformationAsync(
collection: CollectionName,
text: chunks[i],
id: $"{doc.Id}_chunk_{i}",
description: doc.Title,
additionalMetadata: $"source:{doc.Url}|page:{i}");
}
}
private static List<string> SplitIntoChunks(string text, int maxTokens)
{
// Yaklaşık 4 karakter = 1 token
int chunkSize = maxTokens * 4;
int overlap = chunkSize / 5; // %20 örtüşme
var chunks = new List<string>();
int start = 0;
while (start < text.Length)
{
int end = Math.Min(start + chunkSize, text.Length);
chunks.Add(text[start..end]);
start += chunkSize - overlap;
}
return chunks;
}
}
Benzerlik Araması
public async Task<IReadOnlyList<MemoryQueryResult>> SearchAsync(
string query, int topK = 5, double minScore = 0.7)
{
var results = new List<MemoryQueryResult>();
await foreach (var result in _memory.SearchAsync(
collection: CollectionName,
query: query,
limit: topK,
minRelevanceScore: minScore))
{
results.Add(result);
}
return results;
}
RAG Tabanlı Soru Cevaplama
public class RagChatService
{
private readonly Kernel _kernel;
private readonly ISemanticTextMemory _memory;
public async Task<string> AskAsync(string question)
{
// 1. İlgili belgeleri çek
var relevantDocs = await SearchAsync(question, topK: 5, minScore: 0.65);
if (!relevantDocs.Any())
return "Bu konuda elimde yeterli bilgi bulunmuyor.";
// 2. Bağlamı oluştur
var context = string.Join("\n\n---\n\n",
relevantDocs.Select(d =>
$"Kaynak: {d.Metadata.Description}\n{d.Metadata.Text}"));
// 3. LLM'e gönder
var prompt = $"""
Aşağıdaki bağlamı kullanarak soruyu yanıtla.
Bağlamda bulunmayan bilgileri uydurma; bilmiyorsan söyle.
BAĞLAM:
{context}
SORU: {question}
YANIT:
""";
var result = await _kernel.InvokePromptAsync(prompt,
new KernelArguments(new OpenAIPromptExecutionSettings
{
MaxTokens = 1000,
Temperature = 0.2 // Düşük sıcaklık = daha tutarlı
}));
return result.ToString();
}
}
Qdrant ile Production Vector Store
// docker run -p 6333:6333 qdrant/qdrant
builder.Services.AddSingleton<IMemoryStore>(sp =>
new QdrantMemoryStore(
host: "localhost",
port: 6333,
vectorSize: 1536)); // text-embedding-3-small = 1536 boyut
var memory = new MemoryBuilder()
.WithOpenAITextEmbeddingGeneration("text-embedding-3-small", apiKey)
.WithMemoryStore(sp.GetRequiredService<IMemoryStore>())
.Build();
Semantic Kernel Vector Store (Yeni API)
SK 1.x'in yeni Vector Store API'si daha tip güvenli bir yaklaşım sunar:
using Microsoft.SemanticKernel.Data;
// Model tanımı
public class ArticleRecord
{
[VectorStoreRecordKey]
public string Id { get; set; } = "";
[VectorStoreRecordData(IsFilterable = true)]
public string Title { get; set; } = "";
[VectorStoreRecordData(IsFullTextSearchable = true)]
public string Content { get; set; } = "";
[VectorStoreRecordVector(Dimensions: 1536)]
public ReadOnlyMemory<float> Embedding { get; set; }
}
// Kayıt ve arama
public class ArticleVectorService
{
private readonly IVectorStoreRecordCollection<string, ArticleRecord> _collection;
private readonly ITextEmbeddingGenerationService _embeddingService;
public async Task UpsertAsync(ArticleRecord article)
{
article.Embedding = await _embeddingService.GenerateEmbeddingAsync(article.Content);
await _collection.UpsertAsync(article);
}
public async Task<IReadOnlyList<ArticleRecord>> SearchAsync(string query, int topK = 5)
{
var queryEmbedding = await _embeddingService.GenerateEmbeddingAsync(query);
var results = new List<ArticleRecord>();
await foreach (var result in _collection.VectorizedSearchAsync(queryEmbedding,
new VectorSearchOptions { Top = topK }))
{
results.Add(result.Record);
}
return results;
}
}
Hibrit Arama: Anlam + Anahtar Kelime
public async Task<IReadOnlyList<ArticleRecord>> HybridSearchAsync(
string query, int topK = 10)
{
// Vektör araması
var vectorResults = await SearchAsync(query, topK * 2);
// Basit BM25-benzeri keyword filtre
var keywords = query.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var keywordFiltered = vectorResults
.Where(r => keywords.Any(kw =>
r.Title.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
r.Content.Contains(kw, StringComparison.OrdinalIgnoreCase)))
.ToList();
// Karma sonuçları döndür (önce keyword match, sonra pure vector)
return keywordFiltered.Concat(vectorResults.Except(keywordFiltered))
.Take(topK)
.ToList();
}
Sonuç
Semantic Kernel'in Memory ve Vector Store bileşenleri, .NET uygulamalarınıza RAG mimarisi eklemenin en temiz yoludur. Geliştirme ortamında InMemory store, production'da Qdrant veya Azure AI Search kullanın. Chunk boyutu ve örtüşme oranını doğru ayarlamak, arama kalitesini önemli ölçüde artırır.