リクエストとレスポンスを型定義する
Zod や TypeScript の判別可能な Union 型(Discriminated Union)を定義し、Jev の返却値を製品ドメインの型へ確実に変換します。
明確な Fallback 型を用意する
タイムアウトや低信頼度時に undefined を返すのではなく、{ status: "fallback", reason: "low_confidence" } のような明確なバリアントを定義します。
API キーの漏洩を完全に防ぐ
認証トークンやモデル設定はすべてサーバーサイドの環境変数で管理し、クライアントには決して露出させない構成を徹底します。
当サイトの /api/jev/evaluate リレーについて
当サイトの Playground と Builder が呼ぶ /api/jev/evaluate は、TypeSafe のエンドポイントではなく当サイトのデモ用リレーです。当サイト独自のレート制限、Turnstile 検証、日次クォータがかかります。スキーマの試用には便利ですが、本番コードは自分の TYPESAFE_API_KEY で https://api.typesafe.ai/v1/systemone を直接呼ぶか、公式 SDK の base URL を OpenRouter の互換エンドポイント(https://openrouter.ai/api)に向けてください。
typescript / @typesafe-ai/sdk
// npm install @typesafe-ai/sdk (Node.js 22 以上)
// export TYPESAFE_API_KEY="..." # console.typesafe.ai/keys で取得
import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";
const ticket =
"20 分ほど前から API 連携がすべてのリクエストで 500 を返し、直るまで注文処理が一切できません。";
const client = new TypeSafeClient({ defaultModel: "jev-latest" });
const response = await client.systemOne({
state: ticket,
questions: {
department: choice("このチケットはどのチームが担当すべきか", {
billing: "支払い・サブスクリプションの問題",
technical: "不具合・連携の問題",
sales: "料金・アカウントの相談",
}),
frustration: score("顧客のいら立ちの度合い", [
"落ち着いて事実を述べている",
"不満はあるが礼儀は保っている",
"非常に怒っていて語気が強い",
]),
is_urgent: noul("このメッセージは緊急性・時間的制約を示しているか"),
},
});
console.log(response.answers.department.choice); // "billing" | "technical" | "sales"
console.log(response.answers.frustration.score);
console.log(response.answers.is_urgent.noul);typescript / SDK なし(fetch)
// SDK を使わない場合: 同じ契約を POST https://api.typesafe.ai/v1/systemone へ
type ChoiceAnswer = { type: "choice"; choice: string; confidence: number };
type NoulAnswer = { type: "noul"; noul: number };
const res = await fetch("https://api.typesafe.ai/v1/systemone", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.TYPESAFE_API_KEY}`,
},
body: JSON.stringify({
model: "jev-latest",
state: ticket,
questions: {
department: {
type: "choice",
instructions: "このチケットはどのチームが担当すべきか",
criteria: {
billing: "支払い・サブスクリプションの問題",
technical: "不具合・連携の問題",
sales: "料金・アカウントの相談",
},
},
is_urgent: {
type: "noul",
instructions: "このメッセージは緊急性・時間的制約を示しているか",
},
},
}),
});
if (!res.ok) throw new Error(`Jev request failed: ${res.status}`);
const data = (await res.json()) as {
model: string;
answers: { department: ChoiceAnswer; is_urgent: NoulAnswer };
};
// しきい値の判断は自分のコードに置く(モデルは確率と信頼度を報告するだけ)
const lane =
data.answers.department.confidence >= 0.85 &&
data.answers.is_urgent.noul < 0.5
? `auto:${data.answers.department.choice}`
: "human_review";