Contenido

Tutorial de salidas estructuradas en Ollama: genera JSON con un formato fijo

Al integrar un modelo en un programa, a menudo no necesitamos un texto, sino datos que el programa pueda leer directamente. Por ejemplo, extraer el nombre, el número de pedido y el tipo de problema de un correo de reclamación.

El parámetro format de Ollama puede obligar al modelo a generar una salida que cumpla el formato JSON que especifiques.

La forma más sencilla: pedir solo JSON

curl http://localhost:11434/api/chat -d '{
  "model": "qwen3:8b",
  "messages": [{ "role": "user", "content": "Enumera tres ciudades de Taiwán y su población. Responde en JSON" }],
  "format": "json",
  "stream": false
}'

La salida garantiza ser JSON válido, pero el modelo decide los nombres y la estructura de los campos, así que pueden variar cada vez.

Especificar un JSON Schema

Incluye el JSON Schema en format para definir los campos con precisión:

curl http://localhost:11434/api/chat -d '{
  "model": "qwen3:8b",
  "messages": [{ "role": "user", "content": "Presenta la ciudad de Taipéi" }],
  "format": {
    "type": "object",
    "properties": {
      "city": { "type": "string" },
      "population": { "type": "integer" },
      "landmarks": { "type": "array", "items": { "type": "string" } }
    },
    "required": ["city", "population", "landmarks"]
  },
  "stream": false
}'

message.content será una cadena JSON que cumple el formato.

Python: definir el formato con Pydantic

from ollama import chat
from pydantic import BaseModel


class Complaint(BaseModel):
    customer_name: str
    order_id: str | None
    category: str          # por ejemplo: logística, calidad, reembolso, otro
    urgency: int           # 1~5
    summary: str


email = """
Hola, soy Wang Xiaomei. El embalaje exterior del secador de pelo del pedido A20261012-88 estaba dañado cuando lo recibí,
y el aparato no responde al conectarlo. Me urge porque viajaré al extranjero la semana que viene. Por favor, gestionen cuanto antes un cambio o una devolución. Gracias.
"""

response = chat(
    model="qwen3:8b",
    messages=[
        {"role": "system", "content": "Extrae información del texto de la reclamación. category solo puede ser: logística, calidad, reembolso, otro."},
        {"role": "user", "content": email},
    ],
    format=Complaint.model_json_schema(),
    options={"temperature": 0},
)

data = Complaint.model_validate_json(response.message.content)
print(data)
print(data.urgency)
customer_name='Wang Xiaomei' order_id='A20261012-88' category='calidad' urgency=4 summary='El embalaje del secador estaba dañado y no funcionaba; solicita un cambio o una devolución cuanto antes'
4

model_validate_json vuelve a validar los tipos para garantizar que el programa reciba datos correctos.

Limitar las opciones: usar Literal o Enum

from typing import Literal


class Complaint(BaseModel):
    category: Literal["logística", "calidad", "reembolso", "otro"]

Así, el modelo solo puede elegir uno de estos cuatro valores, lo que es más fiable que explicarlos en el prompt.

JavaScript: definir el formato con Zod

npm install ollama zod
import ollama from "ollama";
import { z } from "zod";

const Product = z.object({
  name: z.string(),
  price: z.number(),
  tags: z.array(z.string()),
});

const response = await ollama.chat({
  model: "qwen3:8b",
  messages: [{ role: "user", content: "Esta funda para iPhone cuesta 590 yuanes; es resistente a golpes, delgada y compatible con MagSafe" }],
  format: z.toJSONSchema(Product),
});

const product = Product.parse(JSON.parse(response.message.content));
console.log(product);

(Zod 4 incluye z.toJSONSchema; con versiones anteriores de Zod 3 puedes usar el paquete zod-to-json-schema.)

Ejemplo: clasificación por lotes

from typing import Literal
from ollama import chat
from pydantic import BaseModel


class Review(BaseModel):
    sentiment: Literal["positivo", "negativo", "neutral"]
    topics: list[str]


reviews = [
    "La comida estaba muy rica, pero esperamos casi una hora.",
    "El personal fue muy amable. ¡Volveré!",
    "El precio es normal y el sabor también.",
]

for text in reviews:
    r = chat(
        model="qwen3:8b",
        messages=[{"role": "user", "content": f"Analiza esta reseña: {text}"}],
        format=Review.model_json_schema(),
        options={"temperature": 0},
    )
    print(text, "→", Review.model_validate_json(r.message.content))

Extraer datos de imágenes

Con un modelo de visión, puedes extraer datos estructurados de facturas, tarjetas de visita y capturas de pantalla:

class BusinessCard(BaseModel):
    name: str
    company: str | None
    title: str | None
    phone: str | None
    email: str | None


r = chat(
    model="gemma3",
    messages=[{"role": "user", "content": "Extrae la información de la tarjeta de visita", "images": ["./card.jpg"]}],
    format=BusinessCard.model_json_schema(),
    options={"temperature": 0},
)
print(BusinessCard.model_validate_json(r.message.content))

Consejos para mejorar la precisión

  • Establece temperature en 0 para que la salida sea más estable.
  • Explica también en el prompt el formato y el significado de los campos; no dependas solo del schema. Por ejemplo: «urgency es un entero de 1 a 5; 5 indica la mayor urgencia».
  • Usa nombres de campo claros (order_id es mejor que oid).
  • Permite valores vacíos en los campos que podrían no tener datos (str | None) para evitar que el modelo invente contenido con tal de rellenarlos.
  • Las salidas estructuradas garantizan el formato, pero no la exactitud del contenido. Aun así, valida los datos importantes.

Actualizado 12 de octubre de 2026