0Pricing
AI Prompt Engineering · 课时

比较式评审:A 与 B

使用成对比较提示词对输出进行排序,而不是进行绝对评分。

比较式评审:A 与 B 是 CoddyKit 上的免费 AI Prompt Engineering 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 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)

构建循环赛:循环赛制

比较两个以上的回答时,请使用循环赛:每个回答都与其他每个回答进行比较。获胜次数最多的回答排名第一。

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 个回答,小规模时请使用循环赛(所有配对),大规模时请使用抽样循环赛。埃洛等级分可以根据大量成对比较产生连续排名。请按维度进行成对评审(分别评估准确性、完整性和清晰度),以获得可执行的诊断信号。请记录所有结果,以便进行回归测试和分析不同模型版本的趋势。

常见问题解答

「比较式评审:A 与 B」课时是免费的吗?

是的 — 「比较式评审:A 与 B」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Prompt Engineering 课程的其余内容,请升级到 CoddyKit PRO。 AI Prompt Engineering 课程共包含 4 节课。

「比较式评审:A 与 B」这节课中我会学到什么?

使用成对比较提示词对输出进行排序,而不是进行绝对评分。 你通过在浏览器中直接运行的动手代码来练习 AI Prompt Engineering,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

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

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

「比较式评审:A 与 B」课时需要多长时间?

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

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

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

此课程中的所有课时

  1. 使用 LLM 评估 LLM 输出
  2. 基于评分标准的评分提示词
  3. 比较式评审:A 与 B
  4. LLM 评审中的校准与偏差
← 返回 AI Prompt Engineering