Eval Harness
Evalúa la calidad de salida, ejecuta suites de regresión y controla el CI con JamJet eval.
Eval Harness
JamJet incluye un sistema de evaluación integrado para medir y garantizar la calidad de las salidas — desde verificaciones rápidas ad-hoc hasta suites completas de regresión en CI.
Por qué importa la evaluación
Las salidas de los LLM son probabilísticas. El mismo flujo de trabajo puede producir resultados excelentes en la mayoría de entradas y fallar en casos límite. La evaluación de JamJet te ofrece:
- LLM como juez — un modelo separado califica la calidad de la salida según una rúbrica
- Aserciones — verificaciones estructurales (longitud, presencia de campos, formato)
- Límites de latencia + costo — garantiza SLAs en CI
- Suites de regresión — detecta regresiones antes de que lleguen a producción
Eval inline (workflow)
Añade un nodo eval a tu workflow para calificar salidas y reintentar en caso de fallo:
nodes:
check-quality:
type: eval
scorers:
- type: llm_judge
rubric: "¿La respuesta es precisa, completa y tiene menos de 200 palabras?"
min_score: 4 # escala 1-5
model: claude-haiku-4-5-20251001
- type: assertion
check: "len(output.answer) > 0"
- type: latency
max_ms: 5000
on_fail: retry_with_feedback
max_retries: 2
next: endCuando on_fail: retry_with_feedback, el feedback del evaluador se inyecta automáticamente en el prompt de la siguiente llamada al modelo, creando un bucle de auto-mejora.
Eval de dataset (CLI)
Para evaluación por lotes, crea un dataset JSONL:
{"id": "q1", "input": {"query": "¿Qué es JamJet?"}, "expected": {"topic": "runtime"}}
{"id": "q2", "input": {"query": "¿Cómo lo instalo?"}, "expected": {"topic": "install"}}
{"id": "q3", "input": {"query": "¿Qué modelos soporta?"}, "expected": {}}Ejecútalo:
jamjet eval run dataset.jsonl \
--workflow workflow.yaml \
--rubric "¿La respuesta es precisa y útil?" \
--min-score 4 \
--assert "len(output.answer) >= 50" \
--latency-ms 3000 \
--concurrency 10 \
--fail-below 0.9Ejecutando 50 filas de eval... ████████████████████ 50/50
┌─────────┬────────────┬───────┬──────────┬────────────────────┐
│ Fila │ Estado │ Score │ Latencia │ Nota │
├─────────┼────────────┼───────┼──────────┼────────────────────┤
│ q1 │ ✓ pasado │ 4.8 │ 512ms │ │
│ q2 │ ✓ pasado │ 4.2 │ 623ms │ │
│ q3 │ ✗ fallado │ 2.1 │ 891ms │ Respuesta muy vaga │
└─────────┴────────────┴───────┴──────────┴────────────────────┘
Resultados: 49/50 pasaron (98.0%) — por encima del umbral 90.0% ✓El código de salida es 0 si pasa, 1 si falla — funciona directamente en CI.
Integración con CI
Añade a tu flujo de trabajo de GitHub Actions:
- name: Run eval suite
run: |
jamjet eval run evals/core.jsonl \
--workflow workflow.yaml \
--rubric "Is the answer accurate and complete?" \
--fail-below 0.85
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
JAMJET_URL: http://localhost:7700tip: Inicia el runtime de desarrollo de JamJet en el trabajo de CI antes de ejecutar las evaluaciones:
jamjet dev &y luegosleep 2para que se inicialice.
API de evaluación en Python
Para lógica de evaluación personalizada, usa el paquete de evaluación de Python:
import asyncio
from jamjet.eval import EvalDataset, EvalRunner
from jamjet.eval.scorers import LlmJudgeScorer, AssertionScorer, LatencyScorer
dataset = EvalDataset.from_file("evals/core.jsonl")
runner = EvalRunner(
workflow_path="workflow.yaml",
runtime_url="http://localhost:7700",
scorers=[
LlmJudgeScorer(
rubric="Is the answer accurate and helpful?",
model="claude-haiku-4-5-20251001",
min_score=4,
),
AssertionScorer(check="len(output['answer']) >= 50"),
LatencyScorer(max_ms=3000),
],
concurrency=10,
)
results = asyncio.run(runner.run(dataset))
runner.print_summary(results)
# Verifica la tasa de aprobación general
pass_rate = sum(1 for r in results if r.passed) / len(results)
assert pass_rate >= 0.9, f"Eval failed: {pass_rate:.0%} pass rate"Evaluadores personalizados
Escribe tu propio evaluador heredando de BaseScorer:
from jamjet.eval.scorers import BaseScorer, ScorerResult
class ExactMatchScorer(BaseScorer):
async def score(
self,
output: dict,
*,
expected: dict,
duration_ms: float,
cost_usd: float,
input_data: dict,
) -> ScorerResult:
answer = output.get("answer", "")
expected_answer = expected.get("answer", "")
passed = answer.strip().lower() == expected_answer.strip().lower()
return ScorerResult(
scorer="exact_match",
passed=passed,
score=1.0 if passed else 0.0,
message=None if passed else f"Expected '{expected_answer}', got '{answer}'",
)Tipos de evaluadores
Juez LLM
Usa un modelo separado para calificar la salida en una rúbrica del 1 al 5:
- type: llm_judge
rubric: "Is the answer accurate, complete, and under 200 words?"
min_score: 4 # 1–5 (5 = perfect)
model: claude-haiku-4-5-20251001El juez recibe la entrada, la salida y la rúbrica, y devuelve un objeto JSON con score (1–5) y reason.
Aserción
Expresión Python evaluada contra output y expected:
- type: assertion
check: "len(output.answer) > 0"
# Múltiples aserciones
- type: assertion
check: "'sources' in output"
- type: assertion
check: "output.confidence >= 0.7"Latencia
Verifica que la ejecución se complete dentro de un presupuesto de tiempo:
- type: latency
max_ms: 3000Costo
Verifica que la ejecución se mantenga dentro de un presupuesto de costo:
- type: cost
max_usd: 0.05Formatos de salida
Guarda los resultados en un archivo para análisis posterior:
jamjet eval run dataset.jsonl \
--workflow workflow.yaml \
--output results.json{
"summary": {
"total": 50,
"passed": 47,
"failed": 3,
"pass_rate": 0.94,
"avg_latency_ms": 612,
"avg_cost_usd": 0.0003
},
"rows": [
{
"id": "q1",
"passed": true,
"scorers": [
{ "scorer": "llm_judge", "passed": true, "score": 4.8 }
],
"duration_ms": 512,
"cost_usd": 0.00023
}
]
}