0Pricing
AI Prompt Engineering · 강의

비교 심사: A와 B

절대 점수 없이 출력 결과의 순위를 정하는 쌍별 비교 프롬프트를 작성합니다.

비교 심사: A와 B은(는) CoddyKit의 무료 AI Prompt Engineering 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 AI Prompt Engineering 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. AI Prompt Engineering 강의에는 총 4개의 강의가 포함되어 있습니다.

쌍별(A 대 B) 평가란 무엇입니까?

쌍별 평가(비교 심사라고도 함)는 같은 질문에 대한 두 응답을 평가자에게 제시하고 어느 쪽이 더 나은지 묻습니다. 단일 응답을 1~5점으로 평가하는 대신, 평가자는 상대적인 판단을 내립니다. A가 더 낫거나, B가 더 낫거나, 두 응답이 동률입니다.

이 방법은 일부 절대 채점 편향을 피하며, 응답별 절대 점수보다 더 신뢰할 수 있는 순위를 만들어 내는 경우가 많습니다.

기본 쌍별 평가자 프롬프트

가장 간단한 쌍별 평가자는 어느 응답이 더 나은지와 그 이유를 묻습니다. 핵심은 선택을 강제하는 것입니다. 평가자가 ‘둘 다 좋다’와 같은 모호한 말로 비교를 피하지 못하게 하십시오.

import anthropic
import json

client = anthropic.Anthropic(api_key='sk-ant-...')

PAIRWISE_PROMPT = (
    'Given the same question, compare these two responses and decide which is better.\n\n'
    'Question: {question}\n\n'
    'Response A:\n{response_a}\n\n'
    'Response B:\n{response_b}\n\n'
    'Which response is better? You must pick A, B, or TIE (use TIE only if '
    'they are truly equal in all meaningful ways).\n\n'
    'Return JSON: {{"winner": "A" or "B" or "TIE", '
    '"reason": "<one sentence explaining why the winner is better>"}}'
)

def pairwise_judge(question, response_a, response_b):
    prompt = PAIRWISE_PROMPT.format(
        question=question,
        response_a=response_a,
        response_b=response_b
    )
    r = client.messages.create(
        model='claude-opus-4-5',
        max_tokens=150,
        messages=[{'role': 'user', 'content': prompt}]
    )
    return json.loads(r.content[0].text)

result = pairwise_judge(
    'What is machine learning?',
    'Machine learning is a subset of AI.',
    'Machine learning is a method of data analysis that automates model building.'
)
print(result)

위치 편향을 줄이기 위한 순서 무작위화

위치 편향은 평가자가 첫 번째 선택지를 선호하게 만듭니다. 이를 중화하려면 모든 비교를 두 번 실행하십시오. 한 번은 A를 먼저 제시하고, 한 번은 B를 먼저 제시합니다. 두 순서에서 결과가 일치할 때만 승자를 인정하십시오. 결과가 다르면 동률로 처리하거나 사람의 검토로 넘기십시오.

import anthropic
import json
import random

client = anthropic.Anthropic(api_key='sk-ant-...')

def debiased_pairwise_judge(question, response_a, response_b):
    def single_comparison(first, second, first_label, second_label):
        prompt = (
            f'Question: {question}\n\n'
            f'Response {first_label}:\n{first}\n\n'
            f'Response {second_label}:\n{second}\n\n'
            f'Which is better? Reply with {first_label}, {second_label}, or TIE.'
        )
        r = client.messages.create(
            model='claude-opus-4-5',
            max_tokens=10,
            messages=[{'role': 'user', 'content': prompt}]
        )
        return r.content[0].text.strip()

    # Run A-first
    result_ab = single_comparison(response_a, response_b, 'A', 'B')
    # Run B-first
    result_ba = single_comparison(response_b, response_a, 'B', 'A')

    if result_ab == 'A' and result_ba == 'A':
        return 'A', 'Consistent: A wins in both orderings'
    elif result_ab == 'B' and result_ba == 'B':
        return 'B', 'Consistent: B wins in both orderings'
    else:
        return 'TIE', f'Inconsistent: {result_ab} then {result_ba} — position bias detected'

winner, reason = debiased_pairwise_judge(
    'Explain a hash table.',
    'A hash table maps keys to values.',
    'A hash table is a data structure using a hash function to store key-value pairs for O(1) lookup.'
)
print(f'Winner: {winner} — {reason}')

여러 기준을 사용하는 쌍별 평가

단순히 ‘전체적으로 어느 쪽이 더 나은가?’라고 묻기보다 구체적인 평가 차원에서 비교하도록 평가자에게 요청하십시오. 평가 차원별 쌍별 비교를 사용하면 왜 한 응답이 선호되는지에 대한 실제 활용 가능한 정보를 얻을 수 있습니다.

import anthropic
import json

client = anthropic.Anthropic(api_key='sk-ant-...')

DIMENSIONAL_PAIRWISE = (
    'Compare Response A and Response B on each dimension.\n\n'
    'Question: {question}\n\n'
    'Response A: {response_a}\n\n'
    'Response B: {response_b}\n\n'
    'For each dimension, say A, B, or TIE:\n'
    '1. ACCURACY: Which is more factually correct?\n'
    '2. COMPLETENESS: Which answers the question more fully?\n'
    '3. CLARITY: Which is easier to understand?\n'
    '4. CONCISENESS: Which avoids unnecessary length?\n\n'
    'Return JSON: {{"accuracy":"A/B/TIE", "completeness":"A/B/TIE", '
    '"clarity":"A/B/TIE", "conciseness":"A/B/TIE", "overall":"A/B/TIE"}}'
)

def dimensional_compare(question, a, b):
    r = client.messages.create(
        model='claude-opus-4-5',
        max_tokens=200,
        messages=[{'role': 'user', 'content': DIMENSIONAL_PAIRWISE.format(
            question=question, response_a=a, response_b=b
        )}]
    )
    return json.loads(r.content[0].text)

result = dimensional_compare(
    'How does HTTPS work?',
    'HTTPS encrypts web traffic using SSL/TLS.',
    'HTTPS secures HTTP using TLS. The browser and server perform a handshake, exchange certificates, and establish an encrypted channel for all data transfer.'
)
print(result)

토너먼트 구성: 순환 대진

두 개보다 많은 응답을 비교할 때는 순환 대진 토너먼트를 사용하십시오. 모든 응답을 다른 모든 응답과 비교하는 방식입니다. 가장 많이 이긴 응답이 1위로 순위가 매겨집니다.

from itertools import combinations
from collections import defaultdict

def round_robin_tournament(question, responses):
    """
    responses: list of (label, text) tuples
    Returns ranking by win count.
    """
    wins = defaultdict(int)
    ties = defaultdict(int)

    # Every pair compared once
    for (label_a, text_a), (label_b, text_b) in combinations(responses, 2):
        winner, reason = debiased_pairwise_judge(question, text_a, text_b)

        if winner == 'A':
            wins[label_a] += 1
        elif winner == 'B':
            wins[label_b] += 1
        else:  # TIE
            ties[label_a] += 1
            ties[label_b] += 1

        print(f'{label_a} vs {label_b}: {winner}')

    # Rank by wins, then ties
    ranking = sorted(
        responses,
        key=lambda x: (wins[x[0]], ties[x[0]]),
        reverse=True
    )
    print('\nFinal ranking:')
    for i, (label, _) in enumerate(ranking, 1):
        print(f'{i}. {label}: {wins[label]} wins, {ties[label]} ties')
    return ranking

연속 순위 산정을 위한 엘로 평점

대규모 평가에서는 순환 대진 대신 엘로 평점을 사용하십시오. 각 응답은 1000점에서 시작합니다. 비교가 끝날 때마다 승자는 점수를 얻고 패자는 점수를 잃습니다. 점수 변동 폭은 결과가 얼마나 예상 밖이었는지에 비례합니다. 많은 비교를 거치면 엘로 점수가 신뢰할 수 있는 전체 순위를 만들어 냅니다.

import math

def elo_update(rating_a, rating_b, winner, k=32):
    """
    Update Elo ratings after a match.
    winner: 'A' (A won), 'B' (B won), 'TIE' (draw)
    Returns (new_rating_a, new_rating_b)
    """
    expected_a = 1 / (1 + 10 ** ((rating_b - rating_a) / 400))
    expected_b = 1 - expected_a

    if winner == 'A':
        score_a, score_b = 1, 0
    elif winner == 'B':
        score_a, score_b = 0, 1
    else:  # TIE
        score_a, score_b = 0.5, 0.5

    new_a = rating_a + k * (score_a - expected_a)
    new_b = rating_b + k * (score_b - expected_b)
    return new_a, new_b

# Simulate ratings for 4 model variants
ratings = {'ModelA': 1000, 'ModelB': 1000, 'ModelC': 1000, 'ModelD': 1000}

# After running many pairwise comparisons:
ratings['ModelA'], ratings['ModelB'] = elo_update(ratings['ModelA'], ratings['ModelB'], 'A')
ratings['ModelC'], ratings['ModelD'] = elo_update(ratings['ModelC'], ratings['ModelD'], 'TIE')

ranking = sorted(ratings.items(), key=lambda x: x[1], reverse=True)
for model, score in ranking:
    print(f'{model}: {score:.0f}')

쌍별 평가와 절대 채점 중 선택하기

다음과 같은 경우에는 쌍별 평가를 사용하십시오:

  • 여러 모델이나 프롬프트 변형의 순위를 매기고 싶을 때
  • 절대 점수의 기준 임계값을 정의하기 어려울 때
  • 둘 다 ‘좋지만’ 서로 다른 방식으로 좋은 출력을 비교할 때

다음과 같은 경우에는 절대 채점(평가 기준표)을 사용하십시오:

  • 통과/실패 기준(‘이 응답은 충분히 좋은가?’)이 필요할 때
  • 질의마다 평가할 응답이 하나뿐일 때
  • 문제 해결을 위해 기준별 점수가 필요할 때

대규모 표본 추출 기반 쌍별 평가

N개의 응답에서 모든 쌍을 실행하려면 N*(N-1)/2번의 비교, 즉 O(N^2)의 비교가 필요합니다. N이 클 때는 모든 다른 응답과 비교하는 대신 각 응답을 K개의 무작위 상대와 비교하는 무작위 표본 추출을 사용하십시오. 훨씬 낮은 비용으로 실제 순위를 근사할 수 있습니다.

import random
from collections import defaultdict

def sampled_tournament(question, responses, k_opponents=5):
    """
    Compare each response against k random opponents.
    More efficient than full round-robin for large N.
    """
    wins = defaultdict(int)
    n = len(responses)

    for i, (label, text) in enumerate(responses):
        # Sample k random opponents (not self)
        opponent_indices = random.sample(
            [j for j in range(n) if j != i],
            min(k_opponents, n - 1)
        )
        for j in opponent_indices:
            opp_label, opp_text = responses[j]
            winner, _ = debiased_pairwise_judge(question, text, opp_text)
            if winner == 'A':
                wins[label] += 1
            elif winner == 'B':
                wins[opp_label] += 1

    ranking = sorted(responses, key=lambda x: wins[x[0]], reverse=True)
    for i, (label, _) in enumerate(ranking, 1):
        print(f'{i}. {label}: {wins[label]} wins')
    return ranking

불일치 해석

두 순서의 결과가 다를 때(A-B 순서에서는 A가 이기고 B-A 순서에서는 B가 이기는 경우), 이는 단순한 위치 편향이 아니라 실제로 경계에 있는 비교임을 나타냅니다. 이러한 경계 사례는 더 심층적인 분석을 수행할 가치가 있습니다.

import anthropic
import json

client = anthropic.Anthropic(api_key='sk-ant-...')

def deep_analyze_tie(question, response_a, response_b):
    """When A vs B is a genuine tie, ask the judge to explain strengths of each."""
    analysis_prompt = (
        f'These two responses to the same question are closely matched in quality.\n\n'
        f'Question: {question}\n\n'
        f'Response A: {response_a}\n\n'
        f'Response B: {response_b}\n\n'
        f'Analyze both:\n'
        f'1. What does A do better than B?\n'
        f'2. What does B do better than A?\n'
        f'3. For what audience or context would you prefer A?\n'
        f'4. For what audience or context would you prefer B?'
    )
    r = client.messages.create(
        model='claude-opus-4-5',
        max_tokens=400,
        messages=[{'role': 'user', 'content': analysis_prompt}]
    )
    return r.content[0].text

analysis = deep_analyze_tie(
    'Explain async/await in Python.',
    'Async/await allows non-blocking code execution.',
    'Async/await lets you write asynchronous code that looks synchronous, '
    'using the asyncio event loop to handle I/O without blocking.'
)
print(analysis[:300])

쌍별 결과 기록

모든 쌍별 비교 결과를 기록하여 어떤 조건에서 어떤 응답이 어떤 응답을 이겼는지 검색할 수 있는 기록을 구축하십시오. 이 데이터는 회귀 검증과 시간에 따른 모델 개선을 이해하는 데 유용합니다.

import json
import datetime

def logged_pairwise(question, response_a, response_b,
                    label_a='A', label_b='B', log_file='pairwise_log.jsonl'):
    winner, reason = debiased_pairwise_judge(question, response_a, response_b)

    entry = {
        'timestamp': datetime.datetime.utcnow().isoformat(),
        'question': question[:100],  # Truncate for storage
        'label_a': label_a,
        'label_b': label_b,
        'winner': winner,
        'reason': reason,
        'response_a_length': len(response_a.split()),
        'response_b_length': len(response_b.split()),
    }

    with open(log_file, 'a') as f:
        f.write(json.dumps(entry) + '\n')

    print(f'{label_a} vs {label_b}: {winner} — {reason}')
    return winner

logged_pairwise(
    'What is SQL?',
    'SQL is a database query language.',
    'SQL (Structured Query Language) is used to query and manage relational databases.',
    label_a='ModelV1',
    label_b='ModelV2'
)

쌍별 평가와 절대 채점: 절충점

쌍별 평가와 절대 평가 기준표 채점은 경쟁 관계가 아니라 서로 보완하는 방법입니다. 두 방법을 함께 사용하십시오. 절대 채점으로 최소 품질 기준을 설정하고, 쌍별 비교로 그 기준을 넘는 후보들의 순위를 매기십시오.

핵심 절충점은 다음과 같습니다. 쌍별 평가에는 N개의 응답에 대해 O(N^2)의 비교가 필요한 반면, 절대 채점에는 O(N)이 필요합니다. 규모가 커지면 표본 추출 기반 쌍별 평가나 엘로 평점이 필요해집니다.

지식 확인: 쌍별 평가의 편향 완화

쌍별 LLM 평가에서 위치 편향을 줄이는 가장 효과적인 방법은 무엇입니까?

복습: A 대 B 비교 심사

쌍별 평가는 두 응답을 각각 절대 척도로 평가하는 대신 어느 쪽이 더 나은지 묻습니다. 위치 편향을 통제하려면 모든 비교를 순서를 바꿔 두 번 실행하고, 일관된 결과만 받아들이십시오. N개의 응답에는 N이 작을 때 순환 대진(모든 쌍)을 사용하고, N이 클 때는 표본 추출 토너먼트를 사용하십시오. 엘로 평점은 많은 쌍별 비교를 통해 연속적인 순위를 만들어 냅니다. 실행 가능한 진단 정보를 얻으려면 평가 차원별 쌍별 심사(정확성, 완전성, 명확성을 각각 평가)를 사용하십시오. 모델 버전 전반의 회귀 검증과 추세 분석을 위해 모든 결과를 기록하십시오.

자주 묻는 질문

“비교 심사: A와 B” 강의는 무료인가요?

네 — “비교 심사: A와 B” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Prompt Engineering 강의 전체를 잠금 해제할 수 있습니다. AI Prompt Engineering 강의에는 총 4개의 강의가 포함되어 있습니다.

“비교 심사: A와 B”에서 뭘 배우나요?

절대 점수 없이 출력 결과의 순위를 정하는 쌍별 비교 프롬프트를 작성합니다. 브라우저에서 직접 실행하는 실습 코드로 AI Prompt Engineering을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

AI Prompt Engineering을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 AI Prompt Engineering은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“비교 심사: A와 B” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 AI Prompt Engineering 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 AI Prompt Engineering 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. LLM을 사용해 LLM 출력 평가하기
  2. 평가 기준표 기반 점수 매기기 프롬프트
  3. 비교 심사: A와 B
  4. LLM 심사자의 보정과 편향
← AI Prompt Engineering(으)로 돌아가기