Skip to content
Guides14 min read

Comparar agentes de IA lado a lado: modos binario, de escala y multidimensión

Configura la comparación por pares de agentes en Potato con tres modos: preferencia binaria, escala continua y juicio multicriterio por dimensión con justificación obligatoria.

Potato Team

Por qué comparar por pares al evaluar agentes

Si le pides a alguien que valore la traza de un agente de coding en una escala del 1 al 10, obtienes datos ruidosos, porque cada persona calibra esa escala a su manera. El 7 de un anotador es el 5 de otro. La comparación por pares esquiva ese problema. En lugar de valorar cada traza por separado, los anotadores ven dos lado a lado y dicen cuál es mejor. Ese juicio cara a cara es más fácil de emitir, más consistente entre personas y resulta ser exactamente lo que necesitas para Direct Preference Optimization (DPO) y Reinforcement Learning from Human Feedback (RLHF).

Es el mismo enfoque que se usa para entrenar modelos de recompensa en la alineación de modelos de lenguaje, y se traslada bien a los agentes de coding: recoges preferencias humanas entre pares de trayectorias de agente, entrenas un modelo de recompensa con ellas y luego usas ese modelo para guiar el entrenamiento del agente o para elegir el mejor de N candidatos en inferencia.

Potato tiene tres modos de comparación por pares, cada uno pensado para una necesidad de evaluación y un presupuesto de datos distintos.

La interfaz coloca dos trazas lado a lado:

Side-by-side agent comparison interfaceAnnotators compare two agent traces and select which approach was better

Modo 1: preferencia binaria

Es el modo más simple y rápido. El anotador ve dos trazas lado a lado y hace clic en la mejor. Un botón de empate opcional cubre los casos en los que ambas son igual de buenas o igual de malas.

Cuándo usar el modo binario

Tira del modo binario cuando necesites muchos datos de preferencia y deprisa. Encaja bien para entrenar modelos de recompensa básicos, calcular tasas de victoria entre agentes y montar tablas de clasificación con Elo. La contrapartida es que pierdes matices: sabes qué traza ganó, pero no por cuánto ni en qué aspectos.

Configuración

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"

El flujo de anotación

El anotador ve la pantalla partida. A la izquierda, la traza A renderizada con el CodingTraceDisplay completo: diffs, bloques de terminal, lecturas de archivo, razonamiento. A la derecha, la traza B para la misma tarea. Cada lado se desplaza por su cuenta.

La descripción de la tarea va encima de ambas trazas, para que el anotador sepa qué intentaban hacer los dos agentes.

Debajo hay tres botones: «Agent A is better», «Agent B is better» y «Tie». Con randomize_order activado, qué agente es A y cuál es B se baraja en cada elemento, así que los anotadores no pueden coger la costumbre de elegir siempre el lado izquierdo o el derecho.

Para una evaluación más fina, la interfaz también admite varias dimensiones:

Pairwise preference selection interfaceBinary preference, continuous scale, and multi-dimension modes are available

Modo 2: escala continua

El modo de escala permite que el anotador diga cuánto mejor es una traza, y no solo cuál ganó. En vez de un único clic, arrastra un control deslizante que va de «A mucho mejor» a la izquierda hasta «B mucho mejor» a la derecha, con «Igual» en el centro.

Cuándo usar el modo de escala

Usa el modo de escala cuando importe la intensidad de la preferencia y no solo su dirección. Un deslizante cerca del extremo indica una diferencia clara de calidad; cerca del centro, que las dos andaban parejas. DPO y otras tuberías parecidas pueden ponderar los ejemplos según esa intensidad, apoyándose más en los casos claros.

Configuración

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"

Usar una escala de 5 puntos

Para anotar más rápido con algo menos de granularidad, baja a una escala de 5 puntos:

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

Modo 3: comparación multidimensión

Es el modo más detallado. En lugar de una preferencia global, el anotador juzga cada traza en varias dimensiones independientes. Cada dimensión recibe su propia decisión A/B/empate, y cada decisión exige una justificación escrita.

Cuándo usar el modo multidimensión

Úsalo cuando quieras saber no solo qué agente ganó, sino por qué. Una traza puede tener el código correcto y una eficiencia pésima; otra puede ser eficiente pero saltarse un caso límite. Los datos por dimensión que salen de aquí sirven para entrenar modelos de recompensa específicos por dimensión o para devolver observaciones detalladas a quienes construyen el agente.

Configuración

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"

Preparar los datos de trazas emparejadas

Los tres modos toman trazas emparejadas como entrada. Cada línea del archivo JSONL contiene dos trazas que intentaron la misma tarea.

Formato de los datos

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
      }
    ]
  }
}

Construir pares a partir de trazas sueltas

Si tienes trazas sueltas que abordaron las mismas tareas, la utilidad de emparejado las monta por ti:

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

Exportar los datos de comparación

Pares de preferencia para DPO/RLHF

El formato de exportación principal para las comparaciones por pares son los pares de preferencia para entrenar con DPO o RLHF:

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

En el modo binario, la salida es sencilla:

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"
}

El modo de escala añade la intensidad de la preferencia:

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"
}

El modo multidimensión arrastra las preferencias por dimensión:

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
}

Análisis: tasas de victoria, puntuaciones Elo y desgloses por dimensión

Calcular tasas de victoria

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']})")

Calcular puntuaciones 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}")

Desgloses por dimensión

En las comparaciones multidimensión, mira en qué dimensiones destaca cada agente:

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']})")

Qué funciona en la práctica

Elegir un modo

El modo binario es la opción correcta cuando quieres miles de preferencias rápido, un modelo de recompensa de propósito general o una clasificación tipo ranking. Cuenta con 1 o 2 minutos por comparación.

El modo de escala se gana el sitio cuando la intensidad de la preferencia entra en tu tubería de entrenamiento. A DPO con ponderación por margen le importa la diferencia entre una preferencia fuerte (deslizante en el extremo) y una débil (deslizante cerca del centro). Cuenta con 2 o 3 minutos por comparación.

El modo multidimensión compensa el tiempo extra cuando necesitas saber dónde son fuertes y dónde flojos los agentes, cuando entrenas modelos de recompensa específicos por dimensión o cuando le debes un informe detallado a quienes desarrollan el agente. Cuenta con 4 a 6 minutos por comparación.

Cuántas comparaciones necesitas

Para tasas de victoria fiables, recoge al menos 100 comparaciones por cada par de agentes. Para puntuaciones Elo con cinco agentes o más, entre 200 y 300 comparaciones en total bastan para que la clasificación se estabilice. Para modelos de recompensa con DPO, apunta a 1.000 pares de preferencia o más, repartidos entre tareas fáciles y difíciles.

Aleatorizar el orden

Pon siempre randomize_order: true. El sesgo de posición, la tendencia a preferir la traza que aparece a la izquierda o en la primera pestaña, está bien documentado en los estudios de evaluación humana. Combina la aleatorización con la comprobación attention_checks.type: "duplicate_reversed" para pillar a quien se limita a pulsar siempre el mismo lado.

Qué hacer con los empates

En el modo binario, permite los empates pero vigila su tasa. Si pasa del 30%, es probable que los agentes estén demasiado igualados para una decisión binaria y convenga pasar al modo de escala o al multidimensión. En el modo de escala, el empate es simplemente el punto central. En el modo multidimensión, los empates en dimensiones concretas son esperables y te dicen algo.

Ocultar la identidad del agente

Deja show_agent_identity: false salvo que tengas un motivo real para mostrarla. Si los anotadores saben qué agente produjo una traza, tienden a favorecer al que ya esperaban que fuera mejor.

Combinar modos

Para una evaluación a fondo, pasa primero el modo binario sobre una bolsa grande de pares para sacar la clasificación general, y luego el modo multidimensión sobre un subconjunto más pequeño y estratificado para el detalle diagnóstico. Las comparaciones binarias alimentan el entrenamiento del modelo de recompensa; las multidimensión te dicen dónde concentrar las mejoras del agente.

Para la referencia de configuración detrás de estos modos, consulta la documentación fuente. Si quieres un recorrido más amplio sobre evaluar agentes de principio a fin, empieza por la guía de evaluación de agentes.