0Pricing
AI Prompt Engineering · Lezione

Test dei prompt basati su asserzioni

Verifica degli output con contains(), regex, schema JSON e LLM-as-judge

Test dei prompt basati su asserzioni è una lezione AI Prompt Engineering gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AI Prompt Engineering, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Prompt Engineering include 4 lezioni in totale.

Asserzioni per gli output degli LLM

Il testing basato su asserzioni applica agli LLM lo stesso principio utilizzato nei test unitari: formulare asserzioni esplicite su ciò che l'output deve contenere o non contenere e segnalare immediatamente un errore quando l'asserzione viene violata.

A differenza dei test unitari con funzioni deterministiche, le asserzioni per gli LLM riguardano output testuali probabilistici e richiedono quindi tipi di asserzioni più flessibili: contains, matches_schema, satisfies_regex, llm_judge_score_above.

Asserzioni di base: contains e not_contains

Le asserzioni più semplici verificano la presenza o l'assenza di parole chiave. Sono adatte per attività di classificazione, output strutturati e controlli di sicurezza.

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]}'

Validazione dello schema JSON

Quando il prompt deve restituire JSON strutturato, convalidi l'output rispetto a uno schema. Un errore di validazione dello schema indica un problema di formato: il modello potrebbe aver aggiunto del testo discorsivo oppure la struttura JSON potrebbe essere errata.

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)

Corrispondenza tramite regex

Le asserzioni basate su regex convalidano il formato dell'output con precisione, risultando utili per output che devono seguire uno schema specifico, come date, numeri di telefono o codici strutturati.

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}$')

Valutazione LLM-as-judge

Per gli output senza una risposta predefinita, utilizzi una seconda chiamata LLM per valutarne la qualità. Questa tecnica è chiamata LLM-as-judge. Il modello valutatore riceve il prompt originale, l'output e i criteri di valutazione, quindi restituisce un punteggio.

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}'

Utilizzo di pytest per i test dei prompt

pytest è il framework di test standard per Python e funziona bene per i test dei prompt. Ogni funzione di test corrisponde a un caso di test. pytest li raccoglie, li esegue e ne restituisce automaticamente i risultati.

# 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

Test parametrizzati in pytest

Utilizzi @pytest.mark.parametrize per eseguire la stessa funzione di test su molti input senza ripetere il codice. È il modo più ordinato per creare una suite di test completa.

# 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]

Fixture per condividere lo stato dei prompt

Utilizzi le fixture di pytest per condividere la configurazione iniziale costosa tra i test, ad esempio caricando un modello di prompt o creando un client API una sola volta per sessione di test.

# 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()

Gestione dei test flaky

Gli output degli LLM sono probabilistici: anche con temperature=0, distribuzioni o versioni diverse del modello possono produrre output differenti. Gestisca l'instabilità con una logica di retry e soglie di tolleranza.

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'

Prestazioni e costi dei test

Ogni caso di test è una chiamata API: per 100 casi di test a $0.005/chiamata = $0.50 per un'esecuzione completa dei test. Strategie per gestire i costi:

  • Memorizzi nella cache le risposte per gli input di test statici ed esegua i test dalla cache in CI
  • Esegua la suite completa ogni notte; a ogni PR esegua solo un sottoinsieme di smoke test (10 casi)
  • Utilizzi un modello più economico (gpt-4o-mini) per la maggior parte dei test; utilizzi gpt-4o solo per la suite di regressione
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)

Report dei risultati dei test

pytest produce report dettagliati che evidenziano quali casi di test non sono riusciti e perché. Utilizzi pytest --tb=short -v per messaggi di errore concisi. Per la CI, utilizzi --junitxml per produrre report JUnit XML compatibili con GitHub Actions, GitLab CI e 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()

Verifica delle conoscenze

Quando utilizzerebbe la valutazione LLM-as-judge invece di un'asserzione di corrispondenza esatta nei test dei prompt?

Riepilogo: test dei prompt basati su asserzioni

Principali tipi di asserzioni per gli output degli LLM:

  • contains / not_contains: presenza di parole chiave, utile per etichette e controlli di sicurezza
  • Validazione dello schema JSON: convalida il formato dell'output strutturato
  • Corrispondenza tramite regex: convalida pattern specifici, come date e codici
  • LLM-as-judge: valuta la qualità di testi senza una risposta predefinita

Utilizzi pytest con @pytest.mark.parametrize per creare suite di test ordinate e scalabili. Memorizzi le risposte nella cache per gestire i costi. Esegua un sottoinsieme di smoke test a ogni PR e la suite completa ogni notte. Nella prossima lezione: test di regressione tra gli aggiornamenti dei modelli.

Domande Frequenti

La lezione «Test dei prompt basati su asserzioni» è gratuita?

Sì — il testo completo di «Test dei prompt basati su asserzioni» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AI Prompt Engineering, passa a CoddyKit PRO. Il corso AI Prompt Engineering include 4 lezioni in totale.

Cosa imparerò in «Test dei prompt basati su asserzioni»?

Verifica degli output con contains(), regex, schema JSON e LLM-as-judge Eserciti AI Prompt Engineering con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare AI Prompt Engineering?

Non è richiesta alcuna esperienza precedente. AI Prompt Engineering su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Test dei prompt basati su asserzioni»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione AI Prompt Engineering?

Sì. Ogni lezione AI Prompt Engineering include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Scrittura di casi di test per i prompt
  2. Test dei prompt basati su asserzioni
  3. Test di regressione tra gli aggiornamenti dei modelli
  4. Creazione di una suite di test per i prompt
← Torna a AI Prompt Engineering