Saltar al contenido principal

Evalúa tus modelos con Promptfoo en la API de Inferencia WEC

· 15 min de lectura
Rafael Fernandes
Ingeniero de PLN y Redactor Técnico en WiLine
Share:
Promptfoo+

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 preferido
    nvm use 24
    node -v # v24.x

Ejecutaremos Promptfoo con npx — nada que instalar globalmente.

La página de claves API del portal WiLine 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'
tip

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'
Salida
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

Terminal: primera evaluación, 2 casos ambos APROBADOS 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.

El informe web de Promptfoo 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

Terminal: comparación de 2 modelos, 4 casos todos APROBADOS 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

Terminal: evaluación de rúbrica LLM, 1 APROBADO, línea de tokens de calificación visible 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:

Terminal: umbral de latencia 5000ms, 2 FALLIDOS, Duración: 33s 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

Terminal: umbral de latencia 60000ms, 2 APROBADOS, Duración: 2s Figura 9. Con un umbral de 60 s, las mismas llamadas pasan fácilmente — Duración: 2s.

La latencia de inferencia varía — calibra antes de bloquear

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

Terminal: latencia 30000ms + max_tokens:16, 2 APROBADOS 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

Terminal: evaluación de esquema JSON, Francia y Japón ambos APROBADOS con salida JSON limpia 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

Terminal: tabla de resultados de matriz de 4 modelos — GLM, gemma4, Qwen2.5 todos APROBADOS; qwen3.5:9B todos FALLIDOS con texto de razonamiento 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.

Terminal: resumen de tokens de matriz de modelos — qwen3.5:9B consumió 2,650 tokens frente a ~200 para los demás 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?NotasTokens (3 llamadas)
zai-org/GLM-5.2✅ todos apruebanJSON compacto177
gemma4✅ todos apruebanpoblaciones enteras207
Qwen2.5-3B-Instruct✅ todos apruebanJSON bien formateado, preciso262
qwen3.5:9B❌ todos fallanmodelo de razonamiento — emite su "pensamiento" en lugar de JSON crudo2,650

Dos lecciones se desprenden de inmediato:

  • qwen3.5:9B es 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: $?"

Terminal: código de salida: 100 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; }

Terminal: tabla de evaluación de puerta de CI — mismos resultados de 4 modelos, 9 aprobados / 3 fallidos Figura 15a. La puerta vuelve a ejecutar la evaluación de matriz desde caché — mismo resultado 9/3.

Terminal: ❌ evaluación fallida — bloqueando Figura 15b. Promptfoo sale no cero, se activa la rama ||, y la puerta bloquea.

tip

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

Salida
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.

Terminal: error de ByteString, ambos casos ERROR 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

Terminal: comando de eliminación de clave + confirmación de 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

Salida
Error: La afirmación de costo no admite proveedores que no devuelven costo

Terminal: afirmación de costo, 2 errores 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

Salida
❌ 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 modeloahora 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.