JamJet

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

on_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.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% ✓

終了コードは成功時に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:7700

tip: 評価を実行する前に、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オブジェクトを返します。

アサーション

outputexpectedに対して評価される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
    }
  ]
}

On this page