LLM Evaluation: .NET'te AI Çıktı Kalitesini Ölçme ve İzleme
LLM'leri production'da güvenle kullanmak için yanıt kalitesini sürekli ölçmek şarttır. Modeller zaman içinde drift gösterebilir, yeni prompt değişiklikleri beklenmedik yan etkilere yol açabilir ya da belirli konu kategorilerinde performans düşebilir. Bu yazıda .NET'te LLM değerlendirme çerçevesi kurmayı göstereceğiz.
Temel Değerlendirme Metrikleri
- Faithfulness: Yanıt, kaynak belgelerdeki bilgilerle tutarlı mı?
- Answer Relevance: Yanıt, soruyla ne kadar ilgili?
- Context Recall: İlgili bağlam doğru şekilde alındı mı?
- Hallucination Rate: Model bağlamda olmayan bilgi üretti mi?
- Toxicity: Yanıt zararlı içerik barındırıyor mu?
G-Eval ile LLM-as-Judge
Bir LLM kullanarak başka bir LLM'in yanıtlarını değerlendirin:
public class GEvalService
{
private readonly ChatClient _judgeClient;
public async Task<EvaluationScore> EvaluateFaithfulnessAsync(
string question, string context, string answer)
{
var prompt = $"""
Aşağıdaki soruya verilen yanıtın, sağlanan BAĞLAM ile ne kadar tutarlı olduğunu değerlendir.
Yanıt yalnızca bağlamda bulunan bilgilere dayanmalıdır.
SORU: {question}
BAĞLAM:
{context}
YANIT:
{answer}
GÖREV:
1. Yanıttaki her iddiayı bağlamla karşılaştır
2. Bağlamda desteklenmeyen iddiaları işaretle
3. 0.0 ile 1.0 arasında bir puan ver (1.0 = tamamen tutarlı)
JSON olarak yanıtla:
{{
"score": 0.0,
"reasoning": "...",
"unsupported_claims": ["..."]
}}
""";
var result = await _judgeClient.CompleteChatAsync(
[
new SystemChatMessage("Sen tarafsız bir AI değerlendirme uzmanısın."),
new UserChatMessage(prompt)
],
new ChatCompletionOptions
{
ResponseFormat = ChatResponseFormat.JsonObject,
Temperature = 0,
ModelId = "gpt-4o"
});
return JsonSerializer.Deserialize<EvaluationScore>(result.Value.Content[0].Text)!;
}
public async Task<EvaluationScore> EvaluateAnswerRelevanceAsync(
string question, string answer)
{
var prompt = $"""
Bu soruya verilen yanıtın soruyla ne kadar ilgili olduğunu değerlendir.
SORU: {question}
YANIT: {answer}
0.0-1.0 arası puan ver. JSON: {{"score": 0.0, "reasoning": "..."}}
""";
var result = await _judgeClient.CompleteChatAsync(
[new UserChatMessage(prompt)],
new ChatCompletionOptions
{
ResponseFormat = ChatResponseFormat.JsonObject,
Temperature = 0
});
return JsonSerializer.Deserialize<EvaluationScore>(result.Value.Content[0].Text)!;
}
}
public record EvaluationScore(
double Score,
string Reasoning,
IReadOnlyList<string>? UnsupportedClaims = null);
RAGAS Benzeri Otomatik Değerlendirme
public class RagEvaluator
{
private readonly GEvalService _eval;
private readonly ILogger<RagEvaluator> _logger;
public async Task<RagEvaluationReport> EvaluateAsync(
IEnumerable<RagTestCase> testCases)
{
var scores = new List<RagEvaluationRecord>();
foreach (var tc in testCases)
{
var faithfulness = await _eval.EvaluateFaithfulnessAsync(
tc.Question, tc.Context, tc.ActualAnswer);
var relevance = await _eval.EvaluateAnswerRelevanceAsync(
tc.Question, tc.ActualAnswer);
// Ground truth varsa BLEU/ROUGE benzeri string benzerliği
double? exactMatch = tc.ExpectedAnswer != null
? CalculateSemanticSimilarity(tc.ExpectedAnswer, tc.ActualAnswer)
: null;
scores.Add(new RagEvaluationRecord(
Question: tc.Question,
FaithfulnessScore: faithfulness.Score,
RelevanceScore: relevance.Score,
ExactMatchScore: exactMatch,
HasHallucination: faithfulness.Score < 0.5));
_logger.LogInformation(
"Q: {Q} | Faithfulness: {F:F2} | Relevance: {R:F2}",
tc.Question[..Math.Min(50, tc.Question.Length)],
faithfulness.Score, relevance.Score);
}
return new RagEvaluationReport(
TotalCases: scores.Count,
AvgFaithfulness: scores.Average(s => s.FaithfulnessScore),
AvgRelevance: scores.Average(s => s.RelevanceScore),
HallucinationRate: scores.Count(s => s.HasHallucination) / (double)scores.Count,
Records: scores);
}
}
public record RagTestCase(string Question, string Context, string ActualAnswer, string? ExpectedAnswer = null);
public record RagEvaluationRecord(
string Question, double FaithfulnessScore, double RelevanceScore,
double? ExactMatchScore, bool HasHallucination);
Sürekli İzleme (Production)
public class LlmMonitoringMiddleware
{
private readonly RequestDelegate _next;
private readonly LlmEvalSampler _sampler;
private readonly IMetrics _metrics;
public async Task InvokeAsync(HttpContext context)
{
await _next(context);
// Her 10 istekte bir değerlendirme örneği al
if (_sampler.ShouldSample(rate: 0.1))
{
var requestLog = context.Items["LlmRequest"] as LlmRequestLog;
if (requestLog != null)
{
_ = Task.Run(async () =>
{
var eval = await _evaluator.EvaluateAsync([new RagTestCase(
requestLog.Question,
requestLog.Context,
requestLog.Answer)]);
// Prometheus / OpenTelemetry metriklerine kaydet
_metrics.Gauge("llm.faithfulness", eval.AvgFaithfulness);
_metrics.Gauge("llm.relevance", eval.AvgRelevance);
_metrics.Gauge("llm.hallucination_rate", eval.HallucinationRate);
});
}
}
}
}
Test Seti Oluşturma
public class TestSetGenerator
{
private readonly ChatClient _llm;
// Belgelerden otomatik soru-cevap çiftleri üret
public async Task<IReadOnlyList<RagTestCase>> GenerateTestSetAsync(
IEnumerable<string> documents, int questionsPerDoc = 5)
{
var testCases = new List<RagTestCase>();
foreach (var doc in documents)
{
var prompt = $"""
Aşağıdaki belgeden {questionsPerDoc} farklı soru-cevap çifti üret.
Sorular gerçekçi ve çeşitli olsun (ne, neden, nasıl, kim vb.).
BELGE:
{doc}
JSON array olarak: [{{"question":"...","answer":"..."}}]
""";
var result = await _llm.CompleteChatAsync(
[new UserChatMessage(prompt)],
new ChatCompletionOptions { ResponseFormat = ChatResponseFormat.JsonObject });
var pairs = JsonSerializer.Deserialize<List<QaPair>>(
result.Value.Content[0].Text)!;
testCases.AddRange(pairs.Select(p =>
new RagTestCase(p.Question, doc, "", p.Answer)));
}
return testCases;
}
}
public record QaPair(string Question, string Answer);
Regression Testing Pipeline
// dotnet test ile LLM regresyon testleri
public class LlmRegressionTests
{
private readonly RagEvaluator _evaluator;
[Theory]
[MemberData(nameof(GetTestCases))]
public async Task ResponseQuality_ShouldMeetThresholds(RagTestCase testCase)
{
var report = await _evaluator.EvaluateAsync([testCase]);
Assert.True(report.AvgFaithfulness >= 0.8,
$"Faithfulness too low: {report.AvgFaithfulness:F2}");
Assert.True(report.AvgRelevance >= 0.75,
$"Relevance too low: {report.AvgRelevance:F2}");
Assert.False(report.HallucinationRate > 0.1,
"Hallucination rate exceeds threshold");
}
}
Değerlendirme Sonuçlarını Görselleştirme
Grafana veya Application Insights ile metrikler:
- Faithfulness Trend: Zaman içinde güvenilirlik değişimi
- Hallucination Rate: Konu bazlı halüsinasyon oranı
- P95 Latency: Yanıt süresi dağılımı
- Token Efficiency: Kalite/maliyet oranı
Sonuç
LLM değerlendirmesi, üretim ortamında AI güvenilirliğinin temeli; eksik kalırsa modeli kör uçar gibi kullanırsınız. G-Eval ve RAGAS benzeri yaklaşımlar, otomatik değerlendirmeyi mümkün kılar. Başlangıç için faithfulness ve answer relevance metriklerini ölçmeye başlayın; zamanla test setinizi büyütün ve CI/CD pipeline'ınıza regresyon testleri ekleyin.