本文へスキップ

API リファレンス ​

decidekit 0.1.1。次のエントリーと CLI があります。

  • decidekit:createDecider、saveHead、loadHead と型。Node とブラウザで動きます。
  • decidekit/browser:createWorkerDecider、saveHead、loadHead と同じ型。ブラウザ専用です(Web Worker を起動します)。
  • decidekit/react:useDecider と useDecision。
  • decidekit/switch:switchDecider と textOf。
  • decidekit/typesafe:typesafeClient と choice。TypeSafe の SDK の形です。
  • npx decidekit:コマンドラインの train と decide(Node 22.18 以降)。

createDecider(options?) ​

ts
function createDecider(options?: DeciderOptions): Promise<{
  model: string;
  decide: ...;      // see decide()
  decideMany: ...;  // see decideMany()
  train: ...;       // see train()
  embed: (texts: readonly string[]) => Promise<number[][]>;
}>;

transformers.js で文エンコーダーを読み込み、準備ができると解決します。最初の呼び出しで Hugging Face(または modelHost)からモデルファイルをダウンロードします。以後はキャッシュ(ブラウザのキャッシュか、Node での transformers.js のキャッシュディレクトリ)から読み込みます。

エンコーダーは呼び出したスレッドで動きます。ブラウザでは代わりに createWorkerDecider() を使ってください。そうしないと処理中にページが固まります。

ts
import { createDecider } from "decidekit";

const d = await createDecider();
const r = await d.decide("my card never arrived", ["card_arrival", "refund", "other"]);

DeciderOptions ​

オプション型デフォルト働き
modelstring"debuffjp/decide-small"ONNX の重みを持つ文埋め込みモデルの Hugging Face ID。0.1.1 より前のデフォルト ID は "Xenova/bge-small-en-v1.5" で、重みは同じです。この ID と "Xenova/multilingual-e5-small" は今も動き、"debuffjp/decide-small" と "debuffjp/decide-multilingual" に解決されます。0.1.0 より前のデフォルトは "Xenova/all-MiniLM-L6-v2" でした。それで訓練したヘッドを使い続けるなら渡してください。
device"wasm" | "webgpu" | "cpu"transformers.js の選択(ブラウザは wasm、Node は cpu)エンコーダーを動かす場所。
dtype"q8" | "fp16" | "fp32""q8"エンコーダーの重みの精度。
modelHoststringなし(Hugging Face)0.1.1 で追加。モデルファイルを <modelHost>/<model>/ から配信します。ページまたはプロセス全体に適用されます。セルフホストを参照してください。
onProgress(p: { file: string; loaded: number; total: number }) => voidなしモデルファイルのダウンロード中に呼ばれます。ダウンロード時だけで、キャッシュからの読み込みでは呼ばれません。
temperaturenumberbge-small は 0.03(説明付きラベルは 0.025)、他のモデルは 0.05(0.1.0 より前はすべて 0.05)ゼロショットの類似度に使う softmax の温度。設定すると名前と説明付きラベルに同じ値を使います。独自の温度を持つ訓練済みヘッドには影響しません。

返される model は使用中のモデル ID です。古い ID は新しい ID に解決されます。createDecider({ model: "Xenova/bge-small-en-v1.5" }) の model は "debuffjp/decide-small" です。訓練済みヘッドはこの ID を記録していて、別のモデルで訓練したヘッドを decide() は拒否します。古い ID を記録したヘッドは新しい ID で訓練したものとして扱います。0.1.1 から、2 つの Decide モデルのダウンロードはリビジョンに固定されます。Hugging Face 側の変更でインストール済みのバージョンの判定は変わりません。

decide(text, using, options?) ​

ts
decide<const L extends string>(text: string, using: Choices<L>): Promise<Decision<L>>;
decide<const L extends string>(text: string, using: Choices<L>, options: DecideOptions): Promise<Decision<L, L | null>>;

text に対して選択肢を 1 つ選びます。using は次のいずれかです。

  • ラベルの配列(ゼロショット)。各ラベルは読みやすい単語としてテキストと比べます。_ と - は空白になり、camelCase は分割され、小文字になります。そのため card_arrival と cardArrival はどちらも "card arrival" と読みます。
  • ラベルから説明へのレコード(ゼロショット)。読みやすい名前と、書かれたとおりの説明の両方をテキストと比べ、各ラベルは良いほうのスコアになります。choice が返すのはキーです。
  • 訓練済みの Head。ヘッドの重みでテキストのスコアを出し、当てはめた温度で確率にします。

const L のおかげで、リテラルの配列は as const なしでリテラル型を保ちます。choice の型は string ではなく "card_arrival" | "refund" | "other" です。

options.minConfidence を設定すると、confidence がそれを下回るときの choice は null になり、戻り値の型に null が含まれます。confidence と probabilities はどちらの場合も入っています。

ts
const r = await d.decide(text, { refund: "wants their money back", bug: "something is broken" });
r.choice; // "refund" | "bug"

const s = await d.decide(text, head, { minConfidence: 0.9 });
if (s.choice === null) sendToAPerson(text);

ゼロショットの選択肢の埋め込みは、選択肢のテキストのリストをキーにしてデサイダーごとにキャッシュされます。同じ選択肢で decide() を再び呼ぶと、エンコードするのは新しいテキストだけです。

次の例外を投げます。

  • decidekit: decide() needs at least 2 options, got N:配列やレコードの要素が 2 未満のとき。
  • decidekit: this head was trained on <head model>, but the decider uses <model>. Pass { model: "<head model>" } to createDecider.:ヘッドの model がデサイダーと一致しないとき。

decideMany(texts, using, options?) ​

ts
decideMany<const L extends string>(texts: readonly string[], using: Choices<L>): Promise<Decision<L>[]>;
decideMany<const L extends string>(texts: readonly string[], using: Choices<L>, options: DecideOptions): Promise<Decision<L, L | null>[]>;

多数のテキストに対する decide() です。結果は texts の順に返ります。テキストは 64 件のバッチでエンコードするので、Node では 1 件ずつ decide() を呼ぶよりずっと速くなります。訓練済みヘッドで 1 テキストあたり 0.8-1.5 ms です。例外は decide() と同じです。

ts
const rs = await d.decideMany(reviews, head);
const bugs = reviews.filter((_, i) => rs[i].choice === "bug");

train(examples, options?) ​

ts
train<const L extends string>(examples: readonly Example<L>[], options?: TrainOptions): Promise<Head<L>>;

ラベル付きの例でヘッドを訓練し、それを返して解決します。ヘッドのラベルは examples の中の重複のないラベルを並べ替えたものです。手順は仕組みで、必要な例の数はヘッドを訓練するで説明しています。

同じデサイダーで再訓練すると、前回の訓練セットの埋め込みを再利用します。エンコードするのは前回の呼び出しになかったテキストだけです。キャッシュが持つのは直前のセットだけです。

ts
const head = await d.train([
  { text: "where is my card", label: "card_arrival" },
  { text: "I want my money back", label: "refund" },
  // ...
]);

TrainOptions ​

オプション型デフォルト働き
labelNamesbooleantrue各ラベルの読みやすい名前を開始点のヒントに使います(追加の 1 例として数えます。0.1.0 より前は 3 例)。c17 のような意味のない ID ではオフにしてください。
solver"adam" | "sgd"ラベルごとに 8 例以上で adam、それ以外は sgdヘッドの訓練方法。0.2 で追加(それより前は常に sgd)。solver なしで learningRate か l2 を設定すると sgd になります。
epochsnumberadam は 100、sgd は 60例を通す最大の回数。取り分けた例で最良のエポックを残します。0.2 より前のデフォルトは 60 です。
learningRatenumberadam は 0.05、sgd は 2ステップ幅。
l2numberadam は 0、sgd は 1e-4重み減衰。
holdoutnumber0.1エポック数の選択と温度の当てはめのために取り分ける例の割合。その割合が 200 例未満なら、代わりに 5 分割を使います(すべての例が 1 回ずつ取り分けられます)。
seednumber7分割とシャッフルのシード。同じ実行を正確に再現できます。
calibrateOnExample<L>[]なし正しいとわかっている例で、温度の当てはめにだけ使います。withFallback() 経由の LLM のように、訓練例の出どころがときどき間違えるなら、ラベルごとに約 2 例を渡してください。ラベルはヘッドのものに限ります。
onEpoch(epoch: number, holdoutAccuracy: number) => voidなし取り分けを使う実行のエポックごとに、取り分けた例での精度とともに呼ばれます。epoch は 0 から数えます。5 分割では分割ごとに実行されるので、epoch は分割ごとに 0 に戻ります。全例での最後の再訓練では呼ばれません。

次の例外を投げます。

  • decidekit: example N needs a string text and a non-empty string label, got {...}:最初の不正な例に対して。
  • decidekit: train() needs at least 2 labels, got N。

訓練は 64 件のバッチの間でイベントループに処理を譲るのでページは応答を続けますが、その前のエンコードは譲りません。ブラウザではワーカーで訓練してください。

embed(texts) ​

ts
embed(texts: readonly string[]): Promise<number[][]>;

エンコーダー単体です。テキストごとに、平均プーリングして単位長に正規化したベクトルを 1 つ返します(デフォルトのモデルで 384 個の数)。独自の類似度検索や、どの例が近くにあるかの確認に使えます。

ts
const [a, b] = await d.embed(["refund please", "I want my money back"]);
const similarity = a.reduce((s, x, i) => s + x * b[i], 0); // cosine, since both have length 1

createWorkerDecider(options?, worker?) ​

ts
import { createWorkerDecider } from "decidekit/browser";

function createWorkerDecider(options?: DeciderOptions, worker?: Worker): Promise<{
  decide: ...;      // same overloads as createDecider's
  decideMany: ...;
  train: <const L extends string>(examples: readonly Example<L>[], options?: TrainOptions) => Promise<Head<L>>;
  terminate: () => void;
}>;

Web Worker の中で動く createDecider() です。エンコーダーの処理中もページは描画と入力の処理を続けます。同じ DeciderOptions を受け取り、onProgress と onEpoch はワーカーから中継されます。ワーカーがモデルを読み込むと解決します。

createDecider() との違いは次のとおりです。

  • 結果に embed と model はありません。
  • terminate() はワーカーを止め、保留中の呼び出しをすべて拒否します("decidekit: the worker was terminated")。
  • ワーカーで投げられたエラーは、元のメッセージを持つ Error として返ります。
  • 各呼び出しにメッセージのやり取りで約 1 ms かかります。

worker のデフォルトは new Worker(new URL("./worker.js", import.meta.url), { type: "module" }) です。バンドラーに別のワーカー URL が必要なら、自分で渡してください。ブラウザで使うとバンドラーを参照してください。

ts
const d = await createWorkerDecider({
  onProgress: ({ loaded, total }) => (bar.value = loaded / total),
});
const r = await d.decide(input.value, ["question", "bug_report", "praise"]);

describeLabels(labels, complete, options?) ​

LLM に 1 回だけ尋ねて、各ラベルの 1 文の説明を、そのラベルの典型的なメッセージとして書いてもらいます。decide() に渡せる Record<L, string> を返します。complete(prompt) はあなたの LLM 呼び出しで、返信のテキストを返します。返信に抜けたラベルには読みやすい名前が入ります。options.context はテキストが何かを伝えます("message to a bank's support chat")。dev 分割では、説明でゼロショットの精度が 7 から 10 ポイント上がりました。

describePrompt(labels, options?) はプロンプトを返し、parseDescriptions(labels, reply) は返信を読みます。LLM の呼び出しを自分で(バッチジョブや別のプロセスで)行いたいときに使います。

saveHead(head) ​

ts
function saveHead(head: Head): string;

ヘッドを JSON 文字列へ変換します。重みとバイアスは base64 です。ファイルに書き出して、アプリと一緒に配信してください。

ts
import { writeFile } from "node:fs/promises";
await writeFile("intent.head.json", saveHead(head));

loadHead(json) ​

ts
function loadHead<L extends string = string>(json: string): Head<L>;

saveHead() が書いた文字列をパースします。ラベルの型は string です。ラベルの型を渡せばそれが付きます。たとえば decidekit train --types が書き出す型です。

ts
import { loadHead } from "decidekit/browser";
import type { Label } from "./intent.head.labels.ts";

const head = loadHead<Label>(await (await fetch("/intent.head.json")).text());

loadHead はラベルを L と照合しません。不正な入力には JSON.parse や atob が投げる例外をそのまま投げます。ヘッドは JSON.parse() ではなく loadHead() に通してください。Float32Array の重みを持たないパース済みオブジェクトはヘッドと認識されません。

saveHead と loadHead は decidekit と decidekit/browser の両方からエクスポートされています。ページでは decidekit/browser からインポートしてください。このエントリーならエンコーダーがバンドルに入りません(482 KB に対して 3.6 KB)。

型 ​

Decision ​

ts
interface Decision<L extends string, C extends L | null = L> {
  choice: C;                 // the most likely option; null when below minConfidence
  confidence: number;        // probability of the top option, 0 to 1
  probabilities: Record<L, number>;  // probability of every option, sums to 1
  similarity?: number;       // zero-shot only: best cosine similarity to any option's name or description
}

テキストがどの選択肢の話でもないと similarity は低くなります。bge-small では、話題外のテキストはおおむね 0.5 未満、範囲内のテキストは 0.7 を超えました。

choice が null のときでも、confidence は最上位の選択肢の確率です。

ts
interface Head<L extends string = string> {
  model: string;             // encoder the head was trained on
  labels: readonly L[];      // sorted
  dim: number;               // embedding size (384 for the default model)
  weights: Float32Array;     // labels.length x dim, row-major
  bias: Float32Array;        // one per label
  temperature: number;       // fitted on held-out examples
}

Choices ​

ts
type Choices<L extends string> = readonly L[] | Readonly<Record<L, string>> | Head<L>;

DecideOptions ​

ts
interface DecideOptions {
  minConfidence?: number;    // below this, choice is null
}

Example ​

ts
interface Example<L extends string> {
  text: string;
  label: L;
}

TrainOptions, DeciderOptions ​

train() と createDecider() の表を参照してください。

CLI ​

sh
npx decidekit train <examples.csv|.jsonl> [-o head.json] [--model <hf-id>] [--no-label-names] [--types]
npx decidekit decide <head.json> "<text>" [--min-confidence <0-1>]
npx decidekit --help

train ​

例を読み、ヘッドを訓練し、saveHead() で書き出します。

  • 入力:1 行に 1 つの {"text": ..., "label": ...} オブジェクトを持つ .jsonl ファイル、または CSV として読むその他のファイル。CSV には text と label の列を持つヘッダー行が必要です(他の列は無視します)。フィールドは " で囲めて、引用符の中の "" はリテラルの引用符です。

  • -o, --out <file>:ヘッドの書き出し先。デフォルトは head.json。

  • --model <id>:訓練に使うエンコーダー。デフォルトは debuffjp/decide-small(0.1.1 より前は Xenova/bge-small-en-v1.5、0.1.0 より前は Xenova/all-MiniLM-L6-v2)。

  • --no-label-names:labelNames: false と同じです。

  • --types:<out without .json>.labels.ts も書き出します。

    ts
    // Written by decidekit train --types. Labels of the head next to this file.
    export const labels = ["card_arrival","refund"] as const;
    export type Label = (typeof labels)[number];

例の数、ラベルの数、ラベルごとの最少の例数を stderr に出力し、3 例未満のラベルがあると警告します。最後にファイルサイズ、訓練時間、当てはめた温度を出力します。他の TrainOptions はデフォルトのままです。

sh
npx decidekit train tickets.csv -o support.head.json --types

decide ​

ヘッドを読み込み、ヘッドのモデルでデサイダーを作り、JSON を 1 行 stdout に出力します。

sh
npx decidekit decide support.head.json "my card never arrived" --min-confidence 0.9

出力の形は {"choice":"card_arrival","confidence":<number>} です。

confidence は小数第 4 位に丸めます。--min-confidence を下回ると choice は null です。

終了コード:成功で 0、エラーで 1(メッセージは stderr へ)、使い方の誤りで 2。

withFallback() と decidekit/react ​

withFallback(decider, head, options) とその NONE ラベル(decidekit と decidekit/browser から)は LLM へのフォールバックで説明しています。useDecider() と useDecision()(decidekit/react から)は React で説明しています。

decidekit/typesafe ​

ローカルのデサイダーで使う TypeSafe の SDK の形で、Choice の質問に対応します。違いはガイドを参照してください。

typesafeClient(decider) ​

createDecider() または createWorkerDecider() のデサイダーを包みます。client.systemOne({ state, questions }) は、質問ごとの答えを持つ { answers } で解決します。 各答えには choice、probabilities、confidence があります。confidence は TypeSafe の式 (n * top - 1) / (n - 1) に従うので、decide() の値より低く出ます。

choice(instructions, criteria) ​

Choice の質問です。criteria は選択肢の名前から説明(なければ null)へのマップ、または訓練済みヘッドです。instructions は無視します。decidekit は状態と選択肢を比べるからです。

noul と score もエクスポートしていますが、ローカルでは対応していないので例外を投げます。

decidekit/switch ​

switchDecider(decider, options?) ​

createDecider() のデサイダーを switch の Decider として包みます。choice、bool、score の質問にローカルで答えます。画像は無視します。

  • text(state, question, id):質問に対して Decide が読むもの。デフォルトは textOf(question.instructions, state)。
  • heads:質問 ID ごとの訓練済みヘッド。
  • teacher:選択が minConfidence(デフォルト 0.9)を下回るときに尋ねる switch のデサイダー。その答えは、繰り返す質問ごとに 8 回答ずつヘッドを訓練します。

textOf(instructions, state) ​

指示のあとに、状態を key: value の行で続けたものです。switch のガイドを参照してください。