実装ガイド / TypeScript

Jev TypeScript 実装:サーバー境界での型保護

最終更新日: 2026-11-12

ブラウザに API キーを露出させることなく、サーバー側で Jev 呼び出しを抽象化し、堅牢な Union 型としてビジネスロジックに提供します。

リクエストとレスポンスを型定義する

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";

TypeScript 連携の関連情報