09 β Strukturali natija (JSON + Pydantic)¶
β¬ οΈ Oldingi: 08 β Suhbat xotirasi va kontekst boshqaruvi Β· π Kitob boshi Β· Keyingi: 10 β Function/tool calling asoslari β‘οΈ
Bu bobda: modeldan erkin matn emas, kompyuter o'qiy oladigan strukturali ma'lumot (JSON) olishni o'rganamiz. Avval eng oddiy yo'lni β promptda "JSON qaytar" +
response_format={"type": "json_object"}β ko'ramiz; so'ng kuchliroq json_schema (qat'iy sxema) bilan modelni aniq shaklga majburlaymiz; eng qulay usul β Pydantic bilanclient.beta.chat.completions.parse(...)β javobni avtomatik validatsiyalab, tayyor obyekt qaytarishini o'rganamiz. Validatsiya xatolarini (ValidationError) ushlashni, erkin matndan (masalan, e'londan) nom/narx/joy/telefonni ajratib olishni amalda qilamiz va provayderlararo farqlarni (json_object va json_schema, Gemini) ko'rib chiqamiz.
Muammodan boshlaymiz: erkin matnni ilovaga ulab bo'lmaydi¶
Hozirgacha modeldan matn olardik va uni print qilardik β odam o'qishi uchun ajoyib. Lekin ilova qurishda muammo paydo bo'ladi. Tasavvur qiling, foydalanuvchi e'lonini modelga berib, undan narxni ajratib olmoqchisiz. Model shunday javob beradi:
"Mahsulot β iPhone 15, narxi taxminan 9 mln so'm, Toshkentda sotiladi."
Bu β chiroyli jumla, lekin kod uni ishlata olmaydi. Narxni ma'lumotlar bazasiga yozmoqchisiz β qaysi qismi narx? "9 mln" ni 9000000 songa qanday aylantirasiz? Ertaga model "narxi 9 million so'm atrofida" deb yozsa-chi? Har safar boshqacha shaklda keladigan matnni if/split/regex bilan ajratish β mo'rt va ishonchsiz.
Yechim: modeldan strukturali ma'lumot so'rash. Ya'ni javobni odam uchun emas, kompyuter uchun β aniq maydonlarga bo'lingan, har doim bir xil shaklda. Eng keng tarqalgan format β JSON.
Hayotiy o'xshatish. Erkin matn β bu og'zaki aytilgan manzil: "anavi katta do'kondan o'tib, chap tomonda". Odam tushunadi, lekin navigator (kompyuter) tushunmaydi. JSON esa β to'ldirilgan blank: ko'cha, uy raqami, indeks alohida kataklarda. Navigator buni ishonch bilan o'qiydi. Ilovangizga "manzil"ni og'zaki emas, blank shaklida bering.
JSON nima?
JSON (JavaScript Object Notation) β ma'lumotni kalit: qiymat juftliklari sifatida yozish formati. Python'dagi dictga juda o'xshaydi: {"nom": "iPhone", "narx": 9000000}. Python'da import json bilan ishlanadi: json.loads(matn) β JSON satrini dictga aylantiradi, json.dumps(obj) β teskarisi. Deyarli har til JSON'ni tushunadi β shuning uchun u tizimlararo "umumiy til".
Eng oddiy yo'l: JSON rejimi (json_object)¶
Birinchi qadam β modelga ikki narsani aytish: (1) promptda "JSON qaytar" deyish va (2) response_format={"type": "json_object"} parametri bilan modelni faqat to'g'ri JSON chiqarishga majburlash. Bu parametrsiz model ba'zan "Mana sizga JSON:" deb qo'shimcha gap qo'shadi yoki kod blokiga (```json) o'rab beradi β keyin json.loads xato beradi. json_object rejimi shunday "axlat"ni oldini oladi.
import os, json
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI()
MODEL = "gpt-5.4-mini" # nomlar o'zgaradi β provayder ro'yxatini tekshiring
elon = "Sotaman: iPhone 15, holati zo'r, narxi 9 mln so'm. Toshkent. Tel: 90 123 45 67"
javob = client.chat.completions.create(
model=MODEL,
response_format={"type": "json_object"}, # faqat toza JSON qaytaradi
messages=[
{"role": "system", "content":
"E'londan ma'lumot ajratib oluvchisan. FAQAT JSON qaytar, "
"shu maydonlar bilan: nom (str), narx (butun son, so'mda), "
"joy (str), telefon (str)."},
{"role": "user", "content": elon},
],
)
matn = javob.choices[0].message.content # bu hali ODDIY SATR (str)
data = json.loads(matn) # satrni dict'ga aylantiramiz
print(data["narx"], data["joy"]) # 9000000 Toshkent
Diqqat qiling: javob.choices[0].message.content β baribir satr. JSON rejimi faqat shu satr to'g'ri JSON bo'lishini kafolatlaydi; uni Python obyektiga aylantirish uchun json.loads() kerak.
Hayotiy o'xshatish.
json_objectrejimi β ofitsiantga "menga faqat ovqatni keltir, salfetka va menyuni emas" deyishga o'xshaydi. Sukutda (rejimsiz) model javobni qo'shimcha gaplar bilan "o'rab" beradi; bu rejim esa faqat sof JSON keltirishini ta'minlaydi.
json_object shaklni kafolatlamaydi β faqat 'JSON ekanini' kafolatlaydi
Bu rejim natija to'g'ri JSON ekanini ta'minlaydi, lekin qaysi maydonlar bo'lishini emas. Model narxni unutishi yoki uni satr ("9 mln") qilib qaytarishi mumkin β JSON baribir to'g'ri sanaladi. Shuning uchun: (1) promptda maydonlarni aniq sanang, (2) o'qiyotganda .get() bilan ehtiyot bo'ling, (3) yaxshisi β keyingi bo'limdagi json_schema yoki Pydantic dan foydalaning.
Promptda misol bering (one-shot)
Model qanday JSON kutilayotganini aniq bilsin uchun system promptga bitta namuna qo'shing: Masalan: {"nom": "Velosiped", "narx": 1200000, "joy": "Samarqand", "telefon": "..."}. Bu modelni to'g'ri shaklga yo'naltiradi va xatoni keskin kamaytiradi.
Kuchliroq yo'l: qat'iy sxema (json_schema)¶
json_object "biror JSON" beradi, lekin shaklini kafolatlamaydi. Structured outputs (qat'iy json_schema) esa modelni aniq belgilangan shaklga majburlaydi: qaysi maydonlar bo'lishi, har birining tipi (string, number, ...), qaysilari majburiy. Model aynan shu shaklda qaytaradi β maydon yetishmaydi yoki ortiqcha qo'shilmaydi.
sxema = {
"type": "object",
"properties": {
"nom": {"type": "string"},
"narx": {"type": "integer"}, # so'mda, butun son
"joy": {"type": "string"},
"telefon": {"type": ["string", "null"]}, # bo'lmasligi mumkin
},
"required": ["nom", "narx", "joy", "telefon"],
"additionalProperties": False, # ortiqcha maydon taqiqlanadi
}
javob = client.chat.completions.create(
model=MODEL,
response_format={
"type": "json_schema",
"json_schema": {"name": "elon", "schema": sxema, "strict": True},
},
messages=[
{"role": "system", "content": "E'londan ma'lumot ajrat."},
{"role": "user", "content": elon},
],
)
data = json.loads(javob.choices[0].message.content)
print(data) # barcha maydonlar kafolatlangan
strict: True β eng muhim qism: model sxemadan chetga chiqa olmaydi. Bu json_objectdan ancha ishonchli, chunki endi narx albatta butun son, joy albatta bor.
Hayotiy o'xshatish. json_schema β quyma qolip (forma). Suyuq metallni qolipga quyasiz β u qaysi shaklda quyilsa, o'sha shaklni oladi: na ortiqcha, na kam. Sxema ham javobni shunday "quyadi": natija har doim aniq o'sha shaklda chiqadi.
json_schema'ni hamma provayder qo'llamaydi
Qat'iy json_schema (strict: True) OpenAI va yangi modellarda yaxshi ishlaydi, lekin barcha OpenAI-mos provayder uni qo'llamaydi. Ko'pchilik faqat json_objectni qo'llaydi. Provayderni almashtirayotganda buni sinab ko'ring (4-bobdagi factory yodingizda). Pastda Gemini va boshqalar haqida alohida eslatma bor.
Eng qulay yo'l: Pydantic bilan (parse)¶
Sxemani qo'lda yozish (yuqoridagi sxema lug'ati) β zerikarli va xatoga moyil. Pydantic β Python'da ma'lumot tiplarini sinflar bilan e'lon qiladigan kutubxona. OpenAI SDK Pydantic bilan birga ishlab, eng qulay yo'lni beradi: siz oddiy Python sinfini yozasiz, SDK undan sxema yasaydi, javobni validatsiyalaydi va tayyor obyekt qaytaradi.
Avval o'rnating:
from pydantic import BaseModel
# Kutilayotgan shaklni oddiy Python sinfi sifatida e'lon qilamiz
class Elon(BaseModel):
nom: str
narx: int # so'mda, butun son
joy: str
telefon: str | None = None # bo'lmasligi mumkin
javob = client.beta.chat.completions.parse( # create EMAS β parse!
model=MODEL,
response_format=Elon, # to'g'ridan-to'g'ri sinf!
messages=[
{"role": "system", "content": "E'londan ma'lumot ajrat."},
{"role": "user", "content": elon},
],
)
obyekt = javob.choices[0].message.parsed # .parsed -> Elon namunasi (str emas!)
print(obyekt.narx) # 9000000 (int)
print(obyekt.joy) # Toshkent
print(type(obyekt)) # <class '...Elon'> β tayyor obyekt
Farqni sezing: create o'rniga parse, va .content (satr) o'rniga .parsed (validatsiyalangan obyekt). json.loads ham, qo'lda sxema yozish ham kerak emas β Pydantic hammasini bajaradi. obyekt.narx β haqiqiy int, redaktoringiz ham uni tushunadi (avtomatik to'ldirish ishlaydi).
Hayotiy o'xshatish. Pydantic β bojxona inspektori. Chegaradan o'tayotgan yuk (model javobi) deklaratsiyaga (sinf) mos kelishini tekshiradi: hamma narsa joyidami, tiplar to'g'rimi. Mos bo'lsa β ichkariga o'tkazadi (tayyor obyekt); mos bo'lmasa β to'xtatadi (xato). Ilovangizga faqat "tekshirilgan" ma'lumot kiradi.
Murakkabroq model: nested, list, enum¶
Haqiqiy ma'lumot ko'pincha murakkab: ichida boshqa obyekt (nested), ro'yxat (list) yoki cheklangan tanlov (enum) bo'ladi. Pydantic bularning hammasini qo'llaydi. Masalan, e'lonni boyroq shaklda ajratib olaylik:
from enum import Enum
from pydantic import BaseModel, Field
class Holat(str, Enum): # faqat shu uch qiymatdan biri
yangi = "yangi"
ishlatilgan = "ishlatilgan"
nuqsonli = "nuqsonli"
class Kontakt(BaseModel): # ichki (nested) obyekt
telefon: str
shahar: str
class ElonToliq(BaseModel):
nom: str
narx: int = Field(description="So'mda, butun son") # modelga izoh
holat: Holat # enum: cheklangan tanlov
kontakt: Kontakt # nested obyekt
teglar: list[str] # satrlar ro'yxati
javob = client.beta.chat.completions.parse(
model=MODEL,
response_format=ElonToliq,
messages=[
{"role": "system", "content": "E'londan to'liq ma'lumot ajrat."},
{"role": "user", "content": elon},
],
)
e = javob.choices[0].message.parsed
print(e.holat) # Holat.yangi (faqat ruxsat etilgan qiymat)
print(e.kontakt.shahar) # Toshkent (nested obyektdan)
print(e.teglar) # ['telefon', 'apple', ...]
Field(description=...) β modelga har maydon nima ekanini tushuntiradi (sifatni oshiradi). Enum esa modelni faqat ruxsat etilgan qiymatlarga cheklaydi β holat hech qachon "yaxshi-yomon" kabi kutilmagan qiymat bo'lmaydi. Bu ma'lumotni keyin ishlashda juda qulay.
Pydantic β strukturali natija uchun standart
Real loyihalarda strukturali natija deyarli har doim Pydantic bilan yoziladi: kamroq kod, avtomatik validatsiya, tip-xavfsizlik va o'qilishi oson sxema. Imkon bo'lsa β to'g'ridan-to'g'ri shu yo'ldan boring. json_objectni esa Pydantic/json_schema'ni qo'llamaydigan provayderlar uchun "zaxira" sifatida saqlang.
Validatsiya va xatolarni ushlash¶
Model β bashorat qiluvchi tizim; u har doim to'g'ri shaklda qaytaradi degan kafolat yo'q (ayniqsa json_object rejimida yoki kuchsizroq modellarda). Shuning uchun strukturali natijani olganda doim xatoga tayyor turing.
parse ishlatganda Pydantic javobni avtomatik tekshiradi; mos kelmasa β ValidationError ko'taradi. Uni ushlab, mazmunli ish qilish kerak:
from pydantic import ValidationError
import json
try:
javob = client.beta.chat.completions.parse(
model=MODEL,
response_format=Elon,
messages=[{"role": "user", "content": elon}],
)
obyekt = javob.choices[0].message.parsed
if obyekt is None:
# Model ba'zan javob bermay, "rad etish" (refusal) qaytarishi mumkin
print("Model javobni qaytarmadi:", javob.choices[0].message.refusal)
else:
print("OK:", obyekt)
except ValidationError as e:
# Javob shaklga mos kelmadi (maydon yetishmadi yoki tip xato)
print("Validatsiya xatosi:", e)
except json.JSONDecodeError:
# json_object rejimida: javob umuman to'g'ri JSON bo'lmasa
print("Javob to'g'ri JSON emas")
Qo'lda json.loads ishlatsangiz (json_object yo'li), o'zingiz tekshirishingiz kerak: maydon bormi, tip to'g'rimi. Pydantic'ning afzalligi β buni avtomatik qiladi.
Hayotiy o'xshatish. Validatsiya β xavfsizlik tekshiruvi (metal detektor) kabi. Hamma narsa joyida deb ishonib o'tkazib yuborish o'rniga, har "yo'lovchini" (javobni) tekshirasiz. Bir marta qo'shilgan tekshiruv β keyinchalik ilovangizda yuzaga keladigan ko'plab sirli xatolarning oldini oladi.
Modelga hech qachon ko'r-ko'rona ishonmang
Strukturali natija β kuchli, lekin sehrli emas. narx butun son bo'lsa ham, uning mantiqan to'g'ri ekanini (manfiy emasligi, real ekani) Pydantic bilmaydi. Biznes-mantiq tekshiruvini (if obyekt.narx < 0: ...) o'zingiz qo'shing. Pydantic β shakl tekshiruvi; ma'no tekshiruvi sizning vazifangiz.
Retry β qayta urinish
ValidationError chiqsa, ko'pincha eng oddiy yechim β bir marta qayta so'rash, ehtimol promptga "Avvalgi javobing shaklga mos kelmadi, faqat to'g'ri JSON qaytar" deb qo'shib. Ishlab chiqarish (production) ilovalarida strukturali natija + retry juftligi standart amaliyot; ishonchlilik haqida 23-bobda batafsil to'xtalamiz.
Amaliy misol: e'lonlar ro'yxatini bazaga tayyorlash¶
Endi hammasini birlashtiramiz. Tasavvur qiling, sizda bir nechta erkin e'lon bor (foydalanuvchilar yozgan), va ularni bazaga yozish uchun strukturali shaklga keltirmoqchisiz. Pydantic + parse bilan bu juda toza chiqadi:
import os
from dotenv import load_dotenv
from openai import OpenAI
from pydantic import BaseModel, ValidationError
load_dotenv()
client = OpenAI()
MODEL = "gpt-5.4-mini" # nomlar o'zgaradi β ro'yxatni tekshiring
class Elon(BaseModel):
nom: str
narx: int
joy: str
telefon: str | None = None
def elonni_ajrat(matn: str) -> Elon | None:
"""Bitta erkin e'lon matnidan strukturali Elon obyektini ajratadi."""
try:
javob = client.beta.chat.completions.parse(
model=MODEL,
response_format=Elon,
messages=[
{"role": "system", "content":
"Foydalanuvchi e'lonidan ma'lumot ajratuvchisan. "
"narx β so'mda butun son. Telefon topilmasa null qoldir."},
{"role": "user", "content": matn},
],
)
return javob.choices[0].message.parsed
except ValidationError as e:
print(f" ! ajratib bo'lmadi: {e}")
return None
elonlar = [
"Sotaman iPhone 15, 9 mln so'm, Toshkent, tel 90 123 45 67",
"Velosiped arzon β 1.2 million. Samarqand shahar.",
"Noutbuk Lenovo, narxi kelishilgan, Buxoro, +998 91 222 33 44",
]
baza = [] # bazaga yoziladigan toza yozuvlar
for matn in elonlar:
obyekt = elonni_ajrat(matn)
if obyekt:
baza.append(obyekt.model_dump()) # Pydantic -> dict (DB/JSON uchun)
print(f"OK: {obyekt.nom} | {obyekt.narx} so'm | {obyekt.joy}")
# Endi `baza` β bazaga INSERT qilish yoki json.dumps qilishga tayyor
E'tibor bering: kirish β chalkash, har xil shakldagi matn ("9 mln", "1.2 million", "narxi kelishilgan"); chiqish β bir xil, toza struktura. model_dump() Pydantic obyektini oddiy dictga aylantiradi β uni to'g'ridan-to'g'ri bazaga yozish yoki json.dumps bilan saqlash mumkin. Mana shu β strukturali natijaning asl kuchi: LLM'ni tartibsiz tabiiy til bilan ilovangizning tartibli dunyosi orasidagi ko'prik sifatida ishlatish.
Ma'lumot ajratish (extraction) β eng ko'p uchraydigan amaliy vazifa
Strukturali natijaning real ishlatilishi ko'p: rezyumedan ism/tajriba/ko'nikma ajratish, mijoz xatidan shikoyat turi va shoshilinchlik darajasini aniqlash, hisob-fakturadan summa/sana/yetkazib beruvchini olish, sharhni ijobiy/salbiyga tasniflash. Hammasi bitta naqsh: erkin matn -> Pydantic model -> toza obyekt.
Provayderlararo farqlar¶
Strukturali natija β provayderlararo eng "g'adir-budur" mavzulardan biri. Eng muhim farqlar:
json_objectβ deyarli barcha OpenAI-mos provayderda bor (OpenAI, Groq, DeepSeek, ko'pchilik). Eng keng qo'llab-quvvatlanadigan, lekin shaklni kafolatlamaydi.json_schema/parse(Pydantic, qat'iy sxema) β OpenAI'da to'liq, ba'zi boshqalarda qisman yoki umuman yo'q. Provayderni almashtirsangiz β sinab ko'ring.- Anthropic (Claude) β
response_formatishlatmaydi. Strukturali natija odatda tool calling orqali olinadi: modelga bitta "tool" beriladi va uning argumentlari sxema bo'lib xizmat qiladi. Buni 10-11 boblarda (tool calling) ko'ramiz. - Google (Gemini) β native SDK'da boshqacha:
response_mime_type="application/json"(JSON rejimi) varesponse_schema(sxema) konfiguratsiya orqali beriladi:
from google import genai
from google.genai import types
client = genai.Client() # GEMINI_API_KEY
resp = client.models.generate_content(
model="gemini-2.5-flash",
contents=elon,
config=types.GenerateContentConfig(
response_mime_type="application/json",
response_schema=Elon, # Pydantic sinfini to'g'ridan-to'g'ri beradi
),
)
print(resp.text) # JSON satri
obyekt = resp.parsed # Pydantic obyekt (Gemini SDK ham .parsed beradi)
Hayotiy o'xshatish. Strukturali natija β rozetka turlariga o'xshaydi: g'oya bir xil (tok = strukturali ma'lumot), lekin har mamlakatda (provayderda) "vilka" shakli (
json_object/json_schema/ tool /response_schema) farq qiladi. Maqsad bir xil, faqat ulanish usuli o'zgaradi.
Provayderni almashtirgach, strukturali natijani qayta sinang
Bu kitobning "bitta kod, ko'p provayder" g'oyasi (4-bob) strukturali natijada eng kam ishlaydigan joy. response_format={"type": "json_object"} aksariyatda ishlaydi, lekin json_schema/parseni qabul qilmaydigan provayderda kod sinadi. Strukturali natija ishlatadigan kodni provayder almashganda albatta tekshiring.
Eng portativ minimal yo'l
Iloji boricha ko'p provayderda ishlaydigan kod kerak bo'lsa: response_format={"type": "json_object"} + promptda aniq maydonlar va bitta misol + Python tomonda Pydantic bilan validatsiya (Elon.model_validate_json(matn)). Bu json_schema'ni qo'llamaydigan provayderda ham qat'iy sxema'ning ko'p foydasini beradi.
Xulosa¶
- Erkin matnni ilovaga ishonchli ulab bo'lmaydi β kompyuter o'qiy oladigan strukturali ma'lumot (JSON) kerak: aniq maydonlar, har doim bir xil shakl.
- Eng oddiy yo'l: promptda "JSON qaytar" +
response_format={"type": "json_object"}; natija baribir satr, unijson.loads()bilandictga aylantirasiz. Bu "JSON ekanini" kafolatlaydi, shaklni emas. - Kuchliroq:
json_schema(strict: True) β modelni aniq maydon va tiplarga majburlaydi; natija shakli kafolatlanadi. - Eng qulay: Pydantic +
client.beta.chat.completions.parse(..., response_format=Model)->.parsed. Sinf yozasiz, SDK sxema yasaydi, validatsiyalaydi va tayyor obyekt qaytaradi. Nested,list,Enum,Field(description=...)qo'llanadi. - Validatsiya majburiy:
parsemos kelmasaValidationErrorko'taradi;.parsed is None(refusal) holatini ham tekshiring. Pydantic shaklni tekshiradi β ma'no/biznes-mantiq tekshiruvi sizning zimmangizda. Kerakda retry. - Amaliy naqsh: erkin matn -> Pydantic model -> toza obyekt β e'lon, rezyume, hisob-faktura, sharhdan ma'lumot ajratishning standart usuli;
model_dump()bilan bazaga yozasiz. - Provayderlararo farq bor:
json_objectkeng qo'llanadi,json_schema/parsehammada emas; Claude uchun tool calling, Gemini uchunresponse_mime_type+response_schema. Provayder almashganda qayta sinang.
Amaliy mashqlar¶
-
(Oson) 5-qadamdagi
json_objectmisolini ishga tushiring: bitta e'lon matnidannom,narx,joy,telefonni ajratib,json.loadsbilandictga aylantiring va har bir maydonni alohidaprintqiling. -
(Oson)
ElonPydantic modelini yozing vaclient.beta.chat.completions.parse(...)bilan o'sha e'lonni ajrating.obyekt.narxningtype()ini chop eting βstremas,intekaniga ishonch hosil qiling. -
(O'rtacha) Modelni kengaytiring:
Holatenum (yangi/ishlatilgan/nuqsonli) vateglar: list[str]qo'shing. Bir nechta turli e'londa sinab,holatdoim faqat ruxsat etilgan qiymatlardan biri ekanini tekshiring. -
(O'rtacha) Amaliy misoldagidek uch-to'rt xil shakldagi e'lon ro'yxatini Pydantic +
parsebilan ajratib, har birinimodel_dump()qilib bittalistga to'plang. So'ng butun ro'yxatnijson.dumps(..., ensure_ascii=False, indent=2)bilan faylga yozing. -
(Qiyin)
try/except ValidationErrorbilan mustahkamelonni_ajrat()funksiyasini yozing: validatsiya xatosida promptga "faqat to'g'ri JSON qaytar" eslatmasini qo'shib bir marta qayta urinsin (retry); ikkala urinish ham muvaffaqiyatsiz bo'lsaNoneqaytarsin. Atayin chalkash matn berib, retry mantiqini sinab ko'ring.
β¬ οΈ Oldingi: 08 β Suhbat xotirasi va kontekst boshqaruvi Β· π Kitob boshi Β· Keyingi: 10 β Function/tool calling asoslari β‘οΈ