0Pricing
AI Prompt Engineering · บทเรียน

การทดสอบพรอมต์โดยอิงการยืนยัน

ตรวจสอบข้อมูลส่งออกด้วย contains(), นิพจน์ประจำรูปแบบ โครงร่าง JSON และการให้ LLM เป็นผู้ตัดสิน

การทดสอบพรอมต์โดยอิงการยืนยัน เป็นบทเรียน AI Prompt Engineering ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Prompt Engineering และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Prompt Engineering มีบทเรียนทั้งหมด 4 บทเรียน

การยืนยันผลลัพธ์ของ LLM

การทดสอบด้วยการยืนยันใช้หลักการเดียวกับการทดสอบหน่วยกับ LLM: ระบุข้ออ้างอย่างชัดเจนว่าผลลัพธ์ต้องมีหรือไม่มีอะไร และทำให้การทดสอบล้มเหลวทันทีเมื่อข้ออ้างนั้นไม่เป็นจริง

ต่างจากการทดสอบหน่วยที่ใช้ฟังก์ชันซึ่งให้ผลแน่นอน การยืนยันของ LLM ต้องจัดการกับผลลัพธ์ข้อความที่มีความน่าจะเป็น จึงต้องใช้ประเภทยืนยันที่ยืดหยุ่นมากขึ้น เช่น contains, matches_schema, satisfies_regex, llm_judge_score_above

การยืนยันพื้นฐาน: การมีและการไม่มี

การยืนยันที่ง่ายที่สุดคือการตรวจสอบว่ามีหรือไม่มีคำสำคัญ การยืนยันเหล่านี้เหมาะสำหรับงานจำแนกประเภท ผลลัพธ์ที่มีโครงสร้าง และการตรวจสอบความปลอดภัย

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 ต่อการเรียกใช้การทดสอบเต็มรูปแบบหนึ่งครั้ง กลยุทธ์ในการจัดการค่าใช้จ่าย:

  • เก็บคำตอบจากอินพุตการทดสอบแบบคงที่ไว้ในแคช และเรียกใช้จากแคชใน CI
  • เรียกใช้ชุดการทดสอบเต็มรูปแบบทุกคืน ส่วนในแต่ละ 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 เพื่อดูข้อความข้อผิดพลาดแบบกระชับ สำหรับ CI ให้ใช้ --junitxml เพื่อสร้างรายงาน XML ของ JUnit ที่ใช้งานร่วมกับ 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:

  • การมี / การไม่มี: ตรวจสอบการมีคำสำคัญ เหมาะสำหรับป้ายกำกับและการตรวจสอบความปลอดภัย
  • การตรวจสอบความถูกต้องของแบบแผน JSON: ตรวจสอบรูปแบบผลลัพธ์ที่มีโครงสร้าง
  • การจับคู่ด้วยนิพจน์ปกติ: ตรวจสอบรูปแบบเฉพาะ (วันที่ รหัส)
  • การให้คะแนนโดย LLM ในฐานะผู้ตัดสิน: ประเมินคุณภาพข้อความแบบปลายเปิด

ใช้ pytest ร่วมกับ @pytest.mark.parametrize เพื่อสร้างชุดการทดสอบที่เป็นระเบียบและขยายได้ง่าย เก็บคำตอบไว้ในแคชเพื่อจัดการค่าใช้จ่าย เรียกใช้ชุดย่อยสำหรับการทดสอบเบื้องต้นในแต่ละ PR และเรียกใช้ชุดการทดสอบเต็มรูปแบบทุกคืน บทเรียนถัดไป: การทดสอบการถดถอยเมื่อมีการอัปเดตโมเดล

คำถามที่พบบ่อย

บทเรียน “การทดสอบพรอมต์โดยอิงการยืนยัน” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การทดสอบพรอมต์โดยอิงการยืนยัน” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Prompt Engineering ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Prompt Engineering มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การทดสอบพรอมต์โดยอิงการยืนยัน”

ตรวจสอบข้อมูลส่งออกด้วย contains(), นิพจน์ประจำรูปแบบ โครงร่าง JSON และการให้ LLM เป็นผู้ตัดสิน คุณปฏิบัติ AI Prompt Engineering ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Prompt Engineering หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Prompt Engineering บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “การทดสอบพรอมต์โดยอิงการยืนยัน” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน AI Prompt Engineering นี้ได้ไหม

ได้ บทเรียน AI Prompt Engineering ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การเขียนกรณีทดสอบพรอมต์
  2. การทดสอบพรอมต์โดยอิงการยืนยัน
  3. การทดสอบการถดถอยระหว่างการอัปเดตโมเดล
  4. การสร้างชุดการทดสอบพรอมต์
← กลับไปที่ AI Prompt Engineering