Evalúa tus modelos con Promptfoo en la API de Inferencia WEC
No enviarías código sin pruebas — pero la mayoría de los equipos envían características de LLM con "se ve bien para mí." Evaluaciones (evals) solucionan eso: defines casos de prueba y criterios de aprobación/rechazo, luego mides tu modelo de manera objetiva — detectando regresiones, comparando modelos y bloqueando despliegues.
En esta guía construirás un verdadero arnés de evaluación con Promptfoo apuntando a la API de Inferencia WEC — el mismo punto final compatible con OpenAI que llamas desde tus aplicaciones. Todo aquí se ejecutó en vivo contra https://inference.wiline.com; las salidas son reales.
Requisitos previos
-
Una cuenta de WEC (WiLine Edge Cloud) y una clave de API de Inferencia — ver Inferencia → Claves API.
-
Node.js 22+. Promptfoo considera que Node 20 está al final de su vida útil; con
nvm:nvm install 24 # 24 LTS preferidonvm use 24node -v # v24.x
Ejecutaremos Promptfoo con npx — nada que instalar globalmente.
Figura 1. Crea una clave en Inferencia → Claves API, luego cópiala.
Pon tu clave en la terminal:
export WEC_API_KEY='sk-your-key-here'
Si tu primera evaluación falla con Cannot convert argument to a ByteString, consulta Solución de problemas #1 a continuación.
Paso 1 — Ver qué modelos tienes
La API es compatible con OpenAI, por lo que la lista de modelos es un simple GET /v1/models:
curl -s https://inference.wiline.com/v1/models \
-H "Authorization: Bearer $WEC_API_KEY" | jq -r '.data[].id'
zai-org/GLM-5.2
gemma4
Qwen2.5-3B-Instruct
qwen3.5:9B
whisper-large-v3
whisper-medium
whisper-base
Los modelos whisper-* son de voz a texto; el resto son modelos de chat. Evaluaremos los modelos de chat, comenzando con zai-org/GLM-5.2.
Paso 2 — Tu primera evaluación
Crea una configuración. apiBaseUrl apunta Promptfoo a WEC; apiKeyEnvar le dice qué variable de entorno contiene tu clave:
cat > promptfooconfig.yaml <<'EOF'
description: "Evaluación de API de Inferencia WEC"
providers:
- id: openai:chat:zai-org/GLM-5.2
config:
apiBaseUrl: https://inference.wiline.com/v1
apiKeyEnvar: WEC_API_KEY
temperature: 0
prompts:
- "Responde en una sola palabra. ¿Cuál es la capital de {{country}}?"
tests:
- vars: { country: France }
assert: [{ type: icontains, value: Paris }]
- vars: { country: Japan }
assert: [{ type: icontains, value: Tokyo }]
EOF
npx -y promptfoo@latest eval -c promptfooconfig.yaml
Figura 4. Tu primera evaluación aprobada contra la API de Inferencia WEC.
icontains es una afirmación determinista — una verificación de subcadena sin distinción de mayúsculas. Barata, rápida y perfecta para hechos con una respuesta conocida.
Paso 3 — El informe visual
npx -y promptfoo@latest view
Esto abre una interfaz web local con cada aviso, salida y aprobación/rechazo — mucho más fácil de escanear que la terminal una vez que tienes más de unos pocos casos.
Figura 5. promptfoo view — los mismos resultados en el navegador.
Paso 4 — Agregar un segundo modelo
Agregar un proveedor es un bloque extra. Aquí agregamos Qwen2.5-3B-Instruct junto a GLM-5.2 y ejecutamos los mismos dos casos de prueba contra ambos:
cat > promptfooconfig.yaml <<'EOF'
description: "Evaluación de API de Inferencia WEC — comparación de modelos"
providers:
- id: openai:chat:zai-org/GLM-5.2
config:
apiBaseUrl: https://inference.wiline.com/v1
apiKeyEnvar: WEC_API_KEY
temperature: 0
- id: openai:chat:Qwen2.5-3B-Instruct
config:
apiBaseUrl: https://inference.wiline.com/v1
apiKeyEnvar: WEC_API_KEY
temperature: 0
prompts:
- "Responde en una sola palabra. ¿Cuál es la capital de {{country}}?"
tests:
- vars: { country: France }
assert: [{ type: icontains, value: Paris }]
- vars: { country: Japan }
assert: [{ type: icontains, value: Tokyo }]
EOF
npx -y promptfoo@latest eval -c promptfooconfig.yaml
Figura 6. Ambos modelos aprueban. Los resultados de GLM-5.2 se sirven desde caché (0 nuevas solicitudes); solo Qwen2.5 realiza llamadas en vivo.
Paso 5 — Califica calidad, no subcadenas (rúbrica LLM)
Las verificaciones de subcadenas no pueden juzgar una respuesta abierta. Una afirmación llm-rubric calificada por el modelo utiliza un modelo para puntuar la salida contra criterios — y el calificador puede ejecutarse en WEC también, por lo que no necesitas un proveedor externo:
cat > promptfooconfig.yaml <<'EOF'
description: "Evaluación de API de Inferencia WEC — calificación por modelo (rúbrica LLM)"
providers:
- id: openai:chat:zai-org/GLM-5.2
config:
apiBaseUrl: https://inference.wiline.com/v1
apiKeyEnvar: WEC_API_KEY
temperature: 0
defaultTest:
options:
provider: # el modelo calificador
id: openai:chat:zai-org/GLM-5.2
config:
apiBaseUrl: https://inference.wiline.com/v1
apiKeyEnvar: WEC_API_KEY
prompts:
- "Explica qué es un {{topic}} en dos oraciones, para un principiante."
tests:
- vars: { topic: VPC }
assert:
- type: llm-rubric
value: "Describe una VPC como una red virtual privada e aislada en la nube, y es comprensible para un principiante."
EOF
npx -y promptfoo@latest eval -c promptfooconfig.yaml
Figura 7. La ejecución muestra dos líneas de tokens — Eval (75) y Grading (350) — porque la verificación de rúbrica es en sí misma una llamada de modelo.
Las verificaciones calificadas por el modelo son poderosas, pero no son gratuitas.
Paso 6 — Líneas de defensa: latencia
Una respuesta correcta que tarda 30 segundos sigue siendo un problema de producción. Agrega un límite de latencia a defaultTest (se aplica a cada caso). Siempre ejecuta con --no-cache — las respuestas en caché informan ~0 ms y pasarían la puerta gratis:
cat > promptfooconfig.yaml <<'EOF'
description: "Evaluación de API de Inferencia WEC — límite de latencia"
providers:
- id: openai:chat:zai-org/GLM-5.2
config:
apiBaseUrl: https://inference.wiline.com/v1
apiKeyEnvar: WEC_API_KEY
temperature: 0
defaultTest:
assert:
- type: latency
threshold: 5000 # fallar cualquier cosa más lenta que 5 s
prompts:
- "Responde en una sola palabra. ¿Cuál es la capital de {{country}}?"
tests:
- vars: { country: France }
assert: [{ type: icontains, value: Paris }]
- vars: { country: Japan }
assert: [{ type: icontains, value: Tokyo }]
EOF
npx -y promptfoo@latest eval -c promptfooconfig.yaml --no-cache
Esto falló — las respuestas eran correctas (Paris, Tokyo) pero ambas llamadas tomaron ~33 s, activando la puerta de 5 s:
Figura 8. La línea de defensa se activa: respuestas correctas, latencia incorrecta. Duración: 33s.
Aumenta el umbral a algo realista y vuelve a ejecutar:
sed -i 's/threshold: 5000/threshold: 60000/' promptfooconfig.yaml
npx -y promptfoo@latest eval -c promptfooconfig.yaml --no-cache
Figura 9. Con un umbral de 60 s, las mismas llamadas pasan fácilmente — Duración: 2s.
Las llamadas consecutivas al mismo modelo variaron de ~2 s a ~33 s en el mismo día. Ejecuta algunos pases --no-cache para obtener una línea base realista antes de fijar tu umbral.
Paso 7 — Líneas de defensa: latencia + límite de tokens
La afirmación de cost no funciona contra WEC (ver Solución de problemas #2). Usa latencia + max_tokens juntos como tu línea de defensa de producción en su lugar:
cat > promptfooconfig.yaml <<'EOF'
description: "Evaluación de API de Inferencia WEC — líneas de defensa (latencia + límite de tokens)"
providers:
- id: openai:chat:zai-org/GLM-5.2
config:
apiBaseUrl: https://inference.wiline.com/v1
apiKeyEnvar: WEC_API_KEY
temperature: 0
max_tokens: 16
defaultTest:
assert:
- type: latency
threshold: 30000
prompts:
- "Responde en una sola palabra. ¿Cuál es la capital de {{country}}?"
tests:
- vars: { country: France }
assert: [{ type: icontains, value: Paris }]
- vars: { country: Japan }
assert: [{ type: icontains, value: Tokyo }]
EOF
npx -y promptfoo@latest eval -c promptfooconfig.yaml --no-cache
Figura 11. Con un umbral de 30 s y un límite de 16 tokens, ambos casos aprueban.
Paso 8 — Validar salida estructurada (validación de esquema JSON)
Las aplicaciones reales quieren JSON, no prosa. is-json verifica que la salida sea un JSON válido y coincida con un esquema en una afirmación:
cat > promptfooconfig.yaml <<'EOF'
description: "Evaluación de API de Inferencia WEC — validación de esquema JSON"
providers:
- id: openai:chat:zai-org/GLM-5.2
config:
apiBaseUrl: https://inference.wiline.com/v1
apiKeyEnvar: WEC_API_KEY
temperature: 0
prompts:
- 'Devuelve SOLAMENTE un objeto JSON (sin markdown, sin prosa) con claves "capital" (cadena) y "population_millions" (número) para el país {{country}}.'
defaultTest:
assert:
- type: is-json
value:
type: object
required: [capital, population_millions]
properties:
capital: { type: string }
population_millions: { type: number }
tests:
- vars: { country: France }
assert: [{ type: icontains, value: Paris }]
- vars: { country: Japan }
assert: [{ type: icontains, value: Tokyo }]
EOF
npx -y promptfoo@latest eval -c promptfooconfig.yaml
Figura 12. GLM-5.2 devuelve JSON compacto y válido según el esquema en el primer intento.
Paso 9 — La recompensa: compara todos los modelos WEC
Aquí está lo que no puedes obtener en ningún otro lugar — la misma prueba estricta de JSON a través de todos los modelos de chat en tu cuenta:
cat > promptfooconfig.yaml <<'EOF'
description: "Evaluación de API de Inferencia WEC — matriz de modelos"
providers:
- id: openai:chat:zai-org/GLM-5.2
config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }
- id: openai:chat:gemma4
config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }
- id: openai:chat:Qwen2.5-3B-Instruct
config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }
- id: openai:chat:qwen3.5:9B
config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }
prompts:
- 'Devuelve SOLAMENTE un objeto JSON (sin markdown, sin prosa) con claves "capital" (cadena) y "population_millions" (número) para el país {{country}}.'
defaultTest:
assert:
- type: is-json
value:
type: object
required: [capital, population_millions]
properties:
capital: { type: string }
population_millions: { type: number }
tests:
- vars: { country: France }
assert: [{ type: icontains, value: Paris }]
- vars: { country: Japan }
assert: [{ type: icontains, value: Tokyo }]
- vars: { country: Brazil }
assert: [{ type: icontains, value: Bras }]
EOF
npx -y promptfoo@latest eval -c promptfooconfig.yaml
Figura 13a. 12 casos de prueba a través de 4 modelos — 9 aprobados, 3 fallidos. qwen3.5:9B emite su cadena de pensamiento en lugar de JSON.
Figura 13b. El desglose de tokens cuenta la verdadera historia: el modelo de razonamiento costó 13× más para la misma tarea.
| Modelo | ¿JSON Estricto? | Notas | Tokens (3 llamadas) |
|---|---|---|---|
zai-org/GLM-5.2 | ✅ todos aprueban | JSON compacto | 177 |
gemma4 | ✅ todos aprueban | poblaciones enteras | 207 |
Qwen2.5-3B-Instruct | ✅ todos aprueban | JSON bien formateado, preciso | 262 |
qwen3.5:9B | ❌ todos fallan | modelo de razonamiento — emite su "pensamiento" en lugar de JSON crudo | 2,650 |
Dos lecciones se desprenden de inmediato:
qwen3.5:9Bes un modelo de razonamiento. "Piensa en voz alta", por lo que rompe el JSON estricto a menos que elimines el razonamiento o uses una restricción de formato de respuesta. Elegirlo para salida estructurada fallaría silenciosamente.- El costo varía ~13×. El modelo de razonamiento consumió 2,650 tokens para la misma tarea que los demás hicieron en ~200. Para un trabajo de extracción de JSON, los modelos más pequeños son correctos y mucho más baratos.
Esa es una decisión de selección de modelo que ahora puedes defender con datos — para tus cargas de trabajo, no una tabla de clasificación genérica.
Paso 10 — Hazlo una puerta
Promptfoo sale no cero (100) cuando cualquier prueba falla. Verifica esto primero:
npx -y promptfoo@latest eval -c promptfooconfig.yaml > /dev/null 2>&1; echo "código de salida: $?"
Figura 14. El código de salida 100 confirma que Promptfoo señala fallos de manera confiable — exactamente lo que necesita CI.
Conéctalo como una puerta:
npx -y promptfoo@latest eval -c promptfooconfig.yaml \
&& echo "✅ evaluación aprobada" \
|| { echo "❌ evaluación fallida — bloqueando"; exit 1; }
Figura 15a. La puerta vuelve a ejecutar la evaluación de matriz desde caché — mismo resultado 9/3.
Figura 15b. Promptfoo sale no cero, se activa la rama ||, y la puerta bloquea.
Nunca ejecutes la variante exit 1 en tu terminal interactiva — consulta Solución de problemas #3.
En CI, ni siquiera necesitas el exit 1 — la salida no cero falla el trabajo. En GitLab:
eval:
image: node:22
script:
- npx -y promptfoo@latest eval -c promptfooconfig.yaml
# WEC_API_KEY se establece como una variable CI/CD enmascarada — nunca la comités.
Ahora un cambio de aviso que silenciosamente rompe la salida JSON, o un cambio de modelo que duplica la latencia, falla la tubería antes de que llegue a los usuarios.
Solución de problemas (errores reales)
1. Error de ByteString en la primera evaluación
Error de llamada a la API: TypeError: Cannot convert argument to a ByteString because
the character at index 32 has a value of 8232 which is greater than 255.
Figura 2. Cada llamada a la API falla antes de llegar al servidor — la clave ni siquiera sale de la máquina.
Causa: La clave pegada contiene un carácter Unicode invisible — U+2028 (separador de línea). Algunos terminales y administradores de portapapeles lo insertan silenciosamente al copiar desde ciertas fuentes.
Solución: Elimina los caracteres no imprimibles de la clave:
export WEC_API_KEY=$(printf '%s' "$WEC_API_KEY" | LC_ALL=C tr -cd '[:print:]')
printf 'len=%s\n' "$(printf %s "$WEC_API_KEY" | wc -c)"
# len=25
Figura 3. Después de eliminar, la longitud de la clave confirma que no quedan caracteres extra. Vuelve a ejecutar la evaluación — pasa.
2. Errores de afirmación de costo inmediatamente
Error: La afirmación de costo no admite proveedores que no devuelven costo
Figura 10. La API de Inferencia WEC no devuelve un campo de costo en sus respuestas.
Causa: Promptfoo calcula el costo buscando un precio por token para el modelo en su lista de precios incorporada y multiplicando por los tokens utilizados. Tiene precios para los grandes modelos alojados, pero no para los nombres de modelos autoalojados de WEC, por lo que no tiene un precio que aplicar y la afirmación falla. (La mayoría de las API, incluida OpenAI, no devuelven un costo en dólares en la respuesta — las herramientas lo calculan).
Solución: Omite la afirmación de cost por completo. Realiza un seguimiento del gasto desde la línea Total Tokens que Promptfoo imprime al final de cada ejecución, y usa max_tokens en la configuración del proveedor para limitar el uso de tokens por llamada.
3. exit 1 cierra tu sesión SSH
❌ evaluación fallida — bloqueando
logout
Conexión a 10.80.4.212 cerrada.
Causa: Ejecutar || { echo "❌ evaluación fallida — bloqueando"; exit 1; } directamente en una terminal interactiva cierra el proceso de la terminal en sí — lo que, a través de SSH, cierra la conexión.
Solución: Coloca la puerta en un contexto no interactivo: un objetivo de Makefile, un script de npm, un gancho pre-commit de git, o un trabajo de CI. En esos entornos, exit 1 falla el trabajo sin afectar tu terminal. Si necesitas probar la puerta localmente, usa || echo "❌ evaluación fallida" (sin exit 1) para ver la salida de manera segura.
¿Qué sigue?
Has pasado de "se ve bien para mí" a un verdadero arnés de evaluación automatizable en WEC — con verificaciones deterministas, líneas de defensa, validación de esquemas, rúbricas calificadas por modelos, una matriz de modelos y una puerta de CI.
Próximamente en esta serie:
- JSON confiable: valida el esquema de la salida estructurada de tu modelo — ahora disponible (parte 2): recupera la salida envuelta en cercas y razonamiento con transformaciones, compara modelos por confiabilidad de JSON, y bloquea todo en CI.
- Evaluaciones calificadas por modelo bien hechas — calibrando
llm-rubric, puntuación de factualidad y verificaciones de múltiples jueces para que tu calificador sea confiable. - Capturando alucinaciones — emparejando estas técnicas con la evaluación de alucinaciones RAG de WEC.
- Evaluando audio — puntuando la precisión de la transcripción de Whisper en la API de Inferencia WEC.
