Integración con CI
Dispara evaluaciones desde tu pipeline y usa el resultado como gate de despliegue.
Verica se integra con CI al revés que la mayoría de las herramientas de evals. El eval vive en Verica — el golden set, los criterios, los jueces, la condición de aprobación. El repositorio solo tiene el prompt. CI no define la evaluación: la dispara por ID.
La consecuencia práctica es que el único secreto que tu pipeline necesita es el token de Verica. No hay que inyectar OPENAI_API_KEY en el runner.
Crear un token
En Configuración del proyecto → API Keys: "Tokens para disparar evals de este proyecto desde tu CI/CD. El token se muestra una sola vez al crearlo."
El diálogo pide un Nombre (por ejemplo ci-github) y los Permisos:
| Permiso | Para qué |
|---|---|
| Ejecutar (run) | Disparar ejecuciones de un eval. |
| Leer (read) | Consultar el estado y los resultados de una ejecución. |
| Ingesta de trazas (ingest) | Enviar trazas desde tu aplicación. |
Para CI hacen falta run y read, que vienen marcados por defecto.
El token tiene la forma vk_live_… y se muestra una sola vez: "Guárdalo ahora: no se vuelve a mostrar." Verica guarda solo su hash y un prefijo visible, así que la lista te deja identificarlo sin poder recuperarlo.
Guárdalo en tu CI como VERICA_TOKEN. Puedes revocarlo cuando quieras — "El token dejará de funcionar de inmediato en tu CI/CD."
403. La razón es el radio de daño — una ejecución gasta la credencial BYOK de ese proyecto.El CLI
npx @verica-app/cli run --eval eval_8x2k9d --prompt prompts/agente.txt --junit report.xmlEl paquete es @verica-app/cli, MIT, requiere Node 20 o superior y expone un único comando: run.
Variables de entorno
VERICA_TOKEN— obligatoria.VERICA_BASE_URL— solo para instalaciones propias o desarrollo. Por defectohttps://my.verica.app.
Opciones
Qué ejecutar
| Opción | Qué hace |
|---|---|
--eval <id> | El ID público del eval (eval_…). Obligatorio, salvo que uses --manifest. |
--manifest <archivo> | Un .verica.yml con varios evals. |
--prompt <archivo> | El archivo del prompt de usuario. |
--system-prompt <archivo> | El prompt de sistema, independiente y opcional. |
--tools <archivo> | Definiciones de herramientas en JSON: formato plano de Verica o formato de OpenAI. |
--model <modelo> | Opcional. Si lo omites, se usa el modelo de la última ejecución del eval. |
--sampling <archivo> | Parámetros de sampling en JSON. |
Resultado y gate
| Opción | Qué hace |
|---|---|
--junit <archivo> | Escribe un reporte JUnit XML. |
--junit-mode <modo> | rows (por defecto) o gate. |
--json | Salida legible por máquina en stdout. |
--threshold <0..1> | Sobreescribe el pass rate mínimo del gate. |
--baseline-ref <ref> | Compara contra la última ejecución de esa rama. |
--baseline-run <id> | Compara contra una ejecución concreta (run_…). |
--no-wait | Dispara y sale con código 0, sin esperar ni aplicar el gate. |
Reutilización
| Opción | Qué hace |
|---|---|
--reuse-if-unchanged | Reutiliza una ejecución reciente si nada cambió. |
--reuse-max-age <horas> | Antigüedad máxima aceptable. Por defecto 24, máximo 720. |
--reuse-same-ref | Solo reutiliza ejecuciones de la misma rama. |
Procedencia y transporte
--git-sha, --git-ref y --git-repo-url se deducen solos de las variables de GitHub Actions o GitLab; sirven para que la ejecución enlace al commit y a la rama. --poll-interval (3 s), --timeout (1800 s) y --base-url completan la lista.
--wait sigue aceptándose pero ya no hace nada: esperar es el comportamiento por defecto. Todos los ejemplos históricos lo incluyen; es inofensivo.Códigos de salida
| Código | Significa |
|---|---|
0 | Pasó. |
1 | El gate falló. |
2 | Error de validación o de transporte. |
Con varios evals, el CLI devuelve el peor de todos.
Una ejecución sin condición de aprobación definida sale con 0 y un aviso por stderr: "no pass condition defined; gating disabled (exit 0)". Si quieres que CI bloquee, define el gate.
Estos tres códigos son estables. El resto de la superficie del CLI — flags, formato JSON, salida JUnit — todavía se está asentando: el paquete es 0.x y ahí el que rompe es el minor. Conviene fijar "@verica-app/cli": "~0.1".
La condición de aprobación
El gate se configura en la interfaz, en la pestaña Configuración del eval, no en YAML. "Define cuándo una ejecución por CI 'pasa' y desbloquea el merge. Vacía → las ejecuciones siempre pasan."
Son tres dimensiones que se combinan con Y:
Pass rate mínimo
Un piso absoluto: pasa si el pass rate ≥ X. Al lado del campo, Verica muestra el pass rate de la última ejecución y el promedio de las últimas cinco, para que el número no se elija a ciegas.
--threshold lo sobreescribe desde el CLI.
No-regresión
Bloquear si baja respecto de una referencia: la última ejecución en la rama base, una ejecución fija o la última que pasó. Con una tolerancia en puntos porcentuales.
Confiabilidad
Cuántas filas pueden quedar rotas: exigir ejecución completa (0 errores) o permitir hasta un porcentaje.
"Un 'error' es una fila que no se pudo ejecutar o evaluar, no una fila que se ejecutó y no cumplió el criterio. Para eso usa 'Pass rate mínimo'."
El panel resume la condición en una frase: "Pasa si: pass rate ≥ 90% y no baja más de 2 pts vs. la última ejecución en la rama base y 0 errores de ejecución."
El veredicto se calcula en el servidor cuando la ejecución termina y queda congelado con ella. El CLI no lo recalcula: lo lee.
--reuse-if-unchanged no se puede combinar con --threshold, --baseline-ref ni --baseline-run. El CLI lo rechaza antes de salir a la red.Formatos de salida
JUnit
--junit-mode rows (por defecto) emite un caso de prueba por caso del golden set: los que no pasaron son failure, los N/A son skipped.
--junit-mode gate emite un caso por dimensión del gate, con mensajes como pass rate 94.0% vs. min 90.0%.
failure en el XML: un eval al 92% tiene un 8% de casos fallando por diseño. El modo gate existe para los CI que fallan ante cualquier <failure> en un reporte JUnit.JSON
--json escribe un array en stdout, un elemento por eval, con runId, resultUrl, status, passed, gated, totals, gateResult, model y judge. Es un contrato público, congelado por tests.
El resumen legible va a stderr, así que stdout queda limpio para canalizarlo:
→ 50 casos · judge=rubric+llm · model=gpt-4.1-mini
✓ 47 passed ✗ 3 failed score 0.91Ejemplos por plataforma
on: { pull_request: { paths: ['prompts/**'] } }
jobs:
eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: verica-app/run-action@v1
with:
token: ${{ secrets.VERICA_TOKEN }}
manifest: .verica.ymlCon un solo prompt, sin manifiesto:
- uses: verica-app/run-action@v1
with:
token: ${{ secrets.VERICA_TOKEN }}
eval: eval_8x2k9d
prompt: prompts/support-agent.txt
model: gpt-4.1-mini
baseline-ref: mainLa acción envuelve al CLI, deja el reporte JUnit (verica-results.xml por defecto) y publica un comentario en el pull request con una tabla de eval, gate y pass rate, actualizándolo en cada push. El comentario es lo único específico de GitHub; puedes desactivarlo con comment: false.
verica:
image: node:22
rules: [{ changes: ['prompts/**'] }]
script:
- npx @verica-app/cli run --manifest .verica.yml --junit report.xml
artifacts:
when: always
reports:
junit: report.xmlGuarda VERICA_TOKEN como variable enmascarada de CI/CD. GitLab renderiza el reporte JUnit dentro del merge request.
jobs:
verica:
docker: [{ image: cimg/node:22.0 }]
steps:
- checkout
- run: npx @verica-app/cli run --manifest .verica.yml --junit report.xml
- store_test_results: { path: report.xml }Guarda VERICA_TOKEN como variable de entorno del proyecto.
pipeline {
agent { docker { image 'node:22' } }
environment {
VERICA_TOKEN = credentials('verica-token')
}
stages {
stage('eval') {
steps { sh 'npx @verica-app/cli run --manifest .verica.yml --junit report.xml' }
}
}
post { always { junit 'report.xml' } }
}El prompt: cómo viaja y cómo se versiona
El CLI lee los archivos y manda su contenido en el cuerpo de la petición. Los tres campos son independientes y opcionales, y lo que no mandes se hereda de la versión actual: pasar solo --system-prompt versiona el prompt de sistema y deja intactos los mensajes y las herramientas.
Del lado del servidor:
- Se combinan los campos enviados sobre la versión actual.
- Se compara el contenido resultante. Si es idéntico, se reutiliza la versión; si no, se crea una nueva.
- El modelo y los parámetros de sampling no entran en esa comparación — viajan en la ejecución. Cambiar solo el modelo reutiliza la versión del prompt y crea una ejecución nueva.
- Siempre se encola una ejecución. El deduplicado decide si nace una versión, nunca si se ejecuta.
La respuesta trae el número de versión y si fue nueva, y el CLI lo imprime: prompt v7, new version.
La procedencia (commit, rama, repositorio, que vino de CI) se estampa en la ejecución, no en la versión. Por eso un cambio de modelo, que reutiliza la versión, igual deja registrado su commit.
Validaciones antes de ejecutar
Verica responde 400 con un código estable, antes de gastar un token, si:
- El prompt usa una variable
{{ }}que el golden set no tiene. - El modelo no está en la tabla de precios (en CI esto sí es estricto, porque de ahí se deduce el proveedor).
- El workspace no tiene una credencial BYOK de ese proveedor en el proyecto del eval.
Prompt gestionado desde el repo
Desde Conectar con CI, en la página del eval, puedes marcar el prompt como repo-managed indicando su ruta. A partir de ahí git es la fuente de verdad y la interfaz lo muestra en solo lectura: "Repo-managed · {ruta}".
Ese mismo panel te da el snippet ya armado, con el ID del eval y la URL base.
Varios prompts: .verica.yml
evals:
- id: eval_8x2k9d
prompt: prompts/support-agent.txt
systemPrompt: prompts/support-system.txt
model: gpt-4.1-mini
- id: eval_3n7q1p
prompt: prompts/router.txt
tools: prompts/router-tools.jsonSolo id es obligatorio en cada entrada. tools acepta una ruta o un array en línea. --model desde la línea de comandos sobreescribe el de todas las entradas.
Reutilizar ejecuciones
--reuse-if-unchanged evita pagar dos veces por lo mismo. Verica considera que nada cambió cuando coinciden la versión del prompt, el modelo, los parámetros de sampling, el snapshot del dataset y los graders. El gate no forma parte de esa comparación.
Solo se reutilizan ejecuciones completadas, nunca parciales ni fallidas, y por defecto de las últimas 24 horas (máximo 720). No existe un "para siempre" a propósito: la reutilización no puede ver la deriva del proveedor detrás de un identificador de modelo estable.
Cuando hay reutilización, la API responde 200 en lugar de 202, el CLI marca reused: true, y el evento aparece en el historial del eval con la insignia reutilizada. Sin eso, una reutilización sería invisible.
La API
Si prefieres no usar el CLI, son tres endpoints:
| Endpoint | Permiso | Qué hace |
|---|---|---|
POST /api/v1/evals/:id/runs | run | Dispara una ejecución. 202 nueva, 200 reutilizada. |
GET /api/v1/runs/:id | read | Estado, veredicto, totales y gate. |
GET /api/v1/runs/:id/results | read | El detalle por caso. |
La autenticación es Authorization: Bearer <token>. Los errores son 401 missing_token, 401 invalid_token, 403 insufficient_scope y 403 project_forbidden.
GET /runs/:id/results devuelve también la configuración congelada de los criterios que calificaron — pero solo la de los criterios determinísticos. Las configuraciones de los jueces llevan consignas y no se exponen.
Siguientes pasos
- El Eval — cómo se versiona el prompt.
- Ejecuciones y comparación — cómo se leen los totales.