はじめに
TypeSafe AI が提供する Jev を試しました(Python SDK の初回リリースは 2026-09-14)。
Jev は一般的な LLM のように文章や JSON を生成するモデルではありません。入力された情報に対して「該当するか」「どの候補に分類するか」「どの程度か」といった判断を行い、その結果を確率とともに返す System One Model です。
Jev で利用できる代表的な質問形式は次の3つです。
-
Noul— 真偽・該当性を判定する -
Choice— 定義した候補から選択する -
Score— 定義した尺度で評価する
たとえば問い合わせ対応なら「緊急性があるか」「技術・請求・アカウントのどの問題か」「顧客への影響はどの程度か」を1回のリクエストで判定できます。
LLM の Structured Outputs や Function Calling と似て見えますが、使い方は異なります。
LLM はモデルが構造化された出力や関数の引数を生成します。一方 Jev は、アプリケーション側で質問と取り得る値を先に定義し、その範囲についてモデルに判断させます。
この違いは Function Calling を実装すると分かりやすくなります。たとえば city: Literal["tokyo", "osaka", "nagoya"] のような引数なら Choice で選択できます。しかし query: str のように任意の文字列を必要とする引数を Jev が生成することはできません。
また「候補以外を生成しない」ことと「正しく判断する」ことは別です。Choice に適切な「該当なし」を用意しなければ、入力がどの候補にも該当しなくても、いずれかを選択します。返される確率や confidence をどの閾値で採用するかもアプリケーション側で決める必要があります。
そのため Jev は LLM の置き換えというより、これまで LLM にまとめて任せていた処理から
「分類する」
「該当性を判定する」
「スコアを付ける」
「どの処理へ進むか決める」
といった判断を切り出すためのモデルとして見ると理解しやすいです。
この記事では Playground から始めて API と Python SDK を実際に動かします。さらに TypeSafe の Function Calling cookbook を基に、同じ get_weather を OpenAI の Function Calling と Jev の両方で実装し、出力形式の違いだけでは分からない設計上の差を確認します。
Playground で動作を確認する
真偽・該当性の判定を行う
Playground で簡単に試せます。
任意のテキストの緊急性を下記の構成で Jev に判定させます。
こんにちは3日前からStripeアカウントへの接続を試していますが 何度やっても失敗します
そのせいで売上を失っています
至急対応してください
このメッセージは緊急性を表していますか?
urgency: 97%
複数のタイプ (noul, choice, score) の質問に同時に回答する
- Noul型(真偽・該当性の判定)
- このメッセージは緊急性を表していますか?
- Choice型(選択・分類)
- このメッセージで説明されている主な問題の種類は何ですか?
- Score型(段階評価・スコアリング)
- この問題は、顧客のビジネスにどの程度深刻な影響を与えていますか?
{
"urgency": {
"type": "noul",
"instructions": "このメッセージは緊急性を表していますか?"
},
"issue_type": {
"type": "choice",
"instructions": "このメッセージで説明されている主な問題の種類は何ですか?",
"criteria": {
"technical_issue": "技術的な障害、連携の問題、またはシステムの不具合",
"billing_issue": "請求、支払い、請求書、または料金に関する問題",
"account_issue": "アカウントへのアクセス、認証、またはアカウント設定に関する問題",
"general_inquiry": "その他の一般的な問い合わせ"
}
},
"customer_impact": {
"type": "score",
"instructions": "この問題は、顧客のビジネスにどの程度深刻な影響を与えていますか?",
"criteria": [
"顧客のビジネスへの影響がほとんど、またはまったくない",
"顧客のビジネスに軽微な支障がある",
"通常の事業運営に影響する明確な支障がある",
"売上や収益の損失につながる重大な支障がある",
"継続的に大きな事業損失が発生する重大な影響がある"
]
}
}
回答:
urgency は「緊急性があるか」という二値的な判断に対して、97% true / 3% false と返しています。
Choice は候補の中から分類しています。結果は technical_issue 83%、account_issue 17%、その他は 0% で、全体の Confidence: 77% が表示されています。主として技術的問題と判定しつつ、アカウント問題の可能性も残しています。
Score は5段階(0〜4)の評価です。結果は 3.02 / 4 で、各レベルを見ると 3: 売上や収益の損失につながる重大な支障 が 98% を占めています。3 × 0.98 + 4 × 0.02 = 3.02 なので、Score は各レベルの確率で重み付けした平均です。入力文に「売上を失っています」と明記されているため、この基準に強く対応しています。
API経由で jev を呼び出す
TYPESAFE_API_KEY の取得方法
TYPESAFE_API_KEY は ダッシュボードから取得して、環境変数に保存しておきます。
export TYPESAFE_API_KEY=<YOUR_KEY>
直接 API を使う場合は、次のように cURL で POST リクエストを送ります。
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<'EOF' | jq
{
"state": "こんにちは。3日前からStripeアカウントへの接続を試していますが、何度やっても失敗します。そのせいで売上を失っています。至急対応してください。",
"model": "jev-latest",
"questions": {
"urgency": {
"type": "noul",
"instructions": "このメッセージは緊急性を表していますか?"
}
}
}
EOF
{
"model": "jev-1.13.0",
"answers": {
"urgency": {
"type": "noul",
"noul": 0.97
}
},
"usage": {
"input_tokens": 352,
"output_tokens": 21
}
}
複数の種類の質問を同時に投げます。
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<'EOF' | jq
{
"state": "こんにちは3日前からStripeアカウントへの接続を試していますが 何度やっても失敗します そのせいで売上を失っています 至急対応してください",
"model": "jev-latest",
"questions": {
"urgency": {
"type": "noul",
"instructions": "このメッセージは緊急性を表していますか?"
},
"issue_type": {
"type": "choice",
"instructions": "このメッセージで説明されている主な問題の種類は何ですか?",
"criteria": {
"technical_issue": "技術的な障害、連携の問題、またはシステムの不具合",
"billing_issue": "請求、支払い、請求書、または料金に関する問題",
"account_issue": "アカウントへのアクセス、認証、またはアカウント設定に関する問題",
"general_inquiry": "その他の一般的な問い合わせ"
}
},
"customer_impact": {
"type": "score",
"instructions": "この問題は、顧客のビジネスにどの程度深刻な影響を与えていますか?",
"criteria": [
"顧客のビジネスへの影響がほとんど、またはまったくない",
"顧客のビジネスに軽微な支障がある",
"通常の事業運営に影響する明確な支障がある",
"売上や収益の損失につながる重大な支障がある",
"継続的に大きな事業損失が発生する重大な影響がある"
]
}
}
}
EOF
{
"model": "jev-1.13.0",
"answers": {
"urgency": {
"type": "noul",
"noul": 0.97
},
"issue_type": {
"type": "choice",
"choice": "technical_issue",
"confidence": 0.74,
"probabilities": {
"general_inquiry": 0.0,
"account_issue": 0.19,
"technical_issue": 0.81,
"billing_issue": 0.0
}
},
"customer_impact": {
"type": "score",
"score": 3.02,
"confidence": 0.98,
"legend": {
"0": "顧客のビジネスへの影響がほとんど、またはまったくない",
"1": "顧客のビジネスに軽微な支障がある",
"2": "通常の事業運営に影響する明確な支障がある",
"3": "売上や収益の損失につながる重大な支障がある",
"4": "継続的に大きな事業損失が発生する重大な影響がある"
},
"probabilities": {
"0": 0.0,
"1": 0.0,
"2": 0.0,
"3": 0.98,
"4": 0.02
}
}
},
"usage": {
"input_tokens": 718,
"output_tokens": 85
}
}
Playground とほぼ同じ結果です。ただし Choice は Playground が 83% / 17%(confidence 77%)、API が 81% / 19%(confidence 74%)で、数ポイント違います。入力の句読点が違うことに加え、同じ入力を繰り返し投げても確率は揺れます(後述の SDK の実行では、同一入力の3回で technical が 0.81〜0.83)。閾値を決めるときは、この揺れを前提にしてください。
SDK から jev を使う
typesafe-sdk をインストールします。
pip install typesafe-sdk
uv init
uv add typesafe-sdk
サンプルプログラムを実行します。
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
client = TypeSafeClient()
ticket = "お世話になっております。3日前からStripeアカウントを連携しようとしているのですが、何度やっても失敗します。売上に影響が出ていますので、至急対応をお願いします。"
response = client.system_one(
state=ticket,
questions={
"department": Choice(
instructions="どの部署が対応すべきか",
criteria={
"billing": "支払いやサブスクリプションに関する問題",
"technical": "不具合や連携に関する問題",
"sales": "料金やアカウントに関する質問",
},
),
"frustration": Score(
instructions="顧客がどの程度苛立っているか",
criteria=[
"冷静で、事実を述べているだけ",
"苛立っているが、丁寧さは保っている",
"非常に怒っており、強い言葉を使っている",
],
),
"is_urgent": Noul(
instructions="メッセージから緊急性や時間的な切迫感が伝わる",
),
},
)
print(response.answers["department"].choice) # "technical"
print(response.answers["frustration"].score) # 0.96〜0.97
print(response.answers["is_urgent"].noul) # 0.97
これは TypeSafe 公式の quickstart にある ticket_triage の state、instructions、criteria を日本語に書き換えたものです。Choice の選択肢のキー(billing / technical / sales)と質問名は、コード側の識別子なので英語のままです。
英語版(公式のまま)と日本語版を、それぞれ3回ずつ実行しました。モデルは jev-1.13.0 です。
| 質問 | 英語版 | 日本語版 |
|---|---|---|
department |
technical(0.81〜0.83) |
technical(0.93〜0.96) |
frustration |
1.0 | 0.96〜0.97 |
is_urgent |
0.99 | 0.97 |
department はどちらも technical で、6回とも同じでした。次点の billing は英語版が 0.17〜0.19、日本語版が 0.04〜0.07 で、日本語版のほうが technical への集中が強く出ています。今回の1チケットでは、言語による判定の違いは見られませんでした。複数の言語・入力での精度は確認していません。
frustration は「苛立っているが丁寧」の位置(1.0 付近)で、is_urgent は「至急対応をお願いします」に反応して高い値です。
コーディングエージェントに TypeSafe を利用させる
Claude Code や Codex などのコーディングエージェントに TypeSafe の使い方を覚えさせて、相談しながら実装させられます。
Claude Code 向けの SKILL とプラグインをインストールします。
claude plugin marketplace add typesafe-ai/skills
claude plugin install typesafe@typesafe-ai
アップデートは下記のコマンドで行います。
claude plugin marketplace update typesafe-ai
claude plugin update typesafe@typesafe-ai
インストール後は Claude Code を再起動するか /reload-plugins を実行します。
TypeSafe で function calling を実装する
TypeSafe の Function calling cookbook に実装方法が書かれています。OpenAI API などの function calling とは考え方が異なります。
自然文から「どの関数を、どの引数で呼ぶか」を決める処理を、TypeSafe の Choice と Noul で書く方法を、get_weather.py を TypeSafe と OpenAI の両方で書いて確認します。
- Python 3.12
- typesafe-sdk 0.7.0(モデルは
jev-1.13.0) - openai 3.16.2、モデルは
gpt-4o-mini、Chat Completions API、temperature は指定なし
cookbook のコードは model="jev-1.12" を指定していますが、この API では Unknown model: jev-1.12 と 400 で拒否されました。この記事のコードはモデルを指定せず、返ってきた jev-1.13.0 で動かしています。また cookbook の Dispatcher は docs に実装が見当たらなかったので、質問は直接組みました。
TypeSafe は引数を生成しない
OpenAI の function calling は、モデルが引数の JSON を生成します。TypeSafe は生成しません。かわりに、引数ごとに「候補から選ぶ」「はい/いいえ」の質問を作って1リクエストで投げ、返ってきた答えをコードで関数の引数に組み立てます。
呼ぶ関数の例は下記の通りです。引数は、取りうる値を明示的に決める必要があります。
City = Literal["tokyo", "osaka", "nagoya"]
Unit = Literal["celsius", "fahrenheit"]
def get_weather(city: City, unit: Unit = "celsius") -> str: ...
OpenAI の Function Calling
OpenAI の function calling ガイドの get_weather は、location が enum の無い文字列(説明は「City and country e.g. Bogotá, Colombia」)で、units だけが enum になっています。まずこの形に合わせて試しました。公式の例は両方の引数が required ですが、ここでは unit を省略できるようにしました。
なお公式ガイドの例は Responses API のフラットな形式です。この記事のコードは Chat Completions API で、function の下にスキーマをネストする形式を使っています。
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name, e.g. Tokyo. Supported: Tokyo, Osaka, Nagoya."},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature unit."},
},
"required": ["city"],
}
city は自由な文字列なので、返ってくる値は決まっていません。get_weather は Literal["tokyo", "osaka", "nagoya"] を受け取るので、呼ぶ前にコード側で値を収める処理が要ります。
city に enum を付けると、候補が決まります。省略できる unit は required から外し、説明で既定値を伝えます。
"city": {"type": "string", "enum": ["tokyo", "osaka", "nagoya"], "description": "City name"},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit. Omit if the user does not specify one (defaults to celsius).",
},
...
"required": ["city"],
これでも、返った引数がスキーマに従う保証はありません。strict: True を付けると、生成が enum に従うことが保証されます。かわりに全プロパティを required にする仕様なので、省略できる unit は null を許す形にします。
"strict": True,
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "enum": ["tokyo", "osaka", "nagoya"]},
"unit": {"type": ["string", "null"], "enum": ["celsius", "fahrenheit", None]},
},
"required": ["city", "unit"],
"additionalProperties": False,
}
TypeSafe の Function Calling
質問を4つ作成し、function calling が決めることに対応させます。
| function calling で決めること | 質問 | 型 |
|---|---|---|
| どの関数を呼ぶか(呼ばないか) | is_weather |
Noul |
必須の引数 city の値 |
city |
Choice |
省略できる引数 unit の値 |
unit |
Choice |
unit を省略するかどうか |
unit_given |
Noul |
(記事のコードでは読みやすさのために質問名を is_weather / unit_given としています。実際に実行したコードでは __tool__ / unit? です。)
OpenAI では、関数の選択も引数の値も、1回の生成の中でモデルが決めます。TypeSafe は1問に1つの判断しか答えさせないので、決めることの数だけ質問が要ります。
is_weather が「どの関数か」にあたります。関数が get_weather の1つだけなので、選択肢が「呼ぶ/呼ばない」の2つになり、Noul で足ります。関数が複数あるときは、cookbook のように、各関数の説明を選択肢にした Choice を1つ置きます。cookbook は、全関数の引数の質問も同じリクエストに入れ、選ばれた関数の答えだけを読む形にしています。この記事では関数を1つしか扱っていないので、複数関数の版は試していません。
残りの3問が引数を決めます。city は必須で、値は Choice で選びます。unit は省略できるので、値を選ぶ Choice と、言及したかどうかを聞く unit_given の2問になります。Choice は必ずどれかを返すので、「言っていない」場合を表せません。そのため unit_given が別に要ります。必須の引数なら、値の質問1問で済みます。
from typesafe_sdk import Choice, Noul, TypeSafeClient
QUESTIONS = {
"is_weather": Noul(instructions="Is the user asking for the current weather or temperature of a city?"),
"city": Choice(
instructions="Which city does the user want the weather for?",
criteria={
"tokyo": "Tokyo (東京)",
"osaka": "Osaka (大阪)",
"nagoya": "Nagoya (名古屋)",
"none": "no city is named, or the city is none of Tokyo, Osaka, Nagoya",
},
),
"unit": Choice(
instructions="Which temperature unit does the user want?",
criteria={"celsius": "Celsius (摂氏, °C)", "fahrenheit": "Fahrenheit (華氏, °F)"},
),
"unit_given": Noul(instructions="Does the user say which temperature unit (Celsius or Fahrenheit) they want?"),
}
Choice の criteria は、キーが返り値、値が選択肢の意味の説明になります。キーをそのまま関数の引数に渡せるよう、Literal の値と揃えておくと、あとで変換が要りません。質問は入力文の意味で判定されるので、「東京」「Tokyo」のような表記を列挙する必要はありません。
答えの読み取りと関数呼び出しは次のようなプログラムになります。
client = TypeSafeClient()
def call(text: str) -> str | None:
a = client.system_one(state={"command": text}, questions=QUESTIONS).answers
if a["is_weather"].noul < 0.5:
return None # 天気の話ではない
city = a["city"].choice
if city == "none":
return None # 都市が未指定、または対応外
unit = a["unit"].choice if a["unit_given"].noul >= 0.5 else "celsius"
return get_weather(city, unit)
unit は、unit_given が false に寄っているときは unit の答えを使わず、関数側の既定値にします。unit_given は cookbook の stated に相当する質問です。
None を返した場合に何をするかは、呼び出し側で決めます。
実験結果
6つの入力を、OpenAI の strict 版と TypeSafe に1回ずつ渡しました。strict 版は返る値が enum の中に収まるので、TypeSafe と条件が近いためです。動作確認であり、精度や速度の比較ではありません。下のログは1回目の実行です。
uv run python -m typesafe_examples.get_weather
"大阪の気温を摂氏で教えて"
OpenAI strict: get_weather({'city': 'osaka', 'unit': 'celsius'}) -> osaka: 24°C
OpenAI 通常 : get_weather({'city': 'osaka', 'unit': 'celsius'}) -> osaka: 24°C
OpenAI 自由 : (引数が Literal の外) {'city': 'Osaka', 'unit': 'celsius'}
TypeSafe: get_weather(city='osaka', unit='celsius') conf=0.98
city: osaka=1.00 tokyo=0.00 nagoya=0.00 none=0.00 | p(tool)=0.98 p(unit stated)=0.98
-> osaka: 24°C
"What's the weather in Nagoya in fahrenheit?"
OpenAI strict: get_weather({'city': 'nagoya', 'unit': 'fahrenheit'}) -> nagoya: 70°F
OpenAI 通常 : get_weather({'city': 'nagoya', 'unit': 'fahrenheit'}) -> nagoya: 70°F
OpenAI 自由 : (引数が Literal の外) {'city': 'Nagoya', 'unit': 'fahrenheit'}
TypeSafe: get_weather(city='nagoya', unit='fahrenheit') conf=0.98
city: nagoya=1.00 none=0.00 osaka=0.00 tokyo=0.00 | p(tool)=0.98 p(unit stated)=0.99
-> nagoya: 70°F
"東京の天気は?"
OpenAI strict: get_weather({'city': 'tokyo', 'unit': 'celsius'}) -> tokyo: 22°C
OpenAI 通常 : get_weather({'city': 'tokyo'}) -> tokyo: 22°C
OpenAI 自由 : (引数が Literal の外) {'city': 'Tokyo'}
TypeSafe: get_weather(city='tokyo', unit='celsius' [default]) conf=0.98
city: tokyo=1.00 nagoya=0.00 osaka=0.00 none=0.00 | p(tool)=0.98 p(unit stated)=0.02
-> tokyo: 22°C
"札幌の天気を教えて"
OpenAI strict: get_weather({'city': 'tokyo', 'unit': 'celsius'}) -> tokyo: 22°C
OpenAI 通常 : (呼び出しなし) '現在、札幌の天気を取得することができませんが、他の日本の都市(東京、大阪、名古屋)の天気をお伝えすることはできます。どの都市の天気を知りたいですか?'
OpenAI 自由 : (引数が Literal の外) {'city': '札幌'}
TypeSafe: (呼び出しなし) city が対象外/未指定 → 聞き返す p(none)=1.00
"今日の天気は?"
OpenAI strict: (呼び出しなし) 'どの都市の天気を知りたいですか?東京、大阪、名古屋から選んでください。'
OpenAI 通常 : (呼び出しなし) 'どの都市の天気を知りたいですか?東京、大阪、名古屋の中から教えてください。'
OpenAI 自由 : (呼び出しなし) '調べたい都市名を教えてください。東京、大阪、名古屋から選択できます。'
TypeSafe: (呼び出しなし) city が対象外/未指定 → 聞き返す p(none)=0.99
"ピザを注文して"
OpenAI strict: (呼び出しなし) 'どの種類のピザを注文しますか?また、トッピングやサイズの希望はありますか?'
OpenAI 通常 : (呼び出しなし) 'ピザの注文をお手伝いすることはできませんが、あなたの好みや特別なリクエスト(トッピング、サイズ、スタイルなど)を教えていただければおすすめのピザを提案することができますよ!どのようなピザが食べたいですか?'
OpenAI 自由 : (呼び出しなし) 'どのような種類のピザを注文したいですか?(例:マルゲリータ、ペパロニ、シーフードなど)また、サイズやトッピングの要望も教えてください。'
TypeSafe: (呼び出しなし) get_weather の対象外 p(tool)=0.01
OpenAI strict 版と TypeSafe 版の対比表(1回目):
| 入力 | OpenAI strict | TypeSafe |
|---|---|---|
| 大阪の気温を摂氏で教えて |
osaka / celsius
|
osaka / celsius
|
| What's the weather in Nagoya in fahrenheit? |
nagoya / fahrenheit
|
nagoya / fahrenheit
|
| 東京の天気は? |
tokyo / celsius
|
tokyo / 既定の celsius
|
| 札幌の天気を教えて | tokyo / celsius で呼んだ |
呼ばない(city が none = 1.00) |
| 今日の天気は? | 呼ばない(都市を聞き返す) | 呼ばない(city が none = 0.99) |
| ピザを注文して | 呼ばない(注文内容を聞き返す) | 呼ばない(is_weather = 0.01) |
関数を呼ぶ場合は city / unit の値を書いています。「呼ばない」は、OpenAI では tool call を返さず通常の文章で返答したこと、TypeSafe では call が None を返したことです。
5件は関数を呼ぶかどうかも引数の値も一致しました。「札幌の天気を教えて」だけは、strict 版が対応外の都市を tokyo に丸めて関数を呼んでいます。
同じスクリプトをさらに2回実行したところ、この行は変わりました。
| 実行 | OpenAI strict | OpenAI 通常 |
|---|---|---|
| 1回目 |
tokyo で呼んだ |
呼ばない |
| 2回目 | 呼ばない | 呼ばない |
| 3回目 | 呼ばない | nagoya で呼んだ |
札幌以外の5件は、3回とも strict・通常とも同じ結果でした。TypeSafe は6件とも3回同じ判断で、札幌の none は3回とも 1.00 でした。
違いは出力の形にも出ました。OpenAI が呼ばなかったときは文章が返るので、コードから分かるのは「呼ばれなかった」ことだけです。TypeSafe は is_weather や city の確率が残ります。「東京の天気は?」では、strict 版の unit は celsius で、ユーザーが言った値か補った値かは見分けられません。TypeSafe は unit_given が 0.02 で、言及していないと分かります。
none を入れないと札幌が東京になる
最初の city は tokyo / osaka / nagoya の3択でした。この状態で「札幌の天気を教えて」を投げると tokyo が 0.97、「今日の天気は?」も tokyo が 1.00 で返り、関数が呼ばれました。分類は候補の中から選ぶので、候補に無い答えは出せません。上の結果は、none を足した後のものです。
OpenAI も同じ形の失敗をしました。enum を付けた strict 版と通常版は、札幌に対して tool を呼ばない選択もできますが、呼ぶか呼ばないかは毎回モデルが決めます。上の3回では、strict 版が1回目に tokyo、通常版が3回目に nagoya で呼びました。enum は返る値を候補内に収めるだけで、「候補に無い」と判断する仕組みは含みません。
どちらの場合も、対応外を検出する経路を自分で用意する必要があります。TypeSafe では none の確率が、その経路として残ります。
確信度を使う
各回答には confidence(Choice)や確率(Noul)が付きます。cookbook は、呼び出し全体の確信度を、使った判断のうち最も低いものにしています。1つ間違えれば呼び出し全体が壊れるので、積ではなく最小を使う、という説明でした。この例でも最小を使いました。
Noul には confidence がありません。そこで、0.5 に近いほど不確かだとみなして max(p, 1-p) に換算し、Choice の confidence と比べています。この換算は筆者の判断で、cookbook やドキュメントの定義ではありません。
def certainty(p: float) -> float:
return max(p, 1 - p)
conf = min(certainty(p_tool), city.confidence, certainty(stated), *([a["unit"].confidence] if unit_given else []))
if conf < 0.8:
... # 人に確認する
0.8 に根拠は無く、実データで決める値です。今回の6件では、関数を呼んだ3件の確信度が3回とも 0.98 で、この分岐は1度も実行されていません。
向かない引数
TypeSafe の質問になるのは、取りうる値が決まっている引数(Literal、bool、list[Literal])です。query: str や日付、金額のような自由な値は、質問が作れません。cookbook でも、int や str の引数には質問を作らず、関数の既定値のままにすると説明されています。これらを自然文から取り出したいなら、OpenAI のような引数の生成が要ります。この記事では試していません。
まとめ
Jev を Playground、API、SDK から動かし、Noul、Choice、Score による判定を確認しました。Function Calling を OpenAI と書き比べて、Jev は値を生成するのではなく、事前に定義した候補について判断するモデルであることも確認しました。
分類・判定・スコアリングは新しい処理ではありません。従来はルールベースや、教師データから学習した専用の機械学習モデル、近年では LLM でも実装されてきた領域です。Jev はそこに System One Model という別の実装方法を加えたものと考えています。
使い分けは、筆者の整理として次のようにしました。公式ドキュメントの分類ではありません。
| 条件 | 選択肢 |
|---|---|
| 条件を明確にコードで表現できる | ルールベース |
| 問題が固定され、教師データを用意して専用モデルを運用できる | 機械学習 |
| 自然言語などの意味理解が必要で、答えの候補を事前に定義できる | System One Model |
| 回答文やコード、任意の引数など、開いた出力が必要 | LLM |
今回試した範囲では、city: Literal[...] のような閉じた値は Choice で扱えました。query: str のような任意の値は扱えません。「該当なし」の候補、確信度の閾値、最終的な処理は、コード側で設計する必要があります。
この検証は1つの関数、6つの入力、各1〜3回の実行です。精度や速度を比較したものではありません。
ブログ記事内で利用したソースコード


