Jika kamu membangun aplikasi di atas Gemma 4, kamu butuh structured output — bukan teks bebas. Kamu butuh JSON yang bisa kamu parse, validasi, dan alirkan ke database atau API-mu. Setiap waktu, tanpa pengecualian.
Ini salah satu bagian paling tricky dari bekerja dengan LLM lokal, tapi dengan teknik yang tepat, Gemma 4 bisa sangat reliable mengejutkan. Mari kita bahas setiap metode.
Mengapa Structured Output Penting
Saat kamu menggunakan Gemma 4 sebagai komponen dalam sistem lebih besar — bukan hanya chat dengannya — kamu butuh output yang dapat diprediksi:
# Ini yang kamu inginkan:
{"sentiment": "positive", "confidence": 0.92, "topics": ["pricing", "support"]}
# Ini yang tidak kamu inginkan:
"The sentiment of this text is positive, with a confidence of about 92%..."Yang pertama bisa di-parse dan digunakan secara programatik. Yang kedua membutuhkan ronde parsing lagi, yang menambah latensi, biaya, dan titik kegagalan.
Metode 1: Teknik System Prompt
Pendekatan paling sederhana — beri tahu model persis apa yang kamu inginkan di system prompt:
import requests
import json
response = requests.post("http://localhost:11434/api/chat", json={
"model": "gemma4:26b",
"messages": [
{
"role": "system",
"content": """You are a JSON-only response API.
You MUST respond with valid JSON and nothing else.
No markdown, no explanation, no code blocks — just raw JSON.
Schema:
{
"sentiment": "positive" | "negative" | "neutral",
"confidence": number between 0 and 1,
"topics": string[],
"summary": string (one sentence)
}"""
},
{
"role": "user",
"content": "Analyze: 'The new update is amazing! The UI is so much cleaner and everything loads faster. Only complaint is the price went up.'"
}
],
"stream": False,
})
result = json.loads(response.json()["message"]["content"])
print(result)Ini berfungsi sebagian besar waktu. Tapi "sebagian besar waktu" tidak cukup baik untuk produksi. Model mungkin kadang menambahkan pembukaan seperti "Berikut JSON-nya:" atau membungkus output dalam blok kode markdown.
Metode 2: Parameter Format Ollama
Ollama punya parameter format bawaan yang membatasi output ke JSON valid:
response = requests.post("http://localhost:11434/api/chat", json={
"model": "gemma4:26b",
"messages": [
{
"role": "system",
"content": "Analyze the sentiment of the given text. Return: sentiment (positive/negative/neutral), confidence (0-1), topics (list), summary (one sentence)."
},
{
"role": "user",
"content": "The customer service was terrible but the product itself is excellent."
}
],
"format": "json",
"stream": False,
})
# Ini dijamin valid JSON
result = response.json()["message"]["content"]
parsed = json.loads(result)Flag format: "json" memberitahu Ollama untuk membatasi generasi token agar hanya menghasilkan JSON valid. Ini jauh lebih reliable daripada hanya prompt engineering saja.
Batasan: Ini menjamin sintaks JSON valid, tapi tidak menjamin schema. Model mungkin mengembalikan {"answer": "positive"} alih-alih format yang kamu harapkan. Kamu masih butuh validasi.
Metode 3: Definisi Schema dengan Pydantic
Untuk kode produksi, definisikan schema yang diharapkan dengan Pydantic dan validasi terhadapnya:
from pydantic import BaseModel, Field
from typing import Literal
import json
import requests
class SentimentResult(BaseModel):
sentiment: Literal["positive", "negative", "neutral"]
confidence: float = Field(ge=0, le=1)
topics: list[str]
summary: str
def analyze_sentiment(text: str) -> SentimentResult:
schema_str = json.dumps(SentimentResult.model_json_schema(), indent=2)
response = requests.post("http://localhost:11434/api/chat", json={
"model": "gemma4:26b",
"messages": [
{
"role": "system",
"content": f"""Respond with JSON matching this exact schema:
{schema_str}
No other text. Just valid JSON."""
},
{
"role": "user",
"content": f"Analyze this text: {text}"
}
],
"format": "json",
"stream": False,
})
raw = json.loads(response.json()["message"]["content"])
return SentimentResult.model_validate(raw)
# Penggunaan
result = analyze_sentiment("Great product, terrible shipping time.")
print(f"Sentiment: {result.sentiment} ({result.confidence:.0%})")
print(f"Topics: {', '.join(result.topics)}")Ini memberimu type safety dan validasi. Jika model mengembalikan sesuatu yang tidak diharapkan, Pydantic melempar error yang jelas alih-alih diam-diam merusak datamu.
Metode 4: Pola Validasi dan Retry
Untuk reliabilitas maksimal, tambahkan loop retry:
from pydantic import BaseModel, ValidationError
import json
import requests
import time
def get_structured_output(
prompt: str,
schema_class: type[BaseModel],
model: str = "gemma4:26b",
max_retries: int = 3,
) -> BaseModel:
schema_str = json.dumps(schema_class.model_json_schema(), indent=2)
for attempt in range(max_retries):
try:
response = requests.post("http://localhost:11434/api/chat", json={
"model": model,
"messages": [
{
"role": "system",
"content": f"Respond ONLY with JSON matching this schema:\n{schema_str}"
},
{"role": "user", "content": prompt}
],
"format": "json",
"stream": False,
"options": {
"temperature": 0.1 if attempt == 0 else 0.3,
},
})
raw = json.loads(response.json()["message"]["content"])
return schema_class.model_validate(raw)
except (json.JSONDecodeError, ValidationError) as e:
if attempt == max_retries - 1:
raise ValueError(
f"Failed to get valid output after {max_retries} attempts: {e}"
)
time.sleep(0.5)
raise ValueError("Unreachable")
# Penggunaan
class ProductReview(BaseModel):
rating: int = Field(ge=1, le=5)
pros: list[str]
cons: list[str]
recommendation: bool
review = get_structured_output(
"Review: 'Solid laptop, great keyboard, battery could be better. 4/5 would buy again.'",
ProductReview,
)Pilihan desain kunci:
- Mulai dengan temperature rendah (0.1) untuk konsistensi, naikkan pada retry untuk variasi
- Gunakan
format: "json"untuk menjamin sintaks JSON valid - Validasi dengan Pydantic untuk kebenaran schema
- Batasi retry ke 3 — jika gagal 3 kali, prompt mungkin perlu diperbaiki
Kegagalan Umum dan Solusinya
Model membungkus JSON dalam markdown:
```json
{"key": "value"}
```Solusi: Gunakan format: "json" di Ollama. Jika itu tidak tersedia, hapus markdown:
def clean_json(text: str) -> str:
text = text.strip()
if text.startswith("```"):
text = text.split("\n", 1)[1] # Remove first line
text = text.rsplit("```", 1)[0] # Remove last ```
return text.strip()Model menambahkan field tambahan:
Model mungkin mengembalikan field yang tidak kamu minta. Pydantic menangani ini — secara default mengabaikan field tambahan. Atau set model_config = ConfigDict(extra="forbid") untuk menolaknya.
Model menggunakan tipe salah:
Kadang model mengembalikan "0.92" (string) alih-alih 0.92 (number). model_validate Pydantic menangani sebagian besar type coercion secara otomatis.
Field kosong atau null:
Buat field opsional saat mungkin kosong:
class Result(BaseModel):
name: str
email: str | None = None # Model mungkin tidak menemukan email
topics: list[str] = [] # Default ke list kosongObjek bersarang:
Gemma 4 menangani JSON bersarang dengan baik, tapi jaga nesting maksimal 2-3 level:
class Address(BaseModel):
city: str
country: str
class Person(BaseModel):
name: str
age: int
address: Address # Satu level nesting — baikTips Performa
- Temperature lebih rendah (0.1-0.3) menghasilkan JSON lebih konsisten
- Schema lebih pendek mendapat kepatuhan lebih baik — jangan minta 20 field sekaligus
- Contoh few-shot di system prompt secara dramatis meningkatkan reliabilitas
- Model 26B secara signifikan lebih baik di JSON daripada E4B — lihat perbandingan model
- Thinking mode membantu dengan schema kompleks — lihat panduan thinking mode
Langkah Selanjutnya
- Gunakan output JSON dengan API Ollama di aplikasimu
- Deploy server API JSON dengan vLLM + Docker
- Fine-tune Gemma 4 untuk format JSON spesifikmu
- Pelajari tentang thinking mode untuk tugas terstruktur kompleks
Stop reading. Start building.
~/gemma4 $ Get hands-on with the models discussed in this guide. No deployment, no friction, 100% free playground.
Launch Playground />


