Pydantic — JSON Schema
model_json_schema() mengubah model Pydantic jadi JSON Schema — format yang dipakai LLM untuk mendefinisikan tool.
Intisari
Model.model_json_schema()menghasilkan JSON Schema standar dari sebuah model Pydantic.- JSON Schema adalah satu-satunya bahasa yang dipakai LLM untuk memahami bentuk tool dan output terstruktur.
descriptionpada field ikut masuk ke skema — dan LLM membacanya. Tulis dengan serius.Literalmenjadienumdi skema, sehingga model tahu persis nilai apa yang sah.- Baca halaman ini sebelum Fase 4. Ini titik di mana keterampilan Python-mu berubah jadi keterampilan GenAI.
Satu baris yang mengubah segalanya
from typing import Literal
from pydantic import BaseModel, Field
class CariTiket(BaseModel):
status: Literal["open", "closed", "pending"] = Field(
description="Status tiket yang dicari"
)
limit: int = Field(default=10, ge=1, le=100, description="Jumlah maksimal hasil")
print(CariTiket.model_json_schema())
{
"type": "object",
"title": "CariTiket",
"properties": {
"status": {
"type": "string",
"enum": ["open", "closed", "pending"],
"description": "Status tiket yang dicari"
},
"limit": {
"type": "integer",
"default": 10,
"minimum": 1,
"maximum": 100,
"description": "Jumlah maksimal hasil"
}
},
"required": ["status"]
}
Inilah jembatannya. Kamu menulis Python biasa dengan type hint. Pydantic menerjemahkannya jadi JSON Schema. Dan JSON Schema adalah format yang dipahami LLM untuk mengetahui: tool apa yang tersedia, argumen apa yang diterimanya, dan nilai apa yang sah. Tanpa Fase 2, Fase 4 hanya jadi penyalinan kode tanpa pemahaman.
Di mana JSON Schema muncul
| Tempat | Perannya | Fase |
|---|---|---|
| Definisi tool LLM | Memberitahu model bentuk argumen tool | 4 |
| Structured output | Memaksa model membalas dengan bentuk tertentu | 4 |
| OpenAPI FastAPI | Dokumentasi otomatis di /docs | 5 |
| Bedrock Agents / Converse | Definisi tool versi AWS | 4 & 7 |
Bagaimana tipe Python dipetakan
| Python | JSON Schema |
|---|---|
str | {"type": "string"} |
int | {"type": "integer"} |
float | {"type": "number"} |
bool | {"type": "boolean"} |
list[str] | {"type": "array", "items": {"type": "string"}} |
dict[str, int] | {"type": "object", "additionalProperties": ...} |
Literal["a","b"] | {"enum": ["a", "b"]} |
str | None | {"anyOf": [{"type":"string"}, {"type":"null"}]} |
| Model bersarang | $ref ke $defs |
datetime | {"type": "string", "format": "date-time"} |
Menulis field agar LLM paham
Model bahasa membaca description untuk memutuskan cara mengisi argumen. Bandingkan:
# ❌ Model harus menebak
class Cari(BaseModel):
q: str
n: int = 10
# ✅ Model tahu persis apa yang diminta
class Cari(BaseModel):
"""Cari dokumen di basis pengetahuan internal perusahaan."""
q: str = Field(
description="Kata kunci pencarian dalam bahasa alami. "
"Contoh: 'kebijakan cuti tahunan'"
)
n: int = Field(
default=10, ge=1, le=50,
description="Jumlah dokumen yang dikembalikan. Gunakan 3-5 untuk "
"pertanyaan spesifik, 10+ untuk topik luas."
)
Docstring class menjadi description tingkat skema — itulah penjelasan
kapan tool ini dipakai. Field(description=...) menjelaskan
apa tiap argumennya. Keduanya adalah prompt engineering, hanya saja letaknya di kode.
Batasan yang dipahami LLM
class Permintaan(BaseModel):
status: Literal["open", "closed"] # → enum: model tidak bisa mengarang nilai lain
limit: int = Field(ge=1, le=100) # → minimum/maximum
email: str = Field(pattern=r"^\S+@\S+$") # → pattern
tag: list[str] = Field(max_length=5) # → maxItems
Literal adalah yang paling berdampak. Tanpa itu, model bisa mengarang nilai seperti
"OPEN" atau "aktif" dan kodemu harus menanganinya. Dengan enum
di skema, ruang kemungkinannya tertutup.
Structured output — arah sebaliknya
from pydantic import BaseModel
class Ekstraksi(BaseModel):
nama: str
email: str
minat: list[str]
response = client.messages.parse(
model="claude-opus-5",
max_tokens=4096,
messages=[{"role": "user", "content": "Ekstrak: Budi ([email protected]), tertarik API."}],
output_format=Ekstraksi,
)
data = response.parsed_output # instance Ekstraksi, sudah tervalidasi
Di balik layar: SDK memanggil Ekstraksi.model_json_schema(), mengirimkannya ke API
sebagai batasan format keluaran, lalu memvalidasi balasan model terhadap model yang sama.
Kamu menulis satu class, dan mendapat jaminan bentuk data dari ujung ke ujung.
Menyesuaikan skema
class Tiket(BaseModel):
model_config = ConfigDict(
json_schema_extra={
"examples": [{"judul": "Server down", "prioritas": "high"}]
}
)
judul: str
prioritas: str
# Untuk keperluan API yang bedakan input dan output
Tiket.model_json_schema(mode="validation") # skema untuk data masuk (default)
Tiket.model_json_schema(mode="serialization") # skema untuk data keluar
Model bersarang dan $defs
class Alamat(BaseModel):
kota: str
class Pengguna(BaseModel):
nama: str
alamat: Alamat
{
"type": "object",
"properties": {
"nama": {"type": "string"},
"alamat": {"$ref": "#/$defs/Alamat"}
},
"required": ["nama", "alamat"],
"$defs": {
"Alamat": {
"type": "object",
"properties": {"kota": {"type": "string"}},
"required": ["kota"]
}
}
}
Perhatian untuk Fase 4: beberapa penyedia LLM membatasi kedalaman bersarang atau tidak
mendukung $ref pada definisi tool. Untuk tool, jaga skemanya tetap datar — satu level
field sederhana. Simpan struktur bersarang untuk model internal aplikasimu.
Yang tidak didukung di skema tool ketat
Beberapa batasan Pydantic tidak punya padanan di JSON Schema mode ketat:
- Validator kustom (
@field_validator) — hanya jalan di sisi Python - Skema rekursif
- Sebagian batasan numerik lanjutan
Artinya: LLM tetap bisa mengirim argumen yang lolos skema tapi gagal validator Python-mu.
Selalu jalankan model_validate() pada argumen tool sebelum memakainya —
skema adalah panduan bagi model, bukan pengganti validasi.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.