Skip to content
Guides12 min read

مقارنة وكلاء الذكاء الاصطناعي جنباً إلى جنب: الوضع الثنائي والمقياس والأبعاد المتعددة

إعداد المقارنة الزوجية للوكلاء في Potato بثلاثة أوضاع: التفضيل الثنائي، والمقياس المتصل، والحكم متعدد المعايير لكل بُعد مع تبرير إلزامي.

Potato Team

لماذا المقارنة الزوجية في تقييم الوكلاء

إذا طلبت من أحدهم أن يقيّم تتبع وكيل برمجة على مقياس من 1 إلى 10، فستحصل على بيانات مشوّشة، لأن كل شخص يعاير ذلك المقياس على نحو مختلف. فما هو 7 عند معلّق يكون 5 عند آخر. والمقارنة الزوجية تتجاوز هذا. فبدل تقييم التتبعات كلٌّ على حدة، ينظر المعلّقون إلى تتبعين جنباً إلى جنب ويقولون أيهما أفضل. هذا النوع من الحكم المباشر أسهل على المعلّق، وأكثر اتساقاً بين الأشخاص، وهو بالضبط ما تحتاجه لتحسين التفضيل المباشر (DPO) والتعلم المعزَّز من التغذية الراجعة البشرية (RLHF).

وهو الأسلوب نفسه المستخدم في تدريب نماذج المكافأة لمواءمة نماذج اللغة، وينتقل إلى وكلاء البرمجة بسلاسة: اجمع تفضيلات بشرية بين أزواج من مسارات الوكلاء، ودرّب عليها نموذج مكافأة، ثم استخدم ذلك النموذج لتوجيه تدريب الوكيل أو لاختيار أفضل مرشح من بين N عند الاستدلال.

في Potato ثلاثة أوضاع للمقارنة الزوجية، يناسب كلٌّ منها حاجة تقييم مختلفة وميزانية بيانات مختلفة.

تضع الواجهة التتبعين جنباً إلى جنب:

واجهة مقارنة الوكلاء جنباً إلى جنبAnnotators compare two agent traces and select which approach was better

الوضع 1: التفضيل الثنائي

هذا أبسط الأوضاع وأسرعها. يرى المعلّق تتبعين جنباً إلى جنب فينقر على الأفضل. وثمة زر تعادل اختياري يغطي الحالات التي يتساوى فيها التتبعان جودةً أو رداءةً.

متى تستخدم الوضع الثنائي

استخدم الوضع الثنائي حين تحتاج إلى كمّ كبير من بيانات التفضيل بسرعة. وهو مناسب لتدريب نماذج مكافأة أساسية، وحساب معدلات فوز الوكلاء، وبناء لوحات صدارة بنظام Elo. وعيبه أنك تفقد التفاصيل الدقيقة، إذ تعرف أي تتبع فاز، لا بكم فاز ولا في أي جانب.

التهيئة

yaml
# config.yaml
project_name: "Agent Comparison - Binary"
port: 8000
 
data:
  source: "local"
  input_path: "./data/paired_traces.jsonl"
  data_format: "paired_coding_trace"
 
coding_agent:
  display:
    diff_style: "unified"
    syntax_highlighting: true
    terminal_theme: "dark"
    file_tree:
      enabled: true
      position: "left"
    collapsible:
      auto_collapse_thinking: true
 
comparison:
  layout: "side_by_side"         # "side_by_side" or "tabbed"
  label_a: "Agent A"
  label_b: "Agent B"
  randomize_order: true          # Randomize which trace appears on which side
  show_agent_identity: false     # Hide agent names to avoid bias
  sync_scroll: false             # Independent scrolling for each trace
 
annotation_schemes:
  - annotation_type: pairwise
    name: preference
    description: "Which agent produced a better solution?"
    items_key:
      - value: "a"
        text: "Agent A is better"
        keyboard_shortcut: "1"
      - value: "b"
        text: "Agent B is better"
        keyboard_shortcut: "2"
      - value: "tie"
        text: "Tie (equally good or equally bad)"
        keyboard_shortcut: "3"
    allow_tie: true
  - annotation_type: radio
    name: confidence
    labels:
      - value: "high"
        text: "Very confident"
      - value: "medium"
        text: "Somewhat confident"
      - value: "low"
        text: "Not confident"
 
output:
  path: "./output/"
  format: "jsonl"
 
quality_control:
  inter_annotator_agreement: true
  overlap_percentage: 20
  attention_checks:
 
annotators:
  - username: "judge1"
  - username: "judge2"

سير عمل التعليق التوضيحي

يحصل المعلّق على شاشة مقسومة. في أحد الجانبين يُعرَض التتبع A بكامل عرض تتبع البرمجة: diffs وكتل الطرفية وقراءات الملفات والتفكير. وفي الجانب الآخر التتبع B للمهمة نفسها. ولكل جانب تمرير مستقل.

ويقع وصف المهمة فوق التتبعين معاً حتى يعرف المعلّق ما الذي كان الوكيلان يحاولان فعله.

وتحتهما ثلاثة أزرار: «Agent A is better» و«Agent B is better» و«Tie». ومع تفعيل randomize_order، يُخلط أي وكيل هو A وأيهما B في كل عنصر، فلا يقع المعلّقون في عادة اختيار جانب بعينه.

ولتقييم أدق، تدعم الواجهة أيضاً أبعاداً متعددة:

واجهة اختيار التفضيل الزوجيBinary preference, continuous scale, and multi-dimension modes are available

الوضع 2: المقياس المتصل

يتيح وضع المقياس للمعلّق أن يقول كم يتفوق أحد التتبعين، لا أيهما فاز فقط. فبدل نقرة واحدة، يسحب شريط تمرير يمتد من «A أفضل بكثير» في طرف إلى «B أفضل بكثير» في الطرف الآخر، مع «متساويان» في الوسط.

متى تستخدم وضع المقياس

استخدم وضع المقياس حين تهمك قوة التفضيل لا اتجاهه فحسب. فوجود المؤشر قرب أحد الطرفين يعني فجوة جودة واضحة، ووجوده قرب المنتصف يعني أن الاثنين متقاربان. ويمكن لخطوط أنابيب DPO وما شابهها أن تزن الأمثلة بحسب هذه القوة، فتعتمد أكثر على الأحكام القاطعة.

التهيئة

yaml
# config.yaml
project_name: "Agent Comparison - Scale"
port: 8000
 
data:
  source: "local"
  input_path: "./data/paired_traces.jsonl"
  data_format: "paired_coding_trace"
 
coding_agent:
  display:
    diff_style: "unified"
    syntax_highlighting: true
    terminal_theme: "dark"
    file_tree:
      enabled: true
    collapsible:
      auto_collapse_thinking: true
 
comparison:
  layout: "side_by_side"
  randomize_order: true
  show_agent_identity: false
 
annotation_schemes:
  - annotation_type: pairwise
    name: preference_scale
    description: "Which agent produced a better solution, and by how much?"
    scale:
      points: 7                  # 7-point scale
      labels:
        1: "A is much better"
        2: "A is better"
        3: "A is slightly better"
        4: "Equal"
        5: "B is slightly better"
        6: "B is better"
        7: "B is much better"
      default: 4                 # Start at "Equal"
      show_numeric_value: true
 
output:
  path: "./output/"
  format: "jsonl"
 
quality_control:
  inter_annotator_agreement: true
  overlap_percentage: 20
 
annotators:
  - username: "judge1"
  - username: "judge2"

استخدام مقياس من 5 نقاط

لتعليق توضيحي أسرع وتفصيل أقل قليلاً، انزل إلى مقياس من 5 نقاط:

yaml
annotation_schemes:
  - annotation_type: pairwise
    name: preference_scale_5
    description: "Compare the two solutions"
    scale:
      points: 5
      labels:
        1: "A is clearly better"
        2: "A is somewhat better"
        3: "About equal"
        4: "B is somewhat better"
        5: "B is clearly better"
      default: 3

الوضع 3: المقارنة متعددة الأبعاد

هذا أكثر الأوضاع تفصيلاً. فبدل تفضيل كلي واحد، يحكم المعلّق على كل تتبع في عدة أبعاد مستقلة. ولكل بُعد حكمه الخاص بين A وB والتعادل، ولكل حكم تبرير مكتوب إلزامي.

متى تستخدم الوضع متعدد الأبعاد

استخدمه حين تريد أن تعرف لماذا فاز وكيل، لا أيهما فاز فقط. فقد يكون أحد التتبعين صحيح الكود سيئ الكفاءة، وقد يكون الآخر فعّالاً لكنه يغفل حالة حدّية. والبيانات لكل بُعد الخارجة من هذا يمكن أن تدرّب نماذج مكافأة خاصة بكل بُعد، أو أن تُعيد ملاحظات تفصيلية إلى من يبنون الوكيل.

التهيئة

yaml
# config.yaml
project_name: "Agent Comparison - Multi-Dimension"
port: 8000
 
data:
  source: "local"
  input_path: "./data/paired_traces.jsonl"
  data_format: "paired_coding_trace"
 
coding_agent:
  display:
    diff_style: "unified"
    syntax_highlighting: true
    terminal_theme: "dark"
    file_tree:
      enabled: true
    collapsible:
      auto_collapse_thinking: true
 
comparison:
  layout: "side_by_side"
  randomize_order: true
  show_agent_identity: false
 
annotation_schemes:
  - annotation_type: pairwise
    name: multi_dim_comparison
    description: "Compare the two solutions along each dimension"
 
      - name: "efficiency"
        label: "Efficiency"
        description: >
          How efficient is the agent's process? Does it take unnecessary
          steps, read irrelevant files, or make redundant edits?
        options: ["A", "B", "Tie"]
        require_justification: true
        justification_placeholder: "Which agent was more efficient and why?"
        weight: 0.2
 
      - name: "code_quality"
        label: "Code Quality"
        description: >
          Is the code well-written? Consider readability, naming,
          error handling, documentation, and adherence to existing patterns.
        options: ["A", "B", "Tie"]
        require_justification: true
        justification_placeholder: "Which produces better quality code?"
        weight: 0.2
 
      - name: "communication"
        label: "Communication"
        description: >
          How well does the agent explain its reasoning? Are its thinking
          steps clear and logical? Does it identify the root cause?
        options: ["A", "B", "Tie"]
        require_justification: true
        justification_placeholder: "Which agent communicates its approach better?"
        weight: 0.1
 
      - name: "robustness"
        label: "Robustness"
        description: >
          Does the solution handle edge cases? Does the agent verify its
          changes with tests? Is the fix narrow and targeted or fragile?
        options: ["A", "B", "Tie"]
        require_justification: true
        justification_placeholder: "Which solution is more robust?"
        weight: 0.1
 
 
output:
  path: "./output/"
  format: "jsonl"
 
quality_control:
  inter_annotator_agreement: true
  overlap_percentage: 25         # Higher overlap for this detailed task
  minimum_time_per_instance: 120 # 2 minutes minimum for thorough review
 
annotators:
  - username: "judge1"
  - username: "judge2"

تجهيز بيانات التتبعات المزدوجة

تأخذ الأوضاع الثلاثة كلها تتبعات مزدوجة كمُدخَل. ويحمل كل سطر في ملف JSONL تتبعين حاولا المهمة نفسها.

صيغة البيانات

json
{
  "id": "pair_001",
  "task_description": "Fix the IndexError in process_batch() when the input list is empty",
  "repo": "myorg/myproject",
  "trace_a": {
    "agent": "claude_code",
    "model": "claude-sonnet-4-20250514",
    "structured_turns": [
      {
        "step_idx": 0,
        "type": "file_read",
        "path": "src/batch.py",
        "content": "def process_batch(items):\n    result = items[0]\n    ...",
        "start_line": 10,
        "end_line": 25
      },
      {
        "step_idx": 1,
        "type": "file_edit",
        "path": "src/batch.py",
        "diff": "--- a/src/batch.py\n+++ b/src/batch.py\n@@ -10,3 +10,5 @@\n def process_batch(items):\n+    if not items:\n+        return []\n     result = items[0]\n"
      },
      {
        "step_idx": 2,
        "type": "bash_command",
        "command": "python -m pytest tests/test_batch.py -v",
        "output": "PASSED",
        "exit_code": 0
      }
    ]
  },
  "trace_b": {
    "agent": "swe_agent",
    "model": "gpt-4o",
    "structured_turns": [
      {
        "step_idx": 0,
        "type": "bash_command",
        "command": "find . -name '*.py' | xargs grep 'process_batch'",
        "output": "src/batch.py:def process_batch(items):\ntests/test_batch.py:    process_batch([])",
        "exit_code": 0
      },
      {
        "step_idx": 1,
        "type": "file_read",
        "path": "src/batch.py",
        "content": "def process_batch(items):\n    result = items[0]\n    ...",
        "start_line": 1,
        "end_line": 50
      },
      {
        "step_idx": 2,
        "type": "file_edit",
        "path": "src/batch.py",
        "diff": "--- a/src/batch.py\n+++ b/src/batch.py\n@@ -10,3 +10,6 @@\n def process_batch(items):\n+    if items is None or len(items) == 0:\n+        logger.warning('Empty input to process_batch')\n+        return []\n     result = items[0]\n"
      },
      {
        "step_idx": 3,
        "type": "bash_command",
        "command": "python -m pytest tests/ -v",
        "output": "PASSED (12 tests)",
        "exit_code": 0
      }
    ]
  }
}

بناء الأزواج من تتبعات مفردة

إذا كانت لديك تتبعات مفردة تناولت جميعها المهام نفسها، فستتولى أداة الاقتران تجميعها:

bash
# Generate all possible pairs for each task
potato pair-traces \
  --input ./data/individual_traces.jsonl \
  --output ./data/paired_traces.jsonl \
  --pair_by "task_id" \
  --strategy "all_pairs"
 
# Or sample a fixed number of pairs per task
potato pair-traces \
  --input ./data/individual_traces.jsonl \
  --output ./data/paired_traces.jsonl \
  --pair_by "task_id" \
  --strategy "sample" \
  --pairs_per_task 3

تصدير بيانات المقارنة

أزواج التفضيل لـ DPO/RLHF

صيغة التصدير الأساسية للمقارنات الزوجية هي أزواج تفضيل لتدريب DPO أو RLHF:

bash
potato export \
  --format dpo_preferences \
  --project ./output/ \
  --output ./training_data/preferences.jsonl

في الوضع الثنائي يكون المخرَج بسيطاً:

json
{
  "prompt": "Fix the IndexError in process_batch() when the input list is empty",
  "chosen": {"agent": "claude_code", "trace_id": "trace_a_001", "steps": [...]},
  "rejected": {"agent": "swe_agent", "trace_id": "trace_b_001", "steps": [...]},
  "annotator": "judge1",
  "confidence": "high"
}

ويضيف وضع المقياس قوة التفضيل:

json
{
  "prompt": "Fix the IndexError in process_batch()",
  "chosen": {"agent": "claude_code", "trace_id": "trace_a_001"},
  "rejected": {"agent": "swe_agent", "trace_id": "trace_b_001"},
  "preference_strength": 0.83,
  "scale_value": 2,
  "justification": "Agent A found and fixed the bug in fewer steps with cleaner code"
}

ويحمل الوضع متعدد الأبعاد التفضيلات لكل بُعد:

json
{
  "prompt": "Fix the IndexError in process_batch()",
  "chosen": {"agent": "claude_code", "trace_id": "trace_a_001"},
  "rejected": {"agent": "swe_agent", "trace_id": "trace_b_001"},
  "overall_preference": "A",
  "dimensions": {
    "correctness": {"preference": "Tie", "justification": "Both correctly fix the bug"},
    "efficiency": {"preference": "A", "justification": "A solves it in 3 steps vs 4"},
    "code_quality": {"preference": "B", "justification": "B adds logging and handles None"},
    "communication": {"preference": "A", "justification": "A's reasoning is more focused"},
    "robustness": {"preference": "B", "justification": "B runs full test suite, not just one file"}
  },
  "weighted_score_a": 0.55,
  "weighted_score_b": 0.45
}

التحليل: معدلات الفوز وتصنيفات Elo والتفصيل لكل بُعد

حساب معدلات الفوز

python
import json
from collections import defaultdict
 
with open("training_data/preferences.jsonl") as f:
    prefs = [json.loads(line) for line in f]
 
wins = defaultdict(lambda: {"wins": 0, "losses": 0, "ties": 0})
 
for pref in prefs:
    agent_chosen = pref["chosen"]["agent"]
    agent_rejected = pref["rejected"]["agent"]
 
    if agent_chosen == agent_rejected:
        continue  # Skip self-comparisons
 
    if pref.get("overall_preference") == "Tie":
        wins[agent_chosen]["ties"] += 1
        wins[agent_rejected]["ties"] += 1
    else:
        wins[agent_chosen]["wins"] += 1
        wins[agent_rejected]["losses"] += 1
 
print("Agent Win Rates:")
print("-" * 55)
for agent, record in sorted(wins.items()):
    total = record["wins"] + record["losses"] + record["ties"]
    win_rate = (record["wins"] + 0.5 * record["ties"]) / total * 100
    print(f"  {agent:<20} {win_rate:5.1f}%  "
          f"(W:{record['wins']} L:{record['losses']} T:{record['ties']})")

حساب تصنيفات Elo

python
import json
import math
from collections import defaultdict
 
def compute_elo(preferences, k=32, initial_rating=1500):
    """Compute Elo ratings from pairwise preferences."""
    ratings = defaultdict(lambda: initial_rating)
 
    for pref in preferences:
        agent_a = pref["chosen"]["agent"]
        agent_b = pref["rejected"]["agent"]
 
        ra = ratings[agent_a]
        rb = ratings[agent_b]
 
        # Expected scores
        ea = 1.0 / (1.0 + math.pow(10, (rb - ra) / 400))
        eb = 1.0 / (1.0 + math.pow(10, (ra - rb) / 400))
 
        overall = pref.get("overall_preference", "A")
        if overall == "Tie":
            sa, sb = 0.5, 0.5
        else:
            # "chosen" is the winner
            sa, sb = 1.0, 0.0
 
        ratings[agent_a] = ra + k * (sa - ea)
        ratings[agent_b] = rb + k * (sb - eb)
 
    return dict(ratings)
 
with open("training_data/preferences.jsonl") as f:
    prefs = [json.loads(line) for line in f]
 
ratings = compute_elo(prefs)
 
print("Elo Ratings:")
print("-" * 35)
for agent, rating in sorted(ratings.items(), key=lambda x: -x[1]):
    print(f"  {agent:<20} {rating:.0f}")

التفصيل لكل بُعد

في المقارنات متعددة الأبعاد، انظر في أي الأبعاد يبلي كل وكيل بلاءً حسناً:

python
import json
from collections import defaultdict
 
with open("training_data/preferences.jsonl") as f:
    prefs = [json.loads(line) for line in f]
 
# Only process multi-dimension annotations
multi_dim = [p for p in prefs if "dimensions" in p]
 
dim_wins = defaultdict(lambda: defaultdict(lambda: {"A": 0, "B": 0, "Tie": 0}))
 
for pref in multi_dim:
    agent_a = pref["chosen"]["agent"]
    agent_b = pref["rejected"]["agent"]
    pair_key = f"{agent_a} vs {agent_b}"
 
    for dim_name, dim_data in pref["dimensions"].items():
        dim_wins[dim_name][pair_key][dim_data["preference"]] += 1
 
print("Per-Dimension Win Rates:")
print("=" * 60)
for dim_name, matchups in sorted(dim_wins.items()):
    print(f"\n  {dim_name.upper()}")
    print(f"  {'-' * 50}")
    for pair, counts in matchups.items():
        total = counts["A"] + counts["B"] + counts["Tie"]
        a_rate = (counts["A"] + 0.5 * counts["Tie"]) / total * 100
        print(f"    {pair}: A={a_rate:.0f}% B={100-a_rate:.0f}%  "
              f"(A:{counts['A']} B:{counts['B']} Tie:{counts['Tie']})")

ما الذي ينفع عملياً

اختيار الوضع

الوضع الثنائي هو الخيار الصحيح حين تريد آلاف التفضيلات بسرعة، أو نموذج مكافأة عام الغرض، أو ترتيباً على لوحة صدارة. احسب نحو دقيقة إلى دقيقتين لكل مقارنة.

ويثبت وضع المقياس جدواه حين تغذّي قوة التفضيل خط أنابيب التدريب لديك. فـ DPO مع ترجيح الهامش يهتم بالفرق بين تفضيل قوي (المؤشر عند الطرف) وتفضيل ضعيف (المؤشر قرب المنتصف). احسب من دقيقتين إلى ثلاث لكل مقارنة.

أما الوضع متعدد الأبعاد فيستحق الوقت الإضافي حين تحتاج أن تعرف مواطن قوة الوكلاء وضعفها، أو حين تدرّب نماذج مكافأة خاصة بكل بُعد، أو حين تدين لمطوّري الوكيل بتقرير تفصيلي. احسب من 4 إلى 6 دقائق لكل مقارنة.

كم مقارنة تحتاج

للحصول على معدلات فوز موثوقة، اجمع 100 مقارنة على الأقل لكل زوج وكلاء. ولتصنيفات Elo عبر خمسة وكلاء أو أكثر، تستقر الترتيبات عند 200 إلى 300 مقارنة إجمالاً. ولنماذج مكافأة DPO، استهدف 1000 زوج تفضيل أو أكثر تغطي المهام السهلة والصعبة على السواء.

عشوائية الترتيب

اضبط randomize_order: true دائماً. فتحيّز الموضع، أي الميل إلى تفضيل التتبع الذي يظهر في جانب بعينه أو في التبويب الأول، موثَّق جيداً في دراسات التقييم البشري. واقرن العشوائية بفحص attention_checks.type: "duplicate_reversed" لكشف من يواصل النقر على الجانب نفسه.

التعامل مع التعادل

في الوضع الثنائي، اسمح بالتعادل لكن راقب نسبته. فإن تجاوزت 30%، فالأرجح أن الوكيلين أقرب من أن يفصل بينهما حكم ثنائي، وعليك الانتقال إلى وضع المقياس أو الوضع متعدد الأبعاد. وفي وضع المقياس، التعادل هو نقطة المنتصف ليس إلا. وفي الوضع متعدد الأبعاد، التعادل في أبعاد مفردة متوقَّع ويخبرك بشيء.

إخفاء هوية الوكيل

أبقِ show_agent_identity: false ما لم يكن لديك سبب حقيقي لإظهارها. فإذا عرف المعلّقون أي وكيل أنتج تتبعاً، مالوا إلى تفضيل الوكيل الذي يتوقعون سلفاً أنه الأقوى.

الجمع بين الأوضاع

للحصول على تقييم دقيق، شغّل الوضع الثنائي أولاً على مجموعة كبيرة من الأزواج للحصول على الترتيب العام، ثم شغّل الوضع متعدد الأبعاد على مجموعة فرعية أصغر ومُطبَّقة للحصول على التفاصيل التشخيصية. المقارنات الثنائية تغذّي تدريب نموذج المكافأة؛ والمقارنات متعددة الأبعاد تخبرك أين تركّز تحسينات الوكيل.

للاطلاع على مرجع التهيئة وراء هذه الأوضاع، انظر الوثائق المصدرية. ولشرح أوسع لتقييم الوكلاء من البداية إلى النهاية، ابدأ من دليل تقييم الوكلاء.