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
temperatureen 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_ides mejor queoid). - 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