はじめに 🎯
問い合わせを部署に振り分ける。バグ報告の深刻度を見積もる。レビューがネガティブか判定する。
こういう「ちょっとした判断」をLLMにやらせると、だいたい同じところでつまずきます。
- 自由文で返ってくるのでパースが必要
- JSONで返せと指示してもたまに崩れる
- 同じ入力なのに実行ごとに答えが揺れる
- そして何より、遅いし高い
TypeSafe の Jev は、この用途に振り切ったモデルです。テキストを生成せず、型付きの答えと確率だけを返します。
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "Stripe連携が3日間失敗し続けています。決済が一件も取り込めず、売上計上が止まっています。",
"model": "jev-latest",
"questions": {
"urgency": { "type": "noul", "instructions": "このメッセージは緊急性を示しているか" }
}
}' | jq .
{
"model": "jev-1.13.0",
"answers": {
"urgency": { "type": "noul", "noul": 0.86 }
},
"usage": { "input_tokens": 304, "output_tokens": 21 }
}
返ってくるのは 0.86 という数字だけ。前置きも言い訳も、コードフェンスの取りこぼしもありません。そのまま if に投げられます。
3行まとめ 📝
-
Jev は「System One モデル」 — 文章を生成せず、
noul(Yes/No確率)・choice(選択)・score(段階評価)という型付きの判断を返す - 複数の質問を1リクエストで並列に聞ける — ルーティング・深刻度・感情を同時に取得でき、追加のAPI呼び出しは不要
- 確率と confidence が付いてくる — 「自動処理する/人間にエスカレーションする」の分岐をコード側で設計できる
System One モデルという考え方 🧠
TypeSafe は自社のモデル群を System One と呼んでいます。ダニエル・カーネマンの「速い思考(システム1)」が由来でしょう。Jev はそのフラッグシップかつ第一号モデルです。
公式ドキュメントの整理を表にするとこうなります。
| System One(Jev) | 一般的なLLM | |
|---|---|---|
| 出力 | 型付きの判断と確率 | 生成されたテキスト |
| 得意なこと | 構造化された意思決定 | 説明・要約・文章生成 |
| 確信度 | キャリブレーション済みの確率分布 | (基本的に無い) |
| 使い方 | コードから関数のように呼ぶ | プロンプトで対話する |
思想として明確に打ち出されているのが "code owns the control flow" という原則です。
従来型 : 複雑な判断ロジックを全部コードで書く(自然言語が扱えない)
LLMエージェント : モデルが次のステップを選ぶ(暴走リスクが高い)
System One : コードが制御を持ち、AIは原子的で制約された判断だけを担当
エージェントに全部任せるのではなく、**「コードが常識を必要とする一点だけAIに聞く」**というスタイルです。
セットアップ 🔑
コンソール( https://console.typesafe.ai )にサインアップすると、Playground・Usage・API Keys が並んだシンプルな画面が出てきます。
Cookbook(Parallel questions、SDE cascade、Function calling など)へのリンクが最初から並んでいるのが親切でした。
API キーは「API Keys」→「Create key」から発行します。
あとは環境変数に入れるだけです。
export TYPESAFE_API_KEY='xxxxxxxxxxxx'
エンドポイントは1つだけ。
POST https://api.typesafe.ai/v1/systemone
リクエストボディも3つのフィールドだけです。
| フィールド | 内容 |
|---|---|
state |
判断の対象。文字列でも構造化されたJSONでもOK |
model |
jev-latest を指定しておけばOK |
questions |
質問のマップ。自分で付けたキーで答えが返ってくる |
3つのプリミティブ 🧩
Jev に聞けることは3種類しかありません。この割り切りが気持ちいいところです。
1. noul — Yes/No の確率
「〜か?」という二値の判定。返ってくるのは0〜1の確率そのものです。
"urgency": { "type": "noul", "instructions": "このメッセージは緊急性を示しているか" }
"urgency": { "type": "noul", "noul": 0.86 }
confidence が別途付かないのがポイントで、確率そのものがシグナルという設計です。「個人情報を含むか」「返金を要求しているか」のような、はっきりYes/Noが決まる問いに向いています。
⚠️ 注意:
noulが0.5は「中くらいの強さ」ではなく「YesともNoとも言えない」という意味です。程度を測りたいときは後述のscoreを使います。
2. choice — 定義した選択肢から1つ選ぶ
部署へのルーティングなど、順序のない固定リストから選ばせるときに使います。
"department": {
"type": "choice",
"instructions": "この問い合わせはどのチームが担当すべきか",
"criteria": {
"billing": "決済・請求・返金など、お金の流れに関する問題",
"integration": "外部サービス連携やAPI接続の不具合",
"account": "ログイン、権限、プラン変更に関する問い合わせ",
"other": "上記のいずれにも当てはまらないもの"
}
}
実際に「Stripe連携が3日間失敗し続けています。決済が一件も取り込めず、売上計上が止まっています。」を投げた結果がこちら。
"department": {
"type": "choice",
"choice": "integration",
"confidence": 0.9,
"probabilities": {
"integration": 0.92,
"billing": 0.08,
"other": 0.0,
"account": 0.0
}
}
「決済」という単語に引っ張られず integration を選びつつ、billing にも 0.08 を残している。この分布が見えるのが何よりありがたいです。選択肢は最大255個まで定義できます。
3. score — 段階に沿った評価
深刻度・熟練度・不満の強さなど、程度を測るときのプリミティブです。段階(levels)を自分で言葉で定義します。
"severity": {
"type": "score",
"instructions": "報告されている問題の深刻度はどの程度か",
"criteria": [
"軽微。機能への影響はない",
"機能は劣化しているが、回避策がある",
"業務が止まる。回避策が存在しない"
]
}
"severity": {
"type": "score",
"score": 2.0,
"confidence": 1.0,
"legend": {
"0": "軽微。機能への影響はない",
"1": "機能は劣化しているが、回避策がある",
"2": "業務が止まる。回避策が存在しない"
},
"probabilities": { "0": 0.0, "1": 0.0, "2": 1.0 }
}
これは判断が一段階に振り切れた例です。確率が段階2に100%集中しているため、score もちょうど 2.0、confidence も 1.0 になりました。
面白いのは、判断が割れたときです。score は段階の間の値を取ります。 後述の複合リクエストで同じチケットに「顧客の不満の強さ」を聞いた結果がこちら。
"frustration": {
"type": "score",
"score": 0.96,
"confidence": 0.92,
"legend": {
"0": "冷静に事実を述べているだけ",
"1": "苛立っているが礼儀は保っている",
"2": "強い怒り。解約を示唆している"
},
"probabilities": { "0": 0.05, "1": 0.94, "2": 0.01 }
}
score は 0.96。これは確率分布を加重平均した位置です(0×0.05 + 1×0.94 + 2×0.01 = 0.96)。つまり Jev は段階を1つ選んでいるのではなく、分布の重心を返している。「冷静(0)と苛立ち(1)の、ほぼ苛立ち寄り」という読み方になります。
段階は2〜10レベルまで定義でき、各レベルはそれ単体で読んで意味が通る具体的な記述にするのがコツです。
本命は「複数の質問を一度に聞く」 ⚡
ここからが Jev の本領です。同じ state に対する独立した質問は、1リクエストにまとめると並列に評価されます。
構造化された state を渡し、4つの質問を同時に投げてみます。
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": {
"subject": "決済が取り込めません",
"body": "Stripe連携が3日間失敗し続けています。決済が一件も取り込めず、売上計上が止まっています。問い合わせは今回で3回目です。",
"plan": "Enterprise",
"previous_tickets": 2
},
"model": "jev-latest",
"questions": {
"urgency": { "type": "noul", "instructions": "このメッセージは緊急性を示しているか" },
"department": { "type": "choice", "instructions": "`body` の内容から、どのチームが担当すべきか", "criteria": { "billing": "決済・請求・返金など、お金の流れに関する問題", "integration": "外部サービス連携やAPI接続の不具合", "account": "ログイン、権限、プラン変更に関する問い合わせ", "other": "上記のいずれにも当てはまらないもの" } },
"severity": { "type": "score", "instructions": "顧客の業務への影響度はどの程度か", "criteria": ["軽微。機能への影響はない", "機能は劣化しているが、回避策がある", "業務が止まる。回避策が存在しない"] },
"frustration":{ "type": "score", "instructions": "顧客の不満の強さはどの程度か", "criteria": ["冷静に事実を述べているだけ", "苛立っているが礼儀は保っている", "強い怒り。解約を示唆している"] }
}
}' | jq .
結果を整理するとこうなりました。
| 質問 | 型 | 答え | confidence |
|---|---|---|---|
| urgency | noul | 0.96 | - |
| department | choice | integration | 0.89 |
| severity | score | 2.0 / 2 | 1.00 |
| frustration | score | 0.96 / 2 | 0.92 |
注目したいのが urgency です。単発で聞いたときは 0.86 でしたが、plan: "Enterprise"・previous_tickets: 2・「今回で3回目です」という文脈が加わったことで 0.96 に上がりました。state に構造化データを渡す意味がはっきり出ています。
また `body` の内容から のように、バッククォートで state のフィールドを名指しできるのも実用的です。質問ごとに見るべき場所を絞れます。
この1リクエストの消費は input_tokens: 722 / output_tokens: 91。4つの判断をこれだけで賄えている計算です。
confidence をどう使うか 🚦
Jev の confidence は確率分布の「尖り具合」から導出される統計量です。一箇所に集中していれば高く、散らばっていれば低くなります。
公式が推奨しているのは、この値でそのまま制御フローを組む設計です。
# 深刻度が高く、判断も確信的 → 即エスカレーション
if severity.score >= 1.5 and severity.confidence >= 0.8:
escalate_to_oncall(ticket)
# 部署の判断が怪しい → 人間のトリアージへ
elif department.confidence < 0.7:
route_to_human_triage(ticket)
else:
assign(ticket, department.choice)
ドキュメントで繰り返し強調されているのが、しきい値は操作の重大さごとに変えるべきという点です。読み取り専用の処理なら緩く、破壊的な処理なら 0.9 以上、といった具合に。
そしてもう一つ、忘れてはいけない注意書きがあります。
型付きの出力が保証するのはインターフェースであって、正しさではない
キャリブレーションされているとはいえ、自分のドメインのデータで検証は必要です。まず保守的なしきい値から始めて、実データで調整していくのが定石とされています。
設計のコツ:質問は小さく割る ✂️
ドキュメントで一番刺さったのが、質問の分解に関する指針でした。
スパム判定を例に、「このメッセージはスパムか?」という1つの大きな質問ではなく、こう割ることが推奨されています。
- 認証情報を要求しているか
- 送信者IDとドメインが一致しないか
- 予期しない報酬を提示しているか
- リンク先ドメインが偽装されているか
- …
こう分けておくと、重み付けや優先順位をコード側で変更できるようになります。「今月はフィッシング対策を厚くしたい」と思ったとき、プロンプトを書き直すのではなく係数をいじるだけで済む。判断が再利用可能なデータになるわけです。
一方で、分けすぎて判断している関係性そのものを壊さないことも同時に釘を刺されています。「原子的」=「単語レベルの事実抽出」ではない、と。
料金とリミット 💰
| 項目 | 値 |
|---|---|
| モデルID |
jev-1.13.0(エイリアス: jev-latest) |
| 入力トークン単価 | $0.042 / 1Mトークン($42 / 1Bトークン) |
| 出力トークン単価 | 無料 |
| コンテキスト長 | 64,000トークン(リクエスト全体) |
| state + 最長の質問 | 32,000トークン |
| レート制限 | 250,000 tokens/sec ・ 1,200 requests/min |
入力100万トークンで約6円($0.042)、出力は課金なし。上で試した4問同時のリクエストが722トークンなので、1万件処理しても数十円という水準です。分類や判定をLLMで回していたなら、桁が変わります。
エラーは 401(キー不正)、422(バリデーション失敗)、429/529(レート制限・過負荷)。後者は指数バックオフでのリトライが推奨されていますが、公式SDKなら自動でやってくれます。
SDK も用意されている 📦
curl で試したあとは SDK に移行するのが楽です。JavaScript/TypeScript 版はこんな書き味でした(Node.js 20以上)。
npm install @typesafe-ai/sdk
import { choice, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const response = await client.systemOne({
state: { document: "I was charged twice. Please fix this ASAP." },
questions: {
category: choice("What is this ticket about?", {
billing: null,
technical: null,
other: null,
}),
},
});
console.log(response.answers.category.choice);
質問の定義から返り値の型が自動で推論されるので、response.answers.category.choice がちゃんと "billing" | "technical" | "other" になります。名が体を表していて良いですね。Python SDK もあります。
🔒 Webアプリで使う場合、APIキーは必ずサーバー側に置いてください。
まとめ ✅
Jev を触ってみて感じたのは、**「LLMでやっていたことの一部は、そもそもLLMの仕事ではなかった」**ということでした。
- 分類・判定・スコアリングは、文章生成能力を必要としない
- 必要なのは型付きの答えと、その答えをどれだけ信じていいかという情報
- そこに特化すれば、速く・安く・扱いやすくなる
特に「確率分布がそのまま返ってくる」点は、実運用で効いてきそうです。自動処理と人間のレビューの境界線を、勘ではなくデータで引けるようになります。
こんな処理を書いている人は、一度置き換えを検討する価値があると思います。
- 問い合わせのルーティング
- コンテンツのモデレーション・フラグ立て
- 検索結果のリランキング
- 抽出したデータの検証(怪しいものだけ推論モデルへ回す)
- 自然言語からの関数呼び出し・引数埋め
まずは Playground で自分のドメインのデータを1件投げてみるのが早いです。無料枠で十分に感触が掴めます。

