Evalハーネス
JamJet evalで出力品質をスコアリングし、リグレッションスイートを実行し、CIをゲート制御。
Eval Harness
JamJetには、出力品質の測定と強制のための組み込みeval(評価)システムが含まれています — 簡易的なアドホックチェックから完全なCIリグレッションスイートまで対応。
evalが重要な理由
LLMの出力は確率的です。同じワークフローでも、ほとんどの入力では優れた結果を生み出す一方、エッジケースでは失敗する可能性があります。JamJet evalは以下を提供します:
- LLM-as-judge — 別のモデルがルーブリックに基づいて出力品質をスコアリング
- アサーション — 構造的チェック(長さ、フィールドの存在、フォーマット)
- レイテンシー+コストゲート — CIでSLAを強制
- リグレッションスイート — 本番環境に到達する前にリグレッションを検出
インラインeval(ワークフロー)
ワークフローにevalノードを追加して、出力をスコアリングし、失敗時に再試行します:
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: endon_fail: retry_with_feedbackの場合、スコアラーのフィードバックは自動的に次のモデル呼び出しのプロンプトに注入され、自己改善ループを作成します。
データセットeval(CLI)
バッチ評価の場合は、JSONLデータセットを作成します:
{"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": {}}実行:
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% ✓終了コードは成功時に0、失敗時に1 — CIで直接動作します。
CI統合
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: 評価を実行する前に、CIジョブでJamJet開発ランタイムを起動してください:
jamjet dev &を実行後、sleep 2で初期化を待ちます。
Python評価API
カスタム評価ロジックには、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)
# 全体の合格率を確認
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"カスタムスコアラー
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}'",
)スコアラーの種類
LLMジャッジ
別のモデルを使用して1〜5のルーブリックで出力を評価:
- type: llm_judge
rubric: "Is the answer accurate, complete, and under 200 words?"
min_score: 4 # 1–5 (5 = 完璧)
model: claude-haiku-4-5-20251001ジャッジには入力、出力、ルーブリックが渡され、score(1〜5)とreasonを含むJSONオブジェクトを返します。
アサーション
outputとexpectedに対して評価されるPython式:
- type: assertion
check: "len(output.answer) > 0"
# 複数のアサーション
- type: assertion
check: "'sources' in output"
- type: assertion
check: "output.confidence >= 0.7"レイテンシー
実行が時間制限内に完了したかを確認:
- type: latency
max_ms: 3000コスト
実行がコスト予算内に収まったかを確認:
- type: cost
max_usd: 0.05出力フォーマット
さらなる分析のために結果をファイルに保存:
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
}
]
}