Ocenianie porównawcze: A kontra B
Prompty do porównań parami, umożliwiające uszeregowanie wyników bez oceny absolutnej.
Ocenianie porównawcze: A kontra B to bezpłatna lekcja AI Prompt Engineering na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Prompt Engineering, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Prompt Engineering zawiera 4 lekcji w sumie.
Czym jest ewaluacja parami (A kontra B)?
Ewaluacja parami (nazywana również ocenianiem porównawczym) polega na przedstawieniu sędziemu dwóch odpowiedzi na to samo pytanie i zapytaniu, która z nich jest lepsza. Zamiast oceniać pojedynczą odpowiedź w skali 1–5, sędzia dokonuje oceny względnej: A jest lepsza, B jest lepsza albo odpowiedzi są równorzędne.
To podejście omija niektóre błędy związane z oceną bezwzględną i często zapewnia bardziej wiarygodne rankingi niż bezwzględne oceny poszczególnych odpowiedzi.
Podstawowy prompt sędziego porównującego parami
Najprostszy sędzia porównujący parami pyta, która odpowiedź jest lepsza i dlaczego. Kluczowe jest wymuszenie wyboru — sędzia nie powinien unikać porównania, odpowiadając ogólnikowo, że „obie odpowiedzi są dobre”.
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)Losowanie kolejności w celu ograniczenia stronniczości pozycji
Stronniczość pozycji sprawia, że sędziowie preferują pierwszą opcję. Aby ją zneutralizować, należy przeprowadzić każde porównanie dwa razy: raz z A na pierwszej pozycji i raz z B na pierwszej pozycji. Zwycięzcę należy uznać tylko wtedy, gdy oba porządki dają ten sam wynik. Jeśli wyniki się różnią, należy ogłosić remis albo przekazać sprawę do oceny człowieka.
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}')Porównywanie parami według wielu kryteriów
Należy poprosić sędziego o porównanie odpowiedzi według konkretnych wymiarów, zamiast pytać tylko, „która ogólnie jest lepsza”. Porównanie parami według poszczególnych wymiarów dostarcza użytecznych informacji o tym, dlaczego jedna odpowiedź jest preferowana.
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)Budowanie turnieju: każdy z każdym
Przy porównywaniu więcej niż dwóch odpowiedzi należy użyć turnieju każdy z każdym: każda odpowiedź jest porównywana z każdą inną. Odpowiedź z największą liczbą zwycięstw zajmuje pierwsze miejsce w rankingu.
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 rankingRanking Elo do ciągłego porządkowania
W ewaluacji na dużą skalę należy używać rankingu Elo zamiast turnieju każdy z każdym. Każda odpowiedź zaczyna z wynikiem 1000 punktów. Po każdym porównaniu zwycięzca zyskuje punkty, a przegrany je traci — proporcjonalnie do tego, jak zaskakujący był wynik. Po wielu porównaniach wyniki Elo tworzą wiarygodny globalny ranking.
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}')Kiedy stosować porównywanie parami, a kiedy ocenę bezwzględną
Porównywanie parami należy stosować, gdy:
- chcą Państwo uszeregować wiele modeli lub wariantów promptów
- trudno jest zdefiniować progi ocen bezwzględnych
- porównują Państwo wyniki, które są „dobre”, ale na różne sposoby
Ocenianie bezwzględne (na podstawie rubryki) należy stosować, gdy:
- potrzebny jest próg zaliczenia („czy ta odpowiedź jest wystarczająco dobra?”)
- dla każdego zapytania dostępna jest tylko jedna odpowiedź do oceny
- do debugowania potrzebne są oceny na poziomie poszczególnych kryteriów
Porównywanie parami na podstawie próbkowania w dużej skali
Przeprowadzenie wszystkich porównań parami dla N odpowiedzi wymaga N*(N-1)/2 porównań — O(N^2). Przy dużej wartości N należy użyć losowego próbkowania: każdą odpowiedź porównuje się z K losowo wybranymi przeciwnikami zamiast ze wszystkimi pozostałymi. Pozwala to przybliżyć rzeczywisty ranking przy znacznie niższym koszcie.
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 rankingInterpretowanie rozbieżności
Gdy dwa porządki dają różne wyniki (A wygrywa w kolejności A-B, a B wygrywa w kolejności B-A), sygnalizuje to rzeczywiście graniczne porównanie, a nie tylko stronniczość pozycji. Takie przypadki wymagają dokładniejszej analizy.
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])Rejestrowanie wyników porównań parami
Należy rejestrować wszystkie wyniki porównań parami, aby utworzyć przeszukiwalny zapis tego, które odpowiedzi pokonały inne oraz w jakich warunkach. Dane te są cenne przy testach regresji i analizowaniu zmian jakości modeli w czasie.
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'
)Ewaluacja parami a ocenianie bezwzględne: kompromisy
Ewaluacja parami i ocenianie bezwzględne na podstawie rubryki to podejścia uzupełniające się, a nie konkurencyjne. Należy stosować je razem: ocenianie bezwzględne pozwala ustalić minimalny próg jakości, a porównywanie parami — uszeregować kandydatów, którzy ten próg przekraczają.
Najważniejszy kompromis polega na tym, że porównywanie parami wymaga O(N^2) porównań dla N odpowiedzi, podczas gdy ocenianie bezwzględne wymaga O(N). Przy dużej skali konieczne stają się porównywanie parami na podstawie próbkowania lub rankingi Elo.
Sprawdzenie wiedzy: ograniczanie stronniczości w porównywaniu parami
Jaki jest najskuteczniejszy sposób ograniczenia stronniczości pozycji w ewaluacji LLM metodą porównywania parami?
Podsumowanie: ocenianie porównawcze A kontra B
Ewaluacja parami polega na określeniu, która z dwóch odpowiedzi jest lepsza, zamiast oceniania każdej z nich w skali bezwzględnej. Aby kontrolować stronniczość pozycji, należy przeprowadzić każde porównanie dwa razy, zamieniając kolejność odpowiedzi, i akceptować tylko spójne wyniki. Dla N odpowiedzi należy używać turnieju każdy z każdym przy małej wartości N lub turniejów opartych na próbkowaniu przy dużej wartości N. Rankingi Elo tworzą ciągłe uporządkowanie na podstawie wielu porównań parami. Porównywanie parami według poszczególnych wymiarów (osobno trafności, kompletności i jasności) dostarcza użytecznych informacji diagnostycznych. Wszystkie wyniki należy rejestrować na potrzeby testów regresji i analizy trendów między wersjami modelu.
Często zadawane pytania
Czy lekcja „Ocenianie porównawcze: A kontra B” jest bezpłatna?
Tak — pełny tekst „Ocenianie porównawcze: A kontra B” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Prompt Engineering, przejdź na CoddyKit PRO. Kurs AI Prompt Engineering zawiera 4 lekcji w sumie.
Co nauczysz się w „Ocenianie porównawcze: A kontra B”?
Prompty do porównań parami, umożliwiające uszeregowanie wyników bez oceny absolutnej. Ćwiczysz AI Prompt Engineering z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć AI Prompt Engineering?
Nie wymagamy żadnego doświadczenia. AI Prompt Engineering w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Ocenianie porównawcze: A kontra B”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji AI Prompt Engineering?
Tak. Każda lekcja AI Prompt Engineering zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Używanie LLM do oceny wyników LLM
- Prompty oceniania oparte na rubrykach
- Ocenianie porównawcze: A kontra B
- Kalibracja i uprzedzenia sędziów LLM