JamJet

Eval Harness

Bewerten Sie Ausgabequalität, führen Sie Regressionstests durch und steuern Sie CI mit JamJet eval.

Eval Harness

JamJet enthält ein integriertes Eval-System zur Messung und Durchsetzung der Output-Qualität — von schnellen Ad-hoc-Checks bis hin zu vollständigen CI-Regressions-Suites.

Warum Eval wichtig ist

LLM-Outputs sind probabilistisch. Derselbe Workflow kann bei den meisten Inputs hervorragende Ergebnisse liefern und bei Edge Cases versagen. JamJet Eval bietet dir:

  • LLM-as-judge — ein separates Modell bewertet die Output-Qualität anhand einer Rubrik
  • Assertions — strukturelle Checks (Länge, Feldpräsenz, Format)
  • Latenz- und Kosten-Gates — SLAs in CI durchsetzen
  • Regressions-Suites — Regressionen abfangen, bevor sie Production erreichen

Inline-Eval (Workflow)

Füge einen eval-Node zu deinem Workflow hinzu, um Outputs zu bewerten und bei Fehler zu wiederholen:

nodes:
  check-quality:
    type: eval
    scorers:
      - type: llm_judge
        rubric: "Is the answer accurate, complete, and under 200 words?"
        min_score: 4          # 1-5 scale
        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

Wenn on_fail: retry_with_feedback, wird das Feedback des Scorers automatisch in den Prompt des nächsten Modell-Aufrufs eingefügt und erzeugt damit eine Self-Improvement-Schleife.

Dataset-Eval (CLI)

Für Batch-Evaluation erstellst du ein JSONL-Dataset:

{"id": "q1", "input": {"query": "What is JamJet?"}, "expected": {"topic": "runtime"}}
{"id": "q2", "input": {"query": "How do I install it?"}, "expected": {"topic": "install"}}
{"id": "q3", "input": {"query": "What models does it support?"}, "expected": {}}

Führe es aus:

jamjet eval run dataset.jsonl \
  --workflow workflow.yaml \
  --rubric "Is the answer accurate and helpful?" \
  --min-score 4 \
  --assert "len(output.answer) >= 50" \
  --latency-ms 3000 \
  --concurrency 10 \
  --fail-below 0.9
Running 50 eval rows... ████████████████████ 50/50

┌─────────┬────────────┬───────┬──────────┬────────────────────┐
│ Row     │ Status     │ Score │ Latency  │ Note               │
├─────────┼────────────┼───────┼──────────┼────────────────────┤
│ q1      │ ✓ passed   │ 4.8   │  512ms   │                    │
│ q2      │ ✓ passed   │ 4.2   │  623ms   │                    │
│ q3      │ ✗ failed   │ 2.1   │  891ms   │ Answer too vague   │
└─────────┴────────────┴───────┴──────────┴────────────────────┘

Results: 49/50 passed (98.0%) — above threshold 90.0% ✓

Exit-Code ist 0 bei Erfolg, 1 bei Fehler — funktioniert direkt in CI.

CI-Integration

Fügen Sie zu Ihrem GitHub Actions Workflow hinzu:

- 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: Starten Sie die JamJet-Dev-Runtime im CI-Job vor der Ausführung der Evals: jamjet dev & dann sleep 2, damit sie initialisieren kann.

Python Eval API

Für benutzerdefinierte Eval-Logik verwenden Sie das Python-Eval-Paket:

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)

# Gesamte Pass-Rate prüfen

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"

Custom Scorer

Schreiben Sie Ihren eigenen Scorer durch Ableitung von 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}'",
        )

Scorer-Typen

LLM Judge

Verwendet ein separates Modell, um die Ausgabe auf einer Skala von 1–5 zu bewerten:

- 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

Der Judge erhält den Input, Output und das Rubric und gibt ein JSON-Objekt mit score (1–5) und reason zurück.

Assertion

Python-Ausdruck, der gegen output und expected ausgewertet wird:

- type: assertion
  check: "len(output.answer) > 0"

# Mehrere Assertions

- type: assertion
  check: "'sources' in output"

- type: assertion
  check: "output.confidence >= 0.7"

Latenz

Prüft, ob die Ausführung innerhalb des Zeitbudgets abgeschlossen wurde:

- type: latency
  max_ms: 3000

Kosten

Prüft, ob die Ausführung innerhalb des Kostenbudgets geblieben ist:

- type: cost
  max_usd: 0.05

Ausgabeformate

Ergebnisse zur weiteren Analyse in einer Datei speichern:

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