Pruebas de prompts basadas en aserciones
Compruebe las salidas con contains(), expresiones regulares, esquemas JSON y LLM-as-judge.
Pruebas de prompts basadas en aserciones es una lección gratuita de AI Prompt Engineering en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Prompt Engineering, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Prompt Engineering incluye 4 lecciones en total.
Aserciones para las salidas de LLM
Las pruebas basadas en aserciones aplican a los LLM el mismo principio que se utiliza en las pruebas unitarias: formule afirmaciones explícitas sobre lo que la salida debe contener o no contener y haga que la prueba falle inmediatamente cuando se incumpla la afirmación.
A diferencia de las pruebas unitarias con funciones deterministas, las aserciones para LLM tratan con salidas de texto probabilísticas, lo que requiere tipos de aserción más flexibles: contains, matches_schema, satisfies_regex, llm_judge_score_above.
Aserciones básicas: contains y not_contains
Las aserciones más sencillas comprueban la presencia o ausencia de palabras clave. Funcionan bien para tareas de clasificación, salidas estructuradas y comprobaciones de seguridad.
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]}'Validación con JSON Schema
Cuando su prompt debe devolver JSON estructurado, valide la salida contra un esquema. Un error de validación del esquema significa que el prompt tiene un problema de formato: el modelo añadió texto explicativo o la estructura JSON es incorrecta.
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)Coincidencia con expresiones regulares
Las aserciones con expresiones regulares validan con precisión el formato de la salida. Son útiles para salidas que deben seguir un patrón específico, como fechas, números de teléfono o códigos estructurados.
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}$')Evaluación mediante LLM-as-judge
Para salidas abiertas, utilice una segunda llamada a un LLM para evaluar la calidad. Esto se denomina LLM-as-judge. El modelo juez recibe el prompt original, la salida y los criterios de evaluación, y después devuelve una puntuación.
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}'Uso de pytest para probar prompts
pytest es el framework estándar de pruebas de Python y funciona bien para probar prompts. Cada función de prueba corresponde a un caso de prueba. pytest los recopila, ejecuta y genera un informe automáticamente.
# 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 -vPruebas parametrizadas en pytest
Utilice @pytest.mark.parametrize para ejecutar la misma función de prueba con muchas entradas sin repetir código. Es la forma más limpia de crear un conjunto completo de pruebas.
# 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]Fixtures para compartir el estado de los prompts
Utilice los fixtures de pytest para compartir la preparación costosa entre las pruebas, como cargar una plantilla de prompt o crear un cliente de API una vez por sesión de pruebas.
# 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()Gestión de pruebas inestables
Las salidas de los LLM son probabilísticas; incluso con temperature=0, distintas implementaciones o versiones del modelo pueden producir salidas diferentes. Gestione esta inestabilidad mediante lógica de reintentos y umbrales de tolerancia.
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'Rendimiento y coste de las pruebas
Cada caso de prueba es una llamada a una API: 100 casos de prueba a $0.005/llamada = $0.50 por ejecución completa. Estrategias para controlar el coste:
- Almacene en caché las respuestas para entradas de prueba estáticas y ejecútelas desde la caché en CI
- Ejecute el conjunto completo cada noche; ejecute solo un subconjunto de pruebas de humo (10 casos) en cada PR
- Utilice un modelo más económico (gpt-4o-mini) para la mayoría de las pruebas; utilice gpt-4o solo para el conjunto de pruebas de regresión
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)Informes de resultados de las pruebas
pytest genera informes detallados que destacan qué casos de prueba fallaron y por qué. Utilice pytest --tb=short -v para obtener mensajes de error concisos. Para CI, utilice --junitxml para generar informes XML de JUnit compatibles con GitHub Actions, GitLab CI y 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()Comprobación de conocimientos
¿Cuándo utilizaría la evaluación mediante LLM-as-judge en lugar de una aserción de coincidencia exacta al probar prompts?
Recapitulación: pruebas de prompts basadas en aserciones
Principales tipos de aserciones para salidas de LLM:
- contains / not_contains: presencia de palabras clave; útil para etiquetas y comprobaciones de seguridad
- Validación de JSON Schema: valida el formato de la salida estructurada
- Coincidencia con expresiones regulares: valida patrones específicos (fechas, códigos)
- LLM-as-judge: evalúa la calidad de texto abierto
Utilice pytest con @pytest.mark.parametrize para crear conjuntos de pruebas limpios y escalables. Almacene las respuestas en caché para controlar los costes. Ejecute un subconjunto de pruebas de humo en cada PR y el conjunto completo cada noche. Siguiente lección: pruebas de regresión entre actualizaciones de modelos.
Preguntas frecuentes
¿La lección «Pruebas de prompts basadas en aserciones» es gratis?
Sí — el texto completo de «Pruebas de prompts basadas en aserciones» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Prompt Engineering, actualiza a CoddyKit PRO. El curso de AI Prompt Engineering incluye 4 lecciones en total.
¿Qué aprenderé en «Pruebas de prompts basadas en aserciones»?
Compruebe las salidas con contains(), expresiones regulares, esquemas JSON y LLM-as-judge. Practicas AI Prompt Engineering con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar AI Prompt Engineering?
No se requiere experiencia previa. AI Prompt Engineering en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.
¿Cuánto tiempo toma la lección «Pruebas de prompts basadas en aserciones»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de AI Prompt Engineering?
Sí. Cada lección de AI Prompt Engineering incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Redacción de casos de prueba para prompts
- Pruebas de prompts basadas en aserciones
- Pruebas de regresión entre actualizaciones de modelos
- Creación de un conjunto de pruebas para prompts