0% read

Salida Estructurada de Gemma 4: Como Obtener JSON Confiable Cada Vez

abr. 7, 2026

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 list

Objetos 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 — fine

Consejos 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

gemma4 — interact

Stop reading. Start building.

~/gemma4 $ Get hands-on with the models discussed in this guide. No deployment, no friction, 100% free playground.

Launch Playground />
Gemma 4 AI

Gemma 4 AI

Related Guides

Salida Estructurada de Gemma 4: Como Obtener JSON Confiable Cada Vez | Blog