# 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 Promise;
}>;
```

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

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

```ts
import { createDecider } from "decidekit";

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

### DeciderOptions

| オプション | 型 | デフォルト | 働き |
|---|---|---|---|
| `model` | `string` | `"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"` | エンコーダーの重みの精度。 |
| `modelHost` | `string` | なし（Hugging Face） | 0.1.1 で追加。モデルファイルを `//` から配信します。ページまたはプロセス全体に適用されます。[セルフホスト](/ja/decide/docs/guide-browser#host-the-model-files-yourself)を参照してください。 |
| `onProgress` | `(p: { file: string; loaded: number; total: number }) => void` | なし | モデルファイルのダウンロード中に呼ばれます。ダウンロード時だけで、キャッシュからの読み込みでは呼ばれません。 |
| `temperature` | `number` | bge-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(text: string, using: Choices): Promise>;
decide(text: string, using: Choices, options: DecideOptions): Promise>;
```

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

- **ラベルの配列**（ゼロショット）。各ラベルは読みやすい単語としてテキストと比べます。`_` と `-` は空白になり、camelCase は分割され、小文字になります。そのため `card_arrival` と `cardArrival` はどちらも "card arrival" と読みます。
- **ラベルから説明へのレコード**（ゼロショット）。読みやすい名前と、書かれたとおりの説明の両方をテキストと比べ、各ラベルは良いほうのスコアになります。`choice` が返すのはキーです。
- **訓練済みの [`Head`](#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 , but the decider uses . Pass { model: "" } to createDecider.`：ヘッドの `model` がデサイダーと一致しないとき。

### decideMany(texts, using, options?)

```ts
decideMany(texts: readonly string[], using: Choices): Promise[]>;
decideMany(texts: readonly string[], using: Choices, options: DecideOptions): Promise[]>;
```

多数のテキストに対する `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(examples: readonly Example[], options?: TrainOptions): Promise>;
```

ラベル付きの例でヘッドを訓練し、それを返して解決します。ヘッドのラベルは `examples` の中の重複のないラベルを並べ替えたものです。手順は[仕組み](/ja/decide/docs/how-it-works)で、必要な例の数は[ヘッドを訓練する](/ja/decide/docs/guide-training)で説明しています。

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

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

#### TrainOptions

| オプション | 型 | デフォルト | 働き |
|---|---|---|---|
| `labelNames` | `boolean` | `true` | 各ラベルの読みやすい名前を開始点のヒントに使います（追加の 1 例として数えます。0.1.0 より前は 3 例）。`c17` のような意味のない ID ではオフにしてください。 |
| `solver` | `"adam" \| "sgd"` | ラベルごとに 8 例以上で `adam`、それ以外は `sgd` | ヘッドの訓練方法。0.2 で追加（それより前は常に `sgd`）。`solver` なしで `learningRate` か `l2` を設定すると `sgd` になります。 |
| `epochs` | `number` | `adam` は `100`、`sgd` は `60` | 例を通す最大の回数。取り分けた例で最良のエポックを残します。0.2 より前のデフォルトは `60` です。 |
| `learningRate` | `number` | `adam` は `0.05`、`sgd` は `2` | ステップ幅。 |
| `l2` | `number` | `adam` は `0`、`sgd` は `1e-4` | 重み減衰。 |
| `holdout` | `number` | `0.1` | エポック数の選択と温度の当てはめのために取り分ける例の割合。その割合が 200 例未満なら、代わりに 5 分割を使います（すべての例が 1 回ずつ取り分けられます）。 |
| `seed` | `number` | `7` | 分割とシャッフルのシード。同じ実行を正確に再現できます。 |
| `calibrateOn` | `Example[]` | なし | 正しいとわかっている例で、温度の当てはめにだけ使います。`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;
```

エンコーダー単体です。テキストごとに、平均プーリングして単位長に正規化したベクトルを 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(examples: readonly Example[], options?: TrainOptions) => Promise>;
  terminate: () => void;
}>;
```

Web Worker の中で動く `createDecider()` です。エンコーダーの処理中もページは描画と入力の処理を続けます。同じ [`DeciderOptions`](#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 が必要なら、自分で渡してください。[ブラウザで使う](/ja/decide/docs/guide-browser)と[バンドラー](/ja/decide/docs/bundlers)を参照してください。

```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` を返します。`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(json: string): Head;
```

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

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

const head = loadHead(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）。

## 型 {#types}

### Decision

```ts
interface Decision {
  choice: C;                 // the most likely option; null when below minConfidence
  confidence: number;        // probability of the top option, 0 to 1
  probabilities: Record;  // 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` は最上位の選択肢の確率です。

### Head

```ts
interface Head {
  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 = readonly L[] | Readonly> | Head;
```

### DecideOptions

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

### Example

```ts
interface Example {
  text: string;
  label: L;
}
```

### TrainOptions, DeciderOptions

[train()](#trainoptions) と [createDecider()](#decideroptions) の表を参照してください。

## CLI

```sh
npx decidekit train  [-o head.json] [--model ] [--no-label-names] [--types]
npx decidekit decide  "" [--min-confidence ]
npx decidekit --help
```

### train

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

- 入力：1 行に 1 つの `{"text": ..., "label": ...}` オブジェクトを持つ `.jsonl` ファイル、または CSV として読むその他のファイル。CSV には `text` と `label` の列を持つヘッダー行が必要です（他の列は無視します）。フィールドは `"` で囲めて、引用符の中の `""` はリテラルの引用符です。
- `-o, --out `：ヘッドの書き出し先。デフォルトは `head.json`。
- `--model `：訓練に使うエンコーダー。デフォルトは `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`：`.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":}` です。

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

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

## withFallback() と decidekit/react {#withfallback-and-decidekit-react}

`withFallback(decider, head, options)` とその `NONE` ラベル（`decidekit` と `decidekit/browser` から）は [LLM へのフォールバック](/ja/decide/docs/guide-fallback)で説明しています。`useDecider()` と `useDecision()`（`decidekit/react` から）は [React](/ja/decide/docs/guide-react) で説明しています。

## decidekit/typesafe {#decidekit-typesafe}

ローカルのデサイダーで使う TypeSafe の SDK の形で、Choice の質問に対応します。違いは[ガイド](/ja/decide/docs/guide-typesafe)を参照してください。

### typesafeClient(decider) {#typesafeclient-decider}

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

### choice(instructions, criteria) {#choice-instructions-criteria}

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

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

## decidekit/switch

### switchDecider(decider, options?)

`createDecider()` のデサイダーを [switch](https://github.com/osolmaz/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 のガイド](/ja/decide/docs/guide-switch)を参照してください。