Üç hafta boyunca, ödeme onay mesajlarını işlem kayıtlarına dönüştüren bir boru hattında “Yapılandırılmış Çıktılar” özelliğini etkinleştirdikten sonra, tutma işimizin küçük ama istikrarlı bir oranda (yaklaşık %2 ila %3) eşleşmeyen işlemlere rastladığını fark ettim.
Bu hatalar kilitlenmelere veya bozuk satırlara neden olmuyordu. Sadece gönderici ve tutar değerleri eşleşen, ancak tarih bilgisi eksik olan işlemler söz konusuydu. İlk başta bunun bir zaman dilimi hatası olduğunu düşündüm, ancak durum böyle değildi.
Ham kaynak mesajlarını çıkarılan kayıtlarla karşılaştırdığımda, her yanlış eşleşmenin göndericisinin tarihten bahsetmeyen bir mesajdan geldiği ortaya çıktı. Örneğin: “Chinedu’dan ₦45.000 ödeme alındı, referans TXN-82K91.” Bu tür mesajlarda tarih bilgisi bulunmuyordu. Model ise işlem_tarihi alanını genellikle yürütme işleminin yapıldığı tarihe yakın bir değerle dolduruyordu.
Şema, transaction_date: date, required (zorunlu) olarak tanımlanmıştı. Model hiçbir değer döndüremezdi, bu nedenle boş bırakamazdı. Bu durum, “JSON geçerli” durumunun güvenilirliği sağladığı yanılgısına düşmeme konusunda bir uyarı niteliğindeydi.
Bu durum, daha sessiz türde hataların ortaya çıkabileceği bir döneme işaret ediyordu; bu hatalar ne kilitlenmelere yol açıyor, ne de tür denetimlerini geçemiyordu ve yalnızca aşağı yöndeki bağımlılıklar bu değerin gerçekliğini varsaydığında ortaya çıkıyordu.
Çoğu zaman, “Yapılandırılmış Çıktılar” hakkında yazılanlarda, “bu artık bozuk JSON döndürmüyor” ifadesi kullanılırken, asıl sorun daha derin bir sorundu. Bu durum, güvenilirliğin sadece sözdizimsel doğruluğun ötesinde olduğunu gösteriyordu.
“Yapılandırılmış Çıktılar”, gerçek bir sorunu çözüyordu: Yerel şema zorlaması olmadan, LLM‘den güvenilir JSON elde etmek, regex ayrıştırıcıları, yeniden deneme döngüleri ve modelin neredeyse yalartıldığı komutlar gerektiriyordu: “SADECE JSON döndürün, markdown değil, ön ek yok.”
Modern OpenAI Python SDK’sı ve Pydantic modeli ile bu sorunların çoğu ortadan kalkıyor. Temiz bir mesajla çalıştığınızda, her şey beklendiği gibi çalışır: Tüm anahtarlar mevcut, tüm türler doğrudur, ayrıştırıcıyı bozabilecek herhangi bir işaretleyici bulunmaz.
Ancak daha sonra size şöyle bir mesaj gönderilir: Bu durumda şema tarihin eksikliğine rağmen gereklilik olarak tanımlanmıştır. Bu nedenle bir değerin doldurulması gerekir ve bu durum şemanın esnekliğiyle ilgili değildir. Model, geçerli bir değere ulaşmak için her zaman bir yol bulur.
Geriye dönen şey tür açısından doğrudur. Ancak aynı zamanda tamamen uydurmadır ve yanıt içinde hangi alanların hangisi olduğunu anlamak mümkün değildir.
Çözüm, kod değişikliğinden ziyade zihinsel bir değişim gerektirir: Boş bir alan hata değil, genellikle gerçeğin ifadesidir. Alanları isteğe bağlı hale getirmek, modelin bir şey uydurmasını engeller:
Şimdi tarih eksikse, model bunu söyleyebilir. Bu aynı zamanda çıkarım ile çıkarma arasındaki ayrımı da gösterir. Çıkarım, “metinde ne olduğunu tam olarak belirtin” anlamına gelirken, çıkarım “bu metnin ima ettiğini belirtin” anlamına gelir. Örneğin, “ödeme salı günü yapıldı” ifadesi ve ISO tarih formatını isteyen bir şema, bu durum çıkarıma örnektir.
Bazen çıkarım tam da istediğiniz şeydir, ancak karar sizin olmalıdır, modelin sizin için otomatik olarak yaptığı bir şey olmamalıdır. İsteğe bağlı alanlar, bu kararı kendi kodunuza bırakır:
İsteğe bağlı alanlar, “hiçbir şeyden değer uydurma” sorununu çözer. Ancak başka bir sorunu da çözmez; bu daha kötüdür: model size bir değer verir ve bu değerin gerçekten metinden okunduğunu mu yoksa kalıpla eşleştirilerek mi elde edildiğini anlamanın bir yolu yoktur.
Normal bir sohbet yanıtında, modelin nasıl sonuç çıkardığını izleyebilirsiniz. “Yapılandırılmış Çıktılar” doğrudan son forma geçer. Bu nedenle her değer için ikinci bir alan istemeye başladım: bu değerin arkasındaki kaynak metnin tam bölümü.
Genel Wrapper kısayoldur, en iyi uygulama değildir. value artık temiz bir float yerine birleşim türüdür, bu orijinal şemanın sahip olduğu bazı tür güvenliğini kaybettirir.
Bu takas, birkaç farklı alan türüne sahip bir şema olduğunda geçerlidir; ayrı Ayrı ExtractedFloat, ExtractedDate, ExtractedString yazmak o noktada sadece boş işlerdir. Bir veya iki alan için, özel sınıfları kullanın; bunlar genellikle daha temizdir.
Bu yaklaşım iki açıdan da değerlidir. Değerin belirtilmesi, anahtarın sırayla üretildiği için modelin bir yanıt vermeden önce neye baktığını göstermesi gerektiği anlamına gelir, bu küçük bir “işinizi gösterin” zorunluluğudur.
Ayrıca, incelemeci için somut bir şey sunar; kaynakta olmayan metni içeren veya kaynağın hiçbir yerinde bulunmayan bir metin içeriyorsa, bu eşleşme halüsinasyonun kendisidir.
Ancak bununla çözülmeyen başka sorunlar da vardır; bunlar tarihin eksikliğinden kaynaklanmaz. Örneğin, değerin dünya gerçekliğine uygun olup olmadığıdır.
Şema miktarın bir float olması gerektiğini garanti eder. Ancak bu float’ın negatif olup olmadığı veya işlem_tarihinin gelecekteki bir tarih olup olmadığı gibi konuları ele almaz.
İlk başta, “miktarın sıfırdan büyük olması gerekir” gibi talimatlarla bunu komutta çözmeye çalıştım; bu, bir dil modelinden hesap makinesi gibi davranmasını istemek için tuhaf bir yaklaşımdır. Bir doğrulayıcı bunu her zaman doğru şekilde ve ücretsiz olarak yapar:
Şimdi API, yanıt oluşturulduğu anda yapıyı garanti ediyor ve Pydantic verilerin anlamlı olup olmadığını ayrıştırıldığında garanti ediyor; bu, LLM’nin olmadığı aynı şekilde yapılır.
Doğrulayıcı hata verdiğinde, seçenekleriniz vardır: kaydı bir insana yönlendirin veya hatayı modele geri gönderin ve tekrar denemesini sağlayın. İkinci seçeneği tercih ettim ve maksimum iki kez deneme sınırını belirledim:
MAX_RETRIES sınırı aslında görünenden daha önemlidir. İlk tepkim, bunun yeniden denenmesiydi; bu bir hatadır. Başarısız iki girişim neredeyse her zaman kaynak belgenin gerçek sorun olduğu anlamına gelir, komut değil ve üçüncü otomatik geçiş, bir insanın on saniyede düzeltebileceği bir şey için API çağrılarını boşa harcar.
Bu, OpenAI’ye özgü değildir. Her kod bloğu burada olmasına rağmen, bunu Anthropic’in araç kullanımına veya vLLM ve Outlines ile kendi kendine barındırılan bir kurulumuna dönüştürebilirsiniz; Pydantic modeli değişmez, sadece etrafındaki API çağrısı değişir.
Bu işe yaradığında, başarının tanımı oldukça düşüktü: model ayrıştırıcıyı bozmadan nesneyi doldurabiliyor muydu? Bu, ödüllendirdiği doğru şey değildir; çünkü her şeyi dolduran bir model güvenilir değil, sadece kendinden emin.
“Yapılandırılmış Çıktılar” gerçekten ne yaptığını yapar. Sadece “bozuk JSON döndürmez” ifadesinin ötesinde bir sorunu çözer. Bu, sadece sözdizimsel doğruluğun ötesinde güvenilirliğin ne anlama geldiği konusunda bir uyarı niteliğindedir.
Doğru tanımlarla, hatalarınızın nedenini anlamak daha kolaydır ve bu da daha iyi çözümler bulmanıza yardımcı olur. Bu, yapay zeka ile çalışırken karşılaşılabilecek zorlukların sadece bir örneğidir.
Doğru tanımlarla, hatalarınızın nedenini anlamak daha kolaydır ve bu da daha iyi çözümler bulmanıza yardımcı olur. Bu, yapay zeka ile çalışırken karşılaşılabilecek zorlukların sadece bir örneğidir.
Bu Gelişmeyi Toplulukta Değerlendirin
Haber hakkındaki düşüncelerinizi, donanım deneyimlerinizi ve teknik sorularınızı topluluk üyeleriyle anlık olarak tartışın.
Bir yanıt yazın