Assertionsbaseret prompttest
Kontrol af output med contains(), regex, JSON schema og LLM-as-judge.
Assertionsbaseret prompttest er en gratis Promptteknik til AI-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Promptteknik til AI, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Promptteknik til AI-kurset indeholder 4 lektioner i alt.
Påstande for LLM-uddata
Påstandsbaseret afprøvning af LLM'er anvender det samme princip som ved modultest: Fremsæt eksplicitte påstande om, hvad uddataene skal indeholde eller ikke må indeholde, og markér straks afprøvningen som mislykket, når påstanden overtrædes.
I modsætning til modultest med deterministiske funktioner arbejder LLM-påstande med sandsynlighedsbaserede tekstuddata — derfor kræves der mere fleksible påstandstyper: contains, matches_schema, satisfies_regex, llm_judge_score_above.
Grundlæggende assertions: contains og not_contains
De enkleste assertions kontrollerer, om nøgleord er til stede eller mangler. De fungerer godt til klassifikationsopgaver, strukturerede resultater og sikkerhedskontroller.
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]}'Validering af JSON-skema
Når din prompt skal returnere struktureret JSON, skal du validere resultatet op mod et skema. En fejl i skemavalideringen betyder, at prompten har et formatproblem — enten har modellen tilføjet brødtekst, eller også er JSON-strukturen forkert.
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)Regex-matchning
Regex-assertions validerer resultatformatet præcist — nyttigt til resultater, der skal følge et bestemt mønster, f.eks. datoer, telefonnumre eller strukturerede koder.
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}$')Bedømmelse med LLM som dommer
Til åbne resultater kan du bruge et ekstra LLM-kald til at evaluere kvaliteten. Det kaldes LLM som dommer. Dommermodellen modtager den oprindelige prompt, resultatet og evalueringskriterierne og returnerer derefter en bedømmelse.
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}'Brug af pytest til prompttests
pytest er standardtestrammeværket til Python og fungerer godt til prompttests. Hver testfunktion svarer til ét testtilfælde. pytest indsamler, kører og rapporterer dem automatisk.
# 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 -vParametriserede tests i pytest
Brug @pytest.mark.parametrize til at køre den samme testfunktion med mange input uden at gentage kode. Det er den reneste måde at opbygge en omfattende testsuite på.
# 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 til delt prompttilstand
Brug pytest-fixtures til at dele ressourcekrævende opsætning på tværs af tests — f.eks. til at indlæse en promptskabelon eller oprette en API-klient én gang pr. testsession.
# 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()Håndtering af ustabile tests
LLM-resultater er sandsynlighedsbaserede — selv ved temperature=0 kan forskellige modelimplementeringer eller versioner producere forskellige resultater. Håndter ustabilitet med genforsøgslogik og tolerancetærskler.
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'Testenes ydeevne og omkostninger
Hvert testtilfælde er et API-kald — for 100 testtilfælde ved $0.005/kald = $0.50 pr. fuld testkørsel. Strategier til at styre omkostningerne:
- Gem svar i en cache for statiske testinput, og kør fra cachen i CI
- Kør hele testsuiten hver nat; kør kun et udvalg af smoketests (10 tilfælde) ved hver PR
- Brug en billigere model (gpt-4o-mini) til de fleste tests; kør kun regressionssuiten på gpt-4o
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)Testrapporter
pytest producerer detaljerede rapporter, der fremhæver, hvilke testtilfælde der mislykkedes, og hvorfor. Brug pytest --tb=short -v til korte fejlmeddelelser. I CI kan du bruge --junitxml til at producere JUnit XML-rapporter, der er kompatible med GitHub Actions, GitLab CI og 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()Videnstjek
Hvornår vil du bruge bedømmelse med LLM som dommer i stedet for en assertion med eksakt matchning i prompttests?
Opsummering: Assertionbaseret prompttest
De vigtigste assertiontyper for LLM-resultater:
- contains / not_contains: tilstedeværelse af nøgleord — godt til etiketter og sikkerhedskontroller
- Validering af JSON-skema: validerer formatet for strukturerede resultater
- Regex-matchning: validerer bestemte mønstre (datoer, koder)
- LLM som dommer: evaluerer kvaliteten af åbne tekstresultater
Brug pytest sammen med @pytest.mark.parametrize til rene, skalerbare testsuiter. Gem svar i en cache for at styre omkostningerne. Kør et smoke-udvalg ved hver PR og hele testsuiten hver nat. Næste lektion: regressionstest på tværs af modelopdateringer.
Lær Promptteknik til AI med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 53
- Lektioner
- 199
Ofte stillede spørgsmål
Er lektionen “Assertionsbaseret prompttest” gratis?
Ja — alle 3 lektioner i læringssporet Promptteknik til AI, inklusive “Assertionsbaseret prompttest”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Promptteknik til AI-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Assertionsbaseret prompttest”?
Kontrol af output med contains(), regex, JSON schema og LLM-as-judge. Du øver dig i Promptteknik til AI med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Promptteknik til AI?
Der kræves ingen tidligere erfaring. Promptteknik til AI på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.
Hvor lang tid tager lektionen “Assertionsbaseret prompttest”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Promptteknik til AI-lektion?
Ja. Alle Promptteknik til AI-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Skrivning af prompt-testcases
- Assertionsbaseret prompttest
- Regressionstest på tværs af modelopdateringer
- Opbygning af en prompt-testsuite