Axial coding: de notas a taxonomía
sintetizarás las notas abiertas de tus trazas en una taxonomía de modos de fallo, contarás instancias por categoría, priorizarás por frecuencia e impacto, y decidirás cuándo has mirado suficiente. Sin esto, tu análisis es una lista que no responde "¿cuál es el fallo nº1?".
3.1Sesenta notas y ninguna respuesta
Llevas dos tardes haciendo open coding sobre las trazas del agente de Aurora, la tienda online ficticia que es tu banco de pruebas. Tienes 60 trazas leídas y 60 notas abiertas.
Las relees. Dicen cosas como estas:
1trace_0007 citó la política de electrónica para una consulta de calzado
2trace_0011 se inventó el tracking sin llamar a buscar_pedido
3trace_0019 escaló a humano una pregunta de horarios que estaba en la KB
4trace_0023 respuesta correcta, pero escaló igual
5trace_0028 recuperó la política de envíos para una pregunta de reembolso
6trace_0031 inventó un número de pedido que el cliente nunca dio
7...Sesenta líneas así. Cada una es honesta y concreta. Pero ahora tu lead de Aurora te para en el pasillo y pregunta lo que pregunta toda dirección:
"¿Cuál es el fallo número uno, y qué porcentaje de las veces pasa?"
Miras tus 60 notas. No puedes responder. No tienes un análisis: tienes una lista. Una lista no prioriza, no cuenta, no señala dónde invertir la próxima semana. Hoy conviertes esa lista en algo que sí responde esa pregunta.
3.2Qué vas a poder hacer
Al terminar esta lección sabrás:
- Agrupar notas abiertas heterogéneas en una taxonomía coherente de modos de fallo.
- Contar instancias por categoría y calcular su frecuencia relativa.
- Priorizar las categorías por frecuencia y por impacto, no solo por cuál aparece más.
- Decidir cuándo parar de mirar trazas, aplicando un criterio de saturación explícito.
Necesitas saber antes:
- Haber hecho open coding (N1·L2): anotar el primer fallo de cada traza con una nota concreta, no una etiqueta prematura.
- Leer una traza del agente de Aurora: el flujo retrieval → LLM → tool que viste en el Nivel 0.
- pandas a nivel de cargar un
DataFramey contar valores de una columna.
Esta lección no construye todavía el dataset etiquetado. Eso es N1·L4. Aquí pasas de notas sueltas a una taxonomía priorizada: el plano que dice qué fallo atacar primero.
3.3Recupera
Antes de seguir, responde mentalmente. No mires lo de abajo hasta tener una respuesta.
- En el open coding de L2, ante una traza con dos errores encadenados, ¿cuál anotabas: el primero o el último? ¿Por qué?
- ¿Qué distingue una nota útil ("recuperó política de electrónica para una consulta de calzado") de una prematura ("alucinación")?
- Si en pandas tienes una columna con la categoría de cada traza, ¿qué método te da el conteo de cada valor?
La respuesta a la 1 es el primer fallo: los errores upstream contaminan downstream. Si el retrieval trae el documento equivocado, la generación será fiel a un contexto malo; anotar la generación esconde la causa real (corpus A.3). La respuesta a la 3 es value_counts() — la usarás en un momento.
3.4El concepto: agrupar, contar, priorizar
Empecemos por lo concreto: tus 60 notas. Y subamos hacia el método que las ordena.
De notas a categorías
Mira otra vez las notas del §3.1. Si las lees con atención, varias dicen lo mismo con palabras distintas:
- "citó la política de electrónica para una consulta de calzado" y "recuperó la política de envíos para una pregunta de reembolso" → ambas son el paso RAG recupera la política equivocada.
- "se inventó el tracking sin llamar a buscar_pedido" e "inventó un número de pedido que el cliente nunca dio" → ambas son inventa datos del pedido sin tool-call.
- "escaló una pregunta que estaba en la KB" y "respuesta correcta, pero escaló igual" → ambas son escalado innecesario.
Ese acto de juntar notas que comparten una raíz común tiene nombre.
El axial coding es agrupar las notas abiertas en una failure taxonomy y contar las instancias de cada categoría (corpus A.3, Hamel Husain, evals-faq, ene 2026). Una failure taxonomy es un conjunto de categorías de modo de fallo. El nombre "axial coding" viene de las metodologías cualitativas de las que el error analysis lo adapta. El corpus no lo atribuye a ninguna teoría concreta, solo a esa raíz metodológica.
Una analogía con su límite: es como ordenar una bandeja de correo. Lees cada mensaje (open coding), y luego creas carpetas y arrastras cada uno a la suya (axial coding). El límite de la analogía: aquí las "carpetas" no existen de antemano —emergen de las propias notas—. No las decides antes de mirar; las descubres mirando.
La fuente lo marca sin rodeos: el axial coding es "el paso más importante" del error analysis (corpus A.3). El motivo es que categorizar y contar es lo que te deja priorizar — y priorizar es lo que convierte el análisis en decisiones.
Por qué contar no es opcional
Es común creer que con haber leído las trazas ya "sabes" cuál es el problema. Casi todo el mundo lo cree la primera vez. La intuición engaña: tendemos a recordar el fallo más vívido (el más raro y escandaloso), no el más frecuente.
El conteo corrige ese sesgo. Cuando pones números, "el agente se vuelve loco a veces" se convierte en "el RAG recupera la política equivocada en el 30% de las trazas". Lo primero es una anécdota. Lo segundo es un objetivo medible que tu lead puede defender ante quien firma el presupuesto.
Priorizar: frecuencia y también impacto
Aquí mucha gente se queda corta. Ordenan las categorías por frecuencia, atacan la más alta, y listo.
La frecuencia sola te engaña. Un fallo puede ser raro y aun así ganar la prioridad si su impacto es alto. Pensemos en Aurora:
- "El agente saluda con una mayúscula rara" → frecuente, impacto casi nulo.
- "Promete un reembolso que la política prohíbe" → raro, pero cada caso es dinero perdido y un cliente furioso en redes.
Priorizar solo por frecuencia pondría el saludo por delante del reembolso fantasma. Sería un error de juicio. Por eso priorizamos por frecuencia × impacto: una estimación de cuánto duele un fallo combinada con cuántas veces ocurre. La frecuencia la cuentas con datos; el impacto lo estimas con criterio de producto. Ambos importan.
Saturación: cuándo dejar de mirar
Queda una pregunta incómoda: ¿hasta cuándo sigues leyendo trazas? Si paras pronto, te pierdes categorías. Si no paras nunca, no entregas nada.
El criterio se llama saturación: el punto en que mirar más trazas ya no revela categorías nuevas, solo más instancias de las que ya tienes. La señal operativa del corpus es concreta. Con ≥100 trazas analizadas, si ~20 consecutivas no aportan ninguna categoría nueva, has alcanzado saturación teórica y puedes parar justificadamente (corpus A.3, Hamel Husain, evals-faq, ene 2026).
Atención a las dos condiciones, porque se confunden: necesitas las dos. Veinte trazas seguidas sin novedad sobre un total de 40 no es saturación —has mirado demasiado poco como para confiar—. La barrera de ≥100 protege contra parar antes de tiempo.
Sobre cuántas categorías esperar: conviene mantener la taxonomía pequeña, porque cien categorías no priorizan nada. Eso es una heurística de manejabilidad, no un número objetivo verificado; no persigas una cifra concreta de categorías. Persíguela lo bastante fina para distinguir fallos distintos, y lo bastante gruesa para que quepa en una tabla que tu lead lea de un vistazo.
3.5Míralo funcionar: ocho notas → tabla priorizada
Vamos a recorrer el axial coding completo sobre un subconjunto de las notas de Aurora. El código es corto, pero el juicio detrás no lo es. Lee primero la tabla final; luego seguimos el camino que lleva a ella.
Partimos de estas 8 notas de open coding (de las 60 del §3.1):
1trace_0007 citó la política de electrónica para una consulta de calzado
2trace_0011 se inventó el tracking sin llamar a buscar_pedido
3trace_0019 escaló a humano una pregunta de horarios que estaba en la KB
4trace_0023 respuesta correcta, pero escaló igual
5trace_0028 recuperó la política de envíos para una pregunta de reembolso
6trace_0031 inventó un número de pedido que el cliente nunca dio
7trace_0040 citó la política de devoluciones de electrónica para unas zapatillas
8trace_0044 escaló sin intentar resolver una duda simple de tallaPaso 1 — agrupar en categorías. Leemos cada nota y le asignamos su raíz común. Tres categorías emergen:
rag_politica_equivocada— el paso RAG recupera la política que no toca (0007, 0028, 0040).pedido_inventado_sin_toolcall— afirma datos del pedido sin llamar abuscar_pedido(0011, 0031).escalado_innecesario— escala a humano algo resoluble (0019, 0023, 0044).
Llevamos esas asignaciones a una columna de nuestra hoja de trazas. Usamos pandas, que ya conoces:
1import pandas as pd
2
3# La hoja de trazas, con la nota de open coding y la categoría asignada en axial coding.
4df = pd.DataFrame(data=[
5 {"trace_id": "trace_0007", "categoria_axial": "rag_politica_equivocada"},
6 {"trace_id": "trace_0011", "categoria_axial": "pedido_inventado_sin_toolcall"},
7 {"trace_id": "trace_0019", "categoria_axial": "escalado_innecesario"},
8 {"trace_id": "trace_0023", "categoria_axial": "escalado_innecesario"},
9 {"trace_id": "trace_0028", "categoria_axial": "rag_politica_equivocada"},
10 {"trace_id": "trace_0031", "categoria_axial": "pedido_inventado_sin_toolcall"},
11 {"trace_id": "trace_0040", "categoria_axial": "rag_politica_equivocada"},
12 {"trace_id": "trace_0044", "categoria_axial": "escalado_innecesario"},
13])Paso 2 — contar. Una sola línea hace el trabajo:
1df["categoria_axial"].value_counts()
2# escalado_innecesario 3
3# rag_politica_equivocada 3
4# pedido_inventado_sin_toolcall 2
5# Name: count, dtype: int64
6
7df["categoria_axial"].value_counts(normalize=True)
8# escalado_innecesario 0.375
9# rag_politica_equivocada 0.375
10# pedido_inventado_sin_toolcall 0.250value_counts() devuelve el conteo por categoría, ordenado de mayor a menor; con normalize=True te da la frecuencia relativa. En 8 notas hay empate; sobre las 60 completas las proporciones se separarían.
Paso 3 — priorizar con impacto. El conteo nos da la frecuencia. Ahora añadimos el juicio de producto. Estimamos un impacto por categoría (alto/medio/bajo) y construimos la tabla que tu lead pidió:
| Categoría | Conteo | % | Impacto | Prioridad |
|---|---|---|---|---|
rag_politica_equivocada | 3 | 37,5% | Alto — info falsa al cliente sobre devoluciones/reembolsos | 1 |
escalado_innecesario | 3 | 37,5% | Medio — coste operativo, fricción, pero sin info falsa | 2 |
pedido_inventado_sin_toolcall | 2 | 25% | Alto — afirma datos inexistentes del pedido | 3 |
Fíjate en la decisión: rag_politica_equivocada y escalado_innecesario empatan en frecuencia, pero la primera gana la prioridad nº1 por su impacto —da información falsa al cliente—. El conteo no las separaba; el impacto sí.
El momento de saturación. Supón que estas 8 salen de un análisis ya avanzado: llevas 100 trazas leídas y las últimas 20 (de la 81 a la 100) no han revelado ninguna categoría nueva —todas caen en las tres que ya tienes—. Has cumplido las dos condiciones: ≥100 trazas y ~20 consecutivas sin novedad. Paras, y lo documentas: "saturación a las 100 trazas, sin categoría nueva desde la 80".
Ahora la pregunta de auto-explicación. Antes de leer el párrafo siguiente, responde: ¿por qué priorizamos por frecuencia y por impacto, y no solo por la frecuencia?
Porque la frecuencia sola trataría un fallo raro y caro (reembolso fantasma) como menos urgente que uno frecuente y barato (un saludo feo). La frecuencia mide cuánto pasa; el impacto, cuánto duele. Una buena prioridad necesita las dos: optimizar solo una deja la otra ciega.
3.6Hazlo tú
Ejercicio 1 — andamiaje parcial
Ya tienes un set agrupado a medias. Estos son los conteos crudos de cuatro categorías sobre 80 trazas de Aurora:
1rag_politica_equivocada 26
2escalado_innecesario 19
3pedido_inventado_sin_toolcall 14
4tono_demasiado_formal 8Completa la tabla de priorización. Te damos la primera fila resuelta; rellena las otras tres con un % y un impacto estimado, y asigna la prioridad:
| Categoría | Conteo | % | Impacto | Prioridad |
|---|---|---|---|---|
rag_politica_equivocada | 26 | 32,5% | Alto — info falsa al cliente | 1 |
escalado_innecesario | 19 | ____ | ____ | __ |
pedido_inventado_sin_toolcall | 14 | ____ | ____ | __ |
tono_demasiado_formal | 8 | ____ | ____ | __ |
(Pista para el impacto: ¿cuál de estos fallos da información falsa, cuál cuesta operación, y cuál es cosmético?)
Ejercicio 2 — autónomo
Aquí tienes 6 notas de open coding en bruto de Aurora, sin agrupar:
1t1 recomendó un producto agotado como disponible
2t2 citó la política de electrónica para una consulta de ropa
3t3 escaló una consulta de horarios resoluble con la KB
4t4 dijo que un pedido estaba entregado sin llamar a buscar_pedido
5t5 recuperó la política de garantía para una pregunta de cambio de talla
6t6 afirmó un plazo de envío que no consultó en ninguna fuenteSin mirar la solución:
- Agrupa las 6 notas en categorías (dales un nombre tipo
snake_case). - Cuenta cuántas caen en cada una.
- Decide: con solo estas 6 trazas analizadas, ¿puedes declarar saturación? Justifica contra el criterio.
Antes de seguir, una pregunta de interrogación elaborativa. Respóndela tú primero: ¿por qué el criterio de saturación exige ≥100 trazas y no se conforma con "20 seguidas sin categoría nueva"?
Porque 20 trazas sin novedad sobre un total pequeño no prueba que hayas cubierto el espacio de fallos. Solo prueba que esas 20 se parecían entre sí. Con 30 trazas podrías llevar 20 seguidas iguales por azar del muestreo, sin haber visto aún un modo de fallo entero. La barrera de ≥100 reduce ese riesgo: te obliga a mirar suficiente como para que "no aparecen categorías nuevas" signifique algo.
3.7Comprueba
Sin pistas. Resuelve este caso de decisión.
Estás haciendo error analysis sobre el agente de Aurora. Llevas 90 trazas analizadas. Las últimas 18 han caído todas en categorías que ya tenías —ninguna nueva—. Tu calendario aprieta y te tienta cerrar el análisis aquí.
¿Paras o sigues? Justifica tu decisión contra el criterio de saturación, y nombra el riesgo concreto de parar ahora.
Ver la respuesta razonada
No paras. Sigues hasta al menos 100 trazas. El criterio de saturación tiene dos condiciones y aquí solo cumples una:
- ✅ Racha sin categoría nueva: 18 es del orden de las ~20 que pide el criterio.
- ❌ Volumen mínimo: 90 trazas está por debajo del umbral de ≥100 (corpus A.3).
Faltándote el volumen, "18 seguidas sin novedad" todavía puede ser un artefacto del muestreo, no saturación real.
El riesgo de parar ahora: te pierdes un modo de fallo de baja frecuencia que aún no ha aparecido en tu muestra. Si ese modo es raro pero de alto impacto —el patrón del reembolso fantasma de Aurora—, lo dejarás fuera de la taxonomía. Y lo que no está en la taxonomía no se etiqueta en L4, no se mide en N2, y llega a producción sin red. Mirar 10–20 trazas más cuesta una hora; el fallo que se escapa cuesta mucho más.
Feedback formativo:
- Si dijiste "seguir" y nombraste las dos condiciones por separado: dominas el criterio — no es una regla suelta ("20 seguidas") sino una conjunción ("≥100 y ~20 sin nueva"). Reutilizarás este juicio en el checkpoint C1, donde tendrás que justificar por escrito tu punto de parada.
- Si dijiste "parar" porque viste las 18 seguidas: tu solución optimiza una condición e ignora la otra. La diferencia clave: la racha sin novedad propone saturación; el volumen ≥100 la confirma. ¿Qué le falta a 90 trazas que sí tendrían 100? Vuelve al §3.4, "Saturación", y relee por qué van juntas.
- Si dijiste "seguir" pero no supiste nombrar el riesgo: acertaste la decisión, te faltó el porqué que la defiende ante tu lead. El riesgo concreto es perder un fallo raro y caro. Practícalo verbalizando, para una traza del §3.5, qué pasaría si nunca entrara en tu tabla.
3.8Conecta
Vuelve al pasillo del §3.1, a tu lead preguntando "¿cuál es el fallo nº1 y qué porcentaje de las veces pasa?".
Ahora puedes contestar. Abres la tabla del §3.5 y dices lo siguiente. El fallo nº1 es que el RAG recupera la política equivocada. Pasa en el 37% de las trazas y es de alto impacto, porque da información falsa al cliente. Le sigue el escalado innecesario, mismo porcentaje pero menos grave. Y paraste de mirar a las 100 trazas, cuando 20 seguidas no aportaron nada nuevo. Eso ya no es una lista. Es un análisis defendible.
Lo que sigue construye sobre esta taxonomía:
- En N1·L4 conviertes el fallo nº1 —y los demás— en etiquetas binarias y un dataset versionado. La categoría
rag_politica_equivocadase vuelve un criterio pass/fail sobre cada traza. - Ese fallo nº1 será también la semilla del juez de N2: el primer modo de fallo que decidirás cómo medir, con código o con un LLM-as-judge.
¿Dónde lo aplicarías en tu trabajo? Toma cualquier sistema LLM que hayas tocado. Si te preguntaran hoy "¿cuál es su fallo más frecuente y de mayor impacto?", ¿tendrías una tabla con conteos, o una corazonada? Si es lo segundo, ya sabes qué te falta: contar.
3.9Reflexiona
Tómate dos minutos. Estas preguntas consolidan más que releer.
- ¿Por qué priorizar solo por frecuencia puede llevarte a atacar el fallo equivocado?
- Con tus palabras: ¿qué dos condiciones tiene la saturación, y por qué necesitas las dos?
- ¿Qué sigue sin estar claro? Anótalo. Si es "cómo estimo el impacto sin datos", es la pregunta correcta — es juicio de producto, y mejora viendo más sistemas reales.
Referencia rápida
- Axial coding: agrupar las notas abiertas en una failure taxonomy y contar instancias por categoría. Es "el paso más importante" del error analysis (corpus A.3, Hamel Husain, evals-faq, ene 2026).
- Por qué contar: el conteo corrige el sesgo de recordar el fallo más vívido en vez del más frecuente. Convierte anécdotas en objetivos medibles.
- Priorización: por frecuencia × impacto. La frecuencia la cuentas (
value_counts()); el impacto lo estimas con criterio de producto. Un fallo raro y caro puede ganar a uno frecuente y trivial. - Saturación (criterio de parada): ≥100 trazas analizadas y ~20 consecutivas sin categoría nueva → saturación teórica. Hacen falta las dos condiciones (corpus A.3).
- Tamaño de la taxonomía: mantenla manejable para que quepa en una tabla. Es heurística, no un número objetivo — no persigas una cifra de categorías.
- pandas:
df["categoria_axial"].value_counts()para el conteo;value_counts(normalize=True)para la frecuencia relativa.