TutelaBot a fondo: benchmark de base, Air y Pro v3 sobre tutelas reales

Tres versiones del modelo, 358 tutelas del Juzgado Civil Municipal de Bello y once notebooks después: lo que aprendí sobre prompts, num_ctx y saturación de parámetros en extracción estructurada.

Posted by Daniel Arbelaez on Thursday, April 16, 2026

TutelaBot a fondo: benchmark de base, Air y Pro v3 sobre tutelas reales

Hace una semana escribí sobre TutelaBot, el modelo de IA que Alexander Oviedo Fadul publicó en Ollama para analizar acciones de tutela. Ese post se quedó en la versión base: la de 2 GB que corre en cualquier máquina con 16 de RAM.

Pero la familia son tres modelos. Base (3.2B parámetros), Air (10B) y Pro v3 (16B). Los tres se instalan con un ollama pull y responden al mismo prompt. La diferencia está en lo que hacen con ese prompt.

Pasé tres semanas corriéndolos contra tutelas reales del Juzgado Civil Municipal de Bello. 24 PDFs al principio. 460 después. 358 en la última iteración. Once notebooks, cinco consolidaciones, una tabla de Excel con resultados que cabía en mi cabeza hasta el benchmark número seis.

Este es el resumen de lo que encontré. Qué hace bien cada versión, qué hace mal, y qué aprendí reiterando sobre la misma pregunta: ¿puede un LLM abierto de 3.2B parámetros extraer información estructurada de una tutela colombiana sin inventar datos?

La respuesta corta es sí. Si el prompt está bien hecho y si aceptas que la medida provisional la va a alucinar un 26% de las veces.

La respuesta larga requiere tablas.


Las tres versiones en una tabla

Versión Base Parámetros Tamaño Cuantización Uso sugerido
tutelabot Llama 3.2 3.2B 2.0 GB Q4_K_M Desarrollo local, CPU, iteración rápida
tutelabot-air Llama 3.2 ~10B 10 GB Q4_K_M Balance producción, GPU 8 GB
tutelabot-pro-v3 Llama 3.2 ~16B 16 GB Q4_K_M Máxima precisión, GPU 16 GB+

Las tres comparten ventana de contexto de 128,000 tokens y el mismo conjunto de templates de prompt del autor. Ante documentos pequeños las tres devuelven JSON estructurado sin sudar.

Lo que cambia de una a otra no es el prompt. Es el tamaño del modelo, el hardware que necesita y el costo por segundo que pagas cuando procesas 300 tutelas seguidas.


Cómo medimos esto

No fue una corrida. Fueron cinco iteraciones consolidadas (V1 a V5) y once notebooks independientes. Cada notebook respondía una pregunta distinta:

  • NB02: benchmark formal, 10 PDFs × 3 modelos, con ground truth anotado a mano en 5 de los 10.
  • NB05: extracción masiva sobre 24 PDFs, modelo fijo, sin ground truth, solo cobertura sintáctica.
  • NB06: prompt extendido a 57 campos, para ver qué pasa cuando el schema se agranda.
  • NB07 al NB10: consolidaciones progresivas con más documentos, mejores prompts y configuración de num_ctx ajustada.
  • NB11: auditoría de seis estrategias de extracción (regex-as-context, dual output, self-verification, JSON constrained, evidence grounded, agreement score).

El corpus evolucionó con las corridas. Empezamos con 24 PDFs controlados. Saltamos a 460 documentos mixtos (incluyendo correos, anexos y actas de reparto que clasificamos después como no-tutelas). Terminamos con 358 documentos refinados y un ground truth manual sobre dos casos difíciles.

El prompt de referencia para todos los notebooks se parecía a esto (no es el prompt final que usa Sherlock-docs, es un prompt ilustrativo del tipo de contrato que se le entrega al modelo):

PROMPT_SISTEMA = """
Eres un asistente experto en documentos judiciales colombianos.
Analiza el documento y extrae información en formato JSON estricto
según este schema:

{
  "tipo_documento": "tutela | habeas_corpus | demanda",
  "radicado": "string o null",
  "fecha": "YYYY-MM-DD o null",
  "accionante": {
    "nombre": "string",
    "documento_identidad": "string",
    "tipo_documento_id": "CC | TI | CE"
  },
  "accionado": {
    "nombre": "string",
    "tipo_entidad": "string"
  },
  "hechos": ["string"],
  "derechos_vulnerados": ["string"],
  "pretensiones": ["string"]
}

Reglas:
- Responde SOLO con JSON válido, sin texto adicional.
- Si un campo no aparece en el documento, usa null o [].
- No infieras datos que no estén explícitos.
"""

Tres reglas simples. La última es la que se rompe más seguido, como veremos.


TutelaBot base: rápido, frágil, pero útil

La versión base corre en CPU. Ocupa 2 GB de disco, no pide GPU, no pide configuración especial. Es la puerta de entrada. Para un juzgado que no tiene presupuesto de infraestructura, es la única versión que importa.

Los números del benchmark NB02 sobre 10 PDFs:

Métrica Valor
JSON válido 90% (9/10)
Latencia promedio 6.3s
Latencia P95 9.0s
RAM pico 1,692 MB
Precisión promedio 61%
Recall promedio 75%
F1 promedio 65%

El 10% de fallos en JSON fue el mismo documento: una tutela con tablas anidadas de 38 páginas que saturó el contexto útil del modelo. El P95 de 9 segundos es lo que pagas por documentos grandes; el P50 queda por debajo de 7.

Lo que sorprende no son los números, es la curva. El mismo modelo, el mismo corpus, con un prompt ajustado y num_ctx=32768, alcanza 93.9% de JSON válido sobre 358 documentos. La diferencia no está en el modelo. Está en cómo le hablas.

Lecciones del base:

  1. El prompt pesa más que el tamaño. Cambiar de un prompt genérico a un prompt con role-input-steps-expectation (RISE) pasó la cobertura de accionantes de 0% a 100% en el mismo modelo, sobre los mismos documentos.
  2. num_ctx es el parámetro oculto que nadie documenta. Con num_ctx=8192 y documentos de 50 KB, el modelo trunca el JSON a mitad y se rompe la extracción. Subirlo a 32,768 resuelve el 95% de los fallos sin tocar el prompt.
  3. 6.3 segundos por documento no es lento para un juzgado. El SLA de Sherlock-docs es menos de 2 minutos por documento de 20 páginas. Base entra por debajo del 10% de ese presupuesto.

Si alguien me pregunta por dónde empezar a explorar LLMs aplicados a justicia, empiezo por base. No porque sea el mejor. Porque es el único que te deja iterar el prompt 40 veces en una tarde sin pagar por GPU.


TutelaBot Air: el balance

Air es el modelo de 10B parámetros. Necesita GPU (8 GB de VRAM bastan) y saca los siguientes números sobre el mismo corpus de 10 PDFs:

Métrica Valor
JSON válido 100% (10/10)
Latencia promedio 16.6s
Latencia P95 17.6s
Precisión promedio 70%
Recall promedio 92%
F1 promedio 78%

Pagas 2.5 veces más latencia que en base. Ganas 13 puntos de F1, 17 puntos de recall y eliminas completamente los fallos de JSON. Para un pipeline en producción con validación humana, esos 10 puntos extra de JSON válido son la diferencia entre escribir código de fallback o no.

Donde Air se destaca especialmente es en campos narrativos: derechos invocados, hechos relevantes, pretensiones. El base los captura parcialmente; Air los captura completos, con estructura y sin inventar.

Lecciones del Air:

  1. Es el punto dulce de costo-beneficio. Si tienes GPU de 8 GB, no hay razón para quedarse en base.
  2. Recall de 92% cambia el flujo de validación. El operador valida menos campos vacíos y más campos llenos pero posiblemente incorrectos. Eso reorganiza el tiempo del validador.
  3. La tasa de fallo JSON a cero es subestimada. En un pipeline que procesa 100 documentos al día, cada fallo de JSON requiere manejo de excepciones, reintento y, eventualmente, revisión humana. Quitarlos simplifica tres capas del sistema.

Air es el modelo que usaría en producción si tuviera que elegir uno hoy.


TutelaBot Pro v3: más grande no es mejor

Pro v3 es el modelo de 16B parámetros. Necesita GPU de 16 GB o más. Los números:

Métrica Valor
JSON válido 100% (10/10)
Latencia promedio 98.9s
Latencia P95 116.0s
Precisión promedio 65%
Recall promedio 92%
F1 promedio 76%

Pagas 6 veces más latencia que Air. El F1 baja 2 puntos. La precisión baja 5 puntos. El recall es igual.

Esto es lo que los papers académicos llaman “saturación de retorno”. En extracción estructurada sobre documentos cortos (una tutela promedio tiene entre 5 y 40 páginas), un modelo de 16B no tiene qué hacer con parámetros extra. La tarea es más de comprensión de formato que de razonamiento, y el formato se aprende con mucho menos.

Donde Pro v3 sí ganaría es en tareas de razonamiento complejo: resumir una sentencia de 400 páginas, comparar dos jurisprudencias, redactar un memorando. Eso no es lo que hicimos en el benchmark, porque no es lo que un juzgado de primera instancia necesita hacer automáticamente.

Lecciones del Pro v3:

  1. Para extracción estructurada, no vale la pena. 6x latencia por -2pp F1 no es un trade-off aceptable.
  2. Reservar para tareas generativas. Redacción de borradores, resúmenes, análisis comparado.
  3. El tamaño no compensa un prompt flojo. Lo demuestra el mismo corpus con prompt RISE. El base llega a 92.5% de JSON válido. Pro v3 no va más allá.

La serie V1→V5: cinco iteraciones, cinco lecciones

Las métricas individuales de cada modelo son la mitad de la historia. La otra mitad es cómo llegamos a esas métricas. Cinco iteraciones, cada una resolviendo un problema encontrado en la anterior.

Versión n num_ctx Schema JSON ✓ Latencia P50 Cobertura accionante
V1 249 32768 57 campos 95.2% 6.70s 99.2%
V2 460 32768 57 campos 96.3% 6.24s 93.3% real / 99% nominal
V3 358 8192 57 campos 55.3% 5.44s 93.9% con prompt / 1.6% sin
V4 5 32768 subset + regex 100% 14.30s 100%
V5 358 32768 subset + regex + prompt 92.5% 6.11s 92.5%

V1: Línea base. 249 documentos controlados, modelo base, 57 campos. Primer número de referencia.

V2: Saltamos a 460 documentos y descubrimos el problema de la cobertura nominal vs real. El CSV oficial decía 99.3% en accionante. Al filtrar los “no especifica”, “no aplica” y strings vacíos, la cobertura real bajaba al 93.3%. En radicado la brecha era brutal: 98.9% nominal, 30.7% real. Lección: el modelo cumple con la forma del JSON aunque el contenido no aporte.

V3: El bug del siglo. Bajamos num_ctx a 8192 tokens pensando que aceleraría la extracción. El JSON válido cayó del 96% al 55%. Dos semanas de debug después, la tabla clave fue esta:

Tamaño documento n % fallo con num_ctx=8192
0-5k caracteres 39 2.6%
5-15k 86 3.5%
15-30k 98 24.5%
30-60k 67 100%
60-100k 28 96.4%
100-250k 33 93.9%
>250k 7 100%

El umbral es 30,000 caracteres. Con num_ctx=8192 tokens y un prompt de 2,000 tokens, quedan 6,000 tokens útiles. Eso son aproximadamente 24,000 caracteres. Pasado ese umbral, el modelo trunca el JSON, devuelve } sin cerrar estructuras y todo colapsa. Lección: el parámetro más barato de ajustar es el que más rompe cuando se mueve sin medir.

V4: Piloto con 5 documentos anotados a mano. Prompt refinado con RISE, schema reducido a los 12 campos que realmente usa Sherlock-docs, regex complementario para cédula, email y radicado. JSON 100%, cobertura 100%, latencia 14.3s. El piloto funcionó. Pero 5 documentos no son un corpus.

V5: Escala del piloto sobre los 358 documentos refinados. JSON 92.5%, cobertura útil 92.5%, latencia P50 6.11s. Esta es la corrida que consideramos candidata a producción. La brecha con V4 (92.5% vs 100%) se explica por documentos que en V4 no estaban: escaneados de baja calidad, tutelas colectivas, PDFs con tablas anidadas.

Lección de la serie completa: el modelo no mejoró entre V1 y V5. Lo que mejoró fue todo lo demás. Prompt, configuración, validación, schema, post-procesamiento con regex. El LLM es una pieza más en un sistema, no el sistema entero.


Insights accionables por versión

Si vas a usar uno de estos modelos en tu propio proyecto, estas son las tres cosas que yo haría diferente en cada uno.

Base (3.2B)

  1. Usa num_ctx=32768 siempre. El default de Ollama de 4096 no alcanza para tutelas reales. 32768 es el mínimo. 65536 si los documentos pasan de 80 KB.
  2. Empieza con un schema de 10 campos, no de 57. La cobertura de los 23 campos adicionales del schema completo es marginal y los errores se propagan. Menos schema, más precisión.
  3. Valida siempre con regex paralelo en campos estructurales. Cédula, email y radicado se extraen 4,767 veces más rápido con regex que con LLM, y con mayor precisión. Usa el LLM para resolver ambigüedades, no para extraer patrones deterministas.

Air (10B)

  1. Precalienta el modelo antes del primer documento real. El primer request de Ollama tiene overhead de carga. Si tu pipeline corre en burst, manda una request de warmup.
  2. No pierdas los 10 puntos de JSON extra haciendo post-procesamiento débil. Si pagaste la latencia por JSON 100%, aprovéchalo: parsea estricto, no tolerante.
  3. Mide latencia por percentil, no por promedio. El P95 de Air es 1 segundo más que el promedio. El P99 es otro animal. Si tu SLA es duro, dimensiona por P99.

Pro v3 (16B)

  1. No lo uses para extracción estructurada. El análisis del benchmark deja claro que no aporta sobre Air.
  2. Úsalo para lo que sí sabe hacer. Resumir una sentencia, comparar jurisprudencias, redactar un memo. Ahí Pro v3 muestra de qué es capaz.
  3. Si lo usas, bloquea recursos. 98.9 segundos de latencia por documento significa que un worker de Pro v3 procesa 36 documentos por hora. Planifica infraestructura acorde.

Aplicación práctica: qué hicimos con todo esto

La versión corta es que TutelaBot Air, configurado con num_ctx=32768, prompt RISE, schema reducido a 12 campos y regex paralelo para campos estructurales, es candidato a entrar como Nivel 6 del ensemble NER de Sherlock-docs.

Nivel 1 a 5 son los extractores actuales (Marker, SpaCy, Regex, Address, Contact). Nivel 6 sería TutelaBot como resolver de conflictos y extractor de campos narrativos (hechos, derechos, pretensiones).

El plan de integración, el prompt final afinado para el dominio colombiano específico de Bello, y la auditoría NB11 de seis estrategias de mitigación de alucinaciones (incluyendo por qué descartamos la estrategia de auto-verificación adversarial por sycophancy), merecen su propio post. Pronto.


Lo que todavía no funciona bien

La medida provisional sigue alucinada en 26% de los casos. El modelo reporta medida_provisional=true en documentos donde el regex determinista no encuentra las keywords asociadas. Puede ser que el modelo detecte semánticamente la medida donde el regex falla. Puede ser que la invente. No tenemos ground truth manual suficiente para saberlo. Hay 30 documentos anotados a mano pendientes para resolver esta pregunta.

La concordancia regex↔LLM en cédulas es del 55%. En el 45% restante, el LLM extrae una cédula distinta a la que captura el regex. A veces es el abogado apoderado en lugar del accionante. A veces es un dato de jurisprudencia citada. El modelo no se equivoca. El documento tiene ambigüedad real, y resolverla exige ranking y validación posterior.

El corpus de 358 documentos es pequeño para afirmaciones fuertes. Todos los números de este post hay que leerlos con intervalo de confianza mental. F1 de 78% en Air significa algo entre 74% y 82% si repitiéramos el experimento con otros 358 documentos.

Los documentos escaneados de baja calidad rompen el pipeline antes de llegar al LLM. El OCR de Sherlock-docs tiene sus propios puntos de falla. TutelaBot nunca ve el documento original. Solo recibe el texto que el OCR reconoció. Un 5% de error en OCR se traduce en 10% de error en extracción, porque el modelo no puede recuperar información que nunca recibió.


Después del benchmark tengo una convicción. Los LLMs abiertos, con el prompt y la configuración correctos, son herramientas útiles en legaltech colombiano. No son magia. No reemplazan al operador humano, y tampoco resuelven el volumen por sí mismos.

Pero convierten 15 minutos de captura manual en 20 segundos de validación de campos pre-llenados. Y eso, repetido 80 veces al día en un juzgado de Bello, es tiempo que el equipo puede dedicar al trabajo que sí requiere criterio jurídico.

Alexander Oviedo Fadul publicó TutelaBot en Ollama y cualquiera puede descargarlo. Si alguien está trabajando en un modelo similar para otro tipo de proceso (acciones populares, nulidades electorales, disciplinarios), los datos de este benchmark están disponibles para que no tengan que hacer las 11 corridas desde cero.


Stack técnico: Ollama 0.5+, Llama 3.2 (3.2B/10B/16B, cuantización Q4_K_M), corpus de 358 tutelas del Juzgado Civil Municipal de Bello, 11 notebooks Jupyter, 5 análisis consolidados (V1 a V5). Los modelos están disponibles en ollama.com/bladealex/tutelabot.