JamJet

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: end

Cuando 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.9
Ejecutando 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:7700

tip: Inicia el runtime de desarrollo de JamJet en el trabajo de CI antes de ejecutar las evaluaciones: jamjet dev & y luego sleep 2 para 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-20251001

El 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: 3000

Costo

Verifica que la ejecución se mantenga dentro de un presupuesto de costo:

- type: cost
  max_usd: 0.05

Formatos 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
    }
  ]
}

On this page