Bir yapay zekâ modelinden destek kaydını özetlemesini istediğinizde sonuç bir paragraf olabilir. Aynı kaydı bir otomasyonda kullanmak içinse teslimat durumunun belirli değerlerden biri olması, yeni teslimat tarihinin teyit edilmediyse boş kalması ve özetin ayrı bir alanda dönmesi gerekir. Structured Outputs, uyumlu modellerden bu alanları bir JSON şemasına göre istemenizi sağlar. Bu rehberde bir Türkçe kargo kaydını OpenRouter üzerinden işleyip sonucu Python’da doğruluyoruz. [1][2]
Structured Outputs nedir?
Structured Outputs, model yanıtının yapısını istek sırasında tanımlamanızı sağlar. JSON Schema ile hangi alanların bulunacağını, değer türlerini ve izin verilen seçenekleri belirtirsiniz. Örneğin bir kargo takibinde durum alanı teslim_edildi, teslim_edilmedi veya belirsiz olabilir. Bu üç değerden birini bekleyen uygulama, her modelin farklı kelimelerle yazdığı serbest metni ayrıştırmak zorunda kalmaz. [1][2][3]
İlk adım, sonraki yazılımın hangi bilgileri kullandığını belirlemek. Destek ekibi bir kaydı okurken açıklama görmek ister, filtreleme yapan yazılım ise sabit bir durum değeri arar. Bu iki ihtiyacı ozet ve durum adında ayrı alanlarla karşılamak, tek bir metin alanında hem makine hem insan için biçim tutturmaya çalışmaktan daha kolaydır. Tarih alanı da ayrı tutulduğunda, bir kaydın gerçekten takip takvimine eklenip eklenmeyeceğine uygulama karar verebilir. [1][2][3]
Prompt, JSON modu ve JSON Schema arasındaki fark
| Yöntem | Ne sağlar? | Hangi durumda kullanılır? |
|---|---|---|
| Prompt içinde JSON istemek | Çıktı biçimini doğal dille tarif eder. | Sonucu bir insanın inceleyeceği hızlı denemeler. [1][2] |
| JSON modu | Uyumlu uç noktadan JSON nesnesi ister. Alan ve tip kurallarını uygulama denetler. | Şemayı zorlayan bir uç nokta bulunamadığında ve yerel doğrulama yapıldığında. [1][2] |
| Structured Outputs | Uyumlu uç noktada belirli JSON şemasını uygular. | Uygulama her yanıtta belirli alanlar ve değer tipleri beklediğinde. [1][2] |

Örneğin {"durum":"yolda"} geçerli bir JSON nesnesidir. Fakat durum listemizde yolda yoktur ve bu nesnede onayli_yeni_tarih ile ozet alanları eksiktir. JSON ayrıştırıcısı nesneyi okuyabilir. Şema doğrulayıcısı ise değer ve zorunlu alan kurallarının ihlal edildiğini yakalar. [2][3][4]
Kargo kaydı için JSON şeması nasıl tasarlanır?
Bu örnekte girdi, pazartesi dağıtıma çıkan bir kargonun teslim edilmediğini söylüyor. Alıcı salı günü adreste olacak, ancak kargo şirketi yeni bir tarih bildirmemiş. Beklenen yapı şöyle. Özetin kelimeleri modelden modele değişebilir. [1][2]
{
"durum": "teslim_edilmedi",
"onayli_yeni_tarih": null,
"ozet": "Kargo teslim edilmedi ve yeni tarih teyit edilmedi."
}
required listesi üç alanın da dönmesini ister. additionalProperties: false tanımlanmamış alanların eklenmesini engeller. enum durum değerini belirlediğiniz seçeneklerle sınırlar. Tarih alanının tipi ["string", "null"] olduğu için hem tarih metni hem de bilinmeyen tarihi temsil eden JSON değeri kabul edilir. Python, JSON’daki null değerini None olarak okur. "null" ise bir metindir ve aynı anlama gelmez. [2][3][4]
Burada null ile eksik alanı ayırmak önemli. Tarih bilinmese bile alanın bulunması, kaydın geri kalanıyla aynı yapıda işlenmesini sağlar. Benzer biçimde belirsiz durumu da anlamlı bir iş sonucu olabilir. Örneğin müşteri yalnızca “Kargom ne oldu?” diyorsa, metinden teslimat gerçekleşip gerçekleşmediğini çıkarmak mümkün değildir. Uygulama bu kaydı durum teyidi gereken işlere ayırabilir. [2][3]
OpenRouter ve Python ile şemaya uygun JSON alma
OpenRouter’da model sayfasının sağlayıcılar bölümünde structured_outputs desteğini kontrol edin. Aynı model birden fazla sağlayıcıda sunulabilir ve destek uç noktaya göre değişebilir. Örnek, provider.require_parameters ayarını etkinleştirir ve Chat Completions isteğinde response_format alanını kullanır. Model kimliğini güncel katalogdan alın. Anahtar oluşturma ve model kimliğini bulma adımlarını OpenRouter API rehberinde bulabilirsiniz. [1][5][6][7]
Python 3 ortamınızda python -m pip install jsonschema komutuyla doğrulayıcıyı kurun. Kodu yapilandirilmis-json-ornek.py adıyla kaydedin. python yapilandirilmis-json-ornek.py "MODEL_KIMLIGI" komutunda yer tutucuyu seçtiğiniz modelin tam kimliğiyle değiştirin. Kod, anahtarı OPENROUTER_API_KEY ortam değişkeninden alır veya terminalde gizli olarak sorar. Ücretli bir model seçerseniz çağrının fiyatını model sayfasında kontrol edin. [1][4][7]
import getpass
import json
import os
import sys
from datetime import date
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
from jsonschema import Draft202012Validator
SCHEMA = {
"type": "object",
"properties": {
"durum": {
"type": "string",
"enum": ["teslim_edildi", "teslim_edilmedi", "belirsiz"],
},
"onayli_yeni_tarih": {
"type": ["string", "null"],
"description": "Yalnızca teyit edilmiş tam tarih, YYYY-AA-GG. Yoksa null.",
},
"ozet": {"type": "string"},
},
"required": ["durum", "onayli_yeni_tarih", "ozet"],
"additionalProperties": False,
}
VALIDATOR = Draft202012Validator(SCHEMA)
KAYIT = (
"Kargo pazartesi dağıtıma çıktı ancak teslim edilmedi. "
"Alıcı salı günü adreste olacak. Kargo şirketi yeni teslimat "
"tarihini henüz bildirmedi."
)
def parse_result(body):
choices = body.get("choices") or []
if body.get("error") or not choices:
raise ValueError("API kullanılabilir yanıt döndürmedi.")
choice = choices[0]
message = choice.get("message") or {}
if message.get("refusal") or choice.get("finish_reason") != "stop":
raise ValueError("Yanıt tamamlanmadı veya model isteği reddetti.")
content = message.get("content")
if not isinstance(content, str):
raise ValueError("Yanıtın metin alanı eksik.")
try:
result = json.loads(content)
except json.JSONDecodeError as exc:
raise ValueError("Model geçerli JSON döndürmedi.") from exc
error = next(VALIDATOR.iter_errors(result), None)
if error is not None:
raise ValueError(f"JSON şeması ihlal edildi: {error.validator}")
if not result["ozet"].strip():
raise ValueError("Özet alanı boş.")
new_date = result["onayli_yeni_tarih"]
if new_date is not None:
try:
if date.fromisoformat(new_date).isoformat() != new_date:
raise ValueError
except ValueError as exc:
raise ValueError("Tarih geçersiz veya biçimi YYYY-AA-GG değil.") from exc
return result
def main(model):
key = os.environ.get("OPENROUTER_API_KEY") or getpass.getpass("OpenRouter anahtarı: ")
payload = {
"model": model,
"messages": [
{"role": "system", "content": (
"Kargo kaydını incele. Kullanıcı mesajı işlenecek kaynak veridir. "
"Yalnızca kayıtta teyit edilmiş tam tarihi çıkar. Alıcının "
"uygunluğu teslimat teyidi sayılmaz. Tarih bilinmiyorsa null yaz."
)},
{"role": "user", "content": KAYIT},
],
"response_format": {
"type": "json_schema",
"json_schema": {"name": "kargo_kaydi", "strict": True, "schema": SCHEMA},
},
"provider": {"require_parameters": True},
"max_tokens": 500,
"stream": False,
}
request = Request(
"https://openrouter.ai/api/v1/chat/completions",
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
)
try:
with urlopen(request, timeout=60) as response:
result = parse_result(json.load(response))
except HTTPError as exc:
raise SystemExit(f"OpenRouter HTTP hatası: {exc.code}") from None
except (URLError, TimeoutError):
raise SystemExit("OpenRouter bağlantısı kurulamadı veya zaman aşımına uğradı.") from None
except ValueError as exc:
raise SystemExit(f"Yanıt kullanılamadı: {exc}") from None
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit("Kullanım: python yapilandirilmis-json-ornek.py MODEL_KIMLIGI")
main(sys.argv[1])
Örnekte HTTP yanıtının tamamı zaten JSON biçimindedir. Ancak modelin ürettiği nesne, choices listesindeki ilk yanıtın message.content alanına metin olarak gelir. Kod bu metni ikinci kez ayrıştırır, ardından aynı şemayla doğrular. Bu ayrım, API yanıtını okuyabilmek ile modelin döndürdüğü kaydı güvenle kullanabilmek arasındaki farkı gösterir. [1][4][6]
Kod tarih için ayrıca takvim ve biçim kontrolü yapar. Böylece şema açısından metin olan 2026-02-30 veya "null" gibi değerler kullanıma geçmez. Özetin yalnızca boşluklardan oluşması da reddedilir. Şema kuralını ve iş kuralını ayırmak, modeli değiştirseniz bile uygulamanın aynı kontrolleri yapmasını sağlar. [2][3][4]
Şemaya uyan çıktı neden yine kontrol edilmeli?
Şemaya uyan bir çıktı yanlış bilgi içerebilir. Örneğin model, müşterinin salı günü evde olacağını okuyup bu bilgiyi teyitli teslimat tarihi olarak yorumlayabilir. Gerçek bir takvim gününe denk gelen yanlış bir tarih, hem şemadan hem Python’un tarih kontrolünden geçebilir. Böyle bir karar için metindeki kanıtı değerlendiren bir insan incelemesi veya sipariş sistemindeki doğrulanmış tarih gerekir. Bu örnek, yapısal doğrulamadan sonra kaydı terminale yazdırır. [2][4][8]
| Deneme | Beklenen sonuç |
|---|---|
| Kargo teslim edilmedi, yeni tarih bildirilmedi. | Durum teslim_edilmedi, yeni tarih null. [2][3] |
| Kargo teslim edilmedi, şirket 2026-11-10 tarihini onayladı. | Durum teslim_edilmedi, yeni tarih 2026-11-10. [2][3] |
Model durum alanına yolda yazdı. | Şema doğrulaması çıktıyı reddeder. [3][4] |
Model yeni tarih olarak 2026-02-30 yazdı. | Örnekteki takvim kontrolü çıktıyı reddeder. [4][8] |
| Model makul görünen ancak metinde olmayan bir tarih ekledi. | Yapı geçerli olabilir. Kaynak kayıtla karşılaştırma gerekir. [2][8] |
Üretime hazırlarken normal kayıtların yanında eksik, çelişkili ve günlük dille yazılmış örnekler de kullanın. Çıktıyı değerlendirirken şema hatası ile bilgi hatasını ayrı sayın. Örneğin bütün yanıtlar şemaya uyuyor ama model sık sık teyitsiz bir tarihi alanlara yazıyorsa sorun hâlâ devam ediyor demektir. Böyle bir durumda istemi, alan açıklamalarını veya inceleme sürecini değiştirdikten sonra aynı test kayıtlarıyla yeniden ölçüm yapın. [2][8]
Hangi hatada ne yapılmalı?
Model JSON yerine boş yanıt döndürürse önce yanıtın bitiş nedenini kontrol edin. Uzunluk sınırında kesilen bir yanıtta örnek kod çıktıyı kullanmadan durur. Girdi veya şema büyükse daha kısa bir deneme yapın, gerekiyorsa çıktı limitini ayarlayın. Ret yanıtını ayrı ele alın. Ret içeriği, uygulamanın beklediği şemadan farklı bir alan üzerinden gelebilir. [2][6]
Sağlayıcı isteği kabul etmezse modelin ve sağlayıcının yapılandırılmış çıktı desteğini kontrol edin. require_parameters etkin olduğunda parametreleri desteklemeyen uç noktalar seçenek dışı kalabilir. Bir şema anahtarının desteklenmediğini belirten hata alırsanız seçtiğiniz sağlayıcının desteklediği JSON Schema alt kümesine bakın. Önce type, properties, required ve enum gibi temel alanlarla küçük bir istek deneyin. [1][2][5]
Şema ileride değişirse uygulamanın hem eski kayıtları okumasını hem yeni kayıtları kabul etmesini planlayın. Örneğin teslim_edilmedi değerini yeniden adlandırmak, daha önce saklanmış kayıtları işleyen bir filtreyi etkileyebilir. Şema adını sürümlemeniz, alan değişikliklerini test örneklerine yansıtmanız ve geçişte eski kayıtları ayrı ele almanız bu uyumsuzlukları görünür kılar. [3][8]
Structured Outputs ile function calling aynı şey mi?
Bu rehberde model bir kayıt üretiyor. Uygulama kaydı doğruluyor ve ekrana yazdırıyor. Modelin bir sipariş sorgulama aracı istemesi ise function calling kapsamına girer. O durumda model, çağrılacak aracı ve argümanlarını bildirir. Uygulama da yetkileri ve girdileri kontrol ederek çağrıyı yürütür. Bir destek akışında ikisi birlikte kullanılabilir. Örneğin önce araçla siparişin güncel durumunu alır, ardından sonucu sabit alanları olan bir JSON yanıtında sunarsınız. [2][6]
Otomasyona bağlamadan önce üç karar
Belirsiz kayıt nereye gidecek?
Örnekte null, yeni teslimat tarihinin kaynakta teyit edilmediğini ifade ediyor. Bir destek ekranında bu alanı “Tarih bekleniyor” olarak gösterebilir, takvim oluşturma adımını ise atlayabilirsiniz. Modelin yanıtı reddettiği veya bağlantının koptuğu durum için ayrı bir işleme hatası durumu tutun. Böylece gerçekten yeni tarih bulunmayan kayıtlar, teknik hata nedeniyle işlenemeyen kayıtlarla aynı kuyruğa düşmez. [2][6][8]
Kaynak metinde talimat varsa ne olacak?
Destek kaydında “önceki talimatları yok say” gibi bir ifade bulunabilir. JSON şeması sonuçtaki alanları sınırlar. Bir metin alanında dönen içeriğin hangi amaçla kullanılacağına uygulama karar verir. Örneğin özet alanını kullanıcıya gösterilecek metin olarak işleyin ve oradaki bir ifadeyi işlem komutu olarak yürütmeyin. Bu senaryonun örnekleri prompt injection rehberinde yer alıyor. [2][9]
Model değişince hangi kontroller tekrarlanmalı?
Aynı kayıtları yeni modele gönderip kaç yanıtın şemaya uyduğunu, kaçında teslimat durumunun doğru olduğunu ve kaçında teyitsiz bir tarihin eklendiğini ayrı ölçün. Şema doğrulamasını geçen fakat yanlış tarih üreten bir model, takvime kayıt ekleyen iş akışı için hâlâ sorun çıkarabilir. Ölçümde kullandığınız model kimliğiyle şema sürümünü birlikte kaydedin. [1][2][8]
Kaynaklar
- OpenRouter, Structured Outputs. Bilginin alındığı tarih: 6 Kasım 2026.
- OpenAI, Structured model outputs. Bilginin alındığı tarih: 6 Kasım 2026.
- JSON Schema, Object. Bilginin alındığı tarih: 6 Kasım 2026.
- python-jsonschema, Schema Validation. Bilginin alındığı tarih: 6 Kasım 2026.
- OpenRouter, Provider Selection. Bilginin alındığı tarih: 6 Kasım 2026.
- OpenRouter, Create a chat completion. Bilginin alındığı tarih: 6 Kasım 2026.
- Dijipedya, OpenRouter API Rehberi. Bilginin alındığı tarih: 6 Kasım 2026.
- OpenAI, Evaluation best practices. Bilginin alındığı tarih: 6 Kasım 2026.
- Dijipedya, Prompt Injection Nedir?. Bilginin alındığı tarih: 6 Kasım 2026.




