Si estas construyendo una app sobre Gemma 4, necesitas salida estructurada — no texto de forma libre. Necesitas JSON que puedas parsear, validar y canalizar a tu base de datos o API. Cada vez, sin excepciones.
Esta es una de las partes mas complicadas de trabajar con LLMs locales, pero con las tecnicas correctas, Gemma 4 puede ser sorprendentemente confiable. Vamos a recorrer cada metodo.
Por Que Importa la Salida Estructurada
Cuando estas usando Gemma 4 como un componente en un sistema mas grande — no solo chateando con el — necesitas salida predecible:
# This is what you want:
{"sentiment": "positive", "confidence": 0.92, "topics": ["pricing", "support"]}
# This is what you don't want:
"The sentiment of this text is positive, with a confidence of about 92%..."El primero se puede parsear y usar programaticamente. El segundo requiere otra ronda de parseo, lo que agrega latencia, costo y puntos de falla.
Metodo 1: Tecnica de Prompt del Sistema
El enfoque mas simple — dile al modelo exactamente lo que quieres en el prompt del sistema:
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)Esto funciona la mayoria del tiempo. Pero "la mayoria del tiempo" no es suficiente para produccion. El modelo puede ocasionalmente agregar un preambulo como "Here's the JSON:" o envolver la salida en bloques de codigo markdown.
Metodo 2: Parametro Format de Ollama
Ollama tiene un parametro format integrado que restringe la salida a JSON valido:
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,
})
# This is guaranteed to be valid JSON
result = response.json()["message"]["content"]
parsed = json.loads(result)La bandera format: "json" le dice a Ollama que restrinja la generacion de tokens para producir solo JSON valido. Esto es mucho mas confiable que la ingenieria de prompts por si sola.
Limitacion: Garantiza sintaxis JSON valida, pero no garantiza el esquema. El modelo puede devolver {"answer": "positive"} en lugar de tu formato esperado. Aun necesitas validacion.
Metodo 3: Definicion de Esquema con Pydantic
Para codigo de produccion, define tu esquema esperado con Pydantic y valida contra el:
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)
# Usage
result = analyze_sentiment("Great product, terrible shipping time.")
print(f"Sentiment: {result.sentiment} ({result.confidence:.0%})")
print(f"Topics: {', '.join(result.topics)}")Esto te da seguridad de tipos y validacion. Si el modelo devuelve algo inesperado, Pydantic lanza un error claro en lugar de corromper silenciosamente tus datos.
Metodo 4: Patron de Validacion y Reintento
Para maxima confiabilidad, agrega un bucle de reintento:
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")
# Usage
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,
)Decisiones clave de diseno:
- Comienza con temperatura baja (0.1) para consistencia, aumenta en los reintentos para variedad
- Usa
format: "json"para garantizar sintaxis JSON valida - Valida con Pydantic para correccion de esquema
- Limita reintentos a 3 — si falla 3 veces, el prompt probablemente necesita trabajo
Fallos Comunes y Soluciones
El modelo envuelve JSON en markdown:
```json
{"key": "value"}
```Solucion: Usa format: "json" en Ollama. Si eso no esta disponible, elimina el 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()El modelo agrega campos extra:
El modelo puede devolver campos que no pediste. Pydantic maneja esto — por defecto ignora los campos extra. O configura model_config = ConfigDict(extra="forbid") para rechazarlos.
El modelo usa tipos incorrectos:
A veces el modelo devuelve "0.92" (cadena) en lugar de 0.92 (numero). model_validate de Pydantic maneja la mayoria de la coercion de tipos automaticamente.
Campos vacios o nulos:
Haz los campos opcionales cuando pueden estar vacios:
class Result(BaseModel):
name: str
email: str | None = None # Model might not find an email
topics: list[str] = [] # Default to empty listObjetos anidados:
Gemma 4 maneja JSON anidado bien, pero mantén la anidacion en 2-3 niveles max:
class Address(BaseModel):
city: str
country: str
class Person(BaseModel):
name: str
age: int
address: Address # One level of nesting — fineConsejos de Rendimiento
- Temperatura mas baja (0.1-0.3) produce JSON mas consistente
- Esquemas mas cortos obtienen mejor cumplimiento — no pidas 20 campos a la vez
- Ejemplos few-shot en el prompt del sistema mejoran dramaticamente la confiabilidad
- El modelo 26B es significativamente mejor en JSON que E4B — ve comparacion de modelos
- El modo de pensamiento ayuda con esquemas complejos — ve guia del modo de pensamiento
Siguientes Pasos
- Usa salida JSON con la API de Ollama en tus aplicaciones
- Despliega un servidor API JSON con vLLM + Docker
- Haz fine-tuning de Gemma 4 para tu formato JSON especifico
- Aprende sobre el modo de pensamiento para tareas estructuradas complejas
Stop reading. Start building.
~/gemma4 $ Get hands-on with the models discussed in this guide. No deployment, no friction, 100% free playground.
Launch Playground />


