0Pricing
AI Prompt Engineering · 课时

基于断言的提示词测试

使用 contains()、正则表达式、JSON 模式和 LLM 评审来检查输出。

基于断言的提示词测试 是 CoddyKit 上的免费 AI Prompt Engineering 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Prompt Engineering 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Prompt Engineering 课程共包含 4 节课。

对 LLM 输出进行断言

基于断言的测试应用于 LLM 时,遵循与单元测试相同的原则:明确声明输出必须包含或不得包含哪些内容,并在声明被违反时立即失败。

与使用确定性函数的单元测试不同,LLM 断言处理的是概率性文本输出,因此需要更灵活的断言类型:contains、matches_schema、satisfies_regex、llm_judge_score_above。

基础断言:contains 和 not_contains

最简单的断言会检查关键词是否存在或缺失。这些断言非常适合分类任务、结构化输出和安全检查。

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

JSON 架构验证

当您的提示词应返回结构化 JSON 时,请根据架构验证输出。架构验证失败意味着提示词存在格式问题——可能是模型添加了说明文字,也可能是 JSON 结构不正确。

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)

正则表达式匹配

正则表达式断言可以精确验证输出格式,适用于应遵循特定模式的输出,例如日期、电话号码或结构化代码。

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

LLM 评审评分

对于开放式输出,请使用第二次 LLM 调用来评估质量。这称为 LLM 评审。评审模型会接收原始提示词、输出和评估标准,然后返回分数。

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

使用 pytest 进行提示词测试

pytest 是标准的 Python 测试框架,非常适合提示词测试。每个测试函数对应一个测试用例。pytest 会自动收集、运行并报告测试结果。

# 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

pytest 中的参数化测试

使用 @pytest.mark.parametrize,可以让同一个测试函数处理多个输入,而无需重复编写代码。这是构建全面测试套件最简洁的方式。

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

用于共享提示词状态的测试固件

使用 pytest 的测试固件,在多个测试之间共享开销较大的准备工作,例如每个测试会话只加载一次提示词模板或创建一次 API 客户端。

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

处理不稳定的测试

LLM 的输出具有概率性——即使温度设置为 0,不同的模型部署或版本也可能产生不同的输出。请使用重试逻辑和容差阈值来处理这种不稳定性。

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'

测试性能与成本

每个测试用例都会发起一次 API 调用——100 个测试用例按每次调用 0.005 美元计算,每次完整测试运行的成本为 0.50 美元。管理成本的策略:

  • 为静态测试输入缓存响应,并在持续集成中从缓存运行
  • 每晚运行完整测试套件;每次 PR 只运行一个冒烟测试子集(10 个用例)
  • 大多数测试使用更便宜的模型(gpt-4o-mini);只有回归测试套件使用 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)

测试输出报告

pytest 会生成详细报告,突出显示哪些测试用例失败以及失败原因。使用 pytest --tb=short -v 获取简洁的失败信息。在持续集成中,使用 --junitxml 生成 JUnit XML 报告,该报告兼容 GitHub Actions、GitLab CI 和 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()

知识检查

在提示词测试中,什么时候应使用LLM 评审评分,而不是精确匹配断言?

回顾:基于断言的提示词测试

LLM 输出的主要断言类型:

  • contains / not_contains:关键词是否存在,适合标签和安全检查
  • JSON 架构验证:验证结构化输出格式
  • 正则表达式匹配:验证特定模式(日期、代码)
  • LLM 评审:评估开放式文本的质量

使用 pytest 和 @pytest.mark.parametrize 构建简洁、可扩展的测试套件。缓存响应以控制成本。每次 PR 运行冒烟测试子集;每晚运行完整测试套件。下一课:跨模型更新进行回归测试。

常见问题解答

「基于断言的提示词测试」课时是免费的吗?

是的 — 「基于断言的提示词测试」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Prompt Engineering 课程的其余内容,请升级到 CoddyKit PRO。 AI Prompt Engineering 课程共包含 4 节课。

「基于断言的提示词测试」这节课中我会学到什么?

使用 contains()、正则表达式、JSON 模式和 LLM 评审来检查输出。 你通过在浏览器中直接运行的动手代码来练习 AI Prompt Engineering,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 AI Prompt Engineering 需要有经验吗?

无需任何先前经验。CoddyKit 上的 AI Prompt Engineering 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「基于断言的提示词测试」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 AI Prompt Engineering 课中编写并运行代码吗?

能。每节 AI Prompt Engineering 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 编写提示词测试用例
  2. 基于断言的提示词测试
  3. 跨模型更新的回归测试
  4. 构建提示词测试套件
← 返回 AI Prompt Engineering