BAML vs Instructor: Salidas estructuradas de LLM

Salidas de LLMs con verificación de tipos mediante BAML e Instructor

Índice

Al trabajar con modelos de lenguaje grandes (LLM) en producción, obtener salidas estructuradas y seguras en cuanto a tipos es fundamental. Dos marcos de trabajo populares, BAML e Instructor, adoptan enfoques diferentes para resolver este problema.

Esta comparación te ayuda a elegir la herramienta adecuada para tus aplicaciones de LLM en Python.

circular-flow

Comprender los desafíos de las salidas estructuradas

Los LLM generan naturalmente texto no estructurado, pero las aplicaciones modernas necesitan datos predecibles y analizables. Ya sea que estés construyendo chatbots, pipelines de extracción de datos o agentes de IA, necesitas objetos JSON, tipos de datos validados y manejo de errores, no respuestas en formato libre.

Tanto BAML como Instructor abordan este desafío, pero con filosofías fundamentalmente diferentes: BAML utiliza un enfoque basado en contratos con generación de código, mientras que Instructor aprovecha el sistema de tipos de Python con validación en tiempo de ejecución. Si te interesa un contexto más amplio sobre enfoques de salida estructurada en diferentes proveedores de LLM, comprender estos marcos de trabajo se vuelve aún más valioso. Para información sobre validación, rechazos y fixtures de pytest debajo de cualquier capa de marco de trabajo, consulta Validación de salida estructurada de LLM en Python que funciona.

BAML: Lenguaje específico de dominio para LLMs

BAML (el lenguaje de BoundaryML) introduce un DSL dedicado para definir interacciones con LLM. Escribes archivos .baml que declaran tus prompts, tipos y funciones, y luego BAML genera código cliente seguro en cuanto a tipos para múltiples lenguajes, incluido Python.

Características clave de BAML

Seguridad de tipos entre lenguajes: BAML genera clientes para Python, TypeScript y Ruby desde las mismas definiciones .baml, garantizando coherencia en toda tu stack.

Control de versiones para prompts: Tus prompts viven en archivos .baml, lo que facilita rastrearlos, revisarlos y probarlos de forma independiente del código de la aplicación.

Marco de trabajo de pruebas integrado: BAML incluye herramientas de pruebas para validar el comportamiento de los prompts antes del despliegue, detectando problemas temprano en el desarrollo.

Interfaz de Playground: El playground de BAML te permite iterar sobre los prompts visualmente con retroalimentación inmediata, acelerando los ciclos de desarrollo.

Ejemplo de implementación de BAML

# Primero, define tu esquema en un archivo .baml:
# persona.baml

class Person {
  name string
  age int
  occupation string
  skills string[]
}

function ExtractPerson(text: string) -> Person {
  client GPT4
  prompt #"
    Extrae información de la persona de: {{ text }}
    Devuelve datos estructurados.
  "#
}

El cliente de Python generado proporciona acceso seguro en cuanto a tipos:

from baml_client import b
from baml_client.types import Person

# Usa el cliente generado
text = "John Smith, 34, ingeniero de software experto en Python y Go"
result: Person = b.ExtractPerson(text)

print(f"{result.name} tiene {result.age} años")
print(f"Habilidades: {', '.join(result.skills)}")

El enfoque de BAML brilla cuando tienes múltiples servicios consumiendo los mismos contratos de LLM o cuando necesitas garantías sólidas sobre las formas de datos a través de los límites de los lenguajes.

Instructor: Marco de trabajo nativo de Pydantic para Python

Instructor adopta un enfoque primero en Python, extendiendo los modelos de Pydantic con capacidades de LLM. Se siente natural para los desarrolladores de Python que ya utilizan Pydantic para validación y hints de tipos.

Características clave de Instructor

Cero código boilerplate: Instructor funciona directamente con tus modelos Pydantic existentes utilizando decoradores simples. No requiere generación de código ni pasos de compilación.

Validación rica: Aprovecha todo el ecosistema de validación de Pydantic: validadores personalizados, restricciones de campos, campos computados y estructuras anidadas complejas.

Soporte para múltiples proveedores: Funciona perfectamente con OpenAI, Anthropic, Google y Ollama a través de una interfaz unificada.

Soporte para streaming: Soporte de primera clase para respuestas en streaming con actualizaciones incrementales del modelo Pydantic.

Lógica de reintento: Mecanismos de reintento integrados con backoff exponencial y recuperación de errores basada en validadores.

Ejemplo de implementación de Instructor

from pydantic import BaseModel, Field
from instructor import from_openai
from openai import OpenAI

# Define tu modelo Pydantic
class Person(BaseModel):
    name: str = Field(description="Nombre completo de la persona")
    age: int = Field(ge=0, le=120, description="Edad en años")
    occupation: str
    skills: list[str] = Field(description="Lista de habilidades profesionales")

# Parchea el cliente de OpenAI
client = from_openai(OpenAI())

# Extrae datos estructurados
text = "John Smith, 34, ingeniero de software experto en Python y Go"
result = client.chat.completions.create(
    model="gpt-4",
    response_model=Person,
    messages=[
        {"role": "user", "content": f"Extrae información de la persona: {text}"}
    ]
)

print(f"{result.name} tiene {result.age} años")
print(f"Habilidades: {', '.join(result.skills)}")

La fortaleza de Instructor radica en su simplicidad y su integración con el ecosistema de Python. Si ya estás utilizando Pydantic, la curva de aprendizaje es mínima. Para desarrolladores nuevos en Python o que necesiten una referencia rápida para patrones específicos de Python, nuestra hoja de trucos de Python proporciona recordatorios útiles de sintaxis junto con estos marcos de trabajo.

Comparación detallada: BAML vs Instructor

Experiencia de desarrollo

BAML requiere un paso de compilación adicional y configuración de herramientas. Escribes archivos .baml, ejecutas el generador y luego importas el código generado. Esto crea una separación clara entre la ingeniería de prompts y la lógica de la aplicación, lo cual puede ser beneficioso para equipos más grandes.

Instructor tiene cero fricción de configuración: instalas con pip y estás listo. Tus prompts viven junto a tu código, lo que facilita la iteración rápida para proyectos más pequeños o prototipos.

Seguridad de tipos y validación

BAML proporciona verificación de tipos en tiempo de compilación en el código generado. Tu IDE sabe exactamente qué campos están disponibles antes de ejecutar nada. La coherencia entre lenguajes está garantizada ya que el mismo archivo .baml genera clientes para todos los lenguajes soportados.

Instructor ofrece validación en tiempo de ejecución a través de Pydantic. Aunque los hints de tipos de Python proporcionan soporte del IDE, los errores aparecen durante la ejecución. Esto es estándar para Python, pero significa menos garantía estática que el código generado de BAML.

Trabajo con LLMs locales

Ambos marcos de trabajo soportan modelos locales, lo cual es crucial para la privacidad, el control de costos y el desarrollo sin conexión. Al usar Ollama u otros proveedores de LLM locales, mantienes los mismos beneficios de salida estructurada sin dependencias de API externas. Para un análisis más profundo sobre restringir LLMs con salida estructurada usando Ollama, Qwen3 y Python o Go, estos marcos de trabajo proporcionan abstracciones listas para producción sobre las APIs de nivel inferior.

BAML se conecta a Ollama configurando el cliente en tu archivo .baml:

# En tu archivo .baml:
client OllamaLocal {
  provider ollama
  options {
    model "llama2"
    base_url "http://localhost:11434"
  }
}

Instructor funciona con Ollama a través de la API compatible con OpenAI:

from openai import OpenAI
from instructor import from_openai

client = from_openai(OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"  # clave ficticia
))

Ten en cuenta que al trabajar con modelos locales, debes estar al tanto de posibles problemas de salida estructurada con modelos Ollama y GPT-OSS, ya que no todos los modelos manejan las salidas estructuradas con la misma fiabilidad.

Manejo de errores y reintentos

BAML maneja los reintentos a nivel del marco de trabajo con estrategias configurables. Los errores en la validación del esquema desencadenan re-prompting automático con contexto de error.

Instructor proporciona lógica de reintento decorativa con hooks para comportamiento personalizado. Puedes definir validadores que desencadenen reintentos con prompts modificados:

from instructor import patch
from tenacity import retry, stop_after_attempt

@retry(stop=stop_after_attempt(3))
def extract_with_retry(text: str) -> Person:
    return client.chat.completions.create(
        model="gpt-4",
        response_model=Person,
        messages=[{"role": "user", "content": text}]
    )

Pruebas y observabilidad

BAML incluye un marco de trabajo de pruebas donde puedes escribir casos de prueba directamente en archivos .baml, validando el comportamiento del prompt a través de diferentes entradas. El playground proporciona depuración visual.

Instructor se integra con marcos de trabajo de pruebas estándar de Python. Puedes usar fixtures de pytest, bibliotecas de mocking y ayudantes de aserción igual que con cualquier código Python.

Consideraciones de rendimiento

El rendimiento en tiempo de ejecución es comparable: ambos marcos de trabajo finalmente realizan las mismas llamadas a la API de LLM. La sobrecarga para la validación y el análisis es insignificante en comparación con la latencia de red y el tiempo de inferencia del modelo.

La velocidad de desarrollo difiere significativamente:

  • La generación de código de BAML significa mejor autocompletado y detección de errores más temprana, pero requiere un paso de compilación.
  • El enfoque de decoradores de Instructor significa una iteración más rápida pero descubrimiento de errores en tiempo de ejecución.

Para sistemas de producción que procesan millones de solicitudes, ambos marcos de trabajo manejan la carga igual de bien. Tu elección depende más de las preferencias del flujo de trabajo de desarrollo que de las características de rendimiento.

Cuándo elegir BAML

Selecciona BAML cuando necesites:

  • Soporte multilingüe: Acceder a los mismos contratos de LLM desde servicios de Python, TypeScript y Ruby.
  • Desarrollo basado en contratos: Desarrollo estilo API donde las interfaces de LLM se diseñan antes de la implementación.
  • Colaboración en equipo: Separar flujos de trabajo de ingeniería de prompts del desarrollo de aplicaciones.
  • Garantías de tipado fuerte: Comprobaciones en tiempo de compilación en toda tu stack.
  • Desarrollo visual de prompts: Iteración impulsada por playground en los prompts.

Cuándo elegir Instructor

Elige Instructor cuando quieras:

  • Proyectos solo en Python: No hay necesidad de coherencia entre lenguajes.
  • Prototipado rápido: Configuración mínima para hacer funcionar las salidas estructuradas.
  • Integración con Pydantic: Aprovechar modelos y validadores Pydantic existentes.
  • Despliegue simple: Sin pasos de compilación ni código generado para gestionar.
  • Rico ecosistema de Python: Usar bibliotecas y patrones específicos de Python.

Combinando enfoques

Algunos proyectos se benefician de usar ambos marcos de trabajo. Por ejemplo, podrías usar BAML para APIs orientadas al cliente que necesitan clientes multilingües, mientras usas Instructor para servicios internos de Python que requieren iteración rápida.

También puedes transitar entre marcos de trabajo a medida que tu proyecto madura: comenzando con Instructor para una validación rápida, y luego pasando a BAML cuando necesites soporte de lenguajes más amplio o contratos más estrictos.

Casos de uso en el mundo real

Pipeline de extracción de datos (BAML)

Un sistema de procesamiento de documentos usa BAML para extraer datos estructurados de facturas, contratos y recibos. Las definiciones .baml sirven como contratos entre el equipo de ML y los servicios backend, con clientes TypeScript para el panel web y clientes Python para el procesamiento por lotes.

Bot de soporte al cliente (Instructor)

Un bot de soporte usa Instructor para clasificar tickets, extraer intenciones de usuario y generar respuestas. El equipo itera rápidamente sobre prompts usando modelos Pydantic, con validadores que aseguran que los números de teléfono, correos electrónicos e IDs de tickets extraídos cumplan con los requisitos de formato.

Agente de IA multimodal (Ambos)

Un sistema de agente de IA usa BAML para contratos centrales de comunicación agente-a-agente, garantizando seguridad de tipos en el sistema distribuido, mientras que los agentes individuales usan Instructor internamente para un procesamiento flexible y nativo de Python de las entradas del usuario. Patrones similares se aplican al construir servidores MCP en Python, donde las salidas estructuradas permiten una integración de herramientas fiable con asistentes de IA.

Rutas de migración e integración

Si ya estás usando análisis básico de JSON con LLMs, ambos marcos de trabajo ofrecen rutas de migración sencillas:

De JSON a BAML: Convierte tus esquemas JSON a definiciones de tipo BAML, mueve los prompts a archivos .baml, genera clientes y reemplaza el análisis manual con tipos generados.

De JSON a Instructor: Añade modelos Pydantic que coincidan con tu estructura JSON, instala instructor, parchea tu cliente de OpenAI y reemplaza el análisis de JSON con parámetros response_model.

Ambas migraciones pueden ser incrementales: no necesitas convertir toda tu base de código de una vez.

Perspectivas futuras y comunidad

Ambos marcos de trabajo están siendo desarrollados activamente con comunidades sólidas:

BAML (BoundaryML) se centra en expandir el soporte de lenguajes, mejorar el playground y mejorar las capacidades de pruebas. El respaldo comercial sugiere estabilidad a largo plazo.

Instructor mantiene una fuerte presencia de código abierto con actualizaciones frecuentes, documentación extensa y una adopción creciente. El proyecto está bien mantenido por Jason Liu y colaboradores.

Conclusión

BAML e Instructor representan dos enfoques excelentes pero distintos para las salidas estructuradas de LLM. La filosofía de BAML basada en contratos y multilingüe se adapta a equipos que construyen sistemas distribuidos con requisitos estrictos de tipos. El enfoque nativo de Python y basado en Pydantic de Instructor se ajusta al desarrollo rápido y a las stacks centradas en Python.

Ninguno es universalmente mejor: tu elección depende del tamaño de tu equipo, preferencias de lenguaje, flujo de trabajo de desarrollo y requisitos de seguridad de tipos. Muchos equipos encontrarán que comenzar con Instructor para prototipado y luego adoptar BAML para arquitecturas de producción multiservicio ofrece lo mejor de ambos mundos.

Enlaces útiles

Artículos relacionados en este sitio

Referencias externas

Suscribirse

Recibe nuevas publicaciones sobre sistemas, infraestructura e ingeniería de IA.