Instructor: Mendapatkan Structured Output dari LLM dengan Python
Salah satu tantangan terbesar saat bekerja dengan Large Language Models (LLM) adalah mendapatkan output yang terstruktur dan konsisten. LLM secara default menghasilkan teks bebas, yang sulit di-parse dan diintegrasikan ke dalam aplikasi. Library Instructor hadir untuk menyelesaikan masalah ini dengan memanfaatkan Pydantic untuk validasi dan ekstraksi data terstruktur dari LLM.
Dalam tutorial ini, kita akan mempelajari cara menggunakan Instructor untuk mendapatkan output JSON/Pydantic yang reliable dari berbagai LLM provider seperti OpenAI, Anthropic, dan lainnya.
Apa Itu Instructor?
Instructor adalah library Python yang meng-patch client LLM (seperti OpenAI) agar bisa mengembalikan objek Pydantic yang tervalidasi, bukan sekadar string. Instructor bekerja dengan memanfaatkan function calling atau JSON mode dari LLM, kemudian memvalidasi hasilnya menggunakan Pydantic.
Keunggulan utama Instructor:
- Type-safe: Output dijamin sesuai dengan schema Pydantic yang didefinisikan
- Automatic retry: Jika validasi gagal, Instructor otomatis me-retry dengan feedback error
- Streaming support: Mendukung partial streaming untuk objek yang kompleks
- Multi-provider: Mendukung OpenAI, Anthropic, Google, Mistral, dan lainnya
- Validasi kustom: Bisa menambahkan validator Pydantic untuk business logic
Instalasi
Pertama, install Instructor beserta dependensi yang diperlukan:
pip install instructor openai pydantic
Untuk provider lain, install dependensi tambahan:
# Untuk Anthropic
pip install instructor anthropic
Untuk Google Gemini
pip install instructor google-generativeai
Untuk Mistral
pip install instructor mistralai
Pastikan Anda memiliki API key dari provider yang akan digunakan:
export OPENAIAPIKEY="sk-your-api-key-here"
Penggunaan Dasar dengan OpenAI
Mari mulai dengan contoh sederhana: mengekstrak informasi pengguna dari teks.
import instructor
from openai import OpenAI
from pydantic import BaseModel
Patch client OpenAI dengan Instructor
client = instructor.fromopenai(OpenAI())
Definisikan schema output
class UserInfo(BaseModel):
name: str
age: int
email: str
Ekstrak data terstruktur dari teks
user = client.chat.completions.create(
model="gpt-4o-mini",
responsemodel=UserInfo,
messages=[
{
"role": "user",
"content": "Nama saya Budi Santoso, umur 28 tahun. "
"Email saya budi.santoso@email.com"
}
],
)
print(user)
UserInfo(name='Budi Santoso', age=28, email='budi.santoso@email.com')
print(user.name) # Budi Santoso
print(user.age) # 28
print(user.email) # budi.santoso@email.com
Perhatikan bahwa responsemodel=UserInfo adalah parameter kunci yang memberitahu Instructor schema apa yang diharapkan. Hasilnya bukan dictionary atau string, melainkan objek Pydantic yang sudah tervalidasi.
Pydantic Models yang Lebih Kompleks
Instructor mendukung model Pydantic yang kompleks termasuk nested models, optional fields, enums, dan lists.
from pydantic import BaseModel, Field
from typing import Optional, List
from enum import Enum
class JobLevel(str, Enum):
JUNIOR = "junior"
MID = "mid"
SENIOR = "senior"
LEAD = "lead"
class Skill(BaseModel):
name: str = Field(description="Nama skill atau teknologi")
yearsexperience: int = Field(
description="Tahun pengalaman", ge=0, le=50
)
proficiency: str = Field(
description="Tingkat kemahiran: beginner, intermediate, advanced"
)
class WorkExperience(BaseModel):
company: str
role: str
durationmonths: int = Field(ge=1)
description: str
class CandidateProfile(BaseModel):
name: str
currentrole: str
level: JobLevel
totalyearsexperience: int = Field(ge=0)
skills: List[Skill]
workhistory: List[WorkExperience]
education: str
summary: str = Field(
description="Ringkasan profil kandidat dalam 2-3 kalimat"
)
resumetext = """
Saya Ahmad Rizki, saat ini bekerja sebagai Senior Data Engineer di Tokopedia
selama 3 tahun. Sebelumnya saya bekerja di Gojek sebagai Data Engineer
selama 2 tahun. Saya memiliki pengalaman 5 tahun di bidang data engineering
dengan keahlian utama di Python (5 tahun, advanced), Apache Spark (4 tahun,
advanced), dan SQL (5 tahun, advanced). Saya juga familiar dengan Kafka
(2 tahun, intermediate) dan Kubernetes (1 tahun, beginner).
Pendidikan S1 Teknik Informatika dari ITB.
"""
profile = client.chat.completions.create(
model="gpt-4o",
responsemodel=CandidateProfile,
messages=[
{
"role": "system",
"content": "Ekstrak profil kandidat dari teks resume berikut."
},
{"role": "user", "content": resumetext}
],
)
print(f"Nama: {profile.name}")
print(f"Level: {profile.level.value}")
print(f"Skills:")
for skill in profile.skills:
print(f" - {skill.name}: {skill.yearsexperience} tahun "
f"({skill.proficiency})")
Validasi dengan Pydantic Validators
Salah satu fitur paling powerful dari Instructor adalah kemampuan menambahkan validasi kustom. Jika validasi gagal, Instructor akan otomatis me-retry dan mengirimkan pesan error ke LLM.
from pydantic import BaseModel, Field, fieldvalidator, modelvalidator
class SQLQuery(BaseModel):
query: str = Field(description="SQL query yang valid")
explanation: str = Field(description="Penjelasan query dalam bahasa Indonesia")
tables
used: List[str] = Field(description="Daftar tabel yang digunakan")
@fieldvalidator("query")
@classmethod
def validatenodelete(cls, v: str) -> str:
if "DELETE" in v.upper() or "DROP" in v.upper():
raise ValueError(
"Query tidak boleh mengandung DELETE atau DROP "
"demi keamanan data"
)
return v
@fieldvalidator("query")
@classmethod
def validatehasselect(cls, v: str) -> str:
if not v.strip().upper().startswith("SELECT"):
raise ValueError("Query harus dimulai dengan SELECT")
return v
@modelvalidator(mode="after")
def validatetablesmatchquery(self):
for table in self.tablesused:
if table.lower() not in self.query.lower():
raise ValueError(
f"Tabel '{table}' tidak ditemukan dalam query"
)
return self
result = client.chat.completions.create(
model="gpt-4o-mini",
responsemodel=SQLQuery,
messages=[
{
"role": "user",
"content": "Buatkan query untuk menampilkan 10 customer "
"dengan total pembelian tertinggi dari tabel "
"customers dan orders"
}
],
)
print(f"Query: {result.query}")
print(f"Penjelasan: {result.explanation}")
print(f"Tabel: {result.tablesused}")
Retry Logic dan Error Handling
Instructor memiliki mekanisme retry built-in yang sangat berguna saat LLM menghasilkan output yang tidak valid.
from instructor import retry
from tenacity import retry, stopafterattempt, waitfixed
Konfigurasi retry saat membuat client
client = instructor.fromopenai(
OpenAI(),
maxretries=3, # Maksimal 3 kali retry
)
Atau konfigurasi retry per-request
class StrictOutput(BaseModel):
sentiment: str = Field(
description="Sentiment: positive, negative, atau neutral"
)
confidence: float = Field(ge=0.0, le=1.0)
keyphrases: List[str] = Field(minlength=1, maxlength=5)
@fieldvalidator("sentiment")
@classmethod
def validatesentiment(cls, v: str) -> str:
allowed = {"positive", "negative", "neutral"}
if v.lower() not in allowed:
raise ValueError(
f"Sentiment harus salah satu dari: {allowed}"
)
return v.lower()
result = client.chat.completions.create(
model="gpt-4o-mini",
responsemodel=StrictOutput,
maxretries=5,
messages=[
{
"role": "user",
"content": "Analisis sentimen: 'Produk ini sangat bagus, "
"pengiriman cepat, tapi packaging kurang rapi'"
}
],
)
print(f"Sentimen: {result.sentiment}")
print(f"Confidence: {result.confidence}")
print(f"Key phrases: {result.keyphrases}")
Saat validasi gagal, Instructor secara otomatis:
Streaming dengan Partial Objects
Untuk objek yang besar, Instructor mendukung streaming sehingga Anda bisa menerima data secara bertahap.
from instructor import Partial
class Article(BaseModel):
title: str
summary: str
keypoints: List[str]
conclusion: str
Streaming dengan Partial
stream = client.chat.completions.create(
model="gpt-4o-mini",
responsemodel=Partial[Article],
stream=True,
messages=[
{
"role": "user",
"content": "Tulis artikel pendek tentang manfaat AI "
"dalam healthcare"
}
],
)
for partialarticle in stream:
# Setiap iterasi mendapatkan objek Article yang partially filled
if partialarticle.title:
print(f"Title: {partialarticle.title}")
if partialarticle.keypoints:
print(f"Points so far: {len(partialarticle.keypoints)}")
Contoh Praktis: Ekstraksi Data dari Teks
Berikut contoh real-world untuk mengekstrak informasi produk dari deskripsi e-commerce.
from pydantic import BaseModel, Field
from typing import List, Optional
class ProductSpec(BaseModel):
key: str = Field(description="Nama spesifikasi")
value: str = Field(description="Nilai spesifikasi")
class ExtractedProduct(BaseModel):
name: str = Field(description="Nama produk")
brand: str = Field(description="Merek produk")
priceidr: Optional[int] = Field(
description="Harga dalam Rupiah", default=None
)
category: str = Field(description="Kategori produk")
specifications: List[ProductSpec] = Field(
description="Spesifikasi teknis produk"
)
pros: List[str] = Field(description="Kelebihan produk")
cons: List[str] = Field(description="Kekurangan produk")
productdescription = """
Review Samsung Galaxy S24 Ultra - Harga Rp 19.999.000
Samsung kembali menghadirkan flagship terbaiknya. Galaxy S24 Ultra hadir
dengan layar Dynamic AMOLED 2X 6.8 inci, prosesor Snapdragon 8 Gen 3,
RAM 12GB, dan penyimpanan 256GB. Kamera utama 200MP menghasilkan foto
yang sangat detail. Baterai 5000mAh tahan seharian.
Kelebihannya adalah performa sangat kencang, kamera luar biasa, dan
S Pen yang berguna. Kekurangannya harga yang mahal dan bobot yang cukup
berat di 233 gram.
"""
product = client.chat.completions.create(
model="gpt-4o",
responsemodel=ExtractedProduct,
messages=[
{
"role": "system",
"content": "Ekstrak informasi produk dari review berikut."
},
{"role": "user", "content": productdescription}
],
)
print(f"Produk: {product.brand} {product.name}")
print(f"Harga: Rp {product.priceidr:,}")
print(f"Kategori: {product.category}")
print("Spesifikasi:")
for spec in product.specifications:
print(f" {spec.key}: {spec.value}")
Contoh Praktis: Klasifikasi Teks
Instructor sangat cocok untuk tugas klasifikasi karena output dijamin sesuai dengan kategori yang ditentukan.
from enum import Enum
from typing import List
class TicketPriority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class TicketCategory(str, Enum):
BUG = "bug"
FEATUREREQUEST = "featurerequest"
QUESTION = "question"
COMPLAINT = "complaint"
BILLING = "billing"
class ClassifiedTicket(BaseModel):
priority: TicketPriority
category: TicketCategory
department: str = Field(
description="Departemen yang bertanggung jawab"
)
summary: str = Field(
description="Ringkasan tiket dalam 1 kalimat", maxlength=200
)
suggestedresponse: str = Field(
description="Saran respons awal untuk customer service"
)
requiresescalation: bool = Field(
description="Apakah perlu dieskalasi ke level lebih tinggi"
)
tickettext = """
URGENT: Sistem pembayaran error sejak kemarin. Semua transaksi gagal
dan customer tidak bisa checkout. Sudah kehilangan potensi revenue
sekitar Rp 500 juta. Tolong segera diperbaiki!
"""
classified = client.chat.completions.create(
model="gpt-4o-mini",
responsemodel=ClassifiedTicket,
messages=[
{
"role": "system",
"content": "Klasifikasikan support ticket berikut."
},
{"role": "user", "content": tickettext}
],
)
print(f"Priority: {classified.priority.value}")
print(f"Category: {classified.category.value}")
print(f"Department: {classified.department}")
print(f"Escalation needed: {classified.requiresescalation}")
print(f"Summary: {classified.summary}")
Menggunakan dengan Provider Lain
Instructor tidak terbatas pada OpenAI. Berikut cara menggunakannya dengan Anthropic.
import instructor
from anthropic import Anthropic
Dengan Anthropic Claude
client = instructor.fromanthropic(Anthropic())
class Analysis(BaseModel):
topic: str
mainarguments: List[str]
conclusion: str
result = client.messages.create(
model="claude-sonnet-4-20250514",
maxtokens=1024,
responsemodel=Analysis,
messages=[
{
"role": "user",
"content": "Analisis argumen utama tentang pentingnya "
"regulasi AI di Indonesia"
}
],
)
Tips dan Best Practices
Berikut beberapa tips untuk memaksimalkan penggunaan Instructor:
1. Gunakan Field descriptions yang jelasclass GoodModel(BaseModel):
# Baik - deskripsi jelas
revenue: float = Field(
description="Total revenue dalam juta Rupiah (IDR)"
)
# Kurang baik - tanpa deskripsi
revenue: float
2. Pilih model yang tepat untuk task yang tepat
- Gunakan
gpt-4o-miniuntuk task sederhana (klasifikasi, ekstraksi basic) - Gunakan
gpt-4ountuk task kompleks (nested objects, reasoning)
messages=[
{
"role": "system",
"content": "Kamu adalah asisten yang mengekstrak data. "
"Selalu berikan output dalam bahasa Indonesia. "
"Jika informasi tidak tersedia, gunakan null."
},
{"role": "user", "content": text}
]
4. Handle error dengan graceful
from instructor.exceptions import InstructorRetryException
try:
result = client.chat.completions.create(
model="gpt-4o-mini",
responsemodel=MyModel,
max_retries=3,
messages=[{"role": "user", "content": text}],
)
except InstructorRetryException as e:
print(f"Gagal setelah retry: {e}")
except Exception as e:
print(f"Error: {e}")
Kesimpulan
Instructor adalah library yang sangat berguna untuk siapa saja yang bekerja dengan LLM dan membutuhkan output terstruktur. Dengan memanfaatkan Pydantic, Instructor memberikan type safety, validasi otomatis, dan retry logic yang membuat integrasi LLM ke dalam aplikasi produksi menjadi jauh lebih reliable.
Fitur-fitur utama yang telah kita pelajari:
- Penggunaan dasar dengan OpenAI dan provider lain
- Nested Pydantic models untuk data kompleks
- Validasi kustom dengan field dan model validators
- Retry logic otomatis saat validasi gagal
- Streaming dengan Partial objects
- Contoh praktis untuk ekstraksi data dan klasifikasi
Mulailah dengan use case sederhana seperti ekstraksi data atau klasifikasi, lalu gradually tingkatkan kompleksitas sesuai kebutuhan proyek Anda. Instructor akan menjadi tool yang sangat berharga dalam toolkit AI engineering Anda.