AI 評価ツール連載の 1 本として、本稿は Evalite を取り上げます。
Evalite は Vitest 上に乗った TypeScript ネイティブの LLM eval フレームワークです。.test.ts と同じ感覚で .eval.ts を書き、ローカルで完結する開発用の Web UI で結果を見ます。SaaS アカウントは不要です。
評価サーベイで整理された 4 分類(Behavior / Capabilities / Reliability / Safety)で言えば、Evalite が得意なのは Behavior と Reliability です。pytest ベースの DeepEval、YAML ベースの Promptfoo に対し、TypeScript プロジェクト側から見た「ユニットテストと同じ構文で eval を書く」という立ち位置を狙っています。
作者とライセンス
Evalite は Matt Pocock 氏が立ち上げた OSS です。TypeScript 教材 Total TypeScript でも知られる人物で、2024 年末から開発が始まりました。ライセンスは MIT、GitHub のスター数は 2026 年春時点で 1.7k 前後です。
公式サイトのキャッチコピーは “the TypeScript-native, local-first tool for testing LLM-powered apps” で、設計思想がそのまま一行に凝縮されています。
なぜ TypeScript 専用に出てきたか
既存の LLM eval ツールは、長らく Python 中心でした。DeepEval は pytest、OpenAI Evals は Python registry、Ragas も Python ライブラリです。
一方で、本番の LLM アプリは Vercel AI SDK や LangChain.js など TypeScript で書かれていることが増えています。評価だけ Python に移すのは、型とサンプルコードが二重管理になり、CI も二系統に分かれて辛くなります。
Evalite の売りは、この分断を埋めるところにあります。アプリ本体と同じ tsconfig.json、同じ型、同じ Vitest ランナーで評価を回せるため、プロンプトやモデル呼び出しをそのまま task に差し込めます。
主な機能
主要な機能を列挙します。
evalite()runner —data/task/scorersの 3 点セットで評価を宣言createScorer()— TypeScript で型安全にカスタム採点器を書く API- autoevals 連携 —
Factuality/Levenshtein/NumericDifferenceなどをそのままインポート - Web UI —
localhostで開く開発サーバで、入力・出力・スコア・trace を比較表示 - Vitest 統合 —
.eval.tsを watch モードで走らせ、保存時に即再評価 - CI 向け静的 HTML 出力 — スコア閾値で pass/fail を切り分け可能
- プロバイダ非依存 — OpenAI、Anthropic、Vercel AI SDK、ローカル推論など、何を呼んでも
taskに書けば動く
最小動作例
インストールは yarn add -D evalite vitest autoevals の 1 行です。ファイルは src/translate.eval.ts のように .eval.ts 拡張子で置きます。
import { evalite } from "evalite";
import { Levenshtein } from "autoevals";
evalite("English to French translation", {
data: async () => [
{ input: "Hello, world.", expected: "Bonjour, le monde." },
{ input: "Good morning.", expected: "Bonjour." },
],
task: async (input) => {
// ここは実際の LLM 呼び出しに差し替える
// 例: const { text } = await generateText({ model: openai("gpt-4o-mini"), prompt: ... })
return "Bonjour, le monde.";
},
scorers: [Levenshtein],
});
走らせるコマンドは次の 2 つです。
# watch モードで開発用 UI を起動(localhost にダッシュボードが開く)
npx evalite watch
# 1 回だけ流して CI 向けに終了コードを返す
npx evalite
Vitest が内部で走るので、.eval.ts を保存すると即座に再評価がかかります。ユニットテストと同じ TDD ループが、そのまま eval にも適用できます。
カスタム scorer を書く
組み込みのスコアラだけでは足りない場合、createScorer() で自前の採点器を書けます。たとえば「出力が JSON として parse でき、特定のキーを含む」を判定するスコアラは次のとおりです。
import { createScorer } from "evalite";
const hasRequiredKeys = createScorer<string, string, string>({
name: "HasRequiredKeys",
description: "Output parses as JSON and contains name/age keys",
scorer: ({ output }) => {
try {
const parsed = JSON.parse(output);
const ok = "name" in parsed && "age" in parsed;
return { score: ok ? 1 : 0, metadata: { parsed } };
} catch (e) {
return { score: 0, metadata: { error: String(e) } };
}
},
});
型パラメータは <Input, Output, Expected> の順です。scorer に渡される関数は { input, output, expected } を受け取り、number または { score, metadata } を返します。metadata はダッシュボードに展開表示されるため、デバッグ時の後追い情報を乗せておくと効きます。
Autoevals と Vercel AI SDK の統合
autoevals は Braintrust が公開している採点ライブラリで、Factuality / AnswerRelevancy / Battle といった LLM-judge と、Levenshtein / NumericDifference といった決定論的採点器を同じ API で提供します。Evalite はこれをそのままインポートして使えます。
import { Factuality, NumericDifference } from "autoevals";
evalite("Fact extraction", {
data: async () => [
{ input: "彼女は 2016 年に 42 歳で引退した。", expected: "42" },
],
task: async (input) => extractAge(input),
scorers: [Factuality, NumericDifference],
});
Vercel AI SDK を使っていれば、task で generateText() を呼ぶだけで評価対象のコードがそのまま動きます。本番コードと評価コードを別実装にせずに済むのが、同じ TS エコシステムに乗る恩恵です。
向くケース・向かないケース
向くケース
- 本番コードが TypeScript — アプリと同じ型・同じ Vitest で評価を回せる
- ローカル開発中心でクラウドに送りたくない — SaaS 不要、ファイルは手元
- Vite や Vitest を既に使っている — 設定の多くが流用できる
- Vercel AI SDK や LangChain.js を採用している —
taskにそのまま呼び出しを差し込める
向かないケース
- Python のパイプラインが主流 — DeepEval や Ragas のほうが素直です
- 本番トレースから評価セットを自動生成したい — ここは Phoenix や LangSmith の守備範囲
- 大規模チームでスコア履歴の長期管理が要る — Braintrust や Langfuse の SaaS に軍配があります
- 研究用の capability eval を厳密に組みたい — Inspect AI が標準的です
他ツールとの違い
DeepEval は Python/pytest ベースで、assert_test にメトリクスを渡す形です。Evalite は TypeScript/Vitest ベースで、evalite() が runner を兼ねます。言語の違いが最大の分岐点で、既存コードの言語に合わせて選ぶのが素直です。
Promptfoo は YAML が中心で、プロンプト・モデル・テストケースを宣言的に総当たりします。Evalite はコードで記述するため、ロジックの表現力は高い一方、プロンプト A/B の宣言的比較は Promptfoo のほうが書きやすい場面があります。
Autoevals は採点ライブラリ単体なので、Evalite がランナー、Autoevals がスコアラ、という関係で組み合わさります。本連載の Autoevals 回も合わせてどうぞ。
ざっくり対比すると次の表です。
| ツール | 言語 | 書き方 | ランタイム |
|---|---|---|---|
| DeepEval | Python | pytest | pytest |
| Promptfoo | YAML | 宣言 | 独自 CLI |
| Evalite | TypeScript | コード | Vitest |
chaff grade との連携 — 決定論的採点を混ぜる
LLM-judge は確率的にブレます。一方で「引用が原文にあるか」「数値が合うか」「文体規則の違反率」といった決定論的な採点は、同じ入力に対して必ず同じ結果を返します。
chaffjs の grade は、この決定論的な採点を担うツールです。LLM-judge で「意見」を測り、chaff で「事実」を測る、と役割分担するわけです。
Evalite からは、createScorer に chaff grade を乗せた形で呼び出すのが素直です。
import { createScorer, evalite } from "evalite";
import { grade, toScorer } from "chaffjs/grade";
const chaff = createScorer<string, string, string>({
name: "chaff",
description: "決定論的検査: 事実保持、違反件数、文体率",
scorer: async ({ output, expected }) => {
const scored = toScorer(await grade(output, { reference: expected }));
return {
score: scored.score,
metadata: { reason: scored.reason, ...scored.metadata },
};
},
});
evalite("Summaries keep their facts", {
data: async () => [
{
input: "The team answered 4,812 tickets this quarter.",
expected: "The team answered 4,812 tickets this quarter.",
},
],
// 本番コードでは Vercel AI SDK の generateText() などに差し替える
task: async (input) => input,
scorers: [chaff],
});
ポイントは 3 つあります。grade() は参照テキストに対して事実が落ちていないかを機械的に測ります。toScorer() が返す { score, reason, metadata } をそのまま Evalite の scorer に返せば、Web UI 上で LLM-judge と並べて比較できます。LLM-judge と chaff を scorers 配列に両方並べると、「事実は保てたが文体は崩れた」のような分解した議論が可能になります。
実装例: isamu/lab: examples/evals/evalite(chaff.eval.ts 参照)
ハマりポイント
.eval.ts が走らない
Evalite は拡張子で評価ファイルを拾います。.test.ts のままだと Vitest 側に流れ、Evalite ランナーが拾ってくれません。評価用は必ず .eval.ts にします。
expected を省略するとスコアラが機能しない
Levenshtein や Factuality は expected を参照する前提です。data 側で expected を落とすと、スコアが常に 0 や undefined になります。採点器が参照するフィールドを事前に揃えておきます。
LLM-judge のコスト
Factuality などの LLM-judge は毎回モデルを呼ぶため、データ件数とスコアラ数が増えるとコストが膨らみます。chaff のような決定論的採点に寄せられる部分は寄せ、本当に意味を測りたい部分だけ LLM-judge に残すのが現実的です。
Web UI のポート衝突
デフォルトで localhost:3006 あたりを開きます。既に別のサービスが占有していると起動に失敗するので、--port で明示的に指定しておくと事故を減らせます。
まとめ
Evalite は、TypeScript で LLM アプリを書くチームにとって「評価もアプリと同じ言語で、同じ Vitest 上で回す」ための選択肢です。設計は Vitest を素直に拡張しており、学習コストが低く、SaaS 不要でローカルに完結します。
強みは Behavior / Reliability の日常的な守りです。長期の trace 分析や本番ログからの評価セット抽出は、Phoenix や LangSmith との役割分担が現実解です。LLM-judge の揺れが気になるなら、chaff grade のような決定論的採点を createScorer 経由で混ぜ、「事実」と「意見」を分離するのが実用上のコツです。
まずは evalite() で 1 ファイル書き、npx evalite watch で UI を眺めるところから始めてみてください。
関連リンク
関連記事
- AI eval ツール 10 選 — 比較と使い分け
- AI eval ツール: Autoevals
- AI eval ツール: Promptfoo
- AI eval ツール: DeepEval
- AI エージェント評価サーベイ論文を読み解く
Singularity Society はテクノロジー集団として MulmoClaude / MulmoTerminal / MulmoCast を開発しています。エンジニア・起業家向けの実践プログラム BootCamp も運営しています。
