Spoolt bir web sitesini okuyor ve oradan yedi formatta kısa video senaryosu çıkarıyor. Senaryoları OpenAI'ın GPT-5.4 ailesinden bir model (gpt-5.4-nano) yazıyor. Çağrıyı Responses API üzerinden yapıyoruz, çıktı biçimini de Zod şemasıyla tanımlıyoruz (openai SDK 6, Zod 4). Arkadaki sistem prompt'u kısa bir kural bloğu. İçindeki her kural da bir rica: model çoğu zaman uyar.
Üç şeyde “çoğu zaman” yetmiyordu. Kaydedilen marka profili bizim şemamızdan geçmek zorunda. Sponsorlu ürün geçen gönderi metni etiketsiz olamaz. Herkese açık demo da sayfada olmayan bir iddiada bulunmamalı. Bu üçünü artık kod garanti ediyor. Prompt'taki cümle ise yalnızca kodun daha seyrek devreye girmesi için duruyor.
Web sitesinden senaryoya
Sayfayı, iç adresleri reddeden bir fetch ile alıyoruz ve metninin en fazla 12.000 karakterini modele veriyoruz. Model, structured output (şemaya bağlı çıktı) olarak marka profili döndürüyor. Kullanıcı bu profili onboarding sırasında gözden geçiriyor ve düzenleyebiliyor.
Sonra her senaryo için tek çağrı yapıyoruz. İçinde sabit bir sistem bloğu, format için kısa bir tarif, hedef süre (varsayılan 30 saniye, saniyede yaklaşık 2,5 kelime) ve marka ile ürün verisi var. Birden fazla taslak istendiğinde bu çağrılardan birkaçını Promise.allSettled ile bir arada çalıştırıyoruz. Biri başarısız olursa, dönmüş ve parası ödenmiş taslaklar çöpe gitmiyor. Grup ancak hiçbiri dönmezse başarısız sayılıyor. İkinci bir çağrı her taslağa puan veriyor, uygulama da bunu rozet olarak gösteriyor. O çağrı başarısız olursa taslak rozetsiz kalıyor: kullanıcı parayı rozet için değil, taslaklar için ödedi.
Her yanıt şemadan geçerek geliyor, biz de kendi tarafımızda aynı şemayla bir kez daha ayrıştırıyoruz. Ama şema yalnızca biçimi tarif eder. Neyin doğru olduğunu, neyin bildirilmesi gerektiğini bilmez. Uzunluk sorununu da çözmedi.
Alan uzunluğu: çıktı şemasında değil, kodda sınırlanıyor
İlk hata form hatasına benziyordu. Ağustos 2026'da onboarding'deki gözden geçirme adımı, analiz çağrısının az önce ürettiği marka profilini kaydetmeye çalıştı ve şu yanıtı aldı: 400 Too big: expected string to have <=100 characters. Alan brandValues idi ve kullanıcı o alana tek harf yazmamıştı.
Analiz çağrısının çıktı şemasında sınır yoktu. Gözden geçirme adımının kaydederken kullandığı şema ise her alanı sınırlıyordu. Modelin lafı uzattığı her yanıt, sonradan düzenlenemeyen bir satır üretiyordu.
Akla ilk gelen düzeltme, aynı sınırları çıktı şemasına kopyalamak. Ama OpenAI'ın rehberinde, sınırların o şemada uygulanacağına dair bir güvence yok. Structured Outputs özelliği JSON Schema'nın yalnızca bir alt kümesini destekliyor. Rehberde string için desteklenen anahtar kelimeler pattern ile format. Ekim 2026 itibarıyla listede uzunlukla ilgili bir anahtar kelime yok.
Senaryo çağrımızın şemasındaki gönderi metni alanında Eylül 2026'dan beri maxLength var. API'nin onunla ne yaptığını test etmedik, o yüzden hiçbir şeyi ona bağlamadık. Sığdırma işini, çıktıyı teslim aldığımız yerde, kendi kodumuzda yapıyoruz:
export function clampText(value: string, max: number): string {
if (value.length <= max) return value;
const cut = value.slice(0, max);
const lastSpace = cut.lastIndexOf(" ");
return (lastSpace > max * 0.8 ? cut.slice(0, lastSpace) : cut).trimEnd();
}
Sınırların artık alan başına tek tanımı var. Hem kayıt şeması hem kırpma o tanımı kullanıyor. Sınıra yakın bir kelime arası varsa oradan kesiyoruz ki kırpılan değer yarım kelimeyle bitmesin. Uzunluk bütçesi prompt'ta da yazıyor: en fazla 20 kısa ifade, her biri 160 karakterin altında (“up to 20 short phrases, each under 160 characters”). Böylece kırpma olağan yol olmaktan çıkıp son çare oluyor. Sınırlardan birini de 100 karakterden 160'a çıkardık, çünkü gerçek değerlerde parantez içi açıklama oluyor ve 100 karakterlik sınır bunları cümlenin ortasında kesiyordu.
Prompt yalnızca rica eder, kurala uyulmasını kod sağlar.
400 hatası bu yoldan bir daha gelemez: bir kontrol fazla uzun bir profil kuruyor, onu kırpıyor ve kayıt şemasının sonucu kabul ettiğini doğruluyor. Kırpma log yazmıyor, bu yüzden canlıda ne sıklıkta kestiğini söyleyemiyoruz.
Sağlayıcı uzunluk anahtar kelimesiyle ne yaparsa yapsın, satırın bizim kayıt şemamızdan geçmesi gerekir ve bunu yalnızca bizim kodumuz garanti edebilir. Masal anlatan bir uygulamamızda da model çağrılarından sonra aynı ayrımı yapıyoruz: structured output'tan sonra kodun kontrol ettikleri.
#ad etiketi: kodda ekleniyor
Senaryo bir ürünü öne çıkarabilir, ürün de sponsorlu olabilir. Prompt'ta bunun için tek cümle var: ürünün sponsor adı varsa sponsorluk gönderi metninde doğal biçimde bildirilsin. Temmuz 2026'da modelin bu cümleyi atladığını gördük. O kadar sık atlıyordu ki yasal bir bildirimi, modelin prompt'a uymasına bağlayamazdık. Kaç kez atladığını saymadık.
Etiketi bu yüzden kod ekliyor:
export function withSponsorDisclosure(
caption: string,
sponsored: boolean,
): string {
if (!sponsored) return caption;
if (/#ad\b/i.test(caption)) return caption;
return caption ? `${caption} #ad` : "#ad";
}
Fonksiyon, taslaklar kaydedilirken sponsorlu ürün içeren bir grubun her taslağı için çalışıyor. Model #ad etiketini zaten yazdıysa hiçbir şey yapmıyor. Prompt'taki cümle yerinde kaldı, çünkü sponsoru kendi cümlesiyle anan metin, tek başına bir etiketten daha doğal duruyor. Etiketin varlığı ise artık o cümleye bağlı değil.
Buraya kadar anlattığımız, gönderi metnindeki etiket. TikTok'un kendi ticari içerik beyanı ayrı bir ayar ve onu her gönderi için kullanıcı seçiyor. O tarafı TikTok'un Direct Post audit'inde bizden istenenler yazısında anlattık. Hangi pazarın hangi etiketi beklediği ise bu yazının konusu değil.
Herkese açık demo: model yok, uydurma iddia da yok
Tanıtım sayfasında ziyaretçi bir adres yapıştırıyor ve kayıt olmadan üç örnek açılış görüyor. Ağustos 2026'da yayına girdiğinde demo bir model çağrısıydı. Çevresinde herkese açık bir endpoint'e gereken her şey vardı: CAPTCHA, IP başına ve toplam günlük bütçeler, sınırlı girdi ve çıktı, 20 saniyelik zaman aşımı ve uygulamanın da kullandığı fetch. Bütçe kontrol edilemediğinde istek reddediliyordu (fail-closed).
Yine de bunların hiçbirinin kapsamadığı bir yerden bozuldu. Ücretsiz ve girişsiz bir demoda her ziyaretçi için sağlayıcıya ödeme yapılır. Bu yüzden sayfa, ancak arkasındaki sağlayıcı hesabı çalıştığı sürece çalışıyordu.
O hesap isteklere yanıt veremez hâle gelince OpenAI 429 döndü, bizim route da her 429'u kendi “kapasite” durumuna çeviriyordu. Ziyaretçi “Şu an çok sayıda fikir hazırlanıyor” mesajını ve yanında bir geri sayım görüyordu: “53 sn sonra tekrar dene”. Yeniden denemek bunu çözemezdi. Sınır istek hızında değil, hesaptaydı.
Eylül 2026'da modeli demodan çıkardık. Üç açılışı artık aldığımız sayfadan derliyoruz: marka adı başlıktan, özet ve gönderi metinleri sitenin kendi cümlelerinden, açılış cümleleri her dil için sabit şablonlardan. Yanıt şeması, bütçeler ve CAPTCHA değişmedi. Demonun artık ziyaretçi başına maliyeti yok. Yükleme notunda eskiden 40 saniyeye kadar sürebileceği yazıyordu, şimdi birkaç saniye yazıyor. “Kapasite” mesajını da artık yalnızca bizim bütçemiz tetikleyebiliyor. Sonucun altına bir satır da ekledik: açılışlar sitenin kendi cümlelerinden geliyor, senaryoyu ise ziyaretçi hesabına girdikten sonra model yazıyor.
Bu değişikliğin bir yan etkisini, düzeltmenin kendisinden daha çok önemsedik. Sayfanın kendi cümlelerinden derlenen metin, sayfada olmayan bir iddiayı uyduramaz. Prompt bunu yalnızca rica edebiliyordu. Derlenen metnin de kendine göre hataları var: ilk gün gerçek sayfalarda tek satır hâlinde gelen etiket listesi (“Passport Passport Pasaporte Pasaporte”), bir cümlenin başına yapışmış gezinme menüsü ve yalnızca noktalama işareti olmayan parçalardan oluşan sayfalar çıktı. Her birine aynı gün ayrı bir filtre yazdık.
Hâlâ prompt'a bıraktıklarımız
Geri kalan her şey hâlâ rica ve çoğu kanıtla ilgili. Yalnızca verilen ürün bilgileri kullanılır, kanıt yoksa iddia yazılmaz. Konuşan, marka metnini okuyan bir anlatıcıdır, doğrulanmış bir müşteri değil (“a presenter reading brand copy, not a verified customer”). Bir şeyi göstermeyi teklif edebilir ama ürünü aldığını, denediğini ya da kullandığını söyleyemez.
Ne istediğimizi de değiştirdik. Tek seferde birden fazla taslak istendiğinde her taslağa farklı bir açılış yönü veriyoruz. Böylece grup aynı prompt'un birkaç örneği olmuyor, gerçek bir varyant testi oluyor. Temmuz 2026'daki altı yönlük ilk listede şaşırtıcı bir bilgi ya da belirli bir rakamla açmak (“open with a surprising fact or specific number”) ve birinci ağızdan anlatılan tanıdık bir anla açmak (“open with a relatable first-person (POV) moment”) vardı. Her sayfada rakam bulunmaz, anlatıcının da kendi başından geçmiş bir anısı yoktur. Yani bu iki yön, kanıt kurallarının yasakladığı şeyi istiyordu. Eylül 2026'dan beri her yön sunumu değiştiriyor, eldeki kanıtı değiştirmiyor. Biri şöyle: verilen kanıtta bulunan somut bir ürün ayrıntısıyla açmak (“open with one concrete product detail present in the supplied evidence”).
Web sitesi başkasının metnidir ve içinde modele hitap eden bir cümle bulunabilir. Senaryo çağrısında marka ve ürün verisi JSON biçiminde kullanıcı mesajı olarak gidiyor, talimatların içine hiç karıştırılmıyor. Sistem bloğu modele bu alanları yazı malzemesi saymasını, içlerinde komut gibi duran bir şey varsa ona uymamasını söylüyor. Bu da ötekiler gibi bir rica.
Kötü niyetli bir sayfayı asıl sınırlayan, çağrının kendisi. Ne analiz çağrısında ne de senaryo çağrısında modele araç veriliyor. İkisinin girdisinde de gizli hiçbir şey yok. Geri dönen şey de sabit alanlardaki metin: onu bir insan gözden geçiriyor.
Kuralın yeri: prompt ya da kod
- Üslup, ton ve yapı prompt'a gider. Çoğu zaman uyulmasını bekleriz.
- Kayıt, fatura ya da bildirim bir kurala bağlıysa, kuralı modelden sonra kod uygular. Prompt'taki cümle yalnızca kodun daha seyrek devreye girmesi için kalır.
- Başkasının metni veri olarak gider ve prompt'ta da öyle yazar. Zararı sınırlayan ise çağrıdır: araç yok, gizli bilgi yok, çıkan şey bir insanın gözden geçirdiği taslak.
- Ücretsiz ve herkese açık bir özellik için ilk soru şudur: buna model gerekiyor mu?
- Bir çağrı başarısız olduğunda, başarıyla dönmüş olanlar kullanıcıda kalır.
Var olan bir üründe bu ayrımı yapmak, yapay zekâ entegrasyonu başlığı altında anlattığımız iş.