Yapay zekâ
Modeli çağırmak kolay, çıktısını yayınlamak zor
Kurallarla doğrulanan yapay zekâ içeriği
Podinfly'da yapay zekâ çıktısı Etsy'ye doğrudan gitmez: kural motorundan geçer, yalnız hatalı alan yeniden üretilir, geçemeyen içerik insana gider.
- Yazar
- Samet Kapucuoğlu
- Yayın
- dakikalık okuma
- 4 dakikalık okuma
Bir dil modeli saniyeler içinde akıcı bir Etsy başlığı yazar. Ama başlık 140 karakteri aşarsa Etsy onu kaydetmez; 13 yerine 12 etiketle gelen bir listing de bir etiket hakkını boş bırakır. Podinfly'da modelin yazdığını doğrudan yayınlamıyoruz: önce kural motorundan geçiriyoruz, yalnız kuralı çiğneyen alanı yeniden ürettiriyoruz ve hâlâ geçemeyen içeriği bir insana gösteriyoruz.
Bu yazı o hattın nasıl kurulduğunu, hangi kararları neden aldığımızı ve kodda nasıl göründüğünü anlatıyor.
Bir Etsy listing'inde neler var
Etsy'de satış, bir görsel yükleyip yayınla düğmesine basmakla bitmez. Her ürün için ayrı bir veri paketi hazırlanır: başlık, açıklama, etiketler, kategori, varyantlar, fiyat, ürün özellikleri ve satışa uygun görseller. Bu alanların her birinin kendi kuralı var.
Etsy'nin yardım merkezindeki sınırlar şöyle: başlık en fazla 140 karakter olabilir; bir listing'e en fazla 13 etiket eklenir ve her etiket en fazla 20 karakterdir. Etiketlerde harf, rakam ve boşluk kullanılır; kesme işareti ve kısa çizgi kelime içinde kullanılabilir ama etiket bunlarla başlayamaz.
Podinfly bu sınırların içinde kendi ürün kuralını uygular: başlık 60 ile 110 karakter arasında, etiket sayısı tam 13. Etsy'nin sınırı bir üst sınırdır; boş bırakılan her etiket aramada kullanılmayan bir alandır. Bu yüzden 13'ün altı da kuralı çiğnemek sayılır.
Sınırlar yalnız uzunlukla ilgili değil. Fiziksel bir ürünün listing'inde "digital", "printable" veya "svg" gibi kelimeler alıcıyı yanlış yönlendirir; bunlar dijital ürün kategorisinin kelimeleridir. Başka bir şirketin markası başlığa giremez. Arama hacmi olmayan bir marka adını başlığa yazmak da yer israfıdır.
Model neden tek başına yetmez
İstemde "tam 13 etiket, her biri en fazla 20 karakter" yazmak, çıktının buna uyacağını garanti etmez. Dil modelleri karakter saymakta iyi değildir. Kelimeleri saymazlar, parçalara bölünmüş metin üretirler. Yasaklı bir kelime, eğitim verisinde sık geçtiği için kolayca geri gelir. Aynı kelime hem başlıkta hem üç etikette tekrar eder.
Bu hataların ortak yanı, hepsinin makine tarafından kesin olarak denetlenebilir olması. "Başlık güzel mi" sorusunun cevabı tartışmalıdır; "başlık 110 karakteri aşıyor mu" sorusunun cevabı değildir. İkinci tür kuralları koda yazıyoruz.
Dört adımlık hat
- Şablonlu istem. Her üretim aynı şablondan geçer. Şablon ürün verisini, pazaryeri bağlamını ve beklenen çıktının şemasını taşır. Model, alanları önceden belli olan bir nesne döndürür.
- Kural motoru. Çıktı alan alan denetlenir: uzunluk, etiket sayısı, etiket karakterleri, yasaklı kelimeler, tekrarlar. Her ihlal hangi alanda olduğunu ve nedenini söyler.
- Sınırlı yeniden üretim. İhlal varsa yalnız o alan yeniden üretilir. İhlalin kendisi isteme geri yazılır: "etiket 7, 23 karakter; en fazla 20 olmalı". Deneme sayısı sınırlıdır.
- Güven kapısı. Denemeler bittiğinde hâlâ geçemeyen içerik yayın kuyruğuna girmez; bir insanın önüne gider.
Kural motoru kodda nasıl görünür
Aşağıdaki kod, kuralların nasıl tutulduğunu gösteren sadeleştirilmiş bir örnek. Podinfly'daki asıl kural seti daha uzun ve ürün grubuna göre değişiyor; yapı aynı.
type Listing = { title: string; tags: string[]; description: string };
type Violation = { field: keyof Listing; rule: string; detail: string };
const BANNED = ["digital", "printable", "svg", "mockup"];
const TAG_CHARS = /^[\p{L}\p{N}][\p{L}\p{N} '-]*$/u;
export function validate(listing: Listing): Violation[] {
const out: Violation[] = [];
const { title, tags } = listing;
if (title.length < 60 || title.length > 110) {
out.push({ field: "title", rule: "length", detail: `${title.length} karakter, 60-110 olmalı` });
}
if (tags.length !== 13) {
out.push({ field: "tags", rule: "count", detail: `${tags.length} etiket, tam 13 olmalı` });
}
tags.forEach((tag, i) => {
if (tag.length > 20) out.push({ field: "tags", rule: "tag-length", detail: `etiket ${i + 1}: ${tag.length} karakter` });
if (!TAG_CHARS.test(tag)) out.push({ field: "tags", rule: "tag-chars", detail: `etiket ${i + 1}: izinsiz karakter` });
});
const text = `${title} ${tags.join(" ")}`.toLowerCase();
for (const word of BANNED) {
if (text.includes(word)) out.push({ field: "title", rule: "banned-word", detail: word });
}
const seen = new Set<string>();
for (const tag of tags.map((t) => t.toLowerCase())) {
if (seen.has(tag)) out.push({ field: "tags", rule: "duplicate", detail: tag });
seen.add(tag);
}
return out;
}
Burada iki karar önemli. Birincisi, motor true ya da false döndürmüyor; ihlallerin listesini döndürüyor. Hangi alanın neden kaldığını bilmeden yalnız o alanı yeniden üretemezsiniz. İkincisi, kurallar veri olarak duruyor. Yeni bir ürün grubu geldiğinde motor değişmiyor, kural listesi değişiyor.
Yalnız hatalı alanı yeniden üretmek
İlk denemede başlık doğru, etiketlerden biri uzun olabilir. Bütün listing'i yeniden ürettirmek, doğru olan başlığı da riske atar: yeni başlık bu kez kısa kalabilir. Bu yüzden yeniden üretim alan düzeyinde çalışır.
async function generateListing(product: Product): Promise<Result> {
let draft = await model.generate(promptFor(product));
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
const violations = validate(draft);
if (violations.length === 0) return { status: "ready", listing: draft };
const fields = [...new Set(violations.map((v) => v.field))];
const patch = await model.regenerate(product, draft, fields, violations);
draft = { ...draft, ...patch };
}
const remaining = validate(draft);
if (remaining.length === 0) return { status: "ready", listing: draft };
return { status: "needs-review", listing: draft, violations: remaining };
}
Deneme sayısını sınırlamanın iki nedeni var. Birincisi maliyet: her çağrının bir bedeli var ve sonsuz döngü bütçeyi sessizce yer. İkincisi sinyal: birkaç denemede düzelmeyen bir alan, modelden çok ürün verisine işaret edebilir. Eksik bir ürün özelliği, tanımlanmamış bir varyant ya da anlamı belirsiz bir tasarım adı. Böyle bir alanı insana göstermek, bir deneme daha yapmaktan ucuzdur.
Güven kapısı
Kurala uymayan içerik yayına girmez. Ama kullanıcı bir listing'in neden beklediğini göremezse sistemin bozulduğunu düşünür.
Kural motorunun ihlal listesi döndürmesinin ikinci faydası burada çıkıyor. Bekleyen içerik, neden beklediğini söyleyen bilgiyle birlikte insana gider: hangi alan, hangi kural, ne kadar aşılmış. Düzeltilen içerik aynı motordan yeniden geçer.
Kredi, çıktı kaydedilince düşer
Doğrulama hattının bir de para tarafı var. Podinfly'da üretim kredi harcar ve kullanıcı yalnız işe yarayan çıktı için ödemeli. Bu yüzden sıra şöyle:
- Üretimden önce bakiye yeterli mi diye bakılır.
- Üretim yapılır, doğrulanır ve çıktı kaydedilir.
- Kredi bundan sonra, tekrar korumalı bir anahtarla düşülür.
- Kredi düşme adımı başarısız olursa üretilen çıktı silinir.
Kredi hareketleri yalnız ekleme yapılan bir deftere yazılır; bakiye bu defterden türetilir. Aynı istek iki kez gelirse, örneğin kullanıcı düğmeye iki kez bastığında, ikinci yazım veritabanındaki benzersiz anahtara çarpar ve hiç gerçekleşmez. Böylece "kredim düştü ama listing yok" ya da "aynı iş için iki kez ödedim" durumları yapı gereği oluşmaz.
Bu hattan çıkardıklarımız
Kuralı isteme yazdığınızda modelden bir şey rica etmiş olursunuz; koda yazdığınızda sonucu denetlersiniz. Biz ikisini de yapıyoruz, son kararı kod veriyor. Hata raporunu da alan düzeyinde tutuyoruz: hangi alan, hangi kuralı, ne kadar aştı.
Modelin çözemediği içerik bir insanın önüne gidiyor. Para da çıktıya bağlı: kullanıcı yalnız kullanabildiği sonuç için kredi harcıyor.
Bu hat bugün Podinfly'ın listing akışında çalışıyor. Ürünün geri kalanını, gerçek ekranlarıyla Podinfly proje sayfasında anlattık.
Yazar
Samet Kapucuoğlu
Kurucu ortak, BESK. Yazılım mimarisi ve çok kiracılı SaaS altyapısı.
Bu yazının projesi: Podinfly