Sorties structurées AI_COMPLETE

Les sorties structurées AI_COMPLETE vous permettent de fournir un schéma JSON que les réponses d’achèvement doivent respecter. Cela réduit le besoin de post-traitement dans vos pipelines de données AI et permet une intégration transparente avec les systèmes qui exigent des réponses déterministes. AI_COMPLETE vérifie chaque jeton généré par rapport à votre schéma JSON afin de s’assurer que la réponse est conforme au schéma fourni.

Tous les modèles pris en charge par AI_COMPLETE permettent une sortie structurée, mais les modèles les plus puissants génèrent généralement des réponses de meilleure qualité.

Utilisation des sorties structurées AI_COMPLETE

Pour obtenir une réponse dans un format structuré, spécifiez un objet représentant un schéma JSON comme argument response_format. L’objet de schéma JSON fourni définit la structure, les types de données et les contraintes auxquels le texte généré doit se conformer, y compris les champs obligatoires. Pour les tâches simples, vous n’avez pas besoin de spécifier les détails du format de sortie, ni même d’instruire le modèle de « respond in JSON ». Pour les tâches plus complexes, le fait d’inviter le modèle à répondre en JSON peut améliorer la précision ; voir Optimisation de la précision de l’adhésion à JSON.

ai_complete(
    ...
    response_format => {
        'type': 'json',
        'schema': {
            'type': 'object',
            'properties': {
                'property_name': {
                    'type': 'string'
                },
                ...
            },
            'required': ['property_name', ...]
        }
    }
Copy

Exemple SQL

L’exemple suivant montre comment utiliser l’argument response_format pour spécifier un schéma JSON pour la réponse.

SELECT AI_COMPLETE(
    model => 'mistral-large2',
    prompt => 'Return the customer sentiment for the following review: New kid on the block, this pizza joint! The pie arrived neither in a flash nor a snail\'s pace, but the taste? Divine! Like a symphony of Italian flavors, it was a party in my mouth. But alas, the party was a tad pricey for my humble abode\'s standards. A mixed bag, I\'d say!',
    response_format => {
            'type':'json',
            'schema':{'type' : 'object','properties' : {'sentiment_categories':{'type':'array','items':{'type':'object','properties':
            {'food_quality' : {'type' : 'string'},'food_taste': {'type':'string'}, 'wait_time': {'type':'string'}, 'food_cost': {'type':'string'}},'required':['food_quality','food_taste' ,'wait_time','food_cost']}}}}

    }
);
Copy

Réponse :

{
    "sentiment_categories": [
    {
        "food_cost": "negative",
        "food_quality": "positive",
        "food_taste": "positive",
        "wait_time": "neutral"
    }
    ]
}

L’exemple suivant montre comment utiliser l’argument response_format pour spécifier un schéma JSON pour la réponse et comment utiliser l’argument show_details pour renvoyer des métadonnées d’inférence.

SELECT AI_COMPLETE(
    model => 'mistral-large2',
    prompt => 'Return the customer sentiment for the following review: New kid on the block, this pizza joint! The pie arrived neither in a flash nor a snail\'s pace, but the taste? Divine! Like a symphony of Italian flavors, it was a party in my mouth. But alas, the party was a tad pricey for my humble abode\'s standards. A mixed bag, I\'d say!',
    response_format => {
            'type':'json',
            'schema':{'type' : 'object','properties' : {'sentiment_categories':{'type':'array','items':{'type':'object','properties':
            {'food_quality' : {'type' : 'string'},'food_taste': {'type':'string'}, 'wait_time': {'type':'string'}, 'food_cost': {'type':'string'}},'required':['food_quality','food_taste' ,'wait_time','food_cost']}}}}

    },
    show_details => TRUE
);
Copy

Réponse :

{
    "created": 1738683744,
    "model": "mistral-large2",
    "structured_output": [
        {
        "raw_message": {
            "sentiment_categories": [
            {
                "food_cost": "negative",
                "food_quality": "positive",
                "food_taste": "positive",
                "wait_time": "neutral"
            }
            ]
        },
        "type": "json"
        }
    ],
    "usage": {
        "completion_tokens": 60,
        "prompt_tokens": 94,
        "total_tokens": 154
    }
}

Exemple Python

Note

Les sorties structurées sont prises en charge à partir de la version 1.8.0 de snowflake-ml-python.

L’exemple suivant montre comment utiliser l’argument response_format pour spécifier un schéma JSON pour la réponse.

from snowflake.cortex import complete, CompleteOptions

response_format = {
    "type": "json",
    "schema": {
        "type": "object",
        "properties": {
            "people": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "name": {"type": "string"},
                        "age": {"type": "number"},
                    },
                    "required": ["name", "age"],
                },
            }
        },
        "required": ["people"],
    },
}
prompt = [{
    "role": "user",
    "content": "Please prepare me a data set of 5 ppl and their age",
}]

options = CompleteOptions(
        max_tokens=4096,
        temperature=0.7,
        top_p=1,
        guardrails=False,
        response_format=response_format
    )


result = complete(
model="claude-3-5-sonnet",
prompt=prompt,
session={session_object}, # session created via connector
stream=True,
options=options,
)

output = "".join(result)
print(output)
Copy

Réponse :

{"people": [{"name":"John Smith","age":32},{"name":"Sarah Johnson","age":28},
{"name":"Michael Chen","age":45},{"name":"Emily Davis","age":19},{"name":"Robert Wilson","age":56}]}

Exemple de Pydantic

Pydantic est une bibliothèque de validation de données et de gestion des paramètres pour Python. Cet exemple utilise Pydantic pour définir un schéma pour le format de réponse. Le code exécute les étapes suivantes :

  1. Utilise Pydantic pour définir un schéma

  2. Convertit le modèle Pydantic en un schéma JSON à l’aide de la méthode model_json_schema

  3. Transmet le schéma JSON à la fonction complete en tant qu’argument response_format

Note

Cet exemple est destiné à être exécuté dans une feuille de calcul Snowsight Python, qui dispose déjà d’une connexion avec Snowflake. Pour l’exécuter dans un environnement différent, vous devrez peut-être établir une connexion vers Snowflake à l’aide du Snowflake Connector pour Python.

from pydantic import BaseModel, Field
import json
from snowflake.cortex import complete, CompleteOptions
from snowflake.snowpark.context import get_active_session

class Person(BaseModel):
    age: int = Field(description="Person age")
    name: str = Field(description="Person name")
    people: list[Person] = Field(description="People list")

ppl = Person.model_json_schema()
'''
This is the ppl object, keep in mind there's a '$defs' key used

{'$defs': {'Person': {'properties': {'age': {'description': 'Person age', 'title': 'Age', 'type': 'integer'}, 'name': {'description': 'Person name', 'title': 'Name', 'type': 'string'}}, 'required': ['age', 'name'], 'title': 'Person', 'type': 'object'}}, 'properties': {'people': {'description': 'People list', 'items': {'$ref': '#/$defs/Person'}, 'title': 'People', 'type': 'array'}}, 'required': ['people'], 'title': 'People', 'type': 'object'}

'''

response_format_pydantic={
    "type": "json",
    "schema": ppl,
}
prompt=[{"role": "user", "content": "Please prepare me a data set of 5 ppl and their age"}]
options_pydantic = CompleteOptions(  # random params
        max_tokens=4096,
        temperature=0.7,
        top_p=1,
        guardrails=False,
        response_format=response_format_pydantic
    )
model_name = "claude-3-5-sonnet"


session = get_active_session()
try:
    result_pydantic = complete(
        model=model_name,
        prompt=prompt,
        session=session,
        stream=True,
        options=options_pydantic,
    )
except Exception as err:
    result_pydantic = (chunk for chunk in err.response.text) # making sure it's generator, similar to the valid response

output_pydantic = "".join(result_pydantic)
print(output_pydantic)
Copy

Réponse :

{"people": [{"name":"John Smith","age":32},{"name":"Sarah Johnson","age":45},
{"name":"Mike Chen","age":28},{"name":"Emma Wilson","age":19},{"name":"Robert Brown","age":56}]}

Exemple REST API

Vous pouvez utiliser l’API REST du LLM de Snowflake Cortex pour invoquer COMPLETE avec le LLM de votre choix. Vous trouverez ci-dessous un exemple de fourniture d’un schéma utilisant l’API REST du LLM de Cortex :

curl --location --request POST 'https://<account_identifier>.snowflakecomputing.com/api/v2/cortex/inference:complete'
--header 'Authorization: Bearer <jwt>' \
--header 'Accept: application/json, text/event-stream' \
--header 'Content-Type: application/json' \
--data-raw '{
    "model": "claude-3-5-sonnet",
    "messages": [{
        "role": "user",
        "content": "Order a pizza for a hungry space traveler heading to the planet Zorgon. Make sure to include a special instruction to avoid any intergalactic allergens."
    }],
    "max_tokens": 1000,
    "response_format": {
            "type": "json",
            "schema":
            {
                "type": "object",
                "properties":
                {
                    "crust":
                    {
                        "type": "string",
                        "enum":
                        [
                            "thin",
                            "thick",
                            "gluten-free",
                            "Rigellian fungus-based"
                        ]
                    },
                    "toppings":
                    {
                        "type": "array",
                        "items":
                        {
                            "type": "string",
                            "enum":
                            [
                                "Gnorchian sausage",
                                "Andromedian mushrooms",
                                "Quasar cheese"
                            ]
                        }
                    },
                    "delivery_planet":
                    {
                        "type": "string"
                    },
                    "special_instructions":
                    {
                        "type": "string"
                    }
                },
                "required":
                [
                    "crust",
                    "toppings",
                    "delivery_planet"
                ]
            }
        }
    }
}'
Copy

Réponse :

data: {"id":"4d62e41a-d2d7-4568-871a-48de1463ed2a","model":"claude-3-5-sonnet","choices":[{"delta":{"content":"{\"crust\":","content_list":[{"type":"text","text":"{\"crust\":"}]}}],"usage":{}}

data: {"id":"4d62e41a-d2d7-4568-871a-48de1463ed2a","model":"claude-3-5-sonnet","choices":[{"delta":{"content":" \"thin\"","content_list":[{"type":"text","text":" \"thin\""}]}}],"usage":{}}

data: {"id":"4d62e41a-d2d7-4568-871a-48de1463ed2a","model":"claude-3-5-sonnet","choices":[{"delta":{"content":", \"topping","content_list":[{"type":"text","text":", \"topping"}]}}],"usage":{}}

data: {"id":"4d62e41a-d2d7-4568-871a-48de1463ed2a","model":"claude-3-5-sonnet","choices":[{"delta":{"content":"s\": [\"Quasar","content_list":[{"type":"text","text":"s\": [\"Quasar"}]}}],"usage":{}}

Créer une définition du schéma JSON

Pour obtenir la meilleure précision possible à partir des sorties structurées COMPLETE, suivez les conseils suivants :

  • Utilisez le champ « required » dans le schéma pour spécifier les champs obligatoires. COMPLETE génère une erreur si un champ obligatoire ne peut être extrait.

    Dans l’exemple suivant, le schéma indique à COMPLETE de trouver les personnes mentionnées dans le document. Le champ people est marqué comme obligatoire pour s’assurer que les personnes sont identifiées.

    {
        'type': 'object',
        'properties': {
            'dataset_name': {
                'type': 'string'
            },
            'created_at': {
                'type': 'string'
            },
            'people': {
                'type': 'array',
                'items': {
                    'type': 'object',
                    'properties': {
                        'name': {
                            'type': 'string'
                        },
                        'age': {
                            'type': 'number'
                        },
                        'isAdult': {
                            'type': 'boolean'
                        }
                    }
                }
            }
        },
        'required': [
            'dataset_name',
            'created_at',
            'people'
        ]
    }
    
    Copy

    Réponse :

    {
        "dataset_name": "name",
        "created_at": "date",
        "people": [
            {
                "name": "Andrew",
                "isAdult": true
            }
        ]
    }
    
  • Fournissez des descriptions détaillées des champs à extraire afin que le modèle puisse les identifier plus précisément. Par exemple, le schéma suivant comprend une description de chacun des champs people: name, age et isAdult.

    {
        'type': 'object',
        'properties': {
            'dataset_name': {
                'type': 'string'
            },
            'created_at': {
                'type': 'string'
            },
            'people': {
                'type': 'array',
                'items': {
                    'type': 'object',
                    'properties': {
                        'name': {
                            'type': 'string',
                            'description': 'name should be between 9 to 10 characters'
                        },
                        'age': {
                            'type': 'number',
                            'description': 'Should be a value between 0 and 200'
                        },
                        'isAdult': {
                            'type': 'boolean',
                            'description': 'Persons is older than 18'
                        }
                    }
                }
            }
        }
    }
    
    Copy

Utilisation de la référence JSON

Les références de schémas permettent de résoudre des problèmes pratiques lors de l’utilisation des sorties structurées COMPLETE de Cortex. Grâce aux références, représentées par $ref, vous pouvez définir une seule fois des objets communs tels que des adresses ou des prix, puis y faire référence dans l’ensemble du schéma. Ainsi, lorsque vous devez mettre à jour la logique de validation ou ajouter un champ, vous pouvez le modifier en un seul endroit et non à plusieurs emplacements, ce qui réduit le risque d’erreurs.

L’utilisation de références permet de réduire les bogues dus à des implémentations incohérentes et de simplifier les révisions de code. Les composants référencés créent des hiérarchies plus propres qui représentent mieux les relations entre les entités dans votre modèle de données. Au fur et à mesure que les projets se complexifient, cette approche modulaire vous aide à gérer la dette technique tout en préservant l’intégrité du schéma.

Des bibliothèques tierces telles que Pydantic prennent en charge le mécanisme de référence de manière native en Python, ce qui simplifie l’utilisation des schémas dans votre code.

Les lignes directrices suivantes s’appliquent à l’utilisation des références dans le schéma JSON :

  • Limitation du champ d’application : Le mécanisme $ref est limité au schéma de l’utilisateur ; les références de schémas externes (telles que les URLs HTTP) ne sont pas prises en charge.

  • Placement des définitions : Les définitions d’objets doivent être placées au niveau supérieur du schéma, plus précisément sous la clé definitions ou $defs.

  • Application : Bien que la spécification du schéma JSON recommande l’utilisation de la clé $defs pour les définitions, le mécanisme de validation de Snowflake applique strictement cette structure. Il s’agit d’un exemple d’objet valide $defs :

{
    '$defs': {
        'person':{'type':'object','properties':{'name' : {'type' : 'string'},'age': {'type':'number'}}, 'required':['name','age']}},
    'type': 'object',
    'properties': {'title':{'type':'string'},'people':{'type':'array','items':{'$ref':'#/$defs/person'}}}
}
Copy

Exemple utilisant la référence JSON

Cet exemple SQL illustre l’utilisation des références dans un schéma JSON.

select ai_complete(
    model => 'claude-3-5-sonnet',
    prompt => 'Extract structured data from this customer interaction note: Customer Sarah Jones complained about the mobile app crashing during checkout. She tried to purchase 3 items: a red XL jacket ($89.99), blue running shoes ($129.50), and a fitness tracker ($199.00). The app crashed after she entered her shipping address at 123 Main St, Portland OR, 97201. She has been a premium member since January 2024.',
    'response_format' => {
            'type': 'json',
            'schema': {
'type': 'object',
'$defs': {
    'price': {
        'type': 'object',
        'properties': {
            'amount': {'type': 'number'},
            'currency': {'type': 'string'}
        },
        'required': ['amount']
    },
    'address': {
        'type': 'object',
        'properties': {
            'street': {'type': 'string'},
            'city': {'type': 'string'},
            'state': {'type': 'string'},
            'zip': {'type': 'string'},
            'country': {'type': 'string'}
        },
        'required': ['street', 'city', 'state']
    },
    'product': {
        'type': 'object',
        'properties': {
            'name': {'type': 'string'},
            'category': {'type': 'string'},
            'color': {'type': 'string'},
            'size': {'type': 'string'},
            'price': {'$ref': '#/$defs/price'}
        },
        'required': ['name', 'price']
    }
},
'properties': {
    'customer': {
        'type': 'object',
        'properties': {
            'name': {'type': 'string'},
            'membership': {
                'type': 'object',
                'properties': {
                    'type': {'type': 'string'},
                    'since': {'type': 'string'}
                }
            },
            'shipping_address': {'$ref': '#/$defs/address'}
        },
        'required': ['name']
    },
    'issue': {
        'type': 'object',
        'properties': {
            'type': {'type': 'string'},
            'platform': {'type': 'string'},
            'stage': {'type': 'string'},
            'severity': {'type': 'string', 'enum': ['low', 'medium', 'high', 'critical']}
        },
        'required': ['type', 'platform']
    },
    'cart': {
        'type': 'object',
        'properties': {
            'items': {
                'type': 'array',
                'items': {'$ref': '#/$defs/product'}
            },
            'total': {'$ref': '#/$defs/price'},
            'item_count': {'type': 'integer'}
        }
    },
    'recommended_actions': {
        'type': 'array',
        'items': {
            'type': 'object',
            'properties': {
                'department': {'type': 'string'},
                'action': {'type': 'string'},
                'priority': {'type': 'string', 'enum': ['low', 'medium', 'high', 'urgent']}
            }
        }
    }
},
'required': ['customer', 'issue','cart']
}
        }
    }
);
Copy

Réponse :

{
  "created": 1747313083,
  "model": "claude-3-5-sonnet",
  "structured_output": [
    {
      "raw_message": {
        "cart": {
          "item_count": 3,
          "items": [
            {
              "color": "red",
              "name": "jacket",
              "price": {
                "amount": 89.99,
                "currency": "USD"
              },
              "size": "XL"
            },
            {
              "color": "blue",
              "name": "running shoes",
              "price": {
                "amount": 129.5,
                "currency": "USD"
              }
            },
            {
              "name": "fitness tracker",
              "price": {
                "amount": 199,
                "currency": "USD"
              }
            }
          ],
          "total": {
            "amount": 418.49,
            "currency": "USD"
          }
        },
        "customer": {
          "membership": {
            "since": "2024-01",
            "type": "premium"
          },
          "name": "Sarah Jones",
          "shipping_address": {
            "city": "Portland",
            "state": "OR",
            "street": "123 Main St",
            "zip": "97201"
          }
        },
        "issue": {
          "platform": "mobile",
          "severity": "high",
          "stage": "checkout",
          "type": "app_crash"
        }
      },
      "type": "json"
    }
  ],
  "usage": {
    "completion_tokens": 57,
    "prompt_tokens": 945,
    "total_tokens": 1002
  }
}

Optimisation de la précision de l’adhésion à JSON

Les sorties structurées COMPLETE n’ont généralement pas besoin d’une exigence ; elles comprennent déjà que leur réponse doit être conforme au schéma que vous avez spécifié. Cependant, la complexité de la tâche peut influencer de manière significative la possibilité pour les LLMs de suivre un format de réponse JSON. Plus la tâche est complexe, plus vous pouvez améliorer la précision des résultats en spécifiant un prompt.

  • Les tâches simples telles que la classification de textes, l’extraction d’entités, les paraphrases et les tâches de résumé qui ne nécessitent pas de raisonnement complexe ne requièrent généralement pas de prompt supplémentaire. Pour les modèles plus petits et moins intelligents, l’utilisation de sorties structurées améliore considérablement la précision de l’adhésion à JSON, car elle ignore tout texte fourni par le modèle qui n’est pas lié au schéma fourni.

  • Les tâches de complexité moyenne comprennent toute tâche simple dans laquelle il est demandé au modèle d’effectuer un raisonnement supplémentaire, par exemple de justifier une décision de classification. Pour ces cas d’utilisation, nous vous recommandons d’ajouter « Respond in JSON » dans l’invite afin d’optimiser les performances.

  • Les tâches de raisonnement complexes incitent les modèles à effectuer des tâches ambiguës plus ouvertes, telles que l’évaluation et la notation de la qualité d’un appel en fonction de la pertinence, du professionnalisme et de la fidélité des réponses. Pour ces cas d’utilisation, nous vous recommandons d’utiliser les modèles les plus puissants tels que claude-3-5-sonnet d’Anthropic ou mistral-large2 de Mistral AI et d’ajouter « Respond in JSON », ainsi que des détails sur le schéma que vous souhaitez générer dans le prompt.

Pour obtenir les résultats les plus cohérents, définissez l’option temperature sur 0 lorsque vous appelez COMPLETE, quelle que soit la tâche ou le modèle.

Astuce

Pour traiter les erreurs éventuelles soulevées par un modèle, utilisez TRY_COMPLETE plutôt que COMPLETE.

Considérations relatives aux clients

Les sorties structurées COMPLETE de Cortex entraînent un coût de calcul basé sur le nombre de jetons traités, mais n’entraîne pas de coût de calcul supplémentaire pour la vérification de chaque jeton par rapport au schéma JSON fourni. Toutefois, le nombre de jetons traités (et facturés) augmente avec la complexité du schéma. En général, plus le schéma fourni est grand et complexe, plus il y a de jetons d’entrée et de sortie consommés. Les réponses hautement structurées avec une imbrication profonde (par exemple, des données hiérarchiques) consomment un plus grand nombre de jetons que les schémas plus simples.

Limitations

  • Vous ne pouvez pas utiliser d’espaces dans les clés du schéma.

  • Les caractères autorisés pour les noms de propriétés sont les lettres, les chiffres, le trait d’union et le trait de soulignement. Les noms peuvent comporter 64 caractères au maximum.

  • Vous ne pouvez pas résoudre des schémas externes en utilisant $ref ou $dynamicRef.

Les mots-clés de contraintes suivants ne sont pas pris en charge. L’utilisation d’un mot-clé de contrainte non pris en charge entraîne une erreur.

Type

Mots-clés

entier

multipleOf

number

multipleOf, minimum, maximum, exclusiveMinimum, exclusiveMaximum

string

minLength, maxLength, format

tableau

uniqueItems, contains, minContains, maxContains, minItems, maxItems

objet

patternProperties, minProperties, maxProperties, propertyNames

Ces limites pourraient être corrigées dans les prochaines versions.

Conditions d’erreur

Situation

Exemple de message

Code de statut HTTP

La validation de la requête a échoué. La requête a été annulée car le modèle n’était pas en mesure de générer une réponse valide. Cela peut être dû à une requête mal formée.

please provide a type for the response format object, please provide a schema for the response format object

400

La validation du schéma d’entrée a échoué. La requête a été annulée car le modèle n’était pas en mesure de générer une réponse valide. Cela peut être dû à l’absence de propriétés requises dans les données utiles de la requête ou à l’utilisation de fonctions du schéma JSON non prises en charge, telles que les contraintes, ou à l’utilisation inappropriée du mécanisme $ref (par exemple, en allant au-delà du schéma)

input schema validation error: <reason> avec l’un des motifs ci-dessous :

  • /properties/city additional properties are not allowed

  • /properties/arrondissement regexp pattern ^[a-zA-Z0-9_-]{1,64}$ mismatch on string

  • /properties/province/type sting should be one of [\"object\", \"array\", \"string\", \"number\", \"integer\", \"boolean\", \"null\"]

  • Invalid ref #/http://example.com/custom-email-validator.json#. Please define a valid object in #/$defs/ section

400

La validation de la sortie du modèle a échoué. Le modèle n’a pas pu générer une réponse correspondant au schéma.

json mode output validation error: <reason> avec l’un des motifs ci-dessous :

  • Une erreur s'est produite lors du rétablissement de la sortie du modèle. Le modèle a renvoyé une adresse JSON invalide qui ne peut être analysée en raison de la fin inattendue de l'entrée JSON

422