Revisión de código al estilo de PR de GitHub para agentes de coding con IA
Configura en Potato la anotación de revisión de código al estilo de PR de GitHub, con comentarios en línea sobre el diff, valoraciones de calidad por archivo y veredictos de aprobación o rechazo para la salida de agentes de coding.
Por qué importa la anotación de revisión de código
La mayoría de los benchmarks de agentes de coding reducen la evaluación a un binario: ¿pasaron las pruebas o no? SWE-bench informa del porcentaje de incidencias resueltas. HumanEval informa de pass@k. Son métricas útiles para las tablas de clasificación, pero inútiles para entender la calidad del código.
Un agente puede pasar todas las pruebas y aun así escribir código que nadie querría mantener: código con un agujero de seguridad, con un camino lento o con un estilo que choca con el resto del código base. Un revisor humano pediría cambios en ese PR aunque las pruebas estuvieran en verde. Si quieres agentes que escriban código que la gente realmente fusione, tienes que revisar el código, no solo ejecutar las pruebas.
El esquema de anotación code_review de Potato traslada la experiencia de revisión de PR de GitHub a una herramienta de anotación. Los anotadores ven diffs unificados con resaltado de sintaxis, pulsan sobre las líneas del diff para añadir comentarios en línea, valoran los archivos en un par de dimensiones de calidad y emiten un veredicto de aprobar, pedir cambios o solo comentar, igual que al revisar un pull request real. Para la referencia completa del esquema, consulta la documentación de anotación de agentes de coding y la guía de evaluación de agentes.
Esta es la interfaz de revisión de código en Potato, con comentarios en línea sobre el diff y valoraciones por archivo:
La interfaz de revisión de código de Potato con comentarios en línea sobre el diff y valoraciones de calidad por archivo
Visión general del esquema de revisión de código
El esquema code_review tiene tres capas:
- Comentarios en línea sobre el diff: los anotadores pulsan sobre cualquier línea del diff para adjuntar un comentario categorizado (bug, estilo, rendimiento, seguridad, lógica, sugerencia, pregunta)
- Valoraciones por archivo: cada archivo modificado recibe valoraciones independientes de corrección (1-5) y calidad de código (1-5)
- Veredicto global: el anotador emite un veredicto final: aprobar, pedir cambios o solo comentar
Esto reproduce una revisión de código real, así que a los desarrolladores les resulta natural, y la salida estructurada que produce se traduce directamente al entrenamiento de modelos de revisión de código.
CodingTraceDisplay: cómo se renderizan los diffs
El componente CodingTraceDisplay renderiza las trazas de agentes de coding como una secuencia de llamadas a herramientas y sus salidas, con un tratamiento especial para las ediciones de archivos. Cuando el agente edita un archivo, la vista muestra un diff unificado con:
- Líneas rojas: líneas borradas (con el prefijo
-) - Líneas verdes: líneas añadidas (con el prefijo
+) - Líneas grises: líneas de contexto (sin cambios)
- Números de línea: números de línea antiguos y nuevos en el margen
- Resaltado de sintaxis: resaltado según el lenguaje, deducido de la extensión del archivo
- Pulsar para comentar: al pulsar cualquier línea se abre un formulario de comentario anclado a esa línea
El diff se calcula automáticamente a partir de las operaciones de edición del agente. Si el agente usó una herramienta de buscar y reemplazar, Potato reconstruye los estados anterior y posterior y genera el diff unificado.
Para los agentes que producen varias ediciones de archivos en una sola traza (lo habitual en arreglos de errores del mundo real), cada archivo tiene su propia sección de diff plegable, parecida a la pestaña «Files changed» de un PR de GitHub.
El CodingTraceDisplay renderiza los cambios de código con el resaltado de sintaxis correspondiente:
CodingTraceDisplay renderizando diffs unificados con resaltado de sintaxis y una barra lateral con el árbol de archivos
Categorías de comentarios
Cuando un anotador pulsa una línea del diff para añadir un comentario, selecciona una categoría:
| Categoría | Color | Descripción | Ejemplo |
|---|---|---|---|
bug | Rojo | El código tiene un error funcional | «Esto lanzará un NullPointerException si user es None» |
style | Azul | Problema de estilo o de convención | «El proyecto usa snake_case para las funciones, no camelCase» |
performance | Naranja | Código ineficiente | «Esto consulta la base de datos dentro de un bucle; usa una consulta por lotes» |
security | Morado | Vulnerabilidad de seguridad | «La entrada del usuario pasa directamente a la consulta SQL sin sanear» |
logic | Amarillo | Problema de lógica que puede no provocar un fallo inmediato | «Esta condición debería ser >= y no >, error de uno en el límite» |
suggestion | Verde | Sugerencia de mejora, no un error | «Aquí convendría usar un context manager para gestionar el recurso de forma más limpia» |
question | Gris | Hace falta una aclaración | «¿Por qué se añadió este import? No parece que se use» |
Cada comentario tiene además un cuerpo de texto libre donde el anotador explica el problema en detalle, igual que al escribir un comentario de PR real.
Valoraciones por archivo
Después de revisar el diff de cada archivo, el anotador lo valora en dos dimensiones:
Corrección (1-5):
- 1: no funciona, introduce errores nuevos
- 2: funciona parcialmente, tiene problemas importantes
- 3: funciona en el camino feliz, pero se deja casos límite
- 4: funciona correctamente con problemas menores
- 5: totalmente correcto, gestiona bien los casos límite
Calidad de código (1-5):
- 1: imposible de mantener, sin estructura
- 2: mala calidad, problemas serios de estilo o diseño
- 3: aceptable, sigue las convenciones básicas
- 4: buena calidad, limpio y legible
- 5: excelente, idiomático, bien documentado
Opciones de veredicto
Tras revisar todos los archivos, el anotador elige uno de tres veredictos:
- Aprobar: el código está listo para fusionarse tal cual, o con cambios triviales
- Pedir cambios: el código necesita revisiones importantes antes de fusionarse
- Solo comentar: se da retroalimentación sin tomar una decisión de fusión
Esto se corresponde directamente con los tres estados de revisión de PR de GitHub.
Configuración paso a paso
Paso 1: convertir las trazas del agente de coding
Las trazas de los agentes de coding vienen en muchos formatos. Estos son ejemplos para tres agentes populares.
Desde Claude Code (exportación JSON):
python -m potato.trace_converter \
--input claude_code_sessions/ \
--output data/code_traces.jsonl \
--input-format claude_codeDesde Aider (historial de chat):
python -m potato.trace_converter \
--input aider_logs/ \
--output data/code_traces.jsonl \
--input-format aiderDesde SWE-Agent (directorio de trayectorias):
python -m potato.trace_converter \
--input swe_agent_trajectories/ \
--output data/code_traces.jsonl \
--input-format swe_agent_trajectoryEl conversor produce un formato JSONL estandarizado. Cada línea contiene una traza con la tarea, los pasos del agente y los diffs de los archivos:
{
"trace_id": "swe_bench_123",
"task": "Fix the TypeError in django/db/models/query.py when calling .values() on an empty QuerySet",
"repo": "django/django",
"base_commit": "a1b2c3d",
"steps": [
{
"type": "thought",
"content": "Let me look at the QuerySet.values() method to understand the TypeError."
},
{
"type": "action",
"tool": "search",
"input": "def values",
"file": "django/db/models/query.py"
},
{
"type": "observation",
"content": "Found at line 234: def values(self, *fields):\n ..."
},
{
"type": "action",
"tool": "edit",
"file": "django/db/models/query.py",
"old_content": " def values(self, *fields):\n clone = self._clone()\n clone.query.set_values(fields)",
"new_content": " def values(self, *fields):\n clone = self._clone()\n if not fields and not self.query.values_select:\n return clone\n clone.query.set_values(fields)"
},
{
"type": "action",
"tool": "edit",
"file": "tests/queries/test_values.py",
"old_content": "class ValuesTests(TestCase):",
"new_content": "class ValuesTests(TestCase):\n def test_empty_queryset_values(self):\n \"\"\"Calling .values() on empty QuerySet should not raise TypeError.\"\"\"\n qs = Author.objects.none()\n result = list(qs.values())\n self.assertEqual(result, [])"
}
],
"files_changed": [
{
"path": "django/db/models/query.py",
"diff": "--- a/django/db/models/query.py\n+++ b/django/db/models/query.py\n@@ -234,6 +234,8 @@\n def values(self, *fields):\n clone = self._clone()\n+ if not fields and not self.query.values_select:\n+ return clone\n clone.query.set_values(fields)"
},
{
"path": "tests/queries/test_values.py",
"diff": "--- a/tests/queries/test_values.py\n+++ b/tests/queries/test_values.py\n@@ -1,4 +1,10 @@\n class ValuesTests(TestCase):\n+ def test_empty_queryset_values(self):\n+ \"\"\"Calling .values() on empty QuerySet should not raise TypeError.\"\"\"\n+ qs = Author.objects.none()\n+ result = list(qs.values())\n+ self.assertEqual(result, [])"
}
]
}Paso 2: configurar el esquema de revisión de código
Crea tu config.yaml:
annotation_task_name: "Coding Agent Code Review"
data_files:
- "data/code_traces.jsonl"
item_properties:
id_key: "trace_id"
text_key: "task"
# Display coding agent traces with diff rendering
display:
type: "coding_trace"
trace_key: "steps"
diff_key: "files_changed"
syntax_highlighting: true
show_line_numbers: true
collapse_large_diffs: true
max_uncollapsed_lines: 200
annotation_schemes:
- annotation_type: "code_review"
# Inline comment categories
# File-level ratings
# Overall verdict
# Annotator settings
annotator_config:
allow_back_navigation: true
# Output settings
output:
path: "output/"
format: "jsonl"Paso 3: arrancar el servidor de anotación
potato start config.yaml -p 8000Abre http://localhost:8000. Verás la primera traza de agente de coding con la descripción de la tarea, los pasos de razonamiento del agente y los diffs de los archivos renderizados con resaltado de sintaxis.
Paso 4: el flujo de trabajo del anotador
Este es el recorrido habitual de una revisión:
- Leer la tarea: entender qué se le pidió al agente (por ejemplo, «Arreglar el TypeError en django/db/models/query.py»)
- Revisar la traza: recorrer los pasos de razonamiento del agente para entender su enfoque
- Revisar el diff de cada archivo:
- Leer el diff con el resaltado de sintaxis
- Pulsar cualquier línea para añadir un comentario en línea
- Seleccionar una categoría de comentario (bug, estilo, rendimiento, etc.)
- Escribir el cuerpo del comentario explicando el problema
- Valorar el archivo en corrección (1-5) y calidad de código (1-5)
- Emitir el veredicto: elegir aprobar, pedir cambios o solo comentar
- Enviar: pulsar «Submit» o Ctrl+Enter
Los atajos de teclado agilizan el trabajo:
| Atajo | Acción |
|---|---|
j / k | Navegar entre archivos |
c | Abrir un comentario en la línea seleccionada |
1-5 | Fijar la valoración de la dimensión actual |
a | Poner el veredicto en aprobar |
r | Poner el veredicto en pedir cambios |
Ctrl+Enter | Enviar la revisión |
Formato de exportación
Cada revisión enviada produce un objeto JSON estructurado:
{
"trace_id": "swe_bench_123",
"annotator": "reviewer_01",
"timestamp": "2026-03-22T14:32:11Z",
"review": {
"inline_comments": [
{
"file": "django/db/models/query.py",
"line": 236,
"side": "right",
"category": "logic",
"body": "This early return skips set_values entirely, but if fields are provided later via .values('name'), the previous empty .values() call will have returned a clone that never went through set_values. Consider checking if this clone is still valid downstream."
},
{
"file": "tests/queries/test_values.py",
"line": 5,
"side": "right",
"category": "suggestion",
"body": "Consider adding a test case for .values() followed by .values('name') to verify the chaining behavior after your fix."
}
],
"file_ratings": [
{
"file": "django/db/models/query.py",
"correctness": 3,
"code_quality": 4
},
{
"file": "tests/queries/test_values.py",
"correctness": 4,
"code_quality": 4
}
],
"verdict": "request_changes"
}
}Este formato estructurado sirve directamente para entrenar modelos de revisión de código y para el análisis agregado.
Análisis: trabajar con los datos de revisión
Cargar las revisiones
import json
import pandas as pd
from pathlib import Path
reviews = []
for f in Path("output/").glob("*.jsonl"):
with open(f) as fh:
for line in fh:
reviews.append(json.loads(line))
print(f"Loaded {len(reviews)} code reviews")Distribución de categorías de comentario
from collections import Counter
all_comments = []
for rev in reviews:
for comment in rev["review"]["inline_comments"]:
all_comments.append(comment)
category_counts = Counter(c["category"] for c in all_comments)
print("Comment categories:")
for cat, count in category_counts.most_common():
print(f" {cat}: {count}")Valoraciones medias por archivo
ratings = []
for rev in reviews:
for fr in rev["review"]["file_ratings"]:
ratings.append(fr)
ratings_df = pd.DataFrame(ratings)
print("Average ratings by file:")
print(
ratings_df.groupby("file")[["correctness", "code_quality"]]
.mean()
.round(2)
.to_string()
)Distribución de veredictos
verdict_counts = Counter(rev["review"]["verdict"] for rev in reviews)
total = sum(verdict_counts.values())
print("Verdict distribution:")
for verdict, count in verdict_counts.most_common():
print(f" {verdict}: {count} ({count/total*100:.1f}%)")Tasa de bugs por agente
Si tus trazas incluyen un campo agent, puedes comparar las tasas de bugs entre agentes:
agent_bugs = {}
for rev in reviews:
agent = rev.get("agent", "unknown")
bug_count = sum(
1 for c in rev["review"]["inline_comments"]
if c["category"] == "bug"
)
if agent not in agent_bugs:
agent_bugs[agent] = []
agent_bugs[agent].append(bug_count)
print("Average bugs per review by agent:")
for agent, bugs in sorted(agent_bugs.items()):
print(f" {agent}: {sum(bugs)/len(bugs):.2f} (n={len(bugs)})")Casos de uso
Entrenar modelos de revisión de código
Los comentarios en línea, las valoraciones por archivo y los veredictos estructurados que produce la anotación de revisión de código de Potato son buenos datos de entrenamiento para modelos de revisión automática. Cada revisión aporta:
- Retroalimentación localizada, ligada a líneas concretas del diff
- Problemas categorizados (bug frente a estilo frente a rendimiento)
- Señales de calidad a varias granularidades (línea, archivo, conjunto)
Es el formato de datos que usan herramientas como CodeRabbit y el revisor con IA de Graphite, pero generado por personas expertas en lugar de destilado de un LLM.
Evaluar agentes de coding en SWE-bench
SWE-bench te dice si el agente resolvió la incidencia (las pruebas pasan), pero no si el código es fusionable. Al aplicar anotación de revisión de código sobre las soluciones de SWE-bench puedes distinguir a los agentes que resuelven incidencias con código limpio de los que lo hacen con apaños. Sale una tabla de clasificación más matizada y más ajustada a la experiencia real de los desarrolladores.
Construir conjuntos de datos de calidad de código
Agrega los datos de revisión de muchas trazas para construir conjuntos de datos de problemas frecuentes de calidad en código generado por IA. Estos conjuntos sirven para:
- Ajustar modelos de generación de código para que eviten errores comunes
- Construir linters específicos para los patrones del código generado por IA
- Entrenar clasificadores que marquen problemas probables en la salida del agente antes de la revisión humana
Resumen
El esquema code_review de Potato mete el flujo de revisión de PR de GitHub dentro de la evaluación de agentes. Los comentarios en línea, las valoraciones por archivo y los veredictos que recojas te dan datos estructurados de calidad de código, que es bastante más de lo que dice un resultado de pruebas de pasa/no pasa. Esos son los datos que necesitas tanto si entrenas un modelo de revisión de código, como si separas las soluciones limpias de SWE-bench de las apañadas o simplemente estableces una línea base de calidad para tu agente.