LLM Maliyet Optimizasyonu: Token Yönetimi ve Akıllı Önbellek
LLM API'leri güçlüdür ama maliyetleri hızla birikebilir. GPT-4o ile çalışan bir üretim uygulaması aylık binlerce dolar harcayabilir. Bu yazıda .NET'te uygulayabileceğiniz somut optimizasyon tekniklerini ele alacağız.
1. Token Sayımı ve Bütçe Takibi
using Microsoft.ML.Tokenizers;
public class TokenBudgetService
{
private readonly Tokenizer _tokenizer;
private readonly IDistributedCache _cache;
public TokenBudgetService()
{
// TiktokenTokenizer — GPT modellerinin kullandığı tokenizer
_tokenizer = TiktokenTokenizer.CreateForModel("gpt-4o");
}
public int CountTokens(string text) =>
_tokenizer.CountTokens(text);
public int CountChatTokens(IEnumerable<ChatMessage> messages)
{
// Her mesaj yaklaşık 4 token overhead taşır
const int perMessageOverhead = 4;
const int replyPriming = 3;
return messages.Sum(m => CountTokens(m.Content ?? "") + perMessageOverhead)
+ replyPriming;
}
public async Task<bool> CheckBudgetAsync(string userId, int estimatedTokens)
{
var key = $"token_budget:{userId}:{DateTime.UtcNow:yyyyMMdd}";
var used = int.Parse(await _cache.GetStringAsync(key) ?? "0");
const int dailyLimit = 100_000;
return used + estimatedTokens <= dailyLimit;
}
public async Task RecordUsageAsync(string userId, int tokensUsed)
{
var key = $"token_budget:{userId}:{DateTime.UtcNow:yyyyMMdd}";
var used = int.Parse(await _cache.GetStringAsync(key) ?? "0");
await _cache.SetStringAsync(key, (used + tokensUsed).ToString(),
new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTime.UtcNow.Date.AddDays(1)
});
}
}
2. Semantik Önbellekleme
Aynı veya çok benzer sorular için LLM çağırmak yerine önceki yanıtları döndürün:
public class SemanticCacheService
{
private readonly ITextEmbeddingGenerationService _embedding;
private readonly IVectorStoreRecordCollection<string, CachedResponse> _store;
private const double SimilarityThreshold = 0.95;
public async Task<string?> GetCachedResponseAsync(string prompt)
{
var embedding = await _embedding.GenerateEmbeddingAsync(prompt);
await foreach (var result in _store.VectorizedSearchAsync(embedding,
new VectorSearchOptions { Top = 1 }))
{
if (result.Score >= SimilarityThreshold)
return result.Record.Response;
}
return null;
}
public async Task CacheResponseAsync(string prompt, string response)
{
var embedding = await _embedding.GenerateEmbeddingAsync(prompt);
await _store.UpsertAsync(new CachedResponse
{
Id = Guid.NewGuid().ToString(),
Prompt = prompt,
Response = response,
Embedding = embedding,
CachedAt = DateTime.UtcNow
});
}
}
public record CachedResponse
{
[VectorStoreRecordKey]
public string Id { get; set; } = "";
[VectorStoreRecordData]
public string Prompt { get; set; } = "";
[VectorStoreRecordData]
public string Response { get; set; } = "";
[VectorStoreRecordData]
public DateTime CachedAt { get; set; }
[VectorStoreRecordVector(Dimensions: 1536)]
public ReadOnlyMemory<float> Embedding { get; set; }
}
3. Model Rotasyonu: Göreye Göre Model Seç
public class AdaptiveModelRouter
{
public string SelectModel(string prompt, TaskComplexity complexity)
{
var tokenCount = _tokenizer.CountTokens(prompt);
return complexity switch
{
TaskComplexity.Simple when tokenCount < 500 => "gpt-4o-mini",
TaskComplexity.Simple => "gpt-4o-mini",
TaskComplexity.Medium when tokenCount < 2000 => "gpt-4o",
TaskComplexity.Complex => "gpt-4o",
_ => "gpt-4o-mini"
};
}
public TaskComplexity ClassifyTask(string prompt)
{
// Anahtar kelime tabanlı basit sınıflandırma
var complexKeywords = new[] { "analiz", "compare", "explain in detail",
"step by step", "architecture", "design pattern" };
if (complexKeywords.Any(kw => prompt.Contains(kw, StringComparison.OrdinalIgnoreCase)))
return TaskComplexity.Complex;
return prompt.Length > 500 ? TaskComplexity.Medium : TaskComplexity.Simple;
}
}
public enum TaskComplexity { Simple, Medium, Complex }
4. Sistem Promptunu Optimize Etme
// Kötü: Her istekte tekrar gönderilen uzun sistem promptu
var badPrompt = """
Sen yardımcı bir asistansın. Kullanıcıların sorularını nazikçe,
kibar bir dille, Türkçe olarak yanıtla. Teknik terimleri açıkla.
Asla uygunsuz içerik üretme. Her zaman doğru bilgi ver.
Bilmediğinde belirt. Kısa ve öz ol ama gerektiğinde detay ver.
"""; // ~60 token, her istekte gönderiliyor
// İyi: Kısa ve öz sistem promptu
var goodPrompt = "Türkçe teknik destek asistanı. Kısa, net yanıtlar ver.";
// ~12 token — aynı davranış, %80 daha az token
5. Prompt Sıkıştırma
public class PromptCompressor
{
// Konuşma geçmişini özetleyerek token sayısını düşür
public async Task<string> SummarizeConversationAsync(
IEnumerable<ChatMessage> history,
int keepLastN = 3)
{
var messages = history.ToList();
if (messages.Count <= keepLastN + 2) // system + son N
return BuildContextFromMessages(messages);
var toSummarize = messages[1..^keepLastN]; // system ve son N hariç
var summaryPrompt = $"""
Şu konuşmayı 3 cümleyle özetle, önemli gerçekleri koru:
{string.Join("\n", toSummarize.Select(m => $"{m.Role}: {m.Content}"))}
""";
var summary = await _kernel.InvokePromptAsync(summaryPrompt,
new KernelArguments(new OpenAIPromptExecutionSettings
{
MaxTokens = 200,
ModelId = "gpt-4o-mini" // Özet için ucuz model
}));
// Özet + son N mesajı birleştir
var compressed = new List<ChatMessage> { messages[0] }; // system
compressed.Add(new(AuthorRole.System, $"[Önceki konuşma özeti: {summary}]"));
compressed.AddRange(messages[^keepLastN..]);
return BuildContextFromMessages(compressed);
}
}
6. Maliyet İzleme Dashboard
public class LlmCostTracker
{
private static readonly Dictionary<string, (decimal InputPer1K, decimal OutputPer1K)> Pricing = new()
{
["gpt-4o"] = (0.0025m, 0.01m),
["gpt-4o-mini"] = (0.00015m, 0.0006m),
["text-embedding-3-small"] = (0.00002m, 0m)
};
public decimal CalculateCost(string model, int inputTokens, int outputTokens)
{
if (!Pricing.TryGetValue(model, out var price)) return 0;
return (inputTokens / 1000m * price.InputPer1K)
+ (outputTokens / 1000m * price.OutputPer1K);
}
public async Task RecordAsync(LlmUsageRecord record)
{
record.EstimatedCostUsd = CalculateCost(record.Model, record.InputTokens, record.OutputTokens);
await _repository.SaveAsync(record);
}
}
Özet: Maliyet Azaltma Stratejileri
| Strateji | Tipik Tasarruf | Uygulama Kolaylığı |
|---|---|---|
| Semantik önbellekleme | %30-60 | Orta |
| Ucuz modele yönlendirme | %50-80 | Kolay |
| Sistem promptu kısaltma | %10-20 | Çok kolay |
| Konuşma geçmişi özeti | %20-40 | Orta |
| Önceden hesaplama | %15-30 | Zor |
Sonuç
LLM maliyetlerini kontrol altında tutmak, ölçeklenen uygulamalar için kritik öneme sahiptir. Semantik önbellekleme ve model rotasyonunu birlikte uygulayarak maliyetlerinizi genellikle %60-70 oranında düşürebilirsiniz. Her stratejiyi A/B testleriyle doğrulayın ve yanıt kalitesini koruyun.