API Token Sayısını Azaltarak OpenAI Maliyetini Düşürme

OpenAI API kullanmaya başladığınızda her şey güzel görünür. Birkaç istek gönderirsiniz, harika sonuçlar alırsınız, “bu iş çok kolay” dersiniz. Sonra ay sonu fatura gelir ve gerçeklikle yüzleşirsiniz. Token maliyetleri, özellikle production ortamında yüksek hacimli kullanımda, ciddi bir gider kalemi haline gelebilir. Bu yazıda token sayısını nasıl azaltabileceğinizi, hem sistem yöneticisi bakış açısıyla hem de pratik kod örnekleriyle ele alacağız.

Token Nedir ve Neden Önemlidir

OpenAI API’si metni işlerken karakterleri değil, token adı verilen parçaları kullanır. Kabaca olarak her 4 karakter 1 token’a karşılık gelir, ama bu dil ve içeriğe göre değişir. Türkçe gibi eklemeli dillerde bu oran bazen daha az verimli çalışır.

API maliyeti hem gönderdiğiniz (input) hem de aldığınız (output) token sayısına göre hesaplanır. GPT-4o ile çalışıyorsanız input token’lar daha ucuz, output token’lar daha pahalıdır. Bu asimetri, stratejinizi şekillendirmeniz için önemli bir ipucu verir: model çok şey üretmesin, siz az ama öz gönderin.

Aşağıdaki Python örneği ile mevcut bir isteğin kaç token tükettiğini ölçebilirsiniz:

pip install tiktoken
python3 << 'EOF'
import tiktoken

def token_say(metin, model="gpt-4o"):
    enc = tiktoken.encoding_for_model(model)
    return len(enc.encode(metin))

sistem_prompt = """Sen bir müşteri hizmetleri asistanısın. 
Kullanıcıların sorularını kibarca, profesyonelce ve Türkçe olarak yanıtlıyorsun. 
Her zaman nazik ol, kullanıcıyı dinle ve onlara yardımcı olmaya çalış.
Eğer bir soruya cevap veremiyorsan, bunu açıkça belirt."""

kullanici_mesaji = "Siparişim nerede?"

print(f"Sistem prompt token: {token_say(sistem_prompt)}")
print(f"Kullanıcı mesajı token: {token_say(kullanici_mesaji)}")
print(f"Toplam input token: {token_say(sistem_prompt + kullanici_mesaji)}")
EOF

Bu basit ölçüm alışkanlığını edinmek, optimizasyonun ilk adımıdır. Neyi ölçemiyorsanız, onu yönetemezsiniz.

Sistem Prompt Optimizasyonu

Sistem promptları çoğu zaman göz ardı edilen en büyük token israf kaynağıdır. Her API çağrısında sistem promptu yeniden gönderilir. Eğer 500 token’lık bir sistem promptunuz varsa ve günde 10.000 istek yapıyorsanız, sadece sistem promptu için 5 milyon token harcıyorsunuz demektir.

Gereksiz Kelimelerden Kurtulun

python3 << 'EOF'
import tiktoken

enc = tiktoken.encoding_for_model("gpt-4o")

# Kötü örnek - şişirilmiş sistem promptu
kotu_prompt = """
Sen son derece deneyimli, son teknoloji yapay zeka destekli bir asistansın. 
Görevin kullanıcılara her konuda yardımcı olmak ve onların tüm sorularını 
mümkün olan en kapsamlı ve detaylı şekilde yanıtlamaktır. Her zaman kibar, 
anlayışlı ve profesyonel bir dil kullanmalısın. Kullanıcı deneyimini en üst 
seviyeye çıkarmak için elinden gelenin en iyisini yapmalısın.
"""

# İyi örnek - özlü sistem promptu
iyi_prompt = """Yardımcı bir asistansın. Kısa, net ve doğru yanıt ver."""

print(f"Kötü prompt: {len(enc.encode(kotu_prompt))} token")
print(f"İyi prompt: {len(enc.encode(iyi_prompt))} token")
print(f"Tasarruf: {len(enc.encode(kotu_prompt)) - len(enc.encode(iyi_prompt))} token/istek")
EOF

Aradaki fark küçük görünebilir ama aylık binlerce isteği olan bir sistemde bu fark ciddi paraya dönüşür.

Few-Shot Örnek Sayısını Optimize Edin

Few-shot örnekler modeli yönlendirmek için kullanılır ama aşırıya kaçıldığında token maliyetini katlar. Genellikle 2-3 iyi örnek, 10 ortalama örnekten daha etkilidir.

python3 << 'EOF'
import tiktoken

enc = tiktoken.encoding_for_model("gpt-4o")

# Her örnek yaklaşık bu kadar token tutar
ornek_sablonu = """
Kullanıcı: Ürünüm bozuk geldi ne yapmalıyım?
Asistan: Özür dileriz. Sipariş numaranızı paylaşır mısınız?
"""

# 10 örnek mi, 3 örnek mi?
on_ornek = ornek_sablonu * 10
uc_ornek = ornek_sablonu * 3

print(f"10 örnek: {len(enc.encode(on_ornek))} token")
print(f"3 örnek: {len(enc.encode(uc_ornek))} token")
print(f"Günlük 10.000 istekte tasarruf: {(len(enc.encode(on_ornek)) - len(enc.encode(uc_ornek))) * 10000:,} token")
EOF

Konuşma Geçmişi Yönetimi

Chat tabanlı uygulamalarda konuşma geçmişinin tamamını her seferinde göndermek, en yaygın token israf nedenlerinden biridir. Kullanıcı 20 mesaj yazmışsa, 21. mesajda önceki 20 mesajın tamamını gönderiyorsunuz demektir.

Sliding Window Yaklaşımı

python3 << 'EOF'
from openai import OpenAI
import tiktoken

client = OpenAI()
enc = tiktoken.encoding_for_model("gpt-4o")

def mesaj_token_say(mesajlar):
    toplam = 0
    for mesaj in mesajlar:
        toplam += len(enc.encode(mesaj.get("content", "")))
        toplam += 4  # Her mesaj için overhead
    return toplam

def gecmis_kirp(mesajlar, max_token=2000, sistem_mesaj=None):
    """
    Konuşma geçmişini token limitine göre kırpar.
    Her zaman en son mesajları korur.
    """
    if sistem_mesaj:
        sistem_token = len(enc.encode(sistem_mesaj["content"]))
        max_token -= sistem_token

    kirpilmis = []
    toplam_token = 0

    # Mesajları sondan başa doğru işle
    for mesaj in reversed(mesajlar):
        mesaj_token = len(enc.encode(mesaj["content"])) + 4
        if toplam_token + mesaj_token <= max_token:
            kirpilmis.insert(0, mesaj)
            toplam_token += mesaj_token
        else:
            break

    return kirpilmis

# Örnek kullanım
konusma_gecmisi = [
    {"role": "user", "content": "Merhaba, Python öğrenmek istiyorum"},
    {"role": "assistant", "content": "Harika bir seçim! Python ile başlamak için..."},
    {"role": "user", "content": "Değişkenlerden bahseder misin?"},
    {"role": "assistant", "content": "Değişkenler Python'da veri depolamak için kullanılır..."},
    {"role": "user", "content": "Döngüler nasıl çalışır?"},
]

kirpilmis = gecmis_kirp(konusma_gecmisi, max_token=500)
print(f"Orijinal mesaj sayısı: {len(konusma_gecmisi)}")
print(f"Kırpılmış mesaj sayısı: {len(kirpilmis)}")
print(f"Token tasarrufu: {mesaj_token_say(konusma_gecmisi) - mesaj_token_say(kirpilmis)}")
EOF

Konuşma Özeti Yaklaşımı

Sliding window yerine eski mesajları özetletmek, bağlamı kaybetmeden token tasarrufu sağlar:

python3 << 'EOF'
from openai import OpenAI

client = OpenAI()

def konusmayi_ozetle(eski_mesajlar):
    """Eski konuşma geçmişini özetler"""
    ozet_istegi = [
        {
            "role": "system",
            "content": "Aşağıdaki konuşmayı 2-3 cümleyle özetle. Sadece özeti yaz."
        },
        {
            "role": "user", 
            "content": "n".join([
                f"{m['role']}: {m['content']}" 
                for m in eski_mesajlar
            ])
        }
    ]
    
    yanit = client.chat.completions.create(
        model="gpt-4o-mini",  # Özetleme için mini model yeterli
        messages=ozet_istegi,
        max_tokens=150
    )
    
    return yanit.choices[0].message.content

# Kullanım senaryosu
gecmis = [
    {"role": "user", "content": "Sunucumda disk alanı doldu"},
    {"role": "assistant", "content": "df -h komutu ile disk kullanımını kontrol edebilirsiniz..."},
    {"role": "user", "content": "Hangi dizinler çok yer kaplıyor?"},
    {"role": "assistant", "content": "du -sh /* komutu ile dizin boyutlarını görebilirsiniz..."},
]

ozet = konusmayi_ozetle(gecmis)
print(f"Özet: {ozet}")
print("nYeni konuşma bu özetle devam eder, tam geçmiş yerine")
EOF

Model Seçimi ve Akıllı Yönlendirme

Her görev için en güçlü modeli kullanmak, en pahalı yaklaşımdır. GPT-4o ile basit bir metin sınıflandırması yapmak, Ferrari ile market alışverişine gitmek gibidir.

python3 << 'EOF'
from openai import OpenAI

client = OpenAI()

def akilli_model_sec(gorev_tipi, karmasiklik_skoru):
    """
    Görev tipine ve karmaşıklığa göre model seçer.
    karmasiklik_skoru: 1-10 arası
    """
    if gorev_tipi in ["siniflandirma", "ozet", "basit_soru"] or karmasiklik_skoru <= 3:
        return "gpt-4o-mini"
    elif karmasiklik_skoru <= 7:
        return "gpt-4o"
    else:
        return "gpt-4o"  # Maksimum karmaşıklık

def hizli_siniflandir(kullanici_mesaji):
    """Kullanıcı mesajını kategorize et - mini model yeterli"""
    yanit = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": "Mesajı kategorize et: teknik/fatura/genel. Sadece kategoriyi yaz."
            },
            {"role": "user", "content": kullanici_mesaji}
        ],
        max_tokens=10  # Kategori için 10 token fazlasıyla yeterli
    )
    return yanit.choices[0].message.content.strip()

# Test
test_mesajlari = [
    "Siparişim nerede?",
    "Kubernetes cluster'ımda pod'lar crash oluyor",
    "Faturamda hata var"
]

for mesaj in test_mesajlari:
    kategori = hizli_siniflandir(mesaj)
    print(f"Mesaj: {mesaj[:30]}... -> Kategori: {kategori}")
EOF

max_tokens Parametresini Aktif Kullanın

Çoğu developer max_tokens parametresini ayarlamayı unutur. Model gereksiz yere uzun yanıtlar üretebilir. Bu parametreyi görev tipine göre sıkı tutmak ciddi tasarruf sağlar.

python3 << 'EOF'
from openai import OpenAI

client = OpenAI()

# Görev bazlı max_tokens değerleri
GOREV_TOKEN_LIMITLERI = {
    "siniflandirma": 20,
    "evet_hayir": 5,
    "kisa_ozet": 150,
    "kod_aciklama": 300,
    "detayli_analiz": 800,
    "uzun_icerik": 2000
}

def kontrollü_istek(prompt, gorev_tipi, model="gpt-4o-mini"):
    max_tok = GOREV_TOKEN_LIMITLERI.get(gorev_tipi, 500)
    
    yanit = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
        max_tokens=max_tok,
        temperature=0  # Deterministik yanıt, israfı önler
    )
    
    kullanilan = yanit.usage.total_tokens
    print(f"Görev: {gorev_tipi}, Limit: {max_tok}, Kullanılan: {kullanilan}")
    return yanit.choices[0].message.content

# Test senaryoları
kontrollü_istek("Bu metin olumlu mu yoksa olumsuz mu? 'Ürün harika geldi'", "siniflandirma")
kontrollü_istek("Python'da liste nedir?", "kisa_ozet")
EOF

Önbellekleme ile Tekrarlayan İstekleri Engelleme

Production sistemlerde aynı ya da benzer sorgular tekrar tekrar gönderilir. Bir Redis tabanlı önbellek kurarak identik sorgular için API’ye hiç gitmeyebilirsiniz.

pip install redis openai
python3 << 'EOF'
import hashlib
import json
import redis
from openai import OpenAI

client = OpenAI()
cache = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)

CACHE_TTL = 3600  # 1 saat

def prompt_hash_olustur(messages, model):
    """İstek için benzersiz hash üretir"""
    icerik = json.dumps({"messages": messages, "model": model}, sort_keys=True)
    return hashlib.sha256(icerik.encode()).hexdigest()

def cacheli_istek(messages, model="gpt-4o-mini", max_tokens=500):
    cache_key = f"openai:{prompt_hash_olustur(messages, model)}"
    
    # Önce cache'e bak
    cached = cache.get(cache_key)
    if cached:
        print("Cache hit! API çağrısı yapılmadı.")
        return json.loads(cached)
    
    # Cache'de yoksa API'ye git
    print("Cache miss. API çağrısı yapılıyor...")
    yanit = client.chat.completions.create(
        model=model,
        messages=messages,
        max_tokens=max_tokens
    )
    
    sonuc = {
        "content": yanit.choices[0].message.content,
        "tokens": yanit.usage.total_tokens
    }
    
    # Sonucu cache'e yaz
    cache.setex(cache_key, CACHE_TTL, json.dumps(sonuc))
    return sonuc

# Aynı istek iki kez gönderilirse ikincisi cache'den gelir
mesajlar = [{"role": "user", "content": "Python'da decorator ne işe yarar?"}]

sonuc1 = cacheli_istek(mesajlar)
sonuc2 = cacheli_istek(mesajlar)  # Bu cache'den gelir

print(f"nYanıt: {sonuc1['content'][:100]}...")
EOF

Prompt Sıkıştırma Teknikleri

Uzun dökümanlarla çalışırken tüm içeriği göndermek yerine sadece ilgili bölümleri göndermek büyük tasarruf sağlar.

python3 << 'EOF'
from openai import OpenAI
import tiktoken

client = OpenAI()
enc = tiktoken.encoding_for_model("gpt-4o")

def ilgili_parcalari_sec(dokuman, soru, parca_boyutu=200, top_k=3):
    """
    Basit keyword-based relevance scoring.
    Production'da embedding similarity kullanılması önerilir.
    """
    kelimeler = soru.lower().split()
    parcalar = []
    
    # Dökümanı parçalara böl
    satirlar = dokuman.split('n')
    parca = []
    
    for satir in satirlar:
        parca.append(satir)
        if len(' '.join(parca).split()) >= parca_boyutu:
            parcalar.append(' '.join(parca))
            parca = []
    
    if parca:
        parcalar.append(' '.join(parca))
    
    # Her parçayı skorla
    skorlu_parcalar = []
    for p in parcalar:
        skor = sum(1 for k in kelimeler if k in p.lower())
        skorlu_parcalar.append((skor, p))
    
    # En ilgili parçaları döndür
    skorlu_parcalar.sort(reverse=True)
    return [p for _, p in skorlu_parcalar[:top_k]]

# Örnek
uzun_dokuman = """
Linux sistem yönetimi birçok farklı konuyu kapsar...
Disk yönetimi için fdisk, parted araçları kullanılır...
Network konfigürasyonu için ip, ifconfig komutları mevcuttur...
Log analizi için journalctl, grep, awk sık kullanılır...
Process yönetimi ps, top, htop araçlarıyla yapılır...
Güvenlik duvarı yönetimi iptables veya firewalld ile sağlanır...
"""

soru = "disk yönetimi nasıl yapılır"
ilgili = ilgili_parcalari_sec(uzun_dokuman, soru)

tam_token = len(enc.encode(uzun_dokuman))
secili_token = sum(len(enc.encode(p)) for p in ilgili)

print(f"Tam döküman token: {tam_token}")
print(f"Seçili parçalar token: {secili_token}")
print(f"Tasarruf oranı: %{((tam_token - secili_token) / tam_token * 100):.1f}")
EOF

Maliyet İzleme Sistemi Kurma

Optimizasyon yapıyorsunuz ama etkisini ölçmüyorsanız, ne kadar tasarruf ettiğinizi bilemezsiniz. Basit bir maliyet izleme sistemi kurmak şarttır.

python3 << 'EOF'
from openai import OpenAI
from datetime import datetime
import json

client = OpenAI()

# GPT-4o fiyatlandırması (1M token başına USD)
FIYATLAR = {
    "gpt-4o": {"input": 2.50, "output": 10.00},
    "gpt-4o-mini": {"input": 0.15, "output": 0.60},
}

maliyet_log = []

def maliyet_hesapla(model, input_token, output_token):
    fiyat = FIYATLAR.get(model, FIYATLAR["gpt-4o"])
    input_maliyet = (input_token / 1_000_000) * fiyat["input"]
    output_maliyet = (output_token / 1_000_000) * fiyat["output"]
    return input_maliyet + output_maliyet

def izlenen_istek(messages, model="gpt-4o-mini", **kwargs):
    yanit = client.chat.completions.create(
        model=model,
        messages=messages,
        **kwargs
    )
    
    input_tok = yanit.usage.prompt_tokens
    output_tok = yanit.usage.completion_tokens
    maliyet = maliyet_hesapla(model, input_tok, output_tok)
    
    kayit = {
        "zaman": datetime.now().isoformat(),
        "model": model,
        "input_token": input_tok,
        "output_token": output_tok,
        "maliyet_usd": round(maliyet, 6)
    }
    
    maliyet_log.append(kayit)
    print(f"[{model}] Input: {input_tok}, Output: {output_tok}, Maliyet: ${maliyet:.6f}")
    
    return yanit

# Test istekleri
izlenen_istek(
    [{"role": "user", "content": "2+2 kaçtır?"}],
    model="gpt-4o-mini",
    max_tokens=10
)

izlenen_istek(
    [{"role": "user", "content": "Kubernetes nedir?"}],
    model="gpt-4o-mini",
    max_tokens=100
)

# Özet rapor
toplam = sum(k["maliyet_usd"] for k in maliyet_log)
toplam_token = sum(k["input_token"] + k["output_token"] for k in maliyet_log)
print(f"nToplam istek: {len(maliyet_log)}")
print(f"Toplam token: {toplam_token}")
print(f"Toplam maliyet: ${toplam:.6f}")
print(f"Aylık tahmin (1000 istek/gün): ${toplam/len(maliyet_log)*30000:.2f}")
EOF

Gerçek Dünya Senaryosu: Müşteri Destek Botu Optimizasyonu

Bir e-ticaret şirketinin müşteri destek botunu optimize ettiğimizi düşünelim. Günde 5.000 istek, ortalama 800 token/istek ile başlıyorlar.

Önceki durum:

  • Günlük token: 5.000 x 800 = 4.000.000 token
  • GPT-4o ile aylık maliyet: yaklaşık 300 USD

Uygulanan optimizasyonlar:

  • Sistem promptu sadeleştirme: 400 token tasarruf/istek
  • Sliding window konuşma geçmişi: 150 token tasarruf/istek
  • GPT-4o-mini’ye geçiş (uygun sorgular için): %90 maliyet düşüşü o isteklerde
  • Redis cache (tekrarlayan sorular için %30 cache hit oranı): %30 istek azalması

Sonraki durum:

  • Günlük efektif token: 3.500 x 250 = 875.000 token
  • Karma model stratejisiyle aylık maliyet: yaklaşık 45 USD
  • Tasarruf: %85

Sonuç

OpenAI maliyetlerini düşürmek için tek bir sihirli çözüm yoktur. Bu bir sistem düşünce biçimidir; her katmanda küçük tasarruflar birleşerek büyük fark yaratır.

Öncelik sırasıyla şu adımları uygulayın:

  • Ölçün: tiktoken ile her isteğin kaç token harcadığını bilin
  • Sistem promptunu sadeleştirin: Her kelimeyi sorgulayın, gereksizi atın
  • max_tokens kullanın: Görev bazlı sıkı limitler koyun
  • Model seçimini akıllandırın: Basit görevler için mini model yeterli
  • Konuşma geçmişini yönetin: Tümünü gönderme, akıllıca kırp veya özetle
  • Cache kurun: Tekrarlayan istekler için Redis veya benzeri bir çözüm kullanın
  • İzleyin ve raporlayın: Maliyet trendlerini takip edin, anomalileri erken yakalayın

Son olarak şunu söylemek gerekir: Bu optimizasyonlar sadece maliyet meselesi değil. Daha az token demek, daha hızlı yanıt süresi demektir. Daha hızlı yanıt süresi, daha iyi kullanıcı deneyimi demektir. Yani bu çalışmayı yapmak, hem cebinize hem kullanıcılarınıza iyilik etmektir.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir