Framework de evaluación automática para una skill contable en LLM Automated evaluation framework for a Spanish PGC accounting LLM skill
50 casos de test · 12 categorías · 5 iteraciones · 66% → 94% de precisión · Mitigación de sesgos cognitivos en IA 50 test cases · 12 categories · 5 iterations · 66% → 94% accuracy · AI cognitive bias mitigation
Un pipeline de desarrollo orientado a evaluación para medir y mejorar iterativamente una skill LLM de contabilidad. La skill bajo test genera asientos contables de doble entrada (PGC — Plan General Contable español) a partir de descripciones en lenguaje natural.
La idea central: mejorar una skill de LLM no es cuestión de escribir más instrucciones — es medir los patrones de fallo de forma sistemática, corregir la causa raíz, y verificar que las correcciones no rompen los casos que ya pasaban (regresión).
| Run | Puntuación | % | Versión skill | Cambio principal |
|---|---|---|---|---|
| Run 1 — baseline | 33 / 50 | 66% | v1.0 | Skill inicial |
| Runs 2–3 | 36–38 / 50 | 72–76% | v1.5–v2.0 | Taxonomía IVA, reglas ISP |
| Run 4 | 41 / 50 | 82% | v2.2 | Freno, asientos de nómina |
| Run 5 | 44 / 50 | 88% | v2.6 | Excepción SaaS, 640 vs 641, embargo |
| Teórico (v2.8) | 47 / 50 | 94% | v2.8 | Correcciones dataset + aclaración devengo |
Puntuación teórica verificada manualmente por análisis de casos tras aplicar todas las correcciones v2.8.
| Categoría | Casos | Qué testea |
|---|---|---|
facturas_emitidas |
6 | Ventas B2B a crédito, B2C al contado, descuentos, devoluciones, anticipos, cobros |
facturas_recibidas |
6 | Compras, pagos de servicios, rappels, anticipos a proveedores |
intracomunitario |
4 | Adquisiciones/entregas intracomunitarias, ISP, exenciones IVA |
isp_domestico |
3 | ISP doméstico: ejecución de obras, arrendamiento con renuncia a exención |
nominas_simples |
4 | Devengo nómina (640/642/465/476), paga extra, pago en banco |
nominas_irpf_embargos |
3 | IRPF elevado, embargo judicial (410 + 465 coexisten) |
amortizaciones |
3 | Inmovilizado material (681/281), intangible (680/280) |
periodificaciones |
4 | Gastos anticipados (480), ingresos diferidos (485), accruals |
cierre_regularizacion |
4 | Variación existencias, deterioro, traspaso resultado, reversión |
impuestos |
5 | Liquidación IVA (303), pagos IRPF (111/115), compensación |
trampas_errores |
4 | IVA sobre precio total, datos insuficientes, asientos descuadrados |
leasing_renting |
4 | Renting (gasto corriente), activación leasing, cuotas mensuales |
dataset_v2.json ← 50 casos de test con expected outputs
│
▼
runner.py ──────────────── carga skill_prompt.md como system prompt
│ ───── llama a la API de Claude (temperature=0)
│ ───── parsea la respuesta JSON
▼
results/YYYY-MM-DD_HHMM_sonnet.json ← output raw por caso
│
▼
grader.py ──────────────── valida: coincidencia por prefijo PGC
───── valida: importes ±€0,01 de tolerancia
───── valida: cuadre (Σdebe = Σhaber)
───── valida: flags semánticos
───── reporta por categoría
- Estado — OK vs PENDIENTE_VERIFICACION (freno obligatorio cuando faltan datos)
- Cuadre — total DEBE debe igualar total HABER (±€0,01)
- Líneas del asiento — cada línea esperada debe coincidir por prefijo PGC (no código exacto), importe y lado (debe/haber)
- Flags semánticos —
requiere_periodificacion,isp,freno_nominas,retencion_irpf
La validación por prefijo (startswith) es clave: la skill usa códigos de 8 dígitos específicos de empresa (ej. 47200001), pero el test solo exige el grupo PGC correcto (ej. 472). Esto testea el conocimiento contable, no la memorización de códigos.
La parte más interesante del proyecto fue aplicar teoría de sesgos cognitivos directamente al diseño del prompt. Se identificaron y abordaron seis sesgos:
Problema: el último "pensamiento" del modelo antes de generar output ancla la respuesta. Si el contexto más reciente es un ejemplo positivo, el modelo se vuelve permisivo.
Solución: se añadió una pregunta de pre-vuelo como última instrucción antes del input — forzando al modelo a verificar si faltan datos críticos (% IRPF, cuota SS empresa) antes de escribir ningún JSON.
Problema: el modelo confirma el marco establecido por la pregunta. "Contabiliza esta factura..." lo predispone a producir un asiento aunque los datos sean insuficientes.
Solución: se añadió un checklist de 8 pasos que el modelo debe ejecutar antes de producir el asiento. El checklist resetea el marco de "produce output" a "verifica primero".
Problema: la primera cuenta mencionada en la descripción ancla la elección del modelo, aunque sea incorrecta.
Solución: reglas explícitas para categorías donde la cuenta "obvia" es incorrecta (suscripciones SaaS → 62x no 20x, devengo nómina → 465 no 572).
Problema: cuando existen dos tratamientos contables posibles, el modelo elige el "término medio" — produciendo un asiento híbrido incorrecto bajo ambos criterios.
Solución: reglas binarias con criterios de decisión explícitos. Sin "podría ser cualquiera de los dos" — el prompt fuerza una decisión basada en condiciones concretas.
Problema: cuando el input del usuario implica un resultado esperado (ej. "haz este asiento: DEBE 600 / HABER 400"), el modelo cumple aunque sea incorrecto (descuadrado).
Solución: casos trampa en el dataset (casos 43–46) donde la respuesta correcta es rechazar y devolver PENDIENTE_VERIFICACION.
Problema: el modelo selecciona la interpretación más favorable de datos ambiguos en lugar de señalar la ambigüedad.
Solución: la pregunta de pre-vuelo distingue datos derivables (IVA estándar 21%, estructura del asiento) de datos que deben proporcionarse (% IRPF, tipo SS empresa, importe de la operación). Dato ambiguo = freno.
# Python 3.10+, se recomienda Conda
conda create -n llm-eval python=3.10
conda activate llm-eval
pip install -r requirements.txt# 1. Clonar el repo
git clone https://github.com/jleonceo/llm-eval-contable.git
cd llm-eval-contable
# 2. Configurar API key
cp .env.example .env
# Editar .env: ANTHROPIC_API_KEY=tu_clave_aqui
# 3. Añadir tu skill prompt
cp skill_prompt.example.md skill_prompt.md
# Editar skill_prompt.md con tu system prompt real# Por defecto: Claude Sonnet 4.6
python runner.py
# Modelo explícito
python runner.py --model sonnet
python runner.py --model opus
python runner.py --model haiku
# Evaluar un resultado existente
python grader.py results/2026-05-27_1200_sonnet.jsonEl dataset espera output JSON con esta estructura:
{
"estado": "OK",
"lineas": [
{"cuenta": "47200001", "nombre_cuenta": "IVA soportado", "debe": 21.00, "haber": 0.00}
],
"flags": {
"requiere_periodificacion": false,
"isp": false,
"freno_nominas": false,
"retencion_irpf": false
}
}Sustituye skill_prompt.md por tu system prompt. Ajusta dataset_v2.json con tus casos de test. La lógica de grader.py es genérica y reutilizable.
llm-eval-contable/
├── README.md ← este fichero
├── dataset_v2.json ← 50 casos de test con expected outputs
├── grader.py ← lógica de evaluación y puntuación
├── runner.py ← runner API (carga skill_prompt.md)
├── skill_prompt.md ← tu system prompt (NO incluido — ver .gitignore)
├── skill_prompt.example.md ← plantilla con la estructura requerida
├── requirements.txt
├── .env.example
├── .gitignore
└── results/
└── summary.md ← progresión de puntuaciones por run
¿Por qué validación por prefijo PGC?
El PGC usa una jerarquía de cuentas. Las empresas las extienden con sufijos propios (ej. 47200001 para una cuenta IVA específica). Testear códigos exactos haría el eval frágil y específico de empresa. La validación por prefijo (62x = cualquier cuenta de gasto) testea el conocimiento contable que realmente importa.
¿Por qué temperature=0? Reproducibilidad. Con temperature > 0, el mismo caso puede pasar o fallar entre runs por razones aleatorias, haciendo imposible atribuir cambios de puntuación a cambios en el prompt. Temperature cero hace cada run determinista.
¿Por qué output en JSON? El output estructurado permite evaluación automática. Las explicaciones contables en texto libre son útiles para humanos pero imposibles de evaluar a escala. El formato JSON además obliga al modelo a ser preciso con los importes.
¿Por qué un freno obligatorio (PENDIENTE_VERIFICACION)? Un asiento incorrecto en un sistema contable es peor que ningún asiento. El patrón freno — rechazar y explicar en lugar de adivinar — es el comportamiento correcto para un asistente contable en producción.
Ver results/summary.md para el análisis completo con desglose por categoría y análisis de fallos.
Juan Luis León Rodríguez Analista de Datos · Business Intelligence · IA Aplicada al Negocio
An eval-driven development pipeline to test and iteratively improve an LLM-based accounting assistant. The skill under test generates Spanish double-entry bookkeeping entries (PGC — Plan General Contable) from natural language descriptions.
The core insight: improving an LLM skill is not about writing more instructions — it's about measuring failure patterns systematically, fixing the root cause, and verifying fixes don't break passing cases (regression testing).
| Run | Score | % | Key change |
|---|---|---|---|
| Run 1 — baseline | 33 / 50 | 66% | Initial skill |
| Runs 2–3 | 36–38 / 50 | 72–76% | VAT taxonomy, ISP rules |
| Run 4 | 41 / 50 | 82% | Brake logic, payroll edge cases |
| Run 5 | 44 / 50 | 88% | SaaS exception, 640 vs 641, garnishments |
| Theoretical (v2.8) | 47 / 50 | 94% | Dataset fixes + accrual clarification |
The grader validates each model response against the dataset expected output:
- Account matching by PGC prefix (e.g.
472matches47200001) — tests accounting knowledge, not code memorisation - Balance check — Σdebit = Σcredit ±€0.01
- Semantic flags — exact match on
requiere_periodificacion,isp,freno_nominas,retencion_irpf - Brake pattern — model must return
PENDIENTE_VERIFICACION(empty entry) when essential data is missing
Six cognitive biases were addressed through prompt engineering:
| Bias | Fix |
|---|---|
| Recency | Pre-flight question as last instruction before input |
| Confirmation | 8-step checklist resets the "produce output" frame |
| Anchoring | Explicit rules for cases where the obvious account is wrong |
| False balance | Binary decision criteria, no hedging allowed |
| Sycophancy | Trap cases (43–46) require refusing user-provided wrong entries |
| Cherry picking | Pre-flight distinguishes derivable data from required data |
git clone https://github.com/jleonceo/llm-eval-contable.git
cd llm-eval-contable
pip install -r requirements.txt
cp .env.example .env # add your ANTHROPIC_API_KEY
cp skill_prompt.example.md skill_prompt.md # add your system prompt
python runner.pyMIT — see LICENSE.
Construido con Claude Sonnet 4.6 · Mayo 2026 · Built with Claude Sonnet 4.6 · May 2026