0Pricing
AI Prompt Engineering · Leçon

Tester les prompts par assertions

Vérifiez les sorties avec contains(), les expressions régulières, un schéma JSON et un LLM évaluateur.

Tester les prompts par assertions est une leçon AI Prompt Engineering gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Prompt Engineering, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Prompt Engineering comprend 4 leçons au total.

Vérifications des sorties des LLM

Les vérifications fondées sur des assertions appliquent aux LLM le même principe que celui utilisé pour les essais unitaires : formuler explicitement ce que la sortie doit contenir ou ne pas contenir, puis échouer immédiatement lorsque cette exigence n’est pas respectée.

Contrairement aux essais unitaires utilisant des fonctions déterministes, les vérifications des LLM portent sur des sorties textuelles probabilistes, ce qui nécessite des types de vérification plus souples : contains, matches_schema, satisfies_regex, llm_judge_score_above.

Assertions de base : contient et ne contient pas

Les assertions les plus simples vérifient la présence ou l’absence de mots-clés. Elles conviennent bien aux tâches de classification, aux sorties structurées et aux vérifications de sécurité.

import openai
client = openai.OpenAI(api_key='sk-...')

def call_prompt(system, user, temperature=0):
    resp = client.chat.completions.create(
        model='gpt-4o',
        messages=[
            {'role': 'system', 'content': system},
            {'role': 'user', 'content': user}
        ],
        temperature=temperature
    )
    return resp.choices[0].message.content

# Keyword presence assertion
def assert_contains(output, keyword, case_sensitive=False):
    text = output if case_sensitive else output.lower()
    kw = keyword if case_sensitive else keyword.lower()
    assert kw in text, f'Expected "{keyword}" in output, got: {output[:100]}'

# Keyword absence assertion
def assert_not_contains(output, forbidden, case_sensitive=False):
    text = output if case_sensitive else output.lower()
    kw = forbidden if case_sensitive else forbidden.lower()
    assert kw not in text, f'Forbidden "{forbidden}" found in output: {output[:100]}'

Validation de schéma JSON

Lorsque votre instruction est censée renvoyer un JSON structuré, validez la sortie par rapport à un schéma. Un échec de validation du schéma signifie que l’instruction présente un problème de format : soit le modèle a ajouté du texte explicatif, soit la structure JSON est incorrecte.

import json
from jsonschema import validate, ValidationError

PRODUCT_SCHEMA = {
    'type': 'object',
    'properties': {
        'name': {'type': 'string'},
        'price': {'type': 'number', 'minimum': 0},
        'available': {'type': 'boolean'}
    },
    'required': ['name', 'price', 'available'],
    'additionalProperties': False
}

def assert_valid_json_schema(output, schema):
    try:
        data = json.loads(output.strip())
    except json.JSONDecodeError as e:
        raise AssertionError(f'Output is not valid JSON: {e}\nOutput: {output[:200]}')
    try:
        validate(instance=data, schema=schema)
    except ValidationError as e:
        raise AssertionError(f'JSON does not match schema: {e.message}\nOutput: {output[:200]}')
    return data

# Test
output = call_prompt(
    'Extract product info as JSON: {"name": ..., "price": ..., "available": ...}',
    'Widget Pro costs $49.99 and is in stock.'
)
product = assert_valid_json_schema(output, PRODUCT_SCHEMA)
print('Parsed product:', product)

Correspondance par expressions régulières

Les assertions fondées sur des expressions régulières valident précisément le format de la sortie. Elles sont utiles pour les sorties qui doivent suivre un modèle précis, comme les dates, les numéros de téléphone ou les codes structurés.

import re

def assert_matches_regex(output, pattern, flags=0):
    if not re.search(pattern, output, flags):
        raise AssertionError(
            f'Output does not match pattern /{pattern}/\nOutput: {output[:200]}'
        )

def assert_output_is_label(output, valid_labels):
    cleaned = output.strip().upper()
    assert cleaned in valid_labels, (
        f'Expected one of {valid_labels}, got: {repr(cleaned)}'
    )

# Examples
output = call_prompt('Classify sentiment as POSITIVE, NEGATIVE, or NEUTRAL:', 'Great product!')
assert_output_is_label(output, {'POSITIVE', 'NEGATIVE', 'NEUTRAL'})

date_output = call_prompt('Extract the date in YYYY-MM-DD format:', 'Meeting on November 15, 2024')
assert_matches_regex(date_output, r'^\d{4}-\d{2}-\d{2}$')

Évaluation par un LLM juge

Pour les sorties ouvertes, utilisez un second appel à un LLM pour évaluer la qualité. Cette approche s’appelle LLM comme évaluateur. Le modèle évaluateur reçoit l’instruction d’origine, la sortie et les critères d’évaluation, puis renvoie une note.

def llm_judge_score(original_prompt, output, criteria, max_score=10):
    judge_prompt = (
        f'Evaluate the following AI response on a scale of 1-{max_score}.\n'
        f'Evaluation criteria: {criteria}\n\n'
        f'Original prompt: {original_prompt}\n\n'
        f'AI response: {output}\n\n'
        f'Return only a number from 1 to {max_score}.'
    )
    resp = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': judge_prompt}],
        temperature=0
    )
    score_text = resp.choices[0].message.content.strip()
    return int(score_text)

def assert_llm_score_above(original_prompt, output, criteria, min_score=7):
    score = llm_judge_score(original_prompt, output, criteria)
    assert score >= min_score, f'LLM judge score {score} < minimum {min_score}'

Utiliser pytest pour les vérifications d’instructions

pytest est le cadre standard de vérification en Python et convient bien aux vérifications d’instructions. Chaque fonction de vérification correspond à un cas de vérification. pytest les collecte, les exécute et en génère automatiquement les rapports.

# test_sentiment_prompt.py
import pytest
import openai

client = openai.OpenAI(api_key='sk-...')
SYSTEM_PROMPT = 'Classify the sentiment as POSITIVE, NEGATIVE, or NEUTRAL. Return only the label.'

def classify(text):
    resp = client.chat.completions.create(
        model='gpt-4o',
        messages=[
            {'role': 'system', 'content': SYSTEM_PROMPT},
            {'role': 'user', 'content': text}
        ],
        temperature=0
    )
    return resp.choices[0].message.content.strip().upper()

# pytest automatically discovers functions starting with test_
def test_positive_sentiment():
    assert classify('I love this product!') == 'POSITIVE'

def test_negative_sentiment():
    assert classify('Terrible experience.') == 'NEGATIVE'

def test_neutral_sentiment():
    assert classify('It arrived on time.') == 'NEUTRAL'

# Run: pytest test_sentiment_prompt.py -v

Vérifications paramétrées dans pytest

Utilisez @pytest.mark.parametrize pour exécuter la même fonction de vérification avec de nombreuses entrées sans répéter le code. C’est la manière la plus claire de créer une suite complète de vérifications.

# test_sentiment_parametrized.py
import pytest

TEST_CASES = [
    ('I love this!', 'POSITIVE'),
    ('Worst purchase ever.', 'NEGATIVE'),
    ('It works.', 'NEUTRAL'),
    ('Amazing!', 'POSITIVE'),
    ('Terrible!', 'NEGATIVE'),
    ('OK I guess.', 'NEUTRAL'),
]

@pytest.mark.parametrize('text,expected', TEST_CASES)
def test_sentiment_classification(text, expected):
    result = classify(text)
    assert result == expected, f'For "{text}": expected {expected}, got {result}'

# pytest test_sentiment_parametrized.py -v
# Output shows each test case individually:
# PASSED test_sentiment_parametrized.py::test_sentiment_classification[I love this!-POSITIVE]
# PASSED test_sentiment_parametrized.py::test_sentiment_classification[Worst purchase ever.-NEGATIVE]

Dispositifs de préparation pour partager l’état des instructions

Utilisez les dispositifs de préparation de pytest pour partager une configuration coûteuse entre les vérifications, comme le chargement d’un modèle d’instruction ou la création d’un client d’API une seule fois par session de vérification.

# conftest.py — fixtures available to all test files in the directory
import pytest
import openai

@pytest.fixture(scope='session')
def llm_client():
    return openai.OpenAI(api_key='sk-...')

@pytest.fixture(scope='session')
def sentiment_prompt():
    with open('prompts/sentiment_v3.txt') as f:
        return f.read()

# test_sentiment.py
def test_positive_with_fixture(llm_client, sentiment_prompt):
    resp = llm_client.chat.completions.create(
        model='gpt-4o',
        messages=[
            {'role': 'system', 'content': sentiment_prompt},
            {'role': 'user', 'content': 'I love this!'}
        ],
        temperature=0
    )
    assert 'POSITIVE' in resp.choices[0].message.content.upper()

Gérer les vérifications instables

Les sorties des LLM sont probabilistes : même avec temperature=0, différents déploiements ou différentes versions d’un modèle peuvent produire des sorties différentes. Gérez cette instabilité avec une logique de nouvelle tentative et des seuils de tolérance.

import pytest

def run_with_retry(fn, n=3):
    '''Run fn up to n times, pass if any run succeeds.'''
    failures = []
    for _ in range(n):
        try:
            fn()
            return  # passed
        except AssertionError as e:
            failures.append(str(e))
    raise AssertionError(f'Failed all {n} attempts. Last: {failures[-1]}')

def test_positive_with_retry():
    def check():
        result = classify('I love this!')
        assert result == 'POSITIVE'
    run_with_retry(check, n=3)

# Or use pytest-retry plugin:
# @pytest.mark.flaky(reruns=3)
# def test_positive_sentiment():
#     assert classify('I love this!') == 'POSITIVE'

Performances et coût des vérifications

Chaque cas de vérification correspond à un appel d’API : pour 100 cas à 0,005 $/appel = 0,50 $ par exécution complète. Stratégies pour maîtriser les coûts :

  • Mettez en cache les réponses pour les entrées de vérification statiques et exécutez les vérifications depuis le cache dans l’intégration continue
  • Exécutez la suite complète chaque nuit ; exécutez uniquement un sous-ensemble de vérifications rapides (10 cas) pour chaque PR
  • Utilisez un modèle moins coûteux (gpt-4o-mini) pour la plupart des vérifications ; utilisez gpt-4o uniquement pour la suite de régression
import hashlib, json

RESPONSE_CACHE = {}

def cached_classify(text, use_cache=True):
    key = hashlib.md5(text.encode()).hexdigest()
    if use_cache and key in RESPONSE_CACHE:
        return RESPONSE_CACHE[key]
    result = classify(text)
    RESPONSE_CACHE[key] = result
    return result

# Persist cache to disk for CI
def load_cache(path='test_cache.json'):
    global RESPONSE_CACHE
    try:
        with open(path) as f:
            RESPONSE_CACHE = json.load(f)
    except FileNotFoundError:
        RESPONSE_CACHE = {}

def save_cache(path='test_cache.json'):
    with open(path, 'w') as f:
        json.dump(RESPONSE_CACHE, f, indent=2)

Rapports des sorties de vérification

pytest produit des rapports détaillés qui indiquent quels cas de vérification ont échoué et pourquoi. Utilisez pytest --tb=short -v pour obtenir des messages d’échec concis. Pour l’intégration continue, utilisez --junitxml afin de produire des rapports XML JUnit compatibles avec GitHub Actions, GitLab CI et Jenkins.

# Run test suite and generate reports
# In terminal:
# pytest tests/prompt/ -v --tb=short --junitxml=test_results.xml

# In Python (for programmatic use):
import subprocess

def run_prompt_tests(test_dir='tests/prompt'):
    result = subprocess.run(
        ['pytest', test_dir, '-v', '--tb=short', '--junitxml=test_results.xml'],
        capture_output=True, text=True
    )
    print(result.stdout)
    if result.returncode != 0:
        print('TESTS FAILED')
        print(result.stderr)
    return result.returncode == 0

passed = run_prompt_tests()

Vérification des connaissances

Quand utiliseriez-vous l’évaluation par un LLM juge plutôt qu’une assertion de correspondance exacte lors de la vérification d’instructions ?

Récapitulatif : vérification d’instructions fondée sur des assertions

Principaux types d’assertions pour les sorties des LLM :

  • présence / absence : présence de mots-clés, utile pour les étiquettes et les vérifications de sécurité
  • validation de schéma JSON : valide le format d’une sortie structurée
  • correspondance par expressions régulières : valide des modèles précis (dates, codes)
  • LLM comme évaluateur : évalue la qualité d’un texte ouvert

Utilisez pytest avec @pytest.mark.parametrize pour créer des suites de vérifications claires et évolutives. Mettez les réponses en cache pour maîtriser les coûts. Exécutez un sous-ensemble de vérifications rapides pour chaque PR et la suite complète chaque nuit. Prochaine leçon : vérification des régressions lors des mises à jour des modèles.

Questions Fréquemment Posées

La leçon « Tester les prompts par assertions » est-elle gratuite ?

Oui — le texte complet de « Tester les prompts par assertions » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Prompt Engineering, passe à CoddyKit PRO. Le cours AI Prompt Engineering comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Tester les prompts par assertions » ?

Vérifiez les sorties avec contains(), les expressions régulières, un schéma JSON et un LLM évaluateur. Tu pratiques AI Prompt Engineering avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer AI Prompt Engineering ?

Aucune expérience préalable n'est requise. AI Prompt Engineering sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.

Combien de temps prend la leçon « Tester les prompts par assertions » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon AI Prompt Engineering ?

Oui. Chaque leçon AI Prompt Engineering inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Rédiger des cas de test pour les prompts
  2. Tester les prompts par assertions
  3. Tests de régression lors des mises à jour des modèles
  4. Créer une suite de tests pour les prompts
← Retour à AI Prompt Engineering