Generación restringida con Outlines
Índice de contenidos
- Puntos clave
- ¿Qué es la generación restringida?
- ¿Qué soporta Outlines: JSON, regex y gramáticas?
- ¿Cómo garantiza Outlines una salida válida?
- ¿Cómo se usa Outlines con vLLM y modelos locales?
- ¿Outlines o Instructor?
- Preguntas frecuentes
- ¿La generación restringida ralentiza mucho el modelo?
- ¿Outlines mejora la calidad de las respuestas del modelo?
- ¿Puedo usar Outlines con modelos de OpenAI o Anthropic?
- Conclusión
- Fuentes
Outlines es una biblioteca de Python que aplica generación restringida: obliga al modelo a producir una salida que se ajusta a un esquema JSON, una expresión regular o una gramática, con garantía por construcción y no estadística. Funciona con vLLM, Transformers, Ollama y llama.cpp, y su motor escrito en Rust apenas añade sobrecarga por token.
Outlines es una biblioteca de Python que aplica generación restringida: en lugar de pedirle a un LLM que devuelva JSON y cruzar los dedos, obliga al modelo a emitir solo tokens que mantengan la salida dentro de un esquema, así que el resultado siempre es válido por construcción. En esta guía verás qué formatos soporta (JSON, expresiones regulares y gramáticas), cómo garantiza una salida correcta enmascarando los logits, cómo usarla con vLLM y modelos que ejecutas en tu propia máquina, y en qué se diferencia de Instructor. La misma explicación está disponible en inglés.
Puntos clave
- Outlines es la biblioteca de referencia de la empresa dottxt para generación restringida; supera las 14 000 estrellas en GitHub y los 65 millones de descargas, y su última versión es la v1.3.1, publicada el 30 de junio de 2026.
- La garantía es matemática, no estadística: en cada paso se ponen a cero los tokens que romperían el formato, de modo que un JSON con una llave sin cerrar sencillamente nunca es una opción alcanzable.
- Puedes restringir la salida a un modelo de Pydantic, un JSON Schema, una expresión regular o una gramática libre de contexto en notación Lark; también a una elección entre varias opciones con
Literal. - El motor vive en outlines-core, reescrito en Rust para velocidad y portabilidad; precompila un índice una sola vez y lo reutiliza en cada petición, por lo que el coste por token es mínimo.
- Funciona con 12 backends (vLLM, Transformers, Ollama, llama.cpp, MLX, SGLang, TGI y las API de OpenAI, Anthropic o Gemini), pero la garantía real por construcción solo existe cuando controlas la inferencia en tu propia máquina.
¿Qué es la generación restringida?
La generación restringida (en inglés constrained o guided decoding) consiste en limitar, en cada paso de generación, el conjunto de tokens que el modelo puede elegir para que la secuencia resultante cumpla siempre un formato objetivo. Un LLM produce en cada paso una distribución de probabilidad sobre todo su vocabulario, unos decenas de miles de tokens. Sin restricción, muestrea de esa distribución tal cual y puede colarse una coma de más, una llave sin cerrar o un preámbulo del tipo «aquí tienes el JSON».
Outlines interviene justo ahí. Toma tu especificación de formato y, antes de muestrear, tacha (pone a probabilidad cero) todos los tokens que no podrían extender una cadena válida. El modelo solo elige entre lo que queda. Frente a la técnica general de decodificación restringida, que ya cubrimos como concepto, este artículo se centra en la biblioteca concreta: su API, su motor y su uso práctico.
¿Qué soporta Outlines: JSON, regex y gramáticas?
La fuerza de Outlines está en la variedad de restricciones que acepta con la misma llamada. A partir de la reescritura v1.0, publicada el 18 de junio de 2025, la interfaz se simplificó: creas un modelo con una función de fábrica y lo llamas pasándole el tipo de salida que quieres. Un modelo de Pydantic es la forma más habitual:
import outlines
from transformers import AutoModelForCausalLM, AutoTokenizer
from pydantic import BaseModel
class Factura(BaseModel):
numero: str
total: float
lineas: list[str]
nombre = "HuggingFaceTB/SmolLM2-1.7B-Instruct"
modelo = outlines.from_transformers(
AutoModelForCausalLM.from_pretrained(nombre),
AutoTokenizer.from_pretrained(nombre),
)
# El segundo argumento es el tipo de salida: aqui, un esquema Pydantic
factura = modelo("Extrae los datos de: Factura A-0012, total 128.50", Factura)
print(factura)
Pero el tipo de salida no tiene por qué ser un objeto. Puedes pasar una expresión regular para forzar, por ejemplo, un código postal o una fecha; un Literal para obligar a elegir entre unas cuantas etiquetas; o una gramática libre de contexto en notación Lark cuando la estructura es más compleja que un simple JSON:
from typing import Literal
# Una expresion regular: exactamente un codigo de la forma A-0000
codigo = modelo("Dame un codigo de factura valido", r"[A-Z]-\d{4}")
# Una eleccion cerrada entre etiquetas
sentimiento = modelo("Clasifica: el envio llego tarde", Literal["positivo", "negativo", "neutro"])
Esta combinación (Pydantic, JSON Schema, regex, gramáticas y elecciones cerradas) cubre casi cualquier necesidad de extracción o clasificación estructurada.
¿Cómo garantiza Outlines una salida válida?
Aquí está la diferencia con el clásico bucle de reintentos. Outlines no genera y luego valida: restringe durante la generación. Su motor, la biblioteca outlines-core reescrita en Rust, construye a partir de tu esquema un índice que asocia cada estado de un autómata finito con el conjunto de tokens del vocabulario que son legales en ese estado. Esa idea procede del artículo fundacional de la biblioteca, de Brandon T. Willard y Rémi Louf, que reformula la generación guiada mediante autómatas finitos y afirma que el método «añade poca sobrecarga al proceso de generación de la secuencia de tokens».
El flujo en cada paso es siempre el mismo:
- El modelo produce los logits (las puntuaciones de cada token del vocabulario).
- Outlines consulta el índice para el estado actual y enmascara todos los tokens que no mantendrían la salida dentro del esquema.
- Se normaliza y se muestrea únicamente sobre el subconjunto legal.
- El autómata avanza al siguiente estado y se repite.
La consecuencia es una garantía por construcción: no existe rama del árbol de generación que llegue a producir un JSON roto, porque el token que lo rompería no es alcanzable. El índice se compila una sola vez por esquema y se reutiliza en cada petición, así que el coste marginal por token es muy bajo. Conviene recordar una limitación honesta: la garantía es sintáctica, no semántica. El JSON será válido, pero si el modelo no entendió el documento, los valores seguirán siendo incorrectos.
¿Cómo se usa Outlines con vLLM y modelos locales?
Donde Outlines rinde de verdad es sobre un motor de inferencia que controlas. Se integra con vLLM tanto en modo offline (sin levantar un servidor) como a través del servidor, donde figura como uno de los backends de guided decoding. En modo offline la llamada es directa:
import outlines
from vllm import LLM
# Instala vLLM ANTES que Outlines para evitar conflictos de version
modelo = outlines.from_vllm_offline(LLM("Qwen/Qwen3-4B-Instruct"))
factura = modelo("Extrae los datos de la factura adjunta...", Factura)
El mismo patrón vale para from_llamacpp, from_mlxlm o from_ollama: eliges la función de fábrica según el runtime que ya uses y el resto del código no cambia. Como la restricción se aplica sobre los logits, cualquier modelo abierto sirve, sin necesidad de que esté afinado para llamar a herramientas. Si estás decidiendo qué modelo abierto ejecutar, la comparativa de modelos abiertos con tool calling te ayudará a elegir. Requisito práctico: Outlines exige Python 3.10 o superior (hasta la 3.13).
¿Outlines o Instructor?
Las dos bibliotecas resuelven el mismo dolor (obtener salidas estructuradas fiables) con filosofías opuestas, y la elección depende de dónde se ejecuta tu modelo.
| Criterio | Outlines | Instructor |
|---|---|---|
| Cómo restringe | Enmascara logits durante la decodificación | Valida con Pydantic y reintenta |
| Garantía | Por construcción (matemática) | Por reintento (probabilística) |
| Dónde brilla | Modelos abiertos en tu hardware | API cloud (OpenAI, Anthropic) |
| Coste del error | Ninguno: nunca hay salida inválida | Una llamada extra por cada reintento |
En resumen: si sirves un modelo abierto con vLLM, llama.cpp o Transformers, Outlines te da la garantía más fuerte porque actúa sobre cada token. Si trabajas contra una API cerrada sin acceso al motor de inferencia, Instructor es la vía pragmática, ya que envuelve el cliente y reintenta hasta que la respuesta valida. Outlines también admite modelos por API (con from_openai), pero en ese caso delega en el modo estructurado del proveedor y pierde el enmascarado de tokens.
Preguntas frecuentes
¿La generación restringida ralentiza mucho el modelo?
Poco. El índice del autómata se compila una sola vez por esquema y luego el enmascarado por token es una operación muy barata, por eso el artículo original habla de «poca sobrecarga». En la práctica cada token puede ser algo más lento que sin restricción, pero desaparecen las pasadas completas de reintento, así que en extracción a gran escala la cuenta sale a favor.
¿Outlines mejora la calidad de las respuestas del modelo?
No en el sentido semántico. Garantiza que la salida cumple el formato (que el JSON parsea y respeta el esquema), pero no que los valores sean correctos. Si el modelo interpreta mal el documento de entrada, producirá un JSON perfectamente válido con datos equivocados. La restricción resuelve el formato, no la comprensión.
¿Puedo usar Outlines con modelos de OpenAI o Anthropic?
Sí, con from_openai o from_anthropic, pero conviene entender el matiz: con una API cerrada Outlines no puede enmascarar los logits, así que se apoya en el modo de salida estructurada del propio proveedor. La garantía formal por construcción solo la obtienes cuando controlas la inferencia con vLLM, Transformers o llama.cpp en tu propia máquina.
Conclusión
Outlines mueve la validación de formato desde el código de tu aplicación hasta la propia capa de generación, que es donde el coste marginal es más bajo y la garantía más fuerte. Para extracción estructurada masiva, agentes que emiten llamadas a herramientas o generación de datos sintéticos con esquema fijo, convierte un problema probabilístico en una certeza por construcción. El siguiente paso es sencillo: instala la biblioteca con pip install outlines, elige un modelo abierto que sirvas con vLLM, define tu esquema con Pydantic y compara el resultado con lo que hoy parcheas a base de reintentos.
Fuentes: [1] Documentación oficial de Outlines[1], [2] Outlines en GitHub[2], [3] Efficient Guided Generation for Large Language Models, de Willard y Louf[3], [4] Lanzamiento de outlines-core 0.1.0 en el blog de Hugging Face[4], [5] Paquete outlines en PyPI[5].
Fuentes
Código fuente
Accede a todo el código fuente de este artículo en GitHub.
Ver en GitHub