• 19.08.2026 03:05:15
  • Admin Admin

Yazılım eğitimi odaklı bir RAG asistanını, altın bağlam seti, Recall@k, atıf kapsaması, OpenTelemetry izleri ve CI eşikleriyle ölçün. Hatalı retrieval, gecikme ve maliyeti aynı deneyde ayırın.

RAG Değerlendirmesi ile Yazılım Eğitimi Asistanını Sistematik Ölçmek

Yazılım eğitimi asistanı için ölçülebilir altın veri seti kurun

RAG değerlendirmesini modelin ürettiği akıcı yanıtla değil, önce retrieval katmanıyla ayırın. Her test örneğinde query, beklenen gold_chunk_ids, soru tipi ve kabul edilebilir yanıt koşulu bulunsun. Yazılım eğitimi içeriğinde soru tiplerini hata ayıklama, API kullanımı, kavramsal karşılaştırma ve kod inceleme olarak etiketlemek, örneğin yalnızca API sorularında düşen Recall@k değerini ayrı görebilmenizi sağlar. Başlangıç için 100 sorguluk, üretim dokümanlarından elle doğrulanmış bir JSONL seti yeterlidir; setin en az yüzde 20'sini benzer terimli ama yanlış bağlamlara yönlendiren negatif örneklerden oluşturun.

{
  'id': 'py-042',
  'query': 'asyncio.TaskGroup icinde bir task hata verirse ne olur?',
  'gold_chunk_ids': ['python-async-17', 'python-async-18'],
  'question_type': 'api_semantics',
  'acceptance': {
    'required_concepts': ['cancellation', 'ExceptionGroup'],
    'must_cite_gold_chunk': true
  }
}

Retrieval için iki metriği birlikte hesaplayın: Recall@k, altın parçaların en az birinin ilk k sonuçta bulunup bulunmadığını; MRR ise ilk doğru sonucun sırasını ölçer. Bunun nedeni, doğru parçanın 40. sırada gelmesinin reranker ve bağlam penceresi açısından pratikte işe yaramamasıdır. Aynı dokümanın ardışık parçaları altın kümede ise yalnızca parça kimliğiyle ölçmek yanıltıcı olabilir; hem chunk_id hem document_id seviyesinde rapor üretin. Böylece splitter sınırında kalan bir açıklamanın haksız yere retrieval hatası olarak sayılmasını önlersiniz.

pgvector retrieval ayarlarını Recall@k ile doğrulayın

PostgreSQL üzerinde pgvector kullanıyorsanız HNSW indeksinin yaklaşık en yakın komşu araması, hnsw.ef_search değeri kadar adayı gezer. Varsayılan veya düşük bir değer hızlı görünse de benzer kod bloklarının bulunduğu dokümanlarda doğru parçayı aday kümesine hiç alamayabilir. Her indeks ve embedding modeli değişiminden sonra aynı altın setinde k=8 için Recall@8, MRR ve p95 sorgu süresini kaydedin; yalnızca ortalama gecikmeye bakmayın.

CREATE INDEX IF NOT EXISTS chunks_embedding_hnsw
ON chunks USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 128);

BEGIN;
SET LOCAL hnsw.ef_search = 120;
SELECT chunk_id, document_id, content,
       1 - (embedding <=> $1::vector) AS cosine_similarity
FROM chunks
WHERE tenant_id = $2
ORDER BY embedding <=> $1::vector
LIMIT 40;
COMMIT;

Buradaki kritik edge case, tenant_id filtresinin HNSW aramasından sonra uygulanabilmesidir. Çok kiracılı ve seyrek bir koleksiyonda 40 adayın çoğu başka kiracıdan gelirse filtre sonrası 8 sonuç dahi kalmayabilir. Bunu tespit etmek için sorgu başına candidate_count_before_filter ve result_count_after_filter alanlarını loglayın. Çözüm olarak kiracı başına ayrı indeks, partition edilmiş tablo veya daha yüksek ef_search deneyin; kararınızı aynı 100 sorguda önce-sonra Recall@8 ve p95 süreleriyle verin.

İlk 40 dense sonucu bir cross-encoder ile yeniden sıralamak, özellikle fonksiyon adı ile doğal dil açıklamasının farklı olduğu kaynaklarda yararlıdır. BAAI/bge-reranker-v2-m3 gibi bir reranker'ı yalnızca ilk 40 adayda çalıştırıp ilk 8 parçayı LLM'e gönderin. 40 yerine tüm korpusu rerank etmeyin: cross-encoder her sorgu-parça çiftini birlikte tokenlaştırdığı için maliyet ve gecikme aday sayısıyla doğrusal artar.

Yanıt kalitesini atıf ve kanıt kapsamasıyla ayırın

Bir RAG yanıtının doğru görünmesi, kaynaklarla desteklendiği anlamına gelmez. Yanıt şemasını, her teknik iddianın kaynak parça kimliğini taşıyacağı şekilde zorlayın. Örneğin istemciye düz metin yerine claims dizisi döndürün; her elemanda text ve chunk_ids olsun. Bu yapı, yanıt sonradan HTML'e dönüştürülse bile atıf kapsamasını otomatik test etmeyi mümkün kılar.

import re

def citation_coverage(answer: dict, gold_ids: set[str]) -> dict:
    claims = answer.get('claims', [])
    supported = [c for c in claims if set(c.get('chunk_ids', [])) & gold_ids]
    uncited = [c for c in claims if not c.get('chunk_ids')]
    return {
        'claim_count': len(claims),
        'gold_supported_ratio': len(supported) / max(len(claims), 1),
        'uncited_claim_count': len(uncited),
    }

result = citation_coverage(response_json, set(example['gold_chunk_ids']))
assert result['gold_supported_ratio'] >= 0.80

Bu test tek başına olgusal doğruluk testi değildir: model yanlış bir cümleyi doğru parçaya atıflayabilir. İkinci aşamada, iddia ve kaynak metnini çift olarak inceleyen bir LLM judge kullanın ve çıktıyı JSON Schema ile entailed, contradicted, not_in_context etiketlerine sınırlayın. Judge prompt'una cevap bilgisini değil yalnızca iddia, kaynak ve karar kurallarını verin. Ardından 30-50 örneği insan incelemesiyle karşılaştırıp judge'ın yanlış kabul oranını ölçün; aksi halde otomatik metriğiniz sistematik halüsinasyonu gizleyebilir.

OpenTelemetry ile RAG gecikmesini bileşen bazında profilleyin

Toplam endpoint süresi, gecikmenin embedding isteğinden mi, pgvector sorgusundan mı, reranker'dan mı yoksa token üretiminden mi geldiğini söylemez. OpenTelemetry ile embed, retrieve, rerank ve generate span'ları oluşturun; her spana aday sayısı, bağlam token sayısı ve cache hit bilgisini ekleyin. Jaeger veya Grafana Tempo üzerinde p50, p95 ve p99 değerlerini span adına göre karşılaştırın.

from opentelemetry import trace
tracer = trace.get_tracer('rag-service')

async def answer(query: str, tenant_id: str):
    with tracer.start_as_current_span('embed') as span:
        vector = await embed_query(query)
        span.set_attribute('rag.embedding.cache_hit', False)

    with tracer.start_as_current_span('retrieve') as span:
        candidates = await search_pgvector(vector, tenant_id, limit=40)
        span.set_attribute('rag.candidate_count', len(candidates))

    with tracer.start_as_current_span('rerank') as span:
        context = await rerank(query, candidates, top_n=8)
        span.set_attribute('rag.context_chunks', len(context))

    with tracer.start_as_current_span('generate'):
        return await generate_with_citations(query, context)

Önce-sonra deneyi aynı altın sorgu seti ve aynı eşzamanlılıkla çalıştırın. Örneğin önce embedding isteğini her sorguda sağlayıcıya gönderin, sonra normalize edilmiş sorgu metni ve embedding-model kimliğiyle Redis üzerinde 24 saat TTL'li cache ekleyin. redis-cli --latency-history ile cache altyapısını, OpenTelemetry ile de embed span p95'ini ölçün. Cache hit oranı artarken generate span'ı değişmiyorsa kazanımın mekanizması, yalnızca ağ üzerinden yapılan embedding çağrısının ortadan kalkmasıdır; bunu toplam p95 ve istek başına sağlayıcı maliyetiyle birlikte raporlayın.

Python async servislerinde sık görülen hata, CPU tabanlı reranker'ın event loop üzerinde senkron çalışmasıdır. Bu durumda tek tek span'lar makul görünse bile eşzamanlı isteklerde kuyruk gecikmesi büyür. Canlıya yakın bir ortamda py-spy record --pid $PID --rate 100 -o profile.svg çalıştırın; flame graph'ta tokenizer veya PyTorch çağrıları ana thread'i tutuyorsa reranker'ı ayrı worker sürecine taşıyın ya da asyncio.to_thread ile sınırlı bir executor kullanın. Değişiklikten sonra 20, 50 ve 100 eşzamanlı istekte p95 değerlerini ayrı ayrı karşılaştırın.

Değerlendirme eşiğini CI hattında sürümleyin

Embedding modeli, chunking kuralı veya sistem prompt'u değiştiğinde regresyonu kod incelemesinde gözle yakalamaya çalışmayın. Altın veri setini uygulama koduyla aynı depoda sürümleyin ve CI içinde retrieval ile yanıt değerlendirmesini ayrı komutlar olarak çalıştırın. Eşikler başlangıçta mutlak hedef değil, mevcut üretim davranışınızın ölçülmüş taban çizgisi olmalıdır: örneğin yeni değişiklikte Recall@8'in taban çizgisinden 0.02'den fazla düşmesini reddedin.

name: rag-eval
on: [pull_request]
jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: uv sync --frozen
      - run: uv run python eval_retrieval.py --dataset eval/gold.jsonl --out retrieval.json
      - run: uv run python eval_answers.py --dataset eval/gold.jsonl --out answers.json
      - run: |
          jq -e '.recall_at_8 >= 0.86 and .mrr >= 0.71' retrieval.json
          jq -e '.gold_supported_ratio >= 0.80' answers.json

CI sonucuna veri seti commit SHA'sını, embedding model kimliğini, chunker konfigürasyonunu ve indeks parametrelerini yazın. Aksi halde aynı kod değişikliği, arka planda yeniden indekslenen veri nedeniyle farklı skor üretebilir ve regresyonun kaynağı belirsiz kalır. Üretimde de her gün rastgele örneklenmiş 50 sorguda sonuç dağılımını izleyin; sorgu embedding normu veya en iyi cosine similarity dağılımı belirgin kayarsa yeni doküman türleri ya da veri alım hataları için canary alarmı üretin.

Sık Sorulan Sorular

Yazılım eğitimi RAG asistanında Recall@k nasıl hesaplanır?

Her sorgu için dönen ilk k parça kimliğini altın parça kümesiyle kesiştirin. Kesişim boş değilse sorguya 1, boşsa 0 verin ve tüm sorguların ortalamasını alın. Aynı anda MRR de hesaplayın; doğru parçanın 1., 3. veya 8. sırada olmasının yanıt kalitesine etkisini bu metrik ayırır.

Yazılım eğitimi chatbotunda pgvector HNSW neden doğru sonucu kaçırır?

HNSW yaklaşık arama yaptığı için düşük hnsw.ef_search doğru vektörü aday grafında yeterince gezmeyebilir. Ayrıca tenant_id gibi filtreler adaylar bulunduktan sonra uygulanıyorsa sonuç sayısı düşer. Aynı sorgu setinde ef_search değerlerini 40, 80 ve 120 ile deneyip Recall@8 ve p95 SQL süresini birlikte ölçün.

Yazılım eğitimi için RAG yanıtlarında kaynak atfı nasıl test edilir?

Modelden her iddia için chunk_ids içeren yapılandırılmış JSON isteyin. Otomatik testte iddiaların kaçının altın kaynaklardan en az birini referansladığını hesaplayın. Ardından iddia-kaynak çiftlerini entailment, contradiction ve not_in_context sınıflarıyla değerlendiren JSON Schema kısıtlı bir judge ile kontrol edin; en az 30 örnekte judge sonucunu insan incelemesiyle kalibre edin.

Yazılım eğitimi asistanında RAG gecikmesi hangi araçla profillenir?

OpenTelemetry span'larını Jaeger veya Grafana Tempo'ya göndererek embed, retrieve, rerank ve generate aşamalarının p95 değerlerini ayrı ölçün. Python işleminde beklenmeyen CPU beklemesi varsa py-spy record ile flame graph alın. Bu ikili, dış servis gecikmesi ile event loop'u bloke eden yerel reranker maliyetini birbirinden ayırır.

AI / LLM Discovery

Bu makale Opendart Akademi Yapay Zeka eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt