Ir al contenido

Evaluación con modelos

La evaluación con modelos comprueba si un modelo real es capaz de usar arcane-mcp para hacer algo real en un Arcane real. Mide el uso de herramientas, no la redacción: dar con la acción adecuada, rellenar sus argumentos, añadir la confirmación que exige una acción destructiva, negarse a lo que ninguna acción puede hacer y terminar sin que nadie lo corrija.

Existe porque un servidor MCP es una interfaz para modelos. Una herramienta puede ser clara para una persona y difícil para un modelo —una descripción que no aclara cuál de dos acciones usar, un nombre de parámetro que el modelo solo descubre cuando se lo rechazan— y nada de eso aparece en un test unitario ni en la batería de extremo a extremo. El diseño sigue la evaluación con modelos de gitlab-mcp-server, a menor escala.

Una ejecución plantea cada caso del corpus a cada modelo configurado, en cada superficie configurada (por defecto, la dinámica y la meta; la individual, si se pide):

  1. Un Arcane desechable. make modeleval-up arranca un demonio Docker-in-Docker y un Arcane v2.17.2 que lo gestiona, ambos con el prefijo me- y publicados solo en 127.0.0.1:16000. Nunca es el entorno de extremo a extremo: un modelo en evaluación envía lo que decide enviar, y nada de lo que haga puede llegar a contenedores de los que depende otra batería.
  2. Recursos de partida. Antes de cada intento, el arnés crea lo que necesita el caso (un contenedor en marcha, un volumen, un proyecto Compose, un usuario…) con nombres que llevan una marca nueva, y genera el enunciado con esos nombres.
  3. El servidor. Un servidor arcane-mcp para la superficie, construido con las mismas llamadas que el binario stdio. Al modelo se le muestran exactamente las herramientas que publica ese servidor. La superficie individual solo se ejecuta si se pide (MODELEVAL_SURFACES=dynamic,meta,individual): sus 461 herramientas no caben en una petición, así que cada intento ve todas las herramientas de los dominios del caso y otras de relleno, hasta 128, y lo que se mide es la elección dentro de esa selección, no en todo el catálogo.
  4. La conversación. Las llamadas a herramientas llegan al servidor tal cual y sus respuestas vuelven al modelo. No se corrige ni se repite nada en nombre del modelo; solo se reenvía una petición que el propio proveedor rechazó (límite de peticiones, error del servidor). Un intento termina cuando el modelo responde con texto, o al llegar al tope de turnos: tres por paso en la superficie dinámica (dos en las demás), más cuatro.
  5. Limpieza. Se eliminan los recursos de partida y también todo lo que creó el intento: lo que lleva su marca o lo que no existía antes de empezar.

Cada intento queda escrito en un fragmento: el enunciado tal como se envió, cada turno del proveedor con sus tokens y cada llamada con la acción a la que se resolvió y lo que hizo el servidor con ella. Un fragmento guarda observaciones, no veredictos.

Cada caso es una petición en lenguaje natural, los recursos de partida que necesita y una clave: los pasos ordenados que da un agente correcto, cada uno con una acción canónica del catálogo y los argumentos cuyos valores se comparan. Hay cinco tipos: de lectura (todos los pasos leen), de escritura (un cambio no destructivo), de varios pasos (varios pasos en orden; normalmente, averiguar un ID que el enunciado no da y luego actuar), destructivos (un cambio que el servidor rechaza sin confirm: true) y de rechazo (algo que ninguna acción hace, o una petición demasiado vaga para actuar con seguridad, en la que lo correcto es decirlo o preguntar, sin cambiar nada).

Un enunciado nunca da su propia respuesta. Un test unitario corriente genera cada enunciado en cada superficie y falla si contiene el ID de la acción del caso, el nombre de su herramienta en cualquier superficie, los nombres de las acciones de su dominio, cualquier nombre de argumento que admita el esquema de la acción (también como la frase que forma, de modo que displayName detecta «display name»), o cualquier ID de acción o nombre de herramienta del catálogo. Un segundo test introduce a propósito cada tipo de filtración y demuestra que la auditoría la detecta. Cuando una palabra es de verdad la propia petición, la excepción queda anotada con su motivo. Cada acción que nombra el corpus se comprueba contra el catálogo.

El corpus tiene 94 casos (30 de lectura, 18 de escritura, 15 de varios pasos, 21 destructivos, 10 de rechazo) que declaran 108 pasos sobre 104 de las 461 acciones del catálogo en 46 de sus 52 dominios. El registro completo, caso a caso y con cada enunciado y su clave, está en test/modeleval/corpus.md.

DominioCasosAcciones nombradas
activity22
api_key25
app_image11
auth11
backup11
build11
container99
dashboard11
diagnostics11
environment22
event13
federated_credential12
git_repository33
gitops45
image33
image_update11
job22
mobile_push11
network44
notification11
oidc22
passkey11
port11
project55
registry33
role43
s3_destination11
server12
settings21
stream11
swarm12
swarm_config11
swarm_node12
swarm_secret11
swarm_service33
swarm_stack11
swarm_task11
system11
template24
updater11
user55
variable23
volume55
volume_backup11
vulnerability11
webhook23

La puntuación se calcula a partir de los fragmentos, frente al corpus de HEAD, con un único paquete que comparten el arnés y el publicador. La evaluación mide el uso de herramientas, no a Arcane: un paso cuenta como alcanzado cuando el servidor acepta la llamada del modelo, responda lo que responda Arcane después. En concreto, un paso se alcanza con la primera llamada, posterior al paso anterior, que se resuelve a su acción y que el servidor acepta (pasa el esquema de entrada y, si es destructiva, lleva la confirmación). Si Arcane falla en una llamada aceptada, el paso cuenta como alcanzado: el modelo hizo su parte.

ColumnaNumeradorDenominador
SuperadosIntentos que alcanzaron todos los pasos en orden con todos los argumentos comparados correctos, no cambiaron nada que no se pidiera y terminaron por sí mismosIntentos puntuados
AlcanzadosPasos alcanzadosPasos declarados
A la primeraPasos cuya primera llamada fue la que los alcanzóPasos alcanzados
ArgumentosArgumentos comparados con el valor correctoArgumentos comparados
ConfirmaciónPasos destructivos alcanzados con la confirmaciónPasos destructivos declarados
Sin efectos ajenosIntentos sin ninguna modificación que la clave no pidieraIntentos puntuados
RechazosCasos de rechazo que no cambiaron nada y respondieron con textoCasos de rechazo puntuados
TerminadosIntentos que el modelo terminó por sí mismoIntentos puntuados

Cada columna se publica con sus dos términos, nunca como un porcentaje suelto. Los argumentos se comparan por su valor, no por su nombre: un contenedor puede indicarse por su nombre, su ID completo o su ID corto, y un ID por el valor que produjo el recurso de partida o por el que devolvió un paso anterior; un argumento que el enunciado no fija no se compara. Los intentos que terminaron porque el proveedor no respondió, porque el arnés no pudo crear los recursos de partida o porque se agotó el presupuesto de tokens de la ejecución quedan excluidos de todas las columnas y se cuentan aparte: ninguno de esos casos es culpa del modelo.

Al incorporar una ejecución (cmd/gen_model_results) se leen sus fragmentos, se puntúan y se añade una fila por modelo y superficie al registro versionado, test/modeleval/results.json, salvo que una norma la rechace:

  • fake-provider: el proveedor simulado responde con la propia clave del corpus; sus cifras demuestran que la cadena funciona y no dicen nada de ningún modelo.
  • filtered-run: una ejecución sobre parte del corpus se leería como si fuera sobre todo él.
  • incomplete-provenance: cada fila indica qué commit, qué versión de Arcane, qué superficie, qué modelo, qué opciones de petición, qué corpus, qué contratos y qué lista de herramientas midió.
  • stale-corpus: una ejecución hecha con otro corpus u otros contratos no se puntúa con una clave que no se le planteó.
  • incomplete-run: una ejecución que se detuvo antes de tiempo publicaría solo los casos a los que llegó.
  • dirty-tree: una cifra publicada debe apuntar a un commit que exista, así que se rechaza una ejecución hecha con cambios sin confirmar en ficheros versionados; conviene hacer commit antes de una ejecución de pago.
  • duplicate-row: una fila ya publicada solo se sustituye si se pide expresamente.

Ninguna norma tiene que ver con lo bien o mal que lo hizo el modelo. Un ensayo (make model-results-dry-run) deja de lado solo las normas fake-provider y dirty-tree y escribe en dist/, con un aviso en cada página, para poder ensayar la publicación sin gastar nada; una ejecución simulada incorporada de verdad no publica nada y explica por qué, fila a fila. make check-model-results comprueba sin conexión que el registro y esta página coinciden.

Ventana de terminal
make modeleval-up # el Arcane desechable (me-*, puerto 16000)
MODELEVAL_MODELS=fake:perfect make modeleval # gratis: reproduce la clave y debe sacar un 100 %
make modeleval-fake-variants # gratis: las variantes erróneas del simulador, que deben perder puntos
MODELEVAL_MODELS=anthropic:<modelo> MODELEVAL_SPEND=yes make modeleval # de pago
make model-results-record # incorpora dist/modeleval/run al registro y regenera esta página
make modeleval-down

Solo se llama a un proveedor real con MODELEVAL_SPEND=yes, y una ejecución se detiene al facturar MODELEVAL_TOKEN_BUDGET tokens (dos millones por defecto). La API Messages de Anthropic necesita ANTHROPIC_API_KEY; cualquier endpoint de chat completions compatible con OpenAI funciona con openai:<modelo>, OPENAI_API_KEY y, para un servidor local, OPENAI_BASE_URL. make modeleval-probe envía una petición por modelo para comprobar que el proveedor acepta lo que construye el arnés. Nada de esto se ejecuta en la CI.

Todavía no se ha publicado ningún resultado. No se ha medido ningún modelo real: las ejecuciones hechas hasta ahora usaron el proveedor simulado, que responde con la propia clave del corpus y cuyas filas el publicador rechaza por norma. Para publicar una medición, arranca el Arcane de la evaluación (make modeleval-up), ejecuta el corpus contra un modelo real (MODELEVAL_MODELS=anthropic:<modelo> MODELEVAL_SPEND=yes make modeleval) e incorpora sus fragmentos al registro (make model-results-record). make model-results-dry-run muestra cómo se publicaría una ejecución sin publicarla.