2 puntos por GN⁺ 2024-12-09 | 1 comentarios | Compartir por WhatsApp
  • Ahora es posible restringir las respuestas de modelos locales para que cumplan con un JSON Schema, lo que reduce la carga de parsing posterior y hace más consistentes los resultados de extracción de datos
  • Los usuarios pueden solicitar salidas estructuradas de la misma forma en cURL, Python y JavaScript, pasando el esquema en el parámetro format
  • En Python, usar Pydantic y en JavaScript usar Zod facilita conectar la definición del esquema y la validación de respuestas a nivel de código
  • Los ejemplos devuelven y validan información de países, texto sobre mascotas y resultados de análisis de imágenes con llama3.2-vision en campos JSON definidos
  • Si se necesitan resultados estables, se recomienda incluir “return as JSON” en el prompt y bajar la temperature a 0

Restringir respuestas del modelo con JSON Schema

  • Ollama admite salidas estructuradas para restringir la salida del modelo de modo que siga un formato específico de JSON Schema
  • Las librerías de Ollama para Python y JavaScript también se actualizaron para admitir salidas estructuradas
  • Esta función puede usarse para las siguientes tareas
    • Parsing de datos desde documentos
    • Extracción de datos desde imágenes
    • Estructuración de respuestas de modelos de lenguaje
    • Obtener mayor confiabilidad y consistencia que con JSON mode

Instalación y forma de invocación

  • Es necesario descargar la versión más reciente de Ollama
  • La librería de Python se actualiza a la versión más reciente con el siguiente comando
pip install -U ollama
  • La librería de JavaScript se instala con el siguiente comando
npm i ollama
  • Al solicitar salidas estructuradas, se pasa el esquema en el parámetro format ya sea en la solicitud cURL o en las librerías de Python y JavaScript

Enviar JSON Schema con cURL

  • El ejemplo con cURL envía una solicitud a http://localhost:11434/api/chat, con model configurado como llama3.1 y stream como false
  • En format se incluye un esquema de objeto con name, capital y languages
    • name: cadena
    • capital: cadena
    • languages: arreglo de cadenas
    • Los tres campos están marcados como required
  • La respuesta se devuelve con el formato del JSON Schema incluido en la solicitud
{
  "capital": "Ottawa",
  "languages": [
    "English",
    "French"
  ],
  "name": "Canada"
}

Uso de las librerías de Python y JavaScript

  • Python

    • La librería de Ollama para Python pasa el esquema como objeto JSON en el parámetro format
    • El esquema puede pasarse como dict, y la forma recomendada es serializarlo con model_json_schema() de Pydantic
    • El ejemplo define un modelo Country con los campos name, capital y languages, y valida la respuesta con Country.model_validate_json()
country = Country.model_validate_json(response.message.content)
  • La salida de ejemplo devuelve Canada, Ottawa, English y French en los campos definidos
  • JavaScript

    • La librería de Ollama para JavaScript pasa el esquema como objeto JSON en el parámetro format
    • El esquema puede pasarse como object, y la forma recomendada es usar Zod junto con zodToJsonSchema()
    • El ejemplo define el esquema Country como un objeto Zod y, después de aplicar JSON.parse() al cuerpo de la respuesta, valida con Country.parse()
const country = Country.parse(JSON.parse(response.message.content));

Extracción de datos desde texto e imágenes

  • Extracción de texto sobre mascotas

    • Las salidas estructuradas pueden usarse para extraer la información necesaria desde texto
    • El ejemplo define los modelos Pydantic Pet y PetList para devolver información sobre mascotas en una estructura JSON
      • Pet: name, animal, age, color, favorite_toy
      • PetList: arreglo pets
    • El texto de entrada incluye información sobre dos gatos
      • Luna: 5 años, pelaje gris, le gusta el yarn
      • Loki: 2 años, color negro, le gustan las tennis balls
    • La salida se valida como una lista de objetos Pet conforme al esquema definido
  • Descripción de imágenes con modelo de visión

    • Las salidas estructuradas pueden usarse junto con un modelo de visión
    • El ejemplo analiza una imagen con llama3.2-vision y devuelve un resultado ajustado al esquema ImageDescription
    • El esquema incluye los siguientes campos
      • summary
      • objects
      • scene
      • colors
      • time_of_day
      • setting
      • text_content
    • La solicitud de ejemplo indica analizar en la imagen objetos, escena, colores y texto detectable
    • En las opciones, temperature se configura en 0 para usar una salida más determinística
    • La salida de ejemplo resume una escena de palmeras en la playa y devuelve en campos estructurados objetos como tree y beach, además de colores, hora del día y un entorno exterior

Uso de la API compatible con OpenAI

  • El ejemplo compatible con OpenAI configura el cliente OpenAI con base_url="http://localhost:11434/v1"; y api_key="ollama"
  • Usa client.beta.chat.completions.parse() para pasar el modelo Pydantic PetList en response_format
  • En la respuesta se revisa completion.choices[0].message, y si existe parsed, se imprime el resultado parseado
  • Si existe refusal, se imprime la respuesta de rechazo, y openai.LengthFinishReasonError se trata como un caso de demasiados tokens

Configuración para salidas estables y planes futuros

  • Para definir el esquema de respuesta, se recomienda usar Pydantic en Python o Zod en JavaScript
  • Es recomendable agregar “return as JSON” al prompt para que el modelo entienda la solicitud
  • Si se quiere una salida más determinística, se configura la temperature en 0
  • Los planes a futuro son los siguientes
    • Exposición de logits para generación controlada
    • Mejoras de rendimiento y precisión en las salidas estructuradas
    • Aceleración por GPU para sampling
    • Soporte para formatos adicionales más allá de JSON Schema

1 comentarios

 
GN⁺ 2024-12-09
Opiniones en Hacker News
  • Si necesitas restricciones de salida más fuertes, llama.cpp soporta GBNF
    https://github.com/ggerganov/llama.cpp/blob/master/grammars/...

    • Parece que justo eso es lo que están usando
    • Me pregunto si la calidad de salida para gramáticas arbitrarias resultó satisfactoria
      A simple vista, como los modelos han visto mucho más JSON, da la impresión de que generarían mejor JSON que otros formatos
    • Me pregunto en qué sentido es más potente
  • Es una buena noticia
    Estaba pensando en cómo armar un prompt general para crear datos CSV sin frases innecesarias al principio o al final como "Here is your data" o "Please note blah blah", así que me alegra que ahora se pueda definir exactamente el formato de retorno deseado y pasar la salida estructurada directamente como CSV

    • Aun así, hay que incluir la instrucción de generar CSV para que el prompt entre en el contexto correcto
      Si no, podrías obtener una salida que técnicamente tiene formato CSV, pero no significa nada. Puede ser que el modelo en realidad intentara escribir una respuesta en párrafos, pero el sampler de tokens eligió tokens de baja probabilidad que el modelo no tenía muchas ganas de decir
    • En muchos casos, prellenar el inicio de la salida con \n y hacer que se detenga en puede evitar este problema
  • Funciona. Le pasé la siguiente oración a gemma2:2b y obtuve el JSON que quería
    You have spent 190 at Fresh Mart. Current balance: 5098
    El resultado fue {"amount": 190, "balance": 5098, "category": "Shopping", "place": "Fresh Mart"}

    • Es un JSON bastante inconsistente, pero siendo un modelo muy pequeño y además gemma, es entendible
  • Realmente sorprendente. Era una de las funciones que de verdad quería
    ollama me gusta porque hace sentir que puedes usar un LLM como cualquier otro programa de UNIX, y que los LLM pertenecen de forma natural a UNIX
    Eso sí, me pregunto si alguien lo ha logrado correr bien en GPU AMD. He oído que es más difícil, pero cuando compre una tarjeta el año que viene quisiera apoyar a la competencia

    • Sí se puede. Incluso con GPU integrada
      En una mini PC con una 780M, asigné 16 GB de memoria compartida desde el BIOS y funcionó bastante bien
  • Me pregunto qué efecto tienen estas restricciones en la calidad de salida del LLM
    En algunos casos, si la calidad de salida es mayor, preferiría parsear Markdown o texto plano

    • Al usar modelos de OpenAI, si puedes asumir el costo extra en tokens, una estrategia de dos pasadas funcionó muy bien
      En la primera, usas un modelo pesado y lenguaje natural para manejar el razonamiento en secciones Markdown y entregar la respuesta final en lenguaje natural. Si es posible, la etiquetas claramente con encabezados Markdown
      En la segunda, usas un modelo más barato y rápido para convertir esa respuesta a un formato de salida estructurado, para que la consuma la parte no LLM del pipeline
      Básicamente, con el modo de esquema JSON creas límites claros alrededor de las partes ambiguas en lenguaje natural, y haces que el LLM actúe como un preprocesador que captura su propia salida en un formato útil
    • Depende de qué tan ajustado esté el modelo para salida JSON
      Además, hay que informarle el esquema al modelo. Si no, aparecen más problemas raros de tokenización
      Por ejemplo, si el esquema espera la clave JSON "foobarbaz" y la tokenización BPE estándar es ["foobar", "baz"], las máscaras de tokens que generan las bibliotecas actuales de salida restringida pueden permitir que el modelo elija entre "f", "foo" o "foobar". Si el modelo elige "foo", la restricción fuerza como siguiente token, por ejemplo, "bar" y "baz". Entonces el modelo ve ["foo", "bar", "baz"] en vez de ["foobar", "baz"], y se confunde [0]
      Si el modelo sabe por el prompt que "foobarbaz" es una de las claves del esquema, normalmente preferirá "foobar" antes que "foo"
      [0] En los modelos más recientes, por la normalización, esos tokens están relacionados entre sí, pero no son lo mismo
    • Depende de la situación, pero en términos de inferencia tiene un impacto negativo, aunque en la mayoría de los casos la diferencia no fue muy grande
      Depende mucho de si el LLM y el prompt ya tenían probabilidades de producir una respuesta JSON. Cuanto más fuerzas al LLM, menos probable es que genere una entrada normal
      En modelos pequeños, llegas antes al borde del espacio donde hay capacidad predictiva significativa, y la salida empieza a parecerse más a ruido aleatorio
      No es una medición rigurosa, sino una intuición después de pasar mucho tiempo en varios proyectos con LLM. Todavía no he probado estas herramientas específicas, pero antes ollama ya podía garantizar salidas JSON con algo que parecía una técnica similar, y con un socio también trabajé en algo parecido a jsonformer para oobabooga, otra herramienta de runtime para LLM
    • Sigo de cerca la investigación relacionada. Estoy revisando formas de mejorar el muestreo en general, tanto en velocidad como en precisión
      Espero que, si se incorporan cambios de ese tipo, también sea posible generar estructuras generales no limitadas a JSON
    • Puedo decir que estaba completamente equivocado sobre la utilidad de herramientas como instructor
      Es fácil quemar muchos tokens, pero si lo que intentas hacer justifica ese costo, puedes empujarlo bastante fuerte. Aunque no sea la máxima calidad absoluta, una herramienta que te da un 95% sin esfuerzo merece estar en la caja de herramientas
  • Me pregunto si se puede usar con cualquier modelo compatible
    Pregunto porque en mi hardware solo puedo correr de forma estable modelos de 1B a 3B

    • Soy el autor de la publicación del blog. Debería poder usarse con cualquier modelo
      Con modelos pequeños los resultados pueden ser irregulares, pero pedir “devuelve x como JSON” suele ayudar a la precisión
  • El PR relacionado con esta función estuvo abierto casi 1 año
    Es un poco decepcionante que los mantenedores hayan estado tan callados

    • Soy el autor del post y uno de los mantenedores. Estoy de acuerdo. Los mantenedores respondimos tarde y, en general, queremos fomentar más contribuciones
      Espero que el próximo año podamos atender y fusionar mejor los PR de la comunidad
    • Por el ambiente, parece que van por un camino centrado en la empresa, y por eso ven todo desde esa óptica y desde la maximización de ingresos
    • Yo también quería colaborar en construir esto, pero la conversación se cortó de repente
  • Todavía uso oobabooga. Gracias al soporte de exlv2, se puede hacer inferencia de forma mucho más eficiente en una dual 3090

    • Hace tiempo que no toco ooba, pero me da curiosidad cuál es la situación de exl2 y de métodos de cuantización no homogéneos como q3k_s
      Según recuerdo, aunque exl2 era más rápido, la cuantización gptq parecía ser mejor en precisión, especialmente con profundidades de bits bajas
  • Me pregunto qué valor agregado tiene frente a outlines
    https://www.souzatharsis.com/tamingLLMs/notebooks/structured...

    • Soy el autor del blog. La implementación actual pudo hacerse rápidamente usando llama.cpp GBNF. Por ahora, el mayor valor agregado es haber lanzado la función
      Espero que, con base en investigaciones recientes relacionadas con outlines/xgrammar, podamos actualizar el muestreo para admitir más formatos, aumentar la precisión y mejorar el rendimiento
  • Me pregunto si existe una mejor forma de proporcionar entrada estructurada a un LLM
    Por ejemplo, en un caso en el que se le pasan 100 oraciones y se le pide clasificar cada una de varias maneras. Recibir datos estructurados es fácil, pero mi método de anteponer números de línea se siente tosco

    • Los modelos fueron entrenados con Markdown, JSON y varios lenguajes de programación, así que cualquiera de ellos debería funcionar
      Sin embargo, en este caso lo mejor es darle las oraciones una por una para que el modelo no se confunda
      Si estructuras el prompt como "Clasifica la siguiente oración. Las reglas son ..." + la oración, puedes aprovechar la caché de prefijo, así que incluso podría rendir mejor que hacer la consulta de una sola vez
      Por supuesto, esto es posible cuando hay caché de prefijo y no se cobra por token de entrada. Hoy en día, la mayoría de los proveedores permiten usarla a menor costo si indicas que quieres utilizar la caché de prefijo