0Pricing
AI Prompt Engineering · Aula

Testes de prompts baseados em asserções

Verificação das saídas com contains(), expressões regulares, esquema JSON e LLM como avaliador.

Testes de prompts baseados em asserções é uma aula grátis de AI Prompt Engineering no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de AI Prompt Engineering, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de AI Prompt Engineering inclui 4 aulas no total.

Asserções para saídas de LLM

Os testes baseados em asserções aplicam a LLM o mesmo princípio usado nos testes unitários: faça afirmações explícitas sobre o que a saída deve ou não deve conter e falhe imediatamente quando a afirmação for violada.

Diferentemente dos testes unitários com funções determinísticas, as asserções de LLM lidam com saídas de texto probabilísticas — o que exige tipos de asserção mais flexíveis: contains, matches_schema, satisfies_regex, llm_judge_score_above.

Asserções básicas: contém e não contém

As asserções mais simples verificam a presença ou a ausência de palavras-chave. Elas funcionam bem para tarefas de classificação, saídas estruturadas e verificações de segurança.

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

Validação de esquema JSON

Quando sua instrução deve retornar JSON estruturado, valide a saída em relação a um esquema. Uma falha na validação do esquema significa que há um problema de formato na instrução — o modelo adicionou texto explicativo ou a estrutura JSON está incorreta.

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)

Correspondência com expressões regulares

As asserções de expressões regulares validam o formato da saída com precisão — são úteis para saídas que devem seguir um padrão específico, como datas, números de telefone ou códigos estruturados.

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

Pontuação de LLM como avaliador

Para saídas abertas, use uma segunda chamada de LLM para avaliar a qualidade. Isso é chamado de LLM como avaliador. O modelo avaliador recebe a instrução original, a saída e os critérios de avaliação e, em seguida, retorna uma pontuação.

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

Usando pytest para testes de instruções

pytest é a estrutura padrão de testes do Python e funciona bem para testes de instruções. Cada função de teste corresponde a um caso de teste. O pytest coleta, executa e relata os resultados automaticamente.

# 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

Testes parametrizados no pytest

Use @pytest.mark.parametrize para executar a mesma função de teste com várias entradas sem repetir código. Essa é a maneira mais organizada de criar um conjunto abrangente de testes.

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

Recursos compartilhados para o estado das instruções

Use os recursos de teste do pytest para compartilhar configurações dispendiosas entre os testes — como carregar um modelo de instrução ou criar um cliente de API uma vez por sessão de testes.

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

Como lidar com testes instáveis

As saídas de LLM são probabilísticas — mesmo com temperature=0, diferentes implantações ou versões do modelo podem produzir saídas diferentes. Lide com essa instabilidade usando lógica de novas tentativas e limites de tolerância.

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'

Desempenho e custo dos testes

Cada caso de teste é uma chamada de API — para 100 casos de teste a US$ 0,005 por chamada = US$ 0,50 por execução completa dos testes. Estratégias para gerenciar os custos:

  • Armazene em cache as respostas de entradas de teste estáticas e execute a partir do cache no CI
  • Execute o conjunto completo todas as noites; execute apenas um subconjunto de teste rápido (10 casos) em cada PR
  • Use um modelo mais barato (gpt-4o-mini) na maioria dos testes; execute no gpt-4o apenas para o conjunto de testes de regressão
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)

Relatórios de saída dos testes

pytest produz relatórios detalhados que destacam quais casos de teste falharam e por quê. Use pytest --tb=short -v para obter mensagens de falha concisas. No CI, use --junitxml para produzir relatórios XML do JUnit compatíveis com 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ção de conhecimento

Quando você usaria a pontuação de LLM como avaliador em vez de uma asserção de correspondência exata nos testes de instruções?

Recapitulação: testes de instruções baseados em asserções

Principais tipos de asserção para saídas de LLM:

  • contém / não contém: presença de palavras-chave — útil para rótulos e verificações de segurança
  • validação de esquema JSON: valida o formato da saída estruturada
  • correspondência com expressões regulares: valida padrões específicos (datas, códigos)
  • LLM como avaliador: avalia a qualidade de textos abertos

Use pytest com @pytest.mark.parametrize para criar conjuntos de testes organizados e escaláveis. Armazene as respostas em cache para gerenciar os custos. Execute um subconjunto de teste rápido em cada PR e o conjunto completo todas as noites. Próxima lição: testes de regressão entre atualizações de modelos.

Perguntas Frequentes

A aula “Testes de prompts baseados em asserções” é grátis?

Sim — o texto completo de “Testes de prompts baseados em asserções” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de AI Prompt Engineering, atualize para CoddyKit PRO. O curso de AI Prompt Engineering inclui 4 aulas no total.

O que vou aprender em “Testes de prompts baseados em asserções”?

Verificação das saídas com contains(), expressões regulares, esquema JSON e LLM como avaliador. Você pratica AI Prompt Engineering com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar AI Prompt Engineering?

Nenhuma experiência prévia é necessária. AI Prompt Engineering no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.

Quanto tempo leva a aula “Testes de prompts baseados em asserções”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de AI Prompt Engineering?

Sim. Cada aula de AI Prompt Engineering inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Escrevendo casos de teste para prompts
  2. Testes de prompts baseados em asserções
  3. Testes de regressão entre atualizações de modelos
  4. Criando um conjunto de testes para prompts
← Voltar para AI Prompt Engineering