2 points par GN⁺ 2024-12-09 | 1 commentaires | Partager sur WhatsApp
  • Les réponses des modèles locaux peuvent être contraintes pour respecter un schéma JSON, ce qui réduit la charge de parsing en post-traitement et rend les résultats d’extraction de données plus cohérents
  • Les utilisateurs peuvent demander des sorties structurées de la même manière en passant le schéma dans le paramètre format depuis cURL, Python ou JavaScript
  • Avec Pydantic en Python et Zod en JavaScript, il est plus facile de relier au niveau du code la définition du schéma et la validation des réponses
  • Les exemples renvoient et valident des informations sur un pays, un texte sur des animaux de compagnie et les résultats d’analyse d’images de llama3.2-vision dans des champs JSON prédéfinis
  • Si vous avez besoin de résultats stables, il est recommandé d’ajouter « return as JSON » dans le prompt et de régler la température à 0

Contraindre les réponses du modèle avec JSON Schema

  • Ollama prend en charge les sorties structurées qui permettent de contraindre la sortie du modèle à respecter un format JSON Schema donné
  • Les bibliothèques Python et JavaScript d’Ollama ont également été mises à jour pour prendre en charge les sorties structurées
  • Cette fonctionnalité peut être utilisée pour les tâches suivantes
    • Parser des données depuis des documents
    • Extraire des données à partir d’images
    • Structurer les réponses des modèles de langage
    • Obtenir une fiabilité et une cohérence supérieures au mode JSON

Installation et mode d’appel

  • Il faut télécharger la dernière version de Ollama
  • La bibliothèque Python peut être mise à jour vers la dernière version avec la commande suivante
pip install -U ollama
  • La bibliothèque JavaScript peut être installée avec la commande suivante
npm i ollama
  • Pour demander une sortie structurée, il faut transmettre le schéma dans le paramètre format d’une requête cURL ou via les bibliothèques Python et JavaScript

Transmettre un JSON Schema avec cURL

  • L’exemple cURL envoie une requête à http://localhost:11434/api/chat, avec model défini sur llama3.1 et stream sur false
  • format contient un schéma d’objet avec name, capital et languages
    • name : chaîne de caractères
    • capital : chaîne de caractères
    • languages : tableau de chaînes de caractères
    • Les trois champs sont tous définis comme required
  • La réponse est renvoyée au format du JSON Schema inclus dans la requête
{
  "capital": "Ottawa",
  "languages": [
    "English",
    "French"
  ],
  "name": "Canada"
}

Utilisation des bibliothèques Python et JavaScript

  • Python

    • La bibliothèque Ollama Python transmet le schéma comme objet JSON dans le paramètre format
    • Le schéma peut être passé sous forme de dict, et la méthode recommandée consiste à le sérialiser avec model_json_schema() de Pydantic
    • L’exemple définit les champs name, capital et languages dans le modèle Country, puis valide la réponse avec Country.model_validate_json()
country = Country.model_validate_json(response.message.content)
  • La sortie d’exemple renvoie Canada, Ottawa, English et French dans les champs définis
  • JavaScript

    • La bibliothèque Ollama JavaScript transmet le schéma comme objet JSON dans le paramètre format
    • Le schéma peut être passé comme object, et la méthode recommandée consiste à utiliser Zod avec zodToJsonSchema()
    • L’exemple définit le schéma Country comme objet Zod, puis valide le corps de la réponse avec Country.parse() après JSON.parse()
const country = Country.parse(JSON.parse(response.message.content));

Extraction de données depuis du texte et des images

  • Extraction depuis un texte sur des animaux de compagnie

    • Les sorties structurées peuvent être utilisées pour extraire les informations nécessaires à partir d’un texte
    • L’exemple définit les modèles Pydantic Pet et PetList afin de renvoyer les informations sur les animaux de compagnie dans une structure JSON
      • Pet : name, animal, age, color, favorite_toy
      • PetList : tableau pets
    • Le texte d’entrée contient des informations sur deux chats
      • Luna : 5 ans, pelage gris, aime la laine
      • Loki : 2 ans, noir, aime les tennis balls
    • La sortie est validée comme une liste d’objets Pet conforme au schéma défini
  • Description d’image avec un modèle de vision

    • Les sorties structurées peuvent être utilisées avec un modèle de vision
    • L’exemple analyse une image avec llama3.2-vision et renvoie un résultat conforme au schéma ImageDescription
    • Le schéma comprend les champs suivants
      • summary
      • objects
      • scene
      • colors
      • time_of_day
      • setting
      • text_content
    • La requête d’exemple demande d’analyser dans l’image les objets, la scène, les couleurs et le texte détectable
    • Dans les options, temperature est réglée à 0 pour obtenir une sortie plus déterministe
    • La sortie d’exemple résume une scène de plage avec des palmiers et renvoie sous forme structurée les objets tree et beach, les couleurs, le moment de la journée et le contexte extérieur

Utilisation de l’API compatible OpenAI

  • L’exemple compatible OpenAI configure le client OpenAI avec base_url="http://localhost:11434/v1"; et api_key="ollama"
  • Il utilise client.beta.chat.completions.parse() et transmet le modèle Pydantic PetList dans response_format
  • Dans la réponse, il vérifie completion.choices[0].message et, si parsed est présent, affiche le résultat parsé
  • Si refusal est présent, il affiche la réponse de refus, et openai.LengthFinishReasonError est traité comme un cas où il y a trop de tokens

Réglages pour des sorties stables et feuille de route

  • Pour définir le schéma de réponse, l’usage de Pydantic en Python ou de Zod en JavaScript est recommandé
  • Il est conseillé d’ajouter « return as JSON » dans le prompt pour aider le modèle à comprendre la demande
  • Pour une sortie plus déterministe, réglez la température à 0
  • Les plans à venir sont les suivants
    • Exposition des logits pour une génération contrôlée
    • Amélioration des performances et de la précision des sorties structurées
    • Accélération GPU pour l’échantillonnage
    • Prise en charge de formats supplémentaires au-delà de JSON Schema

1 commentaires

 
GN⁺ 2024-12-09
Avis sur Hacker News
  • Si vous avez besoin de contraintes de sortie plus fortes, llama.cpp prend en charge GBNF
    https://github.com/ggerganov/llama.cpp/blob/master/grammars/...

    • On dirait que c’est précisément ce qu’ils utilisent
    • Je me demande si la qualité de sortie avec des grammaires arbitraires était satisfaisante
      À première vue, comme les modèles ont vu beaucoup plus de JSON, j’aurais tendance à penser qu’ils produisent mieux du JSON que d’autres formats
    • Je me demande en quoi c’est plus puissant
  • C’est une bonne nouvelle
    Je me demandais comment formuler un prompt général pour générer des données CSV sans phrases inutiles avant ou après du genre « Here is your data » ou « Please note blah blah » ; c’est agréable de pouvoir désormais définir précisément le format de retour souhaité et transmettre directement la sortie structurée en CSV

    • Il faut tout de même inclure une instruction de générer du CSV pour que le prompt soit placé dans le bon contexte
      Sinon, on peut obtenir une sortie techniquement au format CSV, mais dénuée de sens. Le modèle essayait peut-être en réalité de rédiger une réponse en paragraphes, et l’échantillonneur de tokens a choisi des tokens de faible probabilité que le modèle n’avait pas vraiment envie de produire
    • Dans beaucoup de cas, on peut éviter ce problème en préremplissant le début de la sortie avec \n et en s’arrêtant à
  • Ça fonctionne. J’ai donné la phrase ci-dessous à gemma2:2b et j’ai obtenu le JSON attendu
    You have spent 190 at Fresh Mart. Current balance: 5098
    Le résultat était {"amount": 190, "balance": 5098, "category": "Shopping", "place": "Fresh Mart"}

    • C’est un JSON assez incohérent, mais pour un très petit modèle, et en plus gemma, ça se comprend
  • Vraiment impressionnant. C’était effectivement l’une des fonctionnalités que je voulais
    Ce qui rend ollama intéressant, c’est qu’il donne l’impression d’utiliser les LLM comme d’autres programmes UNIX, et donne le sentiment que les LLM ont naturellement leur place dans UNIX
    Cela dit, je me demande si quelqu’un a réussi à le faire tourner correctement sur un GPU AMD. J’ai entendu dire que c’était plus difficile, mais j’aimerais soutenir un concurrent quand j’achèterai une carte l’an prochain

    • C’est possible. Même avec un GPU intégré
      Sur un mini-PC équipé d’un 780M, j’ai alloué 16 Go de mémoire partagée via le BIOS et ça tournait plutôt bien
  • Je me demande quel effet ce type de contraintes a sur la qualité de sortie des LLM
    Dans certains cas, si la qualité de sortie est meilleure, je préférerais parser du Markdown ou du texte brut

    • Avec les modèles OpenAI, si l’on pouvait assumer le coût en tokens supplémentaires, une stratégie en deux passes fonctionnait très bien
      La première utilise un modèle lourd et le langage naturel pour traiter le raisonnement dans des sections Markdown et fournir une réponse finale en langage naturel. Si possible, avec des en-têtes Markdown pour étiqueter clairement les sections
      La seconde utilise un modèle moins cher et plus rapide pour convertir cette réponse en un format de sortie structuré, consommé par les parties non-LLM du pipeline
      En gros, le mode schéma JSON sert à créer des frontières nettes autour des parties ambiguës en langage naturel, et le LLM joue le rôle d’un préprocesseur qui capture sa propre sortie dans un format utile
    • Cela dépend du degré de fine-tuning du modèle pour la sortie JSON
      Il faut aussi indiquer le schéma au modèle. Sinon, on rencontre davantage de problèmes étranges de tokenisation
      Par exemple, si le schéma attend la clé JSON "foobarbaz" et que la tokenisation BPE standard est ["foobar", "baz"], les masques de tokens produits par les bibliothèques actuelles de sortie contrainte peuvent autoriser le modèle à choisir entre "f", "foo" et "foobar". Si le modèle choisit "foo", la contrainte force ensuite des tokens comme "bar" et "baz". Le modèle voit alors ["foo", "bar", "baz"] au lieu de ["foobar", "baz"], ce qui le perturbe [0]
      Si le prompt indique au modèle que "foobarbaz" est l’une des clés du schéma, il préférera généralement "foobar" à "foo"
      [0] Dans les modèles récents, ces tokens sont liés à cause de la normalisation, mais ce ne sont pas les mêmes
    • Cela dépend des situations, mais du point de vue de l’inférence, il y a un effet négatif, même si dans la plupart des cas la différence n’était pas très grande
      Cela dépend beaucoup de la probabilité que le LLM et le prompt produisent naturellement une réponse JSON. Plus on force le LLM, moins il a de chances de produire une entrée normale
      Avec les petits modèles, on atteint plus vite les limites de l’espace où il existe une capacité prédictive significative, et la sortie commence à ressembler davantage à du bruit aléatoire
      Ce n’est pas une mesure rigoureuse, plutôt une impression après avoir passé beaucoup de temps sur plusieurs projets LLM. Je n’ai pas encore utilisé ces outils précis, mais ollama permettait déjà auparavant de garantir une sortie JSON avec une méthode qui semblait similaire, et j’ai aussi travaillé avec un partenaire sur quelque chose ressemblant à jsonformer pour oobabooga, un autre outil d’exécution LLM
    • Je continue à suivre les recherches liées à ce sujet. J’étudie des moyens d’améliorer le sampling à la fois en vitesse et en précision
      J’espère que ce type d’évolution permettra aussi une génération structurée générale, pas limitée au JSON
    • On peut dire que je m’étais complètement trompé sur l’utilité d’outils comme instructor
      Il est facile de brûler beaucoup de tokens, mais si ce qu’on cherche à faire justifie ce coût, on peut aller assez loin. Même si ce n’est pas la qualité absolument maximale, un outil qui permet d’obtenir 95 % du résultat sans effort mérite clairement sa place dans la boîte à outils
  • Je me demande si on peut l’utiliser avec n’importe quel modèle pris en charge
    Sur mon matériel, je ne peux faire tourner de manière fiable que des modèles 1B à 3B, d’où ma question

    • Je suis l’auteur du billet de blog. On devrait pouvoir utiliser n’importe quel modèle
      Avec les petits modèles, les résultats peuvent être irréguliers, mais demander « retourne x en JSON » avait tendance à améliorer la précision
  • La PR liée à cette fonctionnalité est restée ouverte pendant près d’un an
    C’est un peu dommage que les mainteneurs soient restés si silencieux

    • Je suis l’auteur de l’article et l’un des mainteneurs. Je suis d’accord. Les mainteneurs ont réagi tard, et nous voulons globalement encourager davantage de contributions
      J’espère que l’an prochain nous pourrons mieux suivre et fusionner les PR de la communauté
    • Vu l’ambiance, on dirait que ça prend une direction centrée sur l’entreprise, et que tout est donc vu à travers ce prisme et celui de la maximisation des revenus
    • J’aurais aussi voulu collaborer pour construire ça avec eux, mais la conversation s’est brusquement arrêtée
  • J’utilise toujours oobabooga. Grâce à la prise en charge de exlv2, l’inférence est bien plus efficace sur deux 3090.

    • Je n’ai pas touché à ooba depuis un moment, mais je me demande où en sont exl2 et les méthodes de quantification non homogènes comme q3k_s.
      Dans mon souvenir, exl2 était plus rapide, mais surtout aux faibles profondeurs de bits, la quantification gptq semblait meilleure en précision.
  • Je me demande quelle valeur ajoutée cela apporte par rapport à outlines.
    https://www.souzatharsis.com/tamingLLMs/notebooks/structured...

    • Je suis l’auteur du billet. L’implémentation actuelle a pu être réalisée rapidement en utilisant llama.cpp GBNF. La principale valeur ajoutée pour l’instant, c’est que la fonctionnalité est sortie.
      À partir des travaux récents autour d’outlines/xgrammar, j’espère que nous pourrons mettre à jour l’échantillonnage afin de prendre en charge davantage de formats, d’améliorer la précision et d’optimiser les performances.
  • Je me demande quelle est la meilleure manière de fournir une entrée structurée à un LLM.
    Par exemple, lorsqu’on lui donne 100 phrases et qu’on lui demande de classer chacune d’elles de plusieurs façons. Recevoir des données structurées est facile, mais ma méthode consistant à préfixer chaque ligne par son numéro me paraît rudimentaire.

    • Les modèles ont été entraînés sur Markdown, JSON et plusieurs langages de programmation, donc l’un de ces formats devrait fonctionner.
      Cela dit, dans ce cas, le mieux est de fournir les phrases une par une pour éviter de perturber le modèle.
      En construisant le prompt sous la forme "Classe la phrase suivante. Les règles sont ..." + la phrase, on peut exploiter le cache de préfixe, ce qui peut même être plus performant que de tout interroger en une seule fois.
      Bien sûr, cela suppose qu’il y ait un cache de préfixe et que la facturation ne se fasse pas au token d’entrée. De nos jours, la plupart des fournisseurs permettent de bénéficier d’un coût plus faible si l’on indique qu’on souhaite utiliser le cache de préfixe.