先日、判断に特化した高速かつ低コストなAIの「Jev」がTypeSafe AIからリリースされて、話題になっています。
Jevは、文章を一切生成せず、型付きの判断だけを返すAIです。
例えば、AIエージェントが自動収集してくる技術情報の関連度フィルタリングのような用途です。
収集される情報の多くは今触っているリポジトリに無関係なため、人間がレビューする前に機械的に絞り込みたい場面です。
LLMに逐一聞く方法は返ってくる散文を解析する手間があるため、「判定だけでいい」用途にはJevが向いています。
この記事では、Jevの基本情報を整理します。
そのうえで、個人的に運用しているAIDD向けDev Container環境のテンプレートを例に、関連度フィルタリングの実装のソースコードと実行結果を見ながら、何ができて何に気をつけるべきかを解説します。
1. Jevとは何か
Jevは、TypeSafe AIが2026年9月15日に公開した System One モデルを、noul / choice / score という3種類の質問プリミティブで呼び出すAPIです。
最大の特徴は、テキストを一切生成せず、型付きの確率的判断だけを返すことです。
判定結果には確率・確信度・分布といった数値が必ず付随するため、「どのくらい確からしい判定か」を後段のロジックで機械的に扱えます。
イメージとしては、決定論的な条件分岐で書くスクリプトと、文章を生成するLLMの中間に位置する存在です。
正解が1つに決まらない「曖昧な判定」を、スクリプトのように機械的に処理できる点が特徴です。
3つの質問プリミティブ
| プリミティブ | 何を返すか | 使いどころ |
|---|---|---|
noul | yes/noの確率 | 「actionableか?」のような二値判定 |
choice | ラベルごとの確率+確信度 | 複数の分類先から1つを選ぶ |
score | 順序付きルーブリック(2〜10段階)のスコア+分布+確信度+凡例 | 「重なりの強さ」のような段階評価 |
料金とベンチマーク
- 入力トークンのみ課金($0.042/1Mトークン)で、出力は無料
- レイテンシは70〜500ms程度
- 第三者ベンチマーク(jev-rerank-bench、8データセット1,617問)ではnDCG@10が0.692。Cohere Rerank 4 Pro(0.691)との差は信頼区間がゼロを跨ぎ有意差が確認されなかった精度でありながら、コストは約5.6分の1
- TypeSafe自身が推奨ユースケースとして「relevance filtering before an expensive context window」(高コストなコンテキストウィンドウに入れる前の関連度フィルタリング)を挙げている
上記の料金・ベンチマークの数値は2026年9月時点の調査に基づくものです。公開前に typesafe.ai で最新情報を確認してください。
理由文が返らない代わりに、choiceなら選択肢ごとの確率分布、scoreなら段階ごとの分布が返ってきます。
「なぜその判定になったか」は、この分布を見て事後に読み解く形になります。
使ってみて分かった注意点
noulの0.5付近は「中間」ではなく「不確実」を意味します。しきい値設計で混同しやすい点ですchoiceの確率は合計1になるため、どの選択肢も的外れでも必ずどれか1位が出ます。「どれにも当たらない」を表すnoneのようなラベルと、確信度(confidence)の併用が必須です- 1リクエスト内の複数の質問は独立並列評価されます。質問を増やしてもレイテンシはほぼ増えず、課金は各質問のcriteria分のトークンに比例します
2. 実装例:リポジトリとの関連度フィルタ
例として使うのは、AIDD(AI駆動開発)向けに自分が運用しているDev Container環境のテンプレートです。
「収集してきた技術記事が、このリポジトリの技術スタック・アーキテクチャに関係あるか」をJevに判定させます。
全体の流れ
設定ファイル群
↓ semgrep + jq/yq
facts.json
↓ 要約
repo-profile.json
↓ 候補記事12件(候補.json)と合わせて
Jev API
↓
answers(確率的な回答)
↓ decide()
keep / review / dropBashリポジトリプロファイル:「事実」と「解釈」の2層構造
判定の土台になるのが repo-profile.json です。.devcontainer/Dockerfile や .github/workflows/*.yml などの設定ファイルから、技術スタックの事実を機械的に抽出した集合が facts.json、そこに人間の解釈を1回加えた最終形が repo-profile.json になります。
まず、semgrepとjq/yqで facts.json を抽出します。
semgrep_json() {
semgrep --config "$SCRIPT_DIR/rules.yaml" --metrics=off --disable-version-check --json --quiet "$@" 2>/dev/null
}
dockerfile_facts=$(semgrep_json .devcontainer/Dockerfile | python3 "$SCRIPT_DIR/slice_dockerfile.py" .devcontainer/Dockerfile)TypeScriptここに「このリポジトリは個人開発である」「関心を持たない領域はこれ」といった、機械抽出だけでは出てこない解釈を人間(このプロジェクトではClaude Code)が1回だけ加えて、repo-profile.json を作ります。
{
"schema_version": 1,
"purpose": "Claude CodeやCodex CLIをDev Container内で安全に実行するためのテンプレートリポジトリ...",
"scale": "個人開発の単独プロジェクト。チーム運用や有料ティア、継続的な運用コストを前提にした解決策は合わない。",
"tech_stack": ["Dev Container", "Docker Compose", "node:24-trixie-slim base image", "..."],
"security_model": { "layers": ["..."], "invariants": ["..."] }
}JSONJevへの5つの質問
実際の質問定義は、TypeScriptで次のように書きます。
// Jevへの質問定義の例
export const questions = {
stack_overlap: score("リポジトリのtech_stackとどれだけ重なるか?", [
"None: このリポジトリに存在しない技術。",
"Adjacent: 同じエコシステムだが、ここで使っている技術ではない。",
"Partial: ここで使っている技術に周辺的に触れている。",
"Direct: ここで使っている技術が中心。",
"Core: コンテナイメージ・egressファイアウォール・エージェントCLI設定が中心。",
]),
actionable: noul("対応するには、このリポジトリの追跡対象ファイルを編集する必要があるか?", {
true: "Dockerfile・Dev Container設定・ファイアウォールのallowlist・ワークフロー・ライフサイクルスクリプト・ドキュメントなど、具体的な編集を伴う。",
false: "ニュースや意見、背景情報のみ、または別リポジトリ向けの内容。",
}),
area: choice("どの部分が変わるか?", {
devcontainer: "コンテナイメージ、Dev Container設定、ライフサイクルスクリプト、同梱のCLIツール。",
security: "egressのallowlist、いずれかの防御層、サンドボックスの緩和、シークレット管理。",
agent_workflow: "エージェントへの指示、skills、subagents、APM連携、プロンプトファイル。",
ci: "GitHub Actionsワークフロー、CIスクリプト、自動実装パイプライン。",
none: "このリポジトリのどの部分にも該当しない。",
}),
// security_impact, operational_fit は省略
} as const;TypeScriptJevはこの説明文を読んで「どの選択肢が候補の内容に一番合うか」を判定するので、ラベル名だけでは判定基準になりません。
operational_fit(運用面のフィット感)という観点も入れています。
技術的にはリポジトリと合致していても、「チーム運用や有料ティアを前提にしていて個人開発にはオーバースペック」という候補を検出するための軸です。
これは加点方式ではなく、ゲートとして使います(後述)。
候補1件 = 1リクエスト
// state組み立て処理の抜粋
function buildState(profile: EntryType, candidate: Candidate): EntryType {
const { expected: _expected, ...payload } = candidate;
return { repository: profile, candidate: payload };
}TypeScriptchoiceは1ラベルしか返せないため、複数候補を1つのstateに詰め込むことはできません。
候補ごとにstate = { repository: プロファイル, candidate: 候補 }を組んで、1件ずつJevに投げます(concurrency件ずつ並列実行)。
answersをkeep/review/dropに変換する
例えば、用意した候補の実物ひとつを判定させるとします。
{
"id": "c-11",
"title": "SYS_ADMIN権限とcustom AppArmorプロファイルでDockerサンドボックスをさらに緩和するテクニック集",
"source": "blog",
"summary": "コンテナ内でより広範なシステムコールを許可するためにSYS_ADMIN capabilityの付与やAppArmorプロファイルの無効化を行う手法を紹介する。ネストされたコンテナ実行や高度なデバッグ用途向け。"
}JSONJevからのレスポンス(answers)は、次のように各質問ごとの確率・確信度・分布の塊です。distributionはキー名でどの段階・ラベルの確率かが分かるようにしています。
{
"stack_overlap": {
"score": 4,
"confidence": 0.80,
"distribution": { "None": 0.01, "Adjacent": 0.03, "Partial": 0.06, "Direct": 0.20, "Core": 0.70 }
},
"actionable": { "noul": 0.93 },
"area": {
"choice": "security",
"confidence": 0.85,
"distribution": { "devcontainer": 0.08, "security": 0.85, "agent_workflow": 0.03, "ci": 0.02, "none": 0.02 }
},
"operational_fit": {
"score": 3,
"confidence": 0.60,
"distribution": { "Overkill": 0.05, "Heavy": 0.10, "Moderate": 0.25, "Light": 0.60 }
},
"security_impact": {
"score": 3,
"confidence": 0.83,
"distribution": { "None": 0.02, "Indirect": 0.05, "Direct": 0.10, "Weakening": 0.83 }
}
}JSON主なプロパティは次の通りです。
| プロパティ | 意味 |
|---|---|
score / noul | 判定の本体(順序スコア、またはyes/noの確率) |
choice | choice質問で選ばれたラベル |
confidence | その判定がどれだけ確からしいか |
distribution | 全選択肢・全段階にわたる確率の内訳(キーがラベルに対応) |
このanswersをしきい値と比較し、最終的なverdict(keep/review/drop)に変換します。
// decide()より抜粋(ロジックの骨格)
const normalizedStackOverlap = answers.stack_overlap.score / (levels - 1);
let score = weights.stack_overlap * normalizedStackOverlap + weights.actionable * answers.actionable.noul;
let verdict = score >= thresholds.keep_score_min ? "keep" : score >= thresholds.review_score_min ? "review" : "drop";
if (answers.area.choice === "none") {
// 領域が「どこにも当たらない」場合は無条件で drop。他のゲートより優先する。
verdict = "drop";
} else {
const isOverkill = answers.operational_fit.score <= thresholds.operational_overkill_max;
const securityRequiresIt = answers.security_impact.score >= thresholds.security_override_min;
if (isOverkill && !securityRequiresIt) {
// 運用面はゲート。ただしセキュリティ上必要な変更は重くても落とさない。
if (verdict === "keep") verdict = "review";
flags.push("operational_overkill");
}
}
if (answers.security_impact.score >= thresholds.security_flag_min) {
// スコアには加算しない。防御層の緩和は良し悪しでなく人間レビューのトリガ。
if (verdict === "keep") verdict = "review";
flags.push("security_review");
}TypeScriptこの処理に先ほどのanswersを通すと、次のようになります。
| 項目 | 計算・判定 |
|---|---|
score | 0.5 × (4/4) + 0.5 × 0.93 = 0.965 |
verdict | いったんkeep_score_min(0.58)以上でkeepに |
flags | security_impact.score(3)がsecurity_flag_min(2.85)以上のためsecurity_reviewが立ち、verdictはreviewに降格 |
1件分の最終的な出力は、候補・回答・判定をまとめた次の形になります。
{
"candidate": { "id": "c-11", "title": "SYS_ADMIN権限とcustom AppArmorプロファイルで...", "source": "blog" },
"answers": { "...": "上記のanswers" },
"decision": { "score": 0.965, "verdict": "review", "flags": ["security_review"] },
"expected": { "verdict": "review", "area": "security" },
"match": { "verdict": true, "area": true }
}JSONexpectedは検証のために候補へあらかじめ付けておいた正解ラベル、matchはそれとdecisionが一致したかを機械的に比較した結果です。
security_impact(防御層への影響度)は、スコアに加算しない設計にしています。
単純加点にすると、「防御層を緩めましょう」という提案が、actionableでスタックとも合致しているために高スコアでkeepされてしまいます。
良し悪しの判断ではなく人間レビューを呼ぶためのフラグとして分離しました。
3. まとめ
関連度フィルタリングのような用途では、文章生成LLMより速くて安く済みます。
判定の質は、質問のcriteriaに書く説明文の作り込みに大きく左右されるため、ラベルを決めるだけでは不十分です。
手元で用意した12件を軽く試した限りでは、keep/review/dropが概ね期待通りに分かれ、実行コストも1回約$0.0006(約0.1円)に収まりました。
総じてJevは、文章を生成せず、確信度・分布付きの型付き判断だけを機械的に返したい場面に強く刺さるAIです。
最後までお読みいただきありがとうございました。
この記事に関するご意見やご感想、ご指摘などありましたら、記事下のコメント欄にてお待ちしております!