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: endWenn 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.9Running 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:7700tip: Starten Sie die JamJet-Dev-Runtime im CI-Job vor der Ausführung der Evals:
jamjet dev &dannsleep 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-20251001Der 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: 3000Kosten
Prüft, ob die Ausführung innerhalb des Kostenbudgets geblieben ist:
- type: cost
max_usd: 0.05Ausgabeformate
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
}
]
}