API リファレンス
モジュール関数
version()
laurus-wasm のビルドバージョン文字列(例: "0.12.1")を返します。
laurus の状態を OPFS に永続化するアプリケーションは、この値を
スタンプとして保存しておくことで、オンディスクフォーマットが
変わった可能性のある別ビルド由来の状態を検出できます(デモの
サンプルが実際にこの方式を使っています)。
Index
検索インデックスの作成・クエリを行うメインエントリポイントです。
静的メソッド
Index.create(schema?, walSyncPolicy?, commitPolicy?)
新しいインメモリ(一時)インデックスを作成します。
- 引数:
schema(Schema, 省略可) – スキーマ定義。省略時は空のスキーマが使用されますwalSyncPolicy(WalSyncPolicy, 省略可) – WAL の永続性ポリシー。省略すると デフォルトのレコードごとの同期を使用します。 WAL 同期ポリシー / 永続性を参照してください。commitPolicy(CommitPolicy, 省略可) – 自動コミットポリシー。省略すると デフォルト(manual: 呼び出し側がコミットを駆動)を使用します。 コミットポリシー / 自動コミットを参照してください。
- 戻り値:
Promise<Index>
Index.open(name, schema?, walSyncPolicy?, commitPolicy?)
OPFS で永続化されたインデックスを開くか、新規作成します。
- 引数:
name(string) – インデックス名(OPFS サブディレクトリ)schema(Schema, 省略可) – スキーマ定義、およびそのセッションで必要な embedder コールバック・ランタイムアナライザー。インデックスを初めて作成する とき、またはスキーマ永続化に対応する前に永続化されたインデックスを開くとき (後述)は必須。それ以降は省略可能です — フィールドスキーマ部分は インデックスのデータと一緒に永続化され、自動的に再読み込みされるためです。 スキーマが既に永続化された状態でschemaを渡した場合、そのフィールド定義 は無視され永続化済みのものが使われます。embedder コールバック・ランタイム アナライザーだけは永続化できないため、必要なセッションでは毎回渡す必要が ありますwalSyncPolicy(WalSyncPolicy, 省略可) – WAL の永続性ポリシー。省略すると デフォルトのレコードごとの同期を使用します。 WAL 同期ポリシー / 永続性を参照してください。commitPolicy(CommitPolicy, 省略可) – 自動コミットポリシー。省略すると デフォルト(manual: 呼び出し側がコミットを駆動)を使用します。 コミットポリシー / 自動コミットを参照してください。
- 戻り値:
Promise<Index> - 例外: この OPFS インデックスにスキーマ永続化対応前のデータが既に存在し、
かつ一度きりの移行を完了するための
schemaが渡されなかった場合に発生します (スキーマはその場で永続化され、以降の再オープンには不要になります)。
インスタンスメソッド
putDocument(id, document)
ドキュメントを置換(upsert)します。
- 引数:
id(string) – ドキュメント識別子document(object) – スキーマフィールドに対応するキーバリューペア
- 戻り値:
Promise<void>
addDocument(id, document)
ドキュメントバージョンを追加します(マルチバージョン RAG パターン)。
- 引数・戻り値:
putDocumentと同じ
putDocuments(docs)
バッチ upsert。ペアをバッチ全体で WAL fsync 1 回で順に適用します。1 バッチ内で重複した ID はデデュープされます(最後が勝ち)。最初の不正エントリで fail-fast し、適用済みの prefix はロールバックされません(再試行は冪等)。
- 引数:
docs(Array<[string, object]>) –[id, document]ペアの配列
- 戻り値:
Promise<void>
addDocuments(docs)
バッチチャンク追記。putDocuments と同様ですが、繰り返した ID は別バージョンとして蓄積されます。
- 引数・戻り値:
putDocumentsと同じ
getDocuments(id)
ドキュメントの全バージョンを取得します。
- 引数:
id(string) - 戻り値:
Promise<object[]>
deleteDocuments(id)
ドキュメントの全バージョンを削除します。
- 引数:
id(string) - 戻り値:
Promise<void>
commit()
書き込みをフラッシュし、変更を検索可能にします。
Index.open() で作成したインデックスの場合、OPFS にも自動永続化されます。
- 戻り値:
Promise<void>
flushWal()
インメモリエンジンの WAL に対して永続性バリアを強制します。wasm 固有の
注意点については WAL 同期ポリシー / 永続性 を
参照してください — 特にこれは OPFS への永続化を行いません。永続的な
永続化には commit() を呼び出してください。
- 戻り値:
Promise<void>
search(query, limit?, offset?, highlight?, rescore?)
DSL 文字列クエリで検索します。
- 引数:
query(string) – クエリ DSL(例:"title:hello")limit(number, デフォルト 10)offset(number, デフォルト 0)highlight(HighlightOptions, 省略可) – フィールドごとのハイライト済みフラグメントを要求する(Issue #1134)。詳細は下記のハイライトを参照。rescoreだけを使う場合はundefinedを渡すrescore(RescoreOptions, 省略可) – 上位の結果を、MultiVector フィールドに対する late interaction で並べ替える(Issue #1351)。詳細は下記の Late interaction による再採点を参照
- 戻り値:
Promise<SearchResult[]>
searchTerm(field, term, limit?, offset?, highlight?)
完全一致タームで検索します。
- 引数:
field(string) – フィールド名term(string) – 検索タームlimit,offset(number, 省略可)highlight(HighlightOptions, 省略可) –searchのhighlight引数と同じ
- 戻り値:
Promise<SearchResult[]>
searchDateTimeRange(field, min?, max?, limit?, offset?, highlight?)
DateTime フィールドを両端を含む範囲で検索します(Issue #1179)。クエリクラスは JS に公開されていないため、これは created_at:[2024-01-01 TO 2024-12-31] のような DSL の範囲指定(search() でも使用可能)に対応する、クエリオブジェクト不要の検索メソッドです。
- 引数:
field(string) – DateTime フィールド名min,max(string, 省略可) – 両端を含む境界。省略(またはnull/undefined)でその側を開放。DSL の日時リテラルのいずれか: RFC 3339("2024-01-01T09:00:00+09:00"、UTC に正規化)、オフセットなしの"YYYY-MM-DDTHH:MM:SS[.fff]"(UTC)、または"YYYY-MM-DD"(その日の 0 時 UTC)。Dateはdate.toISOString()で渡すlimit,offset(number, 省略可)highlight(HighlightOptions, 省略可) –searchのhighlight引数と同じ
- 戻り値:
Promise<SearchResult[]> - 例外: 境界が認識できる日時リテラルでない場合、エラーで reject されます
ハイライト
search、searchTerm、searchDateTimeRange は省略可能な highlight 引数を受け付けます。以下の形の単純なオブジェクトです。
interface HighlightOptions {
fields: string[];
fragmentSize?: number;
maxFragments?: number;
tag?: string;
cssClass?: string;
requireFieldMatch?: boolean;
}
必須なのは fields のみで、それ以外はエンジンの既定 HighlightConfig(タグ "mark"、約150文字のフラグメントを最大5件、requireFieldMatch: true)にフォールバックします。ハイライトは search/searchTerm に渡したクエリに従い、stored: true のテキストフィールドのみハイライト可能です — 保存されていない、テキスト型でない、またはマッチしなかったフィールドは結果の highlights オブジェクトに現れません。highlight を省略する(または undefined を渡す)と、すべての結果の highlights は空のままになります。
const results = await index.search("body:rust", 10, 0, {
fields: ["body"],
tag: "em",
});
// results[0].highlights => { body: ["<em>Rust</em> is a systems programming language"] }
searchVector(field, vector, limit?, offset?, rescore?)
ベクトル類似度で検索します。
- 引数:
field(string) – ベクトルフィールド名vector(number[]) – クエリ埋め込みベクトルlimit,offset(number, 省略可)rescore(RescoreOptions, 省略可) –searchのrescore引数と同じ
- 戻り値:
Promise<SearchResult[]>
searchVectorText(field, text, limit?, offset?, rescore?)
テキストで検索します(登録された埋め込み器で変換)。
- 引数:
field(string) – ベクトルフィールド名text(string) – 埋め込み対象テキストlimit,offset(number, 省略可)rescore(RescoreOptions, 省略可) –searchのrescore引数と同じ
- 戻り値:
Promise<SearchResult[]>
Late interaction による再採点
search、searchVector、searchVectorText は、末尾に省略可能な rescore 引数を受け付けます(Issue #1351)。1 段目(lexical・vector・ハイブリッド)の上位の結果を、MultiVector フィールドのトークンベクトルに対する ColBERT 型の late interaction(MaxSim)で並べ替えます。ほかの検索メソッドには rescore 引数はありません。引数は以下の形の単純なオブジェクトです。
interface RescoreOptions {
field: string;
vectors?: number[][];
text?: string;
windowSize?: number;
}
| キー | 型 | デフォルト | 説明 |
|---|---|---|---|
field | string | – | 採点に使う MultiVector フィールド |
vectors | number[][] | – | クエリのトークンベクトル。文書のトークンベクトルを作ったのと同じモデルで計算する |
text | string | – | クエリのテキスト。フィールドの "token_callback" Embedder が role "query" で埋め込む |
windowSize | number | 100 | 再採点する 1 段目の上位件数。1〜10,000 |
vectors と text はどちらか一方だけを指定します。そうでない場合は Invalid rescore options: set exactly one of vectors or text で例外になります。型の合わない値(vectors の数値でない要素など)も Invalid rescore options: ... で例外になり、未知のキーは無視されます。それ以外の値はエンジンが検索の実行時に検査し、不正な値は rescore: ... を含むエラーになります。たとえば、トークン単位の Embedder がないフィールドへの text、MultiVector ではないフィールド、範囲外の windowSize、次元の合わないクエリベクトルです。
上位 windowSize 件の結果は MaxSim の降順に並び、再採点した結果の score は MaxSim の値になります。window の外の結果(および window 内でフィールドにトークンベクトルを持たない結果)は、1 段目の順序とスコアのまま、再採点した結果の後に続きます。採点・並び順・ページングの詳細は Vector 検索 → Late Interaction による再採点 を参照してください。
// 事前計算したクエリのトークンベクトルで、lexical 検索の上位を再採点する。
const results = await index.search("title:rust", 10, 0, undefined, {
field: "tokens",
vectors: [[1, 0], [0, 1]],
});
// フィールドの token_callback Embedder にクエリのテキストを埋め込ませることもできる。
const reranked = await index.searchVectorText("embedding", "how do lifetimes work", 10, 0, {
field: "body_colbert",
text: "how do lifetimes work",
windowSize: 50,
});
searchGeo3dDistance(field, x, y, z, distanceM, limit?, offset?)
3D ECEF 座標フィールドへの球距離検索。中心 (x, y, z) から distanceM メートル以内
の座標を持つドキュメントを返します。ECEF の理論については
Geo3d の概念 を参照。
- 引数:
field(string) – Geo3d フィールド名x,y,z(number) – 中心 ECEF 座標(メートル)distanceM(number) – 中心からの最大距離(メートル)limit,offset(number, 省略可)
- 戻り値:
Promise<SearchResult[]>
searchGeo3dBoundingBox(field, minX, minY, minZ, maxX, maxY, maxZ, limit?, offset?)
3D ECEF 座標フィールドへの軸並行範囲(AABB)検索。
- 引数:
field(string) – Geo3d フィールド名minX,minY,minZ,maxX,maxY,maxZ(number) – 範囲境界(メートル)limit,offset(number, 省略可)
- 戻り値:
Promise<SearchResult[]>
searchGeo3dNearest(field, x, y, z, k, limit?, offset?, initialRadiusM?, maxRadiusM?)
3D ECEF 座標フィールドへの k 最近傍検索。(x, y, z) から最も近い k 件のドキュ
メントを返します。initialRadiusM / maxRadiusM(オプション)で反復拡張サーチの
探索コーンを調整できます。
- 引数:
field(string) – Geo3d フィールド名x,y,z(number) – 中心 ECEF 座標(メートル)k(number) – 返す近傍件数limit,offset(number, 省略可)initialRadiusM,maxRadiusM(number, 省略可)
- 戻り値:
Promise<SearchResult[]>
stats()
インデックス統計を返します。
- 戻り値:
{ documentCount: number, vectorFields: { [name]: { count, dimension } } }
WAL 同期ポリシー / 永続性
各書き込みは、エンジンのインメモリ先行書き込みログ(WAL)に追記されます。
Index.create と Index.open はオプションの walSyncPolicy を受け付け、
WAL をどの頻度でフラッシュするかを制御します。デフォルト(引数を省略)は
レコードごとの同期です。
class WalSyncPolicy {
static perRecord(): WalSyncPolicy;
static group(
maxRecords?: number,
maxBytes?: number,
maxIntervalMs?: number,
): WalSyncPolicy;
}
| コンストラクタ | 説明 |
|---|---|
WalSyncPolicy.perRecord() | デフォルト。WAL レコードごとにフラッシュします。 |
WalSyncPolicy.group(...) | グループコミット。複数の書き込みにまたがってフラッシュをまとめます。 |
group(...) のパラメータ(引数を省略するとそのデフォルトを維持):
| パラメータ | デフォルト | 説明 |
|---|---|---|
maxRecords | 1024 | この件数のレコードが蓄積されたらフラッシュします。 |
maxBytes | 1048576(1 MiB) | この量の未同期バイトが蓄積されたらフラッシュします。 |
maxIntervalMs | なし | 定期フラッシュタイマー(ミリ秒)。wasm では no-op(注意点を参照)。 |
グループコミットでは、maxRecords または maxBytes のいずれかに達した
時点でエンジン WAL がフラッシュされ、commit() 時にも必ずフラッシュされます。
クラッシュ時には最後の未同期バッチまでを失う可能性があります — これは
SQLite の synchronous = NORMAL と同じトレードオフです。
flushWal()(永続性バリア)
flushWal() はインメモリエンジンの WAL を必要なときにフラッシュします。
- 戻り値:
Promise<void>
WASM の注意点
WebAssembly にはバックグラウンドスレッドや直接のファイルシステムがないため、 ネイティブバインディングとは 2 点で動作が異なります:
maxIntervalMsは no-op です。 定期フラッシュタイマーには バックグラウンドスレッドが必要ですが、wasm では利用できません。 グループコミットはmaxRecords/maxBytesのしきい値到達時とcommit()時にはフラッシュされます。flushWal()はインメモリエンジンの WAL のみをフラッシュします。 OPFS への永続化は引き続きcommit()で行われます。wasm で永続的に 永続化するにはcommit()を呼び出してください。
import { Index, Schema, WalSyncPolicy } from "./pkg/laurus_wasm.js";
const schema = new Schema();
schema.addTextField("title");
// グループコミットを有効化。maxIntervalMs は受け付けられますが wasm では無視されます。
const policy = WalSyncPolicy.group(4096, undefined, 1000);
const index = await Index.open("my-index", schema, policy);
for (let i = 0; i < 10000; i++) {
await index.putDocument(`doc${i}`, { title: `Document ${i}` });
}
await index.flushWal(); // エンジン WAL をフラッシュ(OPFS ではない)
await index.commit(); // 変更を検索可能にし、かつ OPFS に永続化する
コミットポリシー / 自動コミット
コミットはバッファされた書き込みを検索可能なストアに反映します。
Index.create と Index.open はオプションの commitPolicy を受け付け、
エンジンが代わりにコミットするかどうかを制御します。デフォルト(引数を省略)は
manual で、すべての commit() を自分で駆動します。
class CommitPolicy {
static manual(): CommitPolicy;
static everyDocs(n: number): CommitPolicy;
static intervalMs(ms: number): CommitPolicy;
}
| コンストラクタ | 説明 |
|---|---|
CommitPolicy.manual() | デフォルト。自動コミットなし。呼び出し側がすべての commit() を駆動します。 |
CommitPolicy.everyDocs(n) | n 件のドキュメントを適用するたびに自動コミットします。 |
CommitPolicy.intervalMs(ms) | バックグラウンドタイマーで少なくとも ms ミリ秒ごとに自動コミットします(デフォルト: なし)。ネイティブ専用 — wasm では no-op。 |
everyDocs(n) では、エンジンは n 件のドキュメントを適用するたびに 1 回
コミットします。カウンタは単発とバッチの両方の取り込みにまたがり、バッチの
内部でも発火します — n より大きい putDocuments 呼び出しはバッチの
途中で 1 回以上のコミットを引き起こします。everyDocs(0) は有効で、自動
コミットを無効化します。これは CommitPolicy.manual() と等価です。
intervalMs(ms) は everyDocs の時間ベース版です: バックグラウンドタイマーが
少なくとも ms ミリ秒ごとにコミットするため、取り込みがアイドル状態でも
末尾の部分的なバッチがコミットされます。これはネイティブ専用です — 下記の
WASM の注意点を参照してください。
commitPolicy は walSyncPolicy と直交します: walSyncPolicy は永続性の
ために WAL をどの頻度で fsync するかを制御し、commitPolicy はバッファされた
書き込みをいつ検索可能な状態へ反映するかを制御します。両者は独立して設定
できます。
WASM の注意点
walSyncPolicy の maxIntervalMs バックグラウンドタイマー(wasm では no-op)
とは異なり、everyDocs はバックグラウンドスレッドを必要としません —
ドキュメントカウンタは取り込み中にインラインでチェックされます — そのため
自動コミットは WebAssembly 上でも完全に動作します。
一方 intervalMs は walSyncPolicy の maxIntervalMs と同じくバックグラウンド
タイマーに依存しますが、wasm にはバックグラウンドスレッドがありません。
ファクトリはポータブルなポリシーコードがコンパイルできるよう値を構築します
が、タイマーは WebAssembly 上では決して動作せず、intervalMs は wasm では
効果がありません — タイマーによるコミットは一切発火しません。wasm での自動
コミットには everyDocs を使用してください。
import { Index, Schema, CommitPolicy } from "./pkg/laurus_wasm.js";
const schema = new Schema();
schema.addTextField("title");
// 1000 件のドキュメントを適用するたびに自動コミット。
const index = await Index.open(
"my-index",
schema,
undefined,
CommitPolicy.everyDocs(1000),
);
for (let i = 0; i < 10000; i++) {
await index.putDocument(`doc${i}`, { title: `Document ${i}` });
}
// エンジンは 10 回自動コミット済み。明示的な commit() は不要。
Schema
インデックスフィールドと埋め込み器を定義するビルダーです。
コンストラクタ
new Schema()
空のスキーマを作成します。
メソッド
addTextField(name, stored?, indexed?, termVectors?, docValues?, analyzer?, multiValued?, positionIncrementGap?)
全文検索テキストフィールドを追加します。docValues は値を DocValues
(ソート・ファセット・集計が読み取る列指向ストア)にもコピーするかどうかを
制御します(Issue #1047、デフォルト true)。stored も true の場合のみ
有効です。analyzer にはパラメータ不要の組込名("standard" /
"english" / "keyword" / "simple" / "noop")または addAnalyzer()
で登録したランタイム analyzer 名を指定します。multiValued: true を指定すると
文字列の配列を受け付け(Issue #1175)、term クエリはいずれかの要素がタームを含めばマッチし、
フレーズクエリは slop が positionIncrementGap(デフォルト 100。0 にすると要素を連結したものとして付番)
に達しない限り 2 つの要素をまたぎません。値は文字列の配列として読み戻されます。
日本語の形態素解析を行う場合は、まず JapaneseAnalyzer を IPADIC の
バイト列から構築し、addAnalyzer() で登録してください。
JapaneseAnalyzer.fromBytes
と addAnalyzer を参照。
addIntegerField(name, stored?, indexed?, multiValued?, docValues?)
64 ビット整数フィールドを追加します。multiValued: true を指定すると整数配列を受け付け、
範囲クエリはいずれかの値が条件を満たせばマッチ(Lucene 流の “any match”、constant スコア)します。
docValues は上記を参照。
addFloatField(name, stored?, indexed?, multiValued?, docValues?)
64 ビット浮動小数点フィールドを追加します。multiValued: true を指定すると浮動小数点配列を受け付け、
範囲クエリはいずれかの値が条件を満たせばマッチ(Lucene 流の “any match”、constant スコア)します。
docValues は上記を参照。
addBooleanField(name, stored?, indexed?, multiValued?, docValues?)
真偽値フィールドを追加します。multiValued: true を指定すると真偽値の配列を受け付け、
flags:true のような term クエリはいずれかの要素がクエリの値と等しければマッチ
(Lucene 流の “any match”。各要素が独立した term posting になるため、要素の重複はヒット数ではなく
term frequency を増やします)します。値は真偽値の配列として読み戻されます。docValues は上記を参照。
addDatetimeField(name, stored?, indexed?, multiValued?, docValues?)
日時フィールドを追加します。multiValued: true を指定すると RFC 3339 文字列の配列を受け付け、
範囲クエリ(searchDateTimeRange および DSL の日付範囲)はいずれかの時刻が条件を満たせばマッチ
(Lucene 流の “any match”、constant スコア)します。値は UTC に正規化した RFC 3339 文字列の配列として
読み戻されます。docValues は上記を参照。
addGeoField(name, stored?, indexed?, multiValued?, docValues?)
地理座標フィールドを追加します。multiValued: true を指定すると { lat, lon } オブジェクトの配列を受け付け、
距離 / バウンディングボックスクエリはいずれかのポイントが条件を満たせばマッチ(Lucene 流の “any match”)し、
スコアはドキュメント内で最も近いポイントで決まります。docValues は上記を参照。
addGeo3dField(name, stored?, indexed?, multiValued?, docValues?)
3D ECEF カルテシアン座標フィールド(x, y, z はメートル)を追加します。値は
{ x, y, z } オブジェクトで投入します。multiValued: true を指定すると { x, y, z } オブジェクトの配列を受け付け、
3D クエリはいずれかのポイントが条件を満たせばマッチ(Lucene 流の “any match”)し、
スコアはドキュメント内で最も近いポイントで決まります。詳細は
Geo3d の概念 を参照。docValues は上記を参照。
WASM バインディングは Geo3dDistanceQuery / Geo3dBoundingBoxQuery /
Geo3dNearestQuery を JS クラスとして公開していません(wasm-bindgen は
dyn Query トレイトオブジェクトを公開できないため)。代わりに上記の
Index.searchGeo3dDistance / Index.searchGeo3dBoundingBox /
Index.searchGeo3dNearest メソッドを使用してください。
addBytesField(name, stored?, multiValued?)
バイナリデータフィールドを追加します。docValues オプションはありません
—— Bytes の値は設定にかかわらず DocValues に一切書き込まれないためです。
multiValued: true を指定すると base64 文字列の配列を受け付け(Issue #1176)、
単一の base64 文字列と同じ方法でスキーマ対応の変換が要素ごとにデコードします。
Bytes はそもそもインデックスされないため、他の multiValued オプションと異なり
クエリ一致の意味論はなく、保存時の形と取り込み時の許容個数を変えるだけです。
値は MIME を落としたバイト整数配列の配列として読み戻され、スカラーフィールドと
同じ入出力の非対称性を持ちます。
addHnswField(name, dimension, distance?, m?, efConstruction?, defaultEfSearch?, embedder?, quantizer?, subvectorCount?, rerankStorage?, pqCodebookPath?, baseWeight?)
HNSW ベクトルインデックスフィールドを追加します。
distance:"cosine"(デフォルト)、"euclidean"、"dot_product"、"manhattan"、"angular"m: 分岐係数(デフォルト 16)efConstruction: 構築時の探索幅(デフォルト 200)defaultEfSearch: クエリ時のef_search(候補リストサイズ)のスキーマレベルデフォルト。省略時は内部フォールバックの 50 を使用しますquantizer:"scalar_8bit"(デフォルト)または"product_quantization"(subvectorCountが必須)subvectorCount: PQ サブベクトル数。dimensionを割り切れる値を指定しますrerankStorage: 省略(デフォルト)するか、"f32"を指定して完全精度のリランクサイドカーを保存しますpqCodebookPath: 省略(デフォルト)するか、segment 間で再利用する共有 PQ codebook のストレージ相対ファイル名(Issue #631)を指定します(segment ごとの学習の代替)baseWeight: 他の vector フィールドと同時に検索されたときの、このフィールドの相対的なスコアリング優先度(デフォルト1.0、Issue #1084)。ウェイトを参照してください
addFlatField(name, dimension, distance?, embedder?, baseWeight?)
全探索ベクトルインデックスフィールドを追加します。
addIvfField(name, dimension, distance?, nClusters?, nProbe?, embedder?, baseWeight?)
IVF ベクトルインデックスフィールドを追加します。
nClusters: パーティショニングクラスタ数(デフォルト 100)nProbe: 検索時にプローブするクラスタ数(デフォルト 1)
ベクトル量子化とリランクストレージ(HNSW フィールド):
quantizer—"scalar_8bit"(デフォルト、4 倍圧縮)または高圧縮率の"product_quantization"。Product quantization ではsubvectorCount(dimensionを割り切れる値)が必須です。rerankStorage—"f32"を指定すると完全精度の*.hnsw.f32サイドカーを書き出し、厳密な Stage-2 リランクを有効化します。省略すると int8 のみのセグメントを維持します。pqCodebookPath— 共有 PQ codebook のストレージ相対ファイル名(Issue #631)。laurus train pq-codebookCLI コマンドで一度だけ学習します。quantizer: "product_quantization"との組み合わせでのみ意味を持ち、以後の commit は segment ごとの k-means 再学習の代わりに学習済み codebook で encode します。省略すると segment ごとの学習を維持します。
addMultiVectorField(name, dimension, distance?, embedder?, storage?)
MultiVector フィールドを追加します(Issue #1351)。ColBERT 型モデルのトークンごとの埋め込みのように、文書ごとに可変本数のトークンベクトルを保持します。ANN 索引は持たず、検索の対象にもなりません。読むのは late interaction による再採点だけです。スキーマとフィールド → MultiVector フィールドを参照してください。
dimension: 各トークンベクトルの長さ。0 より大きい値distance:"cosine"(デフォルト。書き込み時に L2 正規化)または"dot_product"embedder:addEmbedderで登録した"token_callback"Embedder の名前(省略可)。テキストの値と再採点のクエリテキストを埋め込みますstorage: トークンベクトルのディスク上の要素種別(Issue #1346)—"f32"(デフォルト、正確)、"f16"(2倍小さい)、"int8"(約4倍小さい)
オプションはフィールドの追加時に検証され、dimension が 0 の場合、上記以外の distance、または認識できない storage は例外になります。
値は、トークンごとに 1 つの配列を持つ入れ子の数値配列(tokens: [[0.1, 0.2], [0.3, 0.4]])で、フィールドの次元のベクトルを 1〜8,192 本保持します。フィールドに "token_callback" Embedder がある場合は文字列の値も使え、文書のインデックス時にコールバックが role "document" で埋め込みます。トークンベクトルは保存されないため、getDocuments や検索結果にこのフィールドは含まれません。
schema.addMultiVectorField("tokens", 2, "dot_product");
// ...
await index.putDocument("doc1", { title: "Rust", tokens: [[0.9, 0.2], [0.1, 0.8]] });
上記のどの add*Field メソッドも、name が _(_id を除く)で始まる場合は例外を投げ、何も追加しない。fromToml で読み込んだスキーマはそのようなフィールドを引き続き受け付けるため、永続化済みのスキーマも読み込めるが、そのスキーマから新しい Index を作成すると例外を投げる。詳細はフィールド命名規則を参照。
addAnalyzer(name, analyzer)
事前に構築した analyzer インスタンスを name で登録します。テキスト
フィールドが Named 形式で analyzer を参照するときに、組込名や
schema.analyzers 定義よりも先に解決されます。
現状は JapaneseAnalyzer.fromBytes
で構築した JapaneseAnalyzer のみ受け付けます。ブラウザ WASM では
{ "language": "japanese", "dict": ... } プリセットがファイルシステム
パスを解決できないため、ランタイムレジストリ経由が日本語 analyzer を
利用する唯一の現実的な経路です。
import { JapaneseAnalyzer, Schema } from "laurus-wasm";
import { downloadDictionary, loadDictionaryFiles } from "laurus-wasm/opfs";
await downloadDictionary("./dict/lindera-ipadic.zip", "ipadic");
const f = await loadDictionaryFiles("ipadic");
const ja = JapaneseAnalyzer.fromBytes(
f.metadata, f.dictTrie, f.dictValsIdx, f.dictVals,
f.dictWordsIdx, f.dictWords, f.matrixMtx, f.charDef, f.unk, "normal",
);
const schema = new Schema();
schema.addAnalyzer("ja-ipadic", ja);
schema.addTextField("body", undefined, undefined, undefined, undefined, "ja-ipadic");
addEmbedder(name, config)
名前付き埋め込み器を登録します。WASM では以下の 3 種類の type をサポートします:
"precomputed"— 埋め込みは行いません。ベクトルはputDocument()/searchVector()経由で直接渡します。"callback"— JavaScript コールバックembed: (text) => Promise<number[]>を 登録します。エンジンがインジェスト時およびsearchVectorText()で呼び出します。 Transformers.js などのブラウザ内埋め込みライブラリと組み合わせることで、 エンジン内自動埋め込みが可能になります。"token_callback"— MultiVector フィールド 用に、JavaScript コールバックembed: (text, role) => number[][] | Promise<number[][]>とdimension(正の整数)を登録します(Issue #1351)。late interaction モデルはクエリと 文書を別々にエンコードするため、roleには"query"または"document"が 渡されます。コールバックはトークンごとに長さdimensionのベクトルを 1 本ずつ 返します。値をそのまま返しても Promise で返しても構いません。エンジンは フィールドのテキストの値に対して role"document"で、再採点のtextに 対して role"query"で呼び出します。dimensionはインデックスの作成時・ オープン時に MultiVector フィールドと照合され、一致しない場合はIndex.create/Index.openが例外を投げます (... produces N-dimensional token vectors)。返された配列に数値でない要素が あるとエラーになります(0 として黙って扱われることはありません)。
関数はシリアライズできないため、どちらのコールバック型もスキーマ TOML
(toToml() や Index.open が永続化するスキーマ)には "precomputed" として
記録されます。コールバックが必要なセッションでは、Index.open に渡すスキーマで
毎回登録し直してください。
"token_callback" は、WASM にはないネイティブの candle_colbert Embedder の
代わりになります(Embedding 戦略を参照)。
// Precomputed embedder
schema.addEmbedder("precomputed-embedder", { type: "precomputed" });
// Callback embedder(例: Transformers.js)
schema.addEmbedder("callback-embedder", {
type: "callback",
embed: async (text) => {
const output = await pipeline(text, { pooling: "mean", normalize: true });
return Array.from(output.data);
},
});
// MultiVector フィールド用の Token callback embedder(例: ブラウザで動く ColBERT モデル)
schema.addEmbedder("colbert", {
type: "token_callback",
embed: async (text, role) => myColbert.encode(text, role), // number[][]
dimension: 128,
});
schema.addMultiVectorField("body_colbert", 128, "cosine", "colbert");
addAnalyzerDefinition(name, definition)
カスタムアナライザ定義を登録します。必須のトークナイザに加え、省略可能な文字フィルタ・
トークンフィルタのチェーンで構成されます。上記の
addAnalyzer とは別概念です。あちらは事前構築済みの
ランタイムアナライザオブジェクト(現状 JapaneseAnalyzer のみ)を登録するのに対し、
こちらはシリアライズ可能な JSON 形式のコンポーネント(laurus-cli create index --schema
や他の言語バインディングと同じ形式)からアナライザを宣言します。
definition.tokenizer は必須、definition.charFilters と
definition.tokenFilters は省略可能な配列です。各コンポーネントはスキーマ TOML/JSON
形式と同じ { type: "...", ... } 形式を使います(下記参照)。コンポーネント内部の
キーはこのワイヤ形式に合わせて snake_case のままです。外側のラッパーキー
(charFilters/tokenFilters)のみ、このバインディング独自の camelCase 規約に従います。
組み込みアナライザ用に予約された名前(standard、keyword、english、simple、
noop)は例外になり、その名前を定義したスキーマ(fromToml で読み込んだものなど)で
Index.create や初回の Index.open をする場合も同じです。addAnalyzer は対象外です。
ランタイムアナライザは組み込みより先に解決されるため、組み込みと同じ名前で登録しても効きます。
const schema = new Schema();
schema.addAnalyzerDefinition("ngram3", {
tokenizer: { type: "ngram", min_gram: 3, max_gram: 3 },
});
schema.addTextField("title", undefined, undefined, undefined, undefined, "ngram3");
analyzerNames()
addAnalyzerDefinition で登録された、または TOML から読み込まれたカスタムアナライザの
名前一覧を返します。
Schema.fromToml(tomlStr) (静的メソッド)
laurus-cli create index --schema と同じ形式の TOML 文字列からスキーマを読み込みます。
ファイルパス版は提供しません(ブラウザ WASM ターゲットにはファイルシステムがないため)。
toToml()
このスキーマを laurus-cli と同じ形式の TOML 文字列にシリアライズします。
setDefaultFields(fields)
デフォルト検索フィールドを設定します。
setDynamicFieldPolicy(policy)
ドキュメントに含まれるがスキーマに宣言されていないフィールドの扱いを設定します。policy は "strict" / "dynamic"(デフォルト)/ "ignore" のいずれか(大文字小文字を無視)。不正な値を渡すと例外をスローします。
"strict"— ドキュメントを拒否"dynamic"— 各未宣言フィールドの型を推論してスキーマに追加。警告: integer フィールドに入ってきた float 値は静かに切り捨てられます(3.14→3)"ignore"— 未宣言フィールドを静かに破棄
詳細な挙動マトリクスは スキーマとフィールド を参照してください。
dynamicFieldPolicy()
現在のポリシーを小文字の文字列で返します。
fieldNames()
定義済みフィールド名の配列を返します。
toString()
スキーマの文字列表現("Schema(fields=[...])" 形式)を返します。
アナライザコンポーネント
addAnalyzerDefinition(name, definition) と [analyzers.<name>] TOML
セクションで使用します。definition.tokenizer は単一のオブジェクト、
definition.charFilters/definition.tokenFilters はオブジェクトの配列で、
配列の順序どおりに適用されます。
各コンポーネントの説明を含む正規のリファレンスは スキーマフォーマットリファレンス → アナライザ を参照してください。
トークナイザ(tokenizer、必ず1つ):
type | 必須キー | 省略可能キー |
|---|---|---|
"whitespace" | – | – |
"unicode_word" | – | – |
"regex" | – | pattern(デフォルト \w+)、gaps(デフォルト false) |
"ngram" | min_gram、max_gram | – |
"lindera" | mode、dict | user_dict |
"whole" | – | – |
文字フィルタ(charFilters、トークン化前の生テキストに適用):
type | 必須キー | 省略可能キー |
|---|---|---|
"unicode_normalization" | form("nfc"/"nfd"/"nfkc"/"nfkd") | – |
"pattern_replace" | pattern、replacement | – |
"mapping" | mapping(置換用のオブジェクト) | – |
"japanese_iteration_mark" | – | kanji(デフォルト true)、kana(デフォルト true) |
トークンフィルタ(tokenFilters、トークン化後のトークン列に適用):
type | 必須キー | 省略可能キー |
|---|---|---|
"lowercase" | – | – |
"stop" | – | words(デフォルト: 英語のストップワード) |
"stem" | – | stem_type("porter"/"simple"/"identity") |
"boost" | boost | – |
"limit" | limit | – |
"strip" | – | – |
"remove_empty" | – | – |
"flatten_graph" | – | – |
ここでの "lindera" トークナイザは、上記の JapaneseAnalyzer.fromBytes で構築する
日本語アナライザとは別の経路である点に注意してください。lindera トークナイザ定義の
dict はファイルシステムパスを指定するため、ブラウザでは解決できません。
laurus-wasm を実ファイルシステムを持つブラウザ以外の WASM ホストで動かす場合にのみ
有用です。ブラウザから呼び出す場合は、引き続き JapaneseAnalyzer.fromBytes +
addAnalyzer を使用してください。
SearchResult
interface SearchResult {
id: string;
score: number;
document: object | null;
highlights: Record<string, string[]>;
}
highlights はリクエストの highlight.fields で指定した各フィールドをハイライト済みフラグメント(最も良いものが先頭)にマッピングします。ハイライトされなかったフィールドはオブジェクトに現れず、highlight を要求しなかった場合 highlights は {} になります。詳細はハイライトを参照してください。
Analysis
JapaneseAnalyzer
Lindera 辞書のバイト列から構築する日本語形態素解析 analyzer。
ブラウザ WASM には実ファイルシステムが無いため、標準の
{ "language": "japanese", "dict": "/path/to/ipadic" } プリセットは
利用できません。代わりに Lindera 辞書アーカイブ(典型的には
lindera-ipadic-X.Y.Z.zip)を取得して OPFS ヘルパ で
OPFS に保存し、9 つのコンポーネントバイト配列を
JapaneseAnalyzer.fromBytes に渡してください。
JapaneseAnalyzer.fromBytes(metadata, dictTrie, ..., mode?)
IPADIC のバイト列から analyzer を構築する static ファクトリ。
引数(mode 以外はすべて Uint8Array):
| 引数 | 対応するファイル |
|---|---|
metadata | metadata.json |
dictTrie | dict.trie(prefix trie) |
dictValsIdx | dict.valsidx |
dictVals | dict.vals |
dictWordsIdx | dict.wordsidx |
dictWords | dict.words |
matrixMtx | matrix.mtx |
charDef | char_def.bin |
unk | unk.bin |
mode | "normal"(デフォルト)/ "decompose" |
いずれかのコンポーネントの deserialization に失敗した場合、または mode 文字列が不正な場合は throw します。
import { JapaneseAnalyzer } from "laurus-wasm";
import { loadDictionaryFiles } from "laurus-wasm/opfs";
const f = await loadDictionaryFiles("ipadic");
const ja = JapaneseAnalyzer.fromBytes(
f.metadata, f.dictTrie, f.dictValsIdx, f.dictVals,
f.dictWordsIdx, f.dictWords, f.matrixMtx, f.charDef, f.unk,
"normal",
);
パイプラインは
NFKC 正規化 → 日本語 iteration mark 正規化 → Lindera 形態素解析 → lowercase → 日本語 stop word フィルタ
で、ネイティブ側の japanese プリセットと完全に一致します。
OPFS ヘルパ
laurus-wasm/opfs サブパスは、Lindera 辞書をブラウザの Origin
Private File System にダウンロード・保存・読込するヘルパを提供します。
JapaneseAnalyzer.fromBytes と組み合わせて使用します。
import {
downloadDictionary,
getDictionaryVersion,
loadDictionaryFiles,
hasDictionary,
listDictionaries,
removeDictionary,
} from "laurus-wasm/opfs";
| 関数 | 説明 |
|---|---|
downloadDictionary(url, name, options?) | .zip を fetch し、Web の DecompressionStream API で展開して、Lindera 9 ファイルを OPFS の laurus/dictionaries/<name>/ 配下に保存します。options.onProgress({ phase, loaded?, total? }) で進捗通知を受け取れます。options.version を渡すとファイルと並べてバージョンスタンプを保存します(下記参照)。 |
getDictionaryVersion(name) | downloadDictionary が保存したバージョンスタンプを返します。辞書またはスタンプが存在しない場合は null を返します。 |
loadDictionaryFiles(name) | 9 ファイルを { metadata, dictTrie, dictValsIdx, dictVals, dictWordsIdx, dictWords, matrixMtx, charDef, unk } オブジェクトとして読み出し、JapaneseAnalyzer.fromBytes にそのまま渡せる形にします。 |
hasDictionary(name) | 辞書ディレクトリが OPFS にあれば true。 |
listDictionaries() | 保存済み辞書名の配列を返します。 |
removeDictionary(name) | 辞書ディレクトリを削除します。 |
辞書のバイナリ形式は WASM バイナリにコンパイルされた Lindera
(およびその依存 daachorse)のバージョンに紐づいており、アプリが
Lindera を更新すると OPFS にキャッシュ済みの辞書は読めなくなります
(InvalidAutomatonError でデシリアライズに失敗します)。ダウンロード時に
zip の対象 Lindera バージョンを options.version として渡し、起動時に
getDictionaryVersion(name) を現在のビルドが期待するバージョンと比較して、
不一致なら再ダウンロードしてください。スタンプが null の場合も
不一致として扱います。
ブラウザ CORS の制約により GitHub Releases から直接 fetch できないため、
zip はアプリと同一オリジンで配信してください(Laurus デモではデプロイ
時に ./dict/lindera-ipadic.zip を WASM と同じパスに同梱します)。
WhitespaceTokenizer
const tokenizer = new WhitespaceTokenizer();
const tokens = tokenizer.tokenize("hello world");
// [{ text, position, startOffset, endOffset, boost, stopped, positionIncrement, positionLength, tokenType }]
空白を境界としてテキストを分割し、Token オブジェクトの配列を返します。
startOffset と endOffset は、元テキストの UTF-8 バイトオフセットです。非 ASCII のテキストでは JavaScript の文字列(UTF-16)の添字と一致しないため、エンコードしたバイト列を切り出します: new TextDecoder().decode(new TextEncoder().encode(text).slice(tok.startOffset, tok.endOffset))。
tokenType は "alphanum"、"num"、"cjk"、"katakana"、"hiragana"、"hangul"、"punctuation"、"whitespace"、"synonym"、"email"、"url"、"other" のいずれかです。
SynonymDictionary
const dict = new SynonymDictionary();
dict.addSynonymGroup(["ml", "machine learning"]);
同義語グループの辞書。グループ内のすべての語句が互いに同義語として扱われます。
SynonymGraphFilter
new SynonymGraphFilter(dictionary, keepOriginal = true, boost = 1.0)
dictionary(SynonymDictionary) — 同義語グループのソース。keepOriginal(boolean, デフォルトtrue) — 元のトークンを挿入された同義語と 並べて保持します。boost(number, デフォルト1.0) — 挿入される同義語トークンに適用される スコアブースト。
const filter = new SynonymGraphFilter(dict, true, 0.8);
const expanded = filter.apply(tokens);
SynonymDictionary の同義語でトークンを展開するトークンフィルターです。
apply は各トークンのオフセットと種別を保ち、挿入する同義語には種別 "synonym" と、置き換える語のオフセットを付けます。未知の tokenType を渡すと例外を投げます。手で組み立てたトークンでは tokenType を省略できますが、その場合、複数語の同義語は語のオフセットが連続しているときだけ一致するため、空白で区切られた語には tokenType: "alphanum" を付けてください。