Ana içeriğe geç

Orta8 dk

Yapılandırılmış çıktı alma (JSON, şema)

Önkoşul:Düşünce zinciri (chain-of-thought) ve sınırları

Kanca

Bir modelden “kullanıcının adını ve yaşını JSON olarak ver” istedin. Cevap geldi. Şimdi bunu bir PROGRAMDA nasıl kullanacaksın? Cevap “neredeyse doğru” bir JSON’sa, kodun ÇÖKER.

Sezgi

Serbest metin, İNSAN için okunabilir ama PROGRAM için GÜVENİLMEZDİR. Yapılandırılmış çıktı (JSON, şema), 3. dersteki “FORMAT” parçasının en KESİN biçimidir — ve GERÇEK modellerin JSON isterken yaptığı YAYGIN hatalar VARDIR.

Mekanizma

Dört GERÇEKÇİ model çıktısını, GERÇEK bir Python json.loads() ile ayrıştırmayı deneyelim:

4 örnekten sadece 1'i doğrudan ayrıştırılabiliyor

# Bir modelden "kullanıcının adını ve yaşını JSON olarak ver" istediğinde, GERÇEKTE
# aşağıdaki gibi DÖRT farklı (ama hepsi YAYGIN) biçimde cevap alabilirsin. ÜÇÜ,
# doğrudan json.loads() ile AYRIŞTIRILAMAZ -- bunlar GERÇEK Python hatalarıdır.
ORNEK_CIKTILAR = [
    '{"isim": "Ayşe", "yas": 29}',
    '```json\n{"isim": "Ayşe", "yas": 29}\n```',
    'Elbette, işte istediğiniz JSON: {"isim": "Ayşe", "yas": 29}',
    '{"isim": "Ayşe", "yas": 29,}',
]
ACIKLAMALAR = ["Temiz JSON", "Markdown kod bloğuna sarılmış", "Ekstra açıklama metniyle", "Sondaki virgül (trailing comma) ile"]

print(f"{'#':<3} {'Açıklama':<38} {'Doğrudan json.loads() sonucu'}")
basarili_sayisi = 0
for i, (cikti, aciklama) in enumerate(zip(ORNEK_CIKTILAR, ACIKLAMALAR)):
    try:
        json.loads(cikti)
        sonuc = "BAŞARILI"
        basarili_sayisi += 1
    except json.JSONDecodeError as e:
        sonuc = f"BAŞARISIZ ({e})"
    print(f"{i:<3} {aciklama:<38} {sonuc}")
print(f"\nDoğrudan ayrıştırma: {basarili_sayisi}/{len(ORNEK_CIKTILAR)} başarılı.")
print("Modeller SIKÇA JSON'ı markdown bloğuna sarar, öncesine/sonrasına AÇIKLAMA ekler,")
print("veya son elemandan sonra fazladan bir virgül BIRAKIR -- HEPSİ, saf json.loads() için ÖLÜMCÜLDÜR.")
#   Açıklama                               Doğrudan json.loads() sonucu
0   Temiz JSON                             BAŞARILI
1   Markdown kod bloğuna sarılmış          BAŞARISIZ (Expecting value: line 1 column 1 (char 0))
2   Ekstra açıklama metniyle               BAŞARISIZ (Expecting value: line 1 column 1 (char 0))
3   Sondaki virgül (trailing comma) ile    BAŞARISIZ (Expecting property name enclosed in double quotes: line 1 column 28 (char 27))

Doğrudan ayrıştırma: 1/4 başarılı.
Modeller SIKÇA JSON'ı markdown bloğuna sarar, öncesine/sonrasına AÇIKLAMA ekler,
veya son elemandan sonra fazladan bir virgül BIRAKIR -- HEPSİ, saf json.loads() için ÖLÜMCÜLDÜR.

Matematik

Kısıtlı örnekleme (constrained decoding)
  1. dersteki (NLP kategorisi) top-k/top-p filtrelemesini hatırla: bir sonraki token’ın olasılık dağılımından, GEÇERSİZ adayları ELEMEK mümkündü. Bazı API’ler “JSON modu” sunar — bu, HER adımda SADECE geçerli JSON grameriyle DEVAM EDEBİLECEK token’lara izin vererek, modelin GEÇERSİZ bir çıktı üretmesini MATEMATİKSEL olarak İMKANSIZ hâle getirir.

Kod

Ayrıştırma sorununu SONRADAN çözen sağlam (robust) bir fonksiyon yazalım:

Sağlam ayrıştırıcı: 1/4'ten 4/4'e

# Bu YAYGIN sorunları önceden temizleyen bir "sağlam" (robust) ayrıştırıcı yazalım.
def saglam_ayristir(metin):
    temiz = re.sub(r"```(json)?", "", metin).strip()
    eslesme = re.search(r"(\{.*\}|\[.*\])", temiz, re.DOTALL)
    if eslesme:
        temiz = eslesme.group(1)
    temiz = re.sub(r",\s*([}\]])", r"\1", temiz)  # sondaki virgülü temizle
    return json.loads(temiz)

print(f"\n{'#':<3} {'Açıklama':<38} {'Sağlam ayrıştırıcı sonucu'}")
basarili_sayisi_2 = 0
for i, (cikti, aciklama) in enumerate(zip(ORNEK_CIKTILAR, ACIKLAMALAR)):
    try:
        sonuc_json = saglam_ayristir(cikti)
        sonuc = f"BAŞARILI -> {sonuc_json}"
        basarili_sayisi_2 += 1
    except json.JSONDecodeError as e:
        sonuc = f"BAŞARISIZ ({e})"
    print(f"{i:<3} {aciklama:<38} {sonuc}")
print(f"\nSağlam ayrıştırma: {basarili_sayisi_2}/{len(ORNEK_CIKTILAR)} başarılı -- {basarili_sayisi}'den {basarili_sayisi_2}'ye çıktı.")

#   Açıklama                               Sağlam ayrıştırıcı sonucu
0   Temiz JSON                             BAŞARILI -> {'isim': 'Ayşe', 'yas': 29}
1   Markdown kod bloğuna sarılmış          BAŞARILI -> {'isim': 'Ayşe', 'yas': 29}
2   Ekstra açıklama metniyle               BAŞARILI -> {'isim': 'Ayşe', 'yas': 29}
3   Sondaki virgül (trailing comma) ile    BAŞARILI -> {'isim': 'Ayşe', 'yas': 29}

Sağlam ayrıştırma: 4/4 başarılı -- 1'den 4'ye çıktı.

En iyisi ise, sorunu EN BAŞTAN önlemek — net bir şema istemek:

Şema, formatı baştan netleştirir

# En İYİ çözüm: ayrıştırma sorununu SONRADAN düzeltmek yerine, modelden İSTENEN
# formatı baştan NET bir şema ile belirtmek (3. dersteki "FORMAT" parçası).
BELIRSIZ_ISTEM = "Tell me about a user named Ayşe who is 29 years old."
SEMALI_ISTEM = (
    'Return ONLY valid JSON matching this schema, with no other text: '
    '{"isim": string, "yas": integer}. User: Ayşe, 29 years old.'
)
print(f"\nBelirsiz istem: \"{BELIRSIZ_ISTEM}\"")
print(f"Şemalı istem:   \"{SEMALI_ISTEM}\"")
print("\nŞemalı istem, modele TAM OLARAK hangi alanları, hangi TİPTE beklediğini söylüyor --")
print("bu, 3. dersteki FORMAT parçasının en KESİN biçimidir.")

Belirsiz istem: "Tell me about a user named Ayşe who is 29 years old."
Şemalı istem:   "Return ONLY valid JSON matching this schema, with no other text: {"isim": string, "yas": integer}. User: Ayşe, 29 years old."

Şemalı istem, modele TAM OLARAK hangi alanları, hangi TİPTE beklediğini söylüyor --
bu, 3. dersteki FORMAT parçasının en KESİN biçimidir.
Doğrudan json.loads() ile sağlam ayrıştırıcının başarı sayısını karşılaştıran çubuk grafik; sağlam ayrıştırıcı 4/4, doğrudan yöntem 1/4.
Doğrudan json.loads(), 4 gerçekçi örnekten sadece 1'ini ayrıştırabiliyor. Sağlam bir ayrıştırıcı (markdown/virgül temizleme), hepsini başarıyla işliyor.

Nerede işe yarar

Yapılandırılmış çıktı almanın pratikte İKİ tamamlayıcı stratejisi var:

  • ÖNLEYİCİ: istemde AÇIKÇA bir şema belirt (“SADECE bu JSON’ı döndür, başka METİN ekleme”) — 3. dersteki FORMAT parçasının en NET uygulaması.
  • DÜZELTİCİ: modelin çıktısını, YAYGIN hataları (markdown sarma, ekstra metin, sondaki virgül) TEMİZLEYEN sağlam bir ayrıştırıcıdan GEÇİR — ÖNLEYİCİ strateji her zaman %100 çalışmaz.
  • DOĞRULAMA + YENİDEN DENEME: ayrıştırma BAŞARISIZ olursa, hatayı modele geri BİLDİR ve TEKRAR sormak yaygın bir üretim (production) tekniğidir.

Bu 2 hatayı yaparsın:

  1. Modelin “JSON döndür” dediğinde HER ZAMAN saf, temiz JSON döndüreceğini VARSAYMAK — bu dersteki 4 örnekten 3’ü bunu ÇÜRÜTÜYOR.
  2. Sağlam bir ayrıştırıcıyı GEREKSİZ bulup atlamak — üretim ortamında (production), TEK bir ayrıştırma hatası bile TÜM bir işlem hattını ÇÖKERTEBİLİR.

Kendini test et

1. Notebook'ta 4 gerçekçi model çıktısından SADECE 1'i doğrudan json.loads() ile ayrıştırılabiliyor. Diğer 3'ünün başarısız olmasının nedenleri nelerdir?
  1. Hepsi aynı nedenle başarısız oluyor
  2. Markdown kod bloğuna sarılmış olması, öncesine/sonrasına açıklama metni eklenmiş olması, ve JSON'ın sonunda fazladan bir virgül (trailing comma) bırakılması -- ÜÇÜ de modellerin GERÇEKTEN yaptığı yaygın hatalardır (doğru cevap)
  3. Türkçe karakterler JSON formatını bozar
  4. json.loads() fonksiyonu hatalıdır

Neden: dogrudan-ayristirma-basarisiz bloğunda ÜÇ farklı GERÇEK Python hatası (JSONDecodeError) görülüyor -- her biri farklı bir yaygın formatlama sorununa karşılık geliyor.

2. Sağlam (robust) ayrıştırıcı, başarı oranını 1/4'ten 4/4'e NASIL çıkarıyor?
  1. Modelin kendisini değiştirerek
  2. json.loads() çağrılmadan ÖNCE, markdown kod bloğu işaretlerini kaldırarak, metindeki İLK { } veya [ ] bloğunu bularak, ve sondaki fazladan virgülleri temizleyerek -- yani AYRIŞTIRMA ÖNCESİ bir 'temizlik' adımı ekleyerek (doğru cevap)
  3. Rastgele deneme yaparak
  4. Hataları görmezden gelerek

Neden: saglam-ayristirici bloğundaki saglam_ayristir() fonksiyonu üç ADIMDA (markdown temizleme, JSON alt-dizisini bulma, virgül temizleme) metni json.loads()'a GEÇERLİ hale getiriyor.

3. 'Kısıtlı örnekleme' (constrained decoding / JSON modu) NASIL çalışır?
  1. Model çıktısını üretimden sonra düzeltir
  2. NLP kategorisinin 22. dersindeki top-k/top-p filtrelemesine BENZER şekilde, HER üretim adımında SADECE geçerli JSON grameriyle uyumlu token'lara İZİN VERİR -- bu da geçersiz bir çıktının üretilmesini baştan İMKANSIZ kılar (doğru cevap)
  3. Modelin eğitim verisini JSON ile sınırlar
  4. Sadece İngilizce JSON üretebilir

Neden: MathBox'ta bu AÇIKÇA NLP kategorisinin 22. dersindeki top-k/top-p filtrelemesiyle KARŞILAŞTIRILIYOR -- ikisi de olasılık dağılımından GEÇERSİZ adayları eleme fikrine dayanıyor.

Özet

Özet

  • Serbest metin çıktısı, program tarafından GÜVENİLİR şekilde ayrıştırılamayabilir.
  • 4 gerçekçi örnekten SADECE 1'i doğrudan json.loads() ile ayrıştırılabildi -- diğerleri markdown sarma, ekstra metin, veya sondaki virgül YÜZÜNDEN başarısız oldu.
  • Sağlam bir ayrıştırıcı (temizleme adımlarıyla), başarı oranını 4/4'e çıkardı.
  • En iyi strateji ÖNLEYİCİDİR: istemde net bir şema belirtmek (3. dersteki FORMAT parçası).
  • Bazı API'ler, NLP kategorisinin 22. dersindeki top-k/top-p filtrelemesine benzer 'kısıtlı örnekleme' ile geçersiz JSON üretimini baştan engeller.
Sonraki adım: Prompt'u iyileştirme döngüsü: ölç, değiştir, karşılaştır →