@graphorin/reranker-transformersjs 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +4 -4
- package/dist/cross-encoder.d.ts +1 -1
- package/dist/cross-encoder.js +3 -3
- package/dist/cross-encoder.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/reranker.d.ts +1 -1
- package/dist/reranker.js.map +1 -1
- package/dist/text-extraction.d.ts +3 -3
- package/dist/text-extraction.js +3 -3
- package/dist/text-extraction.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @graphorin/reranker-transformersjs
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6), [`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6), [`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a), [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a)]:
|
|
8
|
+
- @graphorin/memory@0.6.0
|
|
9
|
+
- @graphorin/core@0.6.0
|
|
10
|
+
|
|
11
|
+
## 0.5.0
|
|
12
|
+
|
|
13
|
+
First version published to the npm registry (with Sigstore build
|
|
14
|
+
provenance). The 0.2.0, 0.3.0, and 0.4.0 versions were internal lockstep
|
|
15
|
+
milestones and were never published. All `@graphorin/*` packages release
|
|
16
|
+
lockstep at the same version; the full release notes for 0.2.0-0.5.0 live
|
|
17
|
+
in the repository-level
|
|
18
|
+
[CHANGELOG](https://github.com/o-stepper/graphorin/blob/main/CHANGELOG.md).
|
|
19
|
+
|
|
3
20
|
## 0.1.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
> entirely in-process. Implements the `ReRanker` contract from
|
|
8
8
|
> `@graphorin/memory/search`.
|
|
9
9
|
>
|
|
10
|
-
> Project Graphorin · v0.
|
|
10
|
+
> Project Graphorin · v0.6.0 · MIT License · © 2026 Oleksiy Stepurenko ·
|
|
11
11
|
> <https://github.com/o-stepper/graphorin>
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
## Status
|
|
16
16
|
|
|
17
|
-
- **Published:** v0.
|
|
17
|
+
- **Published:** v0.6.0 (optional sub-pack)
|
|
18
18
|
- **Default model:** locale-aware (`Xenova/bge-reranker-base` for
|
|
19
19
|
English, `BAAI/bge-reranker-v2-m3` for everything else).
|
|
20
20
|
- **Default precision:** FP16 (`'fp16'` dtype).
|
|
@@ -110,7 +110,7 @@ const reranker = createCrossEncoderReranker<MyRecord>({
|
|
|
110
110
|
|
|
111
111
|
## Related decisions
|
|
112
112
|
|
|
113
|
-
- ADR-024
|
|
113
|
+
- ADR-024 - Reciprocal Rank Fusion default + pluggable rerankers.
|
|
114
114
|
|
|
115
115
|
---
|
|
116
116
|
|
|
@@ -120,4 +120,4 @@ MIT © 2026 Oleksiy Stepurenko
|
|
|
120
120
|
|
|
121
121
|
---
|
|
122
122
|
|
|
123
|
-
**Project Graphorin** · v0.
|
|
123
|
+
**Project Graphorin** · v0.6.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
|
package/dist/cross-encoder.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ declare function _resetPipelineFactoryCacheForTesting(): void;
|
|
|
71
71
|
* pair order. Cross-encoder classifiers return either a single-best
|
|
72
72
|
* `{label, score}` per pair (the default single-logit bge exports) or an array
|
|
73
73
|
* of `topk` entries. For the array shape we read the POSITIVE label's
|
|
74
|
-
* confidence
|
|
74
|
+
* confidence - NOT the max of any label (PS-16): an irrelevant pair's most
|
|
75
75
|
* confident class is the *negative* one, so taking the max would invert the
|
|
76
76
|
* ranking for any 2-label classifier. When no label looks positive (single-logit
|
|
77
77
|
* or unrecognised labels) we fall back to the top score.
|
package/dist/cross-encoder.js
CHANGED
|
@@ -31,8 +31,8 @@ function _resetPipelineFactoryCacheForTesting() {
|
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
33
|
* True when a classifier label names the *positive* (relevant) class. Matches
|
|
34
|
-
* the conventional binary forms
|
|
35
|
-
* `entailment`, `true`, `yes`
|
|
34
|
+
* the conventional binary forms - `LABEL_1`, `positive`, `relevant`,
|
|
35
|
+
* `entailment`, `true`, `yes` - using exact words so it does not mis-fire on a
|
|
36
36
|
* negative label that merely contains one (`irrelevant` ⊃ `relevant`).
|
|
37
37
|
*/
|
|
38
38
|
function isPositiveLabel(label) {
|
|
@@ -45,7 +45,7 @@ function isPositiveLabel(label) {
|
|
|
45
45
|
* pair order. Cross-encoder classifiers return either a single-best
|
|
46
46
|
* `{label, score}` per pair (the default single-logit bge exports) or an array
|
|
47
47
|
* of `topk` entries. For the array shape we read the POSITIVE label's
|
|
48
|
-
* confidence
|
|
48
|
+
* confidence - NOT the max of any label (PS-16): an irrelevant pair's most
|
|
49
49
|
* confident class is the *negative* one, so taking the max would invert the
|
|
50
50
|
* ranking for any 2-label classifier. When no label looks positive (single-logit
|
|
51
51
|
* or unrecognised labels) we fall back to the top score.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-encoder.js","names":["CACHED_PIPELINE_FACTORY: CrossEncoderPipelineFactory | null","out: number[]"],"sources":["../src/cross-encoder.ts"],"sourcesContent":["/**\n * Lazy `@huggingface/transformers` text-classification pipeline used to\n * score query / passage pairs. The runner caches the first-loaded\n * pipeline per `(model, dtype, device, revision, cacheDir)` tuple so\n * repeated `rerank(...)` calls inside the same process do not pay the\n * model-load cost twice.\n *\n * Tests inject a stub `pipelineFactory`; the production path lazily\n * imports `@huggingface/transformers` so plain inspection of the\n * package (e.g. for documentation generation) does not pull in the\n * native ONNX runtime.\n *\n * @packageDocumentation\n */\n\n/**\n * Numeric dtype hint. Default `'fp16'` per Phase 16 §\n * `@graphorin/reranker-transformersjs`.\n *\n * @stable\n */\nexport type RerankerDtype = 'fp32' | 'fp16' | 'q8' | 'q4';\n\n/**\n * Output shape returned by `@huggingface/transformers`'\n * text-classification pipeline. Each pair returns either a single\n * `{ label, score }` object (top-k = 1) or an array of them. We\n * normalise on the array form upstream so the cross-encoder always\n * sees a consistent shape.\n *\n * @internal\n */\nexport interface ClassifierResult {\n readonly label: string;\n readonly score: number;\n}\n\n/** @internal */\nexport type CrossEncoderPipeline = (\n pairs: ReadonlyArray<{ text: string; text_pair: string }>,\n options?: { topk?: number; signal?: AbortSignal },\n) => Promise<ReadonlyArray<ClassifierResult> | ReadonlyArray<ReadonlyArray<ClassifierResult>>>;\n\n/** @internal */\nexport type CrossEncoderPipelineFactory = (\n task: 'text-classification',\n model: string,\n options: {\n revision?: string;\n cache_dir?: string;\n dtype?: RerankerDtype;\n device?: string;\n },\n) => Promise<CrossEncoderPipeline>;\n\n/**\n * Raised when the `@huggingface/transformers` peer is missing or the\n * configured cross-encoder model fails to load.\n *\n * @stable\n */\nexport class CrossEncoderLoadError extends Error {\n override readonly name = 'CrossEncoderLoadError';\n}\n\n/** @internal */\nlet CACHED_PIPELINE_FACTORY: CrossEncoderPipelineFactory | null = null;\n\n/** @internal */\nexport async function loadDefaultPipelineFactory(): Promise<CrossEncoderPipelineFactory> {\n if (CACHED_PIPELINE_FACTORY !== null) return CACHED_PIPELINE_FACTORY;\n try {\n const mod = (await import('@huggingface/transformers')) as unknown as {\n pipeline: CrossEncoderPipelineFactory;\n };\n CACHED_PIPELINE_FACTORY = mod.pipeline;\n return CACHED_PIPELINE_FACTORY;\n } catch (err) {\n throw new CrossEncoderLoadError(\n \"[graphorin/reranker-transformersjs] required peer '@huggingface/transformers' is not installed.\",\n { cause: err },\n );\n }\n}\n\n/**\n * Test-only helper. Drops the cached pipeline factory so the next\n * loader call re-imports the peer.\n *\n * @internal\n */\nexport function _resetPipelineFactoryCacheForTesting(): void {\n CACHED_PIPELINE_FACTORY = null;\n}\n\n/**\n * True when a classifier label names the *positive* (relevant) class. Matches\n * the conventional binary forms
|
|
1
|
+
{"version":3,"file":"cross-encoder.js","names":["CACHED_PIPELINE_FACTORY: CrossEncoderPipelineFactory | null","out: number[]"],"sources":["../src/cross-encoder.ts"],"sourcesContent":["/**\n * Lazy `@huggingface/transformers` text-classification pipeline used to\n * score query / passage pairs. The runner caches the first-loaded\n * pipeline per `(model, dtype, device, revision, cacheDir)` tuple so\n * repeated `rerank(...)` calls inside the same process do not pay the\n * model-load cost twice.\n *\n * Tests inject a stub `pipelineFactory`; the production path lazily\n * imports `@huggingface/transformers` so plain inspection of the\n * package (e.g. for documentation generation) does not pull in the\n * native ONNX runtime.\n *\n * @packageDocumentation\n */\n\n/**\n * Numeric dtype hint. Default `'fp16'` per Phase 16 §\n * `@graphorin/reranker-transformersjs`.\n *\n * @stable\n */\nexport type RerankerDtype = 'fp32' | 'fp16' | 'q8' | 'q4';\n\n/**\n * Output shape returned by `@huggingface/transformers`'\n * text-classification pipeline. Each pair returns either a single\n * `{ label, score }` object (top-k = 1) or an array of them. We\n * normalise on the array form upstream so the cross-encoder always\n * sees a consistent shape.\n *\n * @internal\n */\nexport interface ClassifierResult {\n readonly label: string;\n readonly score: number;\n}\n\n/** @internal */\nexport type CrossEncoderPipeline = (\n pairs: ReadonlyArray<{ text: string; text_pair: string }>,\n options?: { topk?: number; signal?: AbortSignal },\n) => Promise<ReadonlyArray<ClassifierResult> | ReadonlyArray<ReadonlyArray<ClassifierResult>>>;\n\n/** @internal */\nexport type CrossEncoderPipelineFactory = (\n task: 'text-classification',\n model: string,\n options: {\n revision?: string;\n cache_dir?: string;\n dtype?: RerankerDtype;\n device?: string;\n },\n) => Promise<CrossEncoderPipeline>;\n\n/**\n * Raised when the `@huggingface/transformers` peer is missing or the\n * configured cross-encoder model fails to load.\n *\n * @stable\n */\nexport class CrossEncoderLoadError extends Error {\n override readonly name = 'CrossEncoderLoadError';\n}\n\n/** @internal */\nlet CACHED_PIPELINE_FACTORY: CrossEncoderPipelineFactory | null = null;\n\n/** @internal */\nexport async function loadDefaultPipelineFactory(): Promise<CrossEncoderPipelineFactory> {\n if (CACHED_PIPELINE_FACTORY !== null) return CACHED_PIPELINE_FACTORY;\n try {\n const mod = (await import('@huggingface/transformers')) as unknown as {\n pipeline: CrossEncoderPipelineFactory;\n };\n CACHED_PIPELINE_FACTORY = mod.pipeline;\n return CACHED_PIPELINE_FACTORY;\n } catch (err) {\n throw new CrossEncoderLoadError(\n \"[graphorin/reranker-transformersjs] required peer '@huggingface/transformers' is not installed.\",\n { cause: err },\n );\n }\n}\n\n/**\n * Test-only helper. Drops the cached pipeline factory so the next\n * loader call re-imports the peer.\n *\n * @internal\n */\nexport function _resetPipelineFactoryCacheForTesting(): void {\n CACHED_PIPELINE_FACTORY = null;\n}\n\n/**\n * True when a classifier label names the *positive* (relevant) class. Matches\n * the conventional binary forms - `LABEL_1`, `positive`, `relevant`,\n * `entailment`, `true`, `yes` - using exact words so it does not mis-fire on a\n * negative label that merely contains one (`irrelevant` ⊃ `relevant`).\n */\nfunction isPositiveLabel(label: string): boolean {\n const l = label.toLowerCase().trim();\n if (l === '1' || /(?:^|[_-])1$/.test(l)) return true; // '1', 'label_1', 'class-1'\n return /^(?:positive|relevant|entailment|true|yes|pos)$/.test(l);\n}\n\n/**\n * Normalises the raw pipeline output to a flat `score[]` aligned with the input\n * pair order. Cross-encoder classifiers return either a single-best\n * `{label, score}` per pair (the default single-logit bge exports) or an array\n * of `topk` entries. For the array shape we read the POSITIVE label's\n * confidence - NOT the max of any label (PS-16): an irrelevant pair's most\n * confident class is the *negative* one, so taking the max would invert the\n * ranking for any 2-label classifier. When no label looks positive (single-logit\n * or unrecognised labels) we fall back to the top score.\n *\n * @internal\n */\nexport function extractPairScores(\n raw: ReadonlyArray<ClassifierResult> | ReadonlyArray<ReadonlyArray<ClassifierResult>>,\n pairCount: number,\n): number[] {\n const out: number[] = new Array(pairCount).fill(0) as number[];\n for (let i = 0; i < pairCount; i++) {\n const cell = raw[i];\n if (cell === undefined) continue;\n if (Array.isArray(cell)) {\n let best = Number.NEGATIVE_INFINITY;\n let positive = Number.NEGATIVE_INFINITY;\n let sawPositive = false;\n for (const entry of cell) {\n if (entry === undefined) continue;\n if (entry.score > best) best = entry.score;\n if (isPositiveLabel(entry.label)) {\n sawPositive = true;\n if (entry.score > positive) positive = entry.score;\n }\n }\n const chosen = sawPositive ? positive : best;\n out[i] = Number.isFinite(chosen) ? chosen : 0;\n } else {\n const single = cell as ClassifierResult;\n out[i] = single.score;\n }\n }\n return out;\n}\n"],"mappings":";;;;;;;AA6DA,IAAa,wBAAb,cAA2C,MAAM;CAC/C,AAAkB,OAAO;;;AAI3B,IAAIA,0BAA8D;;AAGlE,eAAsB,6BAAmE;AACvF,KAAI,4BAA4B,KAAM,QAAO;AAC7C,KAAI;AAIF,6BAHa,MAAM,OAAO,8BAGI;AAC9B,SAAO;UACA,KAAK;AACZ,QAAM,IAAI,sBACR,mGACA,EAAE,OAAO,KAAK,CACf;;;;;;;;;AAUL,SAAgB,uCAA6C;AAC3D,2BAA0B;;;;;;;;AAS5B,SAAS,gBAAgB,OAAwB;CAC/C,MAAM,IAAI,MAAM,aAAa,CAAC,MAAM;AACpC,KAAI,MAAM,OAAO,eAAe,KAAK,EAAE,CAAE,QAAO;AAChD,QAAO,kDAAkD,KAAK,EAAE;;;;;;;;;;;;;;AAelE,SAAgB,kBACd,KACA,WACU;CACV,MAAMC,MAAgB,IAAI,MAAM,UAAU,CAAC,KAAK,EAAE;AAClD,MAAK,IAAI,IAAI,GAAG,IAAI,WAAW,KAAK;EAClC,MAAM,OAAO,IAAI;AACjB,MAAI,SAAS,OAAW;AACxB,MAAI,MAAM,QAAQ,KAAK,EAAE;GACvB,IAAI,OAAO,OAAO;GAClB,IAAI,WAAW,OAAO;GACtB,IAAI,cAAc;AAClB,QAAK,MAAM,SAAS,MAAM;AACxB,QAAI,UAAU,OAAW;AACzB,QAAI,MAAM,QAAQ,KAAM,QAAO,MAAM;AACrC,QAAI,gBAAgB,MAAM,MAAM,EAAE;AAChC,mBAAc;AACd,SAAI,MAAM,QAAQ,SAAU,YAAW,MAAM;;;GAGjD,MAAM,SAAS,cAAc,WAAW;AACxC,OAAI,KAAK,OAAO,SAAS,OAAO,GAAG,SAAS;QAG5C,KAAI,KADW,KACC;;AAGpB,QAAO"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { CrossEncoderRerankerOptions, RERANKER_ID, TransformersJsReRanker, creat
|
|
|
6
6
|
//#region src/index.d.ts
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* @graphorin/reranker-transformersjs
|
|
9
|
+
* @graphorin/reranker-transformersjs - cross-encoder reranker adapter
|
|
10
10
|
* for the Graphorin framework.
|
|
11
11
|
*
|
|
12
12
|
* Wraps `@huggingface/transformers@^4.1.0` to score `(query, passage)`
|
|
@@ -32,13 +32,13 @@ import { CrossEncoderRerankerOptions, RERANKER_ID, TransformersJsReRanker, creat
|
|
|
32
32
|
* multilingual baseline).
|
|
33
33
|
*
|
|
34
34
|
* Operators that want a narrower / language-specific cross-encoder
|
|
35
|
-
* pass an explicit `model` option
|
|
35
|
+
* pass an explicit `model` option - the package's defaults
|
|
36
36
|
* deliberately avoid privileging any single language pair.
|
|
37
37
|
*
|
|
38
38
|
* @packageDocumentation
|
|
39
39
|
*/
|
|
40
40
|
/** Canonical version constant. Mirrors the `package.json` version. */
|
|
41
|
-
declare const VERSION = "0.
|
|
41
|
+
declare const VERSION = "0.6.0";
|
|
42
42
|
//#endregion
|
|
43
43
|
export { type ClassifierResult, CrossEncoderLoadError, type CrossEncoderPipeline, type CrossEncoderPipelineFactory, type CrossEncoderRerankerOptions, DEFAULT_ENGLISH_MODEL, DEFAULT_MULTILINGUAL_MODEL, type LocaleTag, type PassageExtractor, RERANKER_ID, type RerankerDtype, TransformersJsReRanker, VERSION, _resetPipelineFactoryCacheForTesting, createCrossEncoderReranker, defaultPassageExtractor, extractPairScores, loadDefaultPipelineFactory, mergeAndDedupe, pickRerankerModel };
|
|
44
44
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { RERANKER_ID, TransformersJsReRanker, createCrossEncoderReranker, mergeA
|
|
|
5
5
|
|
|
6
6
|
//#region src/index.ts
|
|
7
7
|
/**
|
|
8
|
-
* @graphorin/reranker-transformersjs
|
|
8
|
+
* @graphorin/reranker-transformersjs - cross-encoder reranker adapter
|
|
9
9
|
* for the Graphorin framework.
|
|
10
10
|
*
|
|
11
11
|
* Wraps `@huggingface/transformers@^4.1.0` to score `(query, passage)`
|
|
@@ -31,13 +31,13 @@ import { RERANKER_ID, TransformersJsReRanker, createCrossEncoderReranker, mergeA
|
|
|
31
31
|
* multilingual baseline).
|
|
32
32
|
*
|
|
33
33
|
* Operators that want a narrower / language-specific cross-encoder
|
|
34
|
-
* pass an explicit `model` option
|
|
34
|
+
* pass an explicit `model` option - the package's defaults
|
|
35
35
|
* deliberately avoid privileging any single language pair.
|
|
36
36
|
*
|
|
37
37
|
* @packageDocumentation
|
|
38
38
|
*/
|
|
39
39
|
/** Canonical version constant. Mirrors the `package.json` version. */
|
|
40
|
-
const VERSION = "0.
|
|
40
|
+
const VERSION = "0.6.0";
|
|
41
41
|
|
|
42
42
|
//#endregion
|
|
43
43
|
export { CrossEncoderLoadError, DEFAULT_ENGLISH_MODEL, DEFAULT_MULTILINGUAL_MODEL, RERANKER_ID, TransformersJsReRanker, VERSION, _resetPipelineFactoryCacheForTesting, createCrossEncoderReranker, defaultPassageExtractor, extractPairScores, loadDefaultPipelineFactory, mergeAndDedupe, pickRerankerModel };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @graphorin/reranker-transformersjs
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @graphorin/reranker-transformersjs - cross-encoder reranker adapter\n * for the Graphorin framework.\n *\n * Wraps `@huggingface/transformers@^4.1.0` to score `(query, passage)`\n * pairs in-process. Plug into the memory hybrid-search pipeline as a\n * drop-in replacement for the built-in `RRFReranker`:\n *\n * ```ts\n * import { createMemory } from '@graphorin/memory';\n * import { createCrossEncoderReranker } from '@graphorin/reranker-transformersjs';\n *\n * const memory = createMemory({\n * store,\n * embedder,\n * reranker: createCrossEncoderReranker({ locale: 'en' }),\n * });\n * ```\n *\n * Locale-aware default model:\n *\n * - `'en'` / `'en-*'` → `Xenova/bge-reranker-base` (278M parameters,\n * FP16 quantized).\n * - Every other locale → `BAAI/bge-reranker-v2-m3` (568M parameters,\n * multilingual baseline).\n *\n * Operators that want a narrower / language-specific cross-encoder\n * pass an explicit `model` option - the package's defaults\n * deliberately avoid privileging any single language pair.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant. Mirrors the `package.json` version. */\nexport const VERSION = '0.6.0';\n\nexport {\n _resetPipelineFactoryCacheForTesting,\n type ClassifierResult,\n CrossEncoderLoadError,\n type CrossEncoderPipeline,\n type CrossEncoderPipelineFactory,\n extractPairScores,\n loadDefaultPipelineFactory,\n type RerankerDtype,\n} from './cross-encoder.js';\nexport {\n DEFAULT_ENGLISH_MODEL,\n DEFAULT_MULTILINGUAL_MODEL,\n type LocaleTag,\n pickRerankerModel,\n} from './model-selection.js';\nexport {\n type CrossEncoderRerankerOptions,\n createCrossEncoderReranker,\n mergeAndDedupe,\n RERANKER_ID,\n TransformersJsReRanker,\n} from './reranker.js';\nexport {\n defaultPassageExtractor,\n type PassageExtractor,\n} from './text-extraction.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,UAAU"}
|
package/dist/reranker.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ interface CrossEncoderRerankerOptions<TRecord extends MemoryRecord = MemoryRecor
|
|
|
38
38
|
*/
|
|
39
39
|
readonly idleEvictionMs?: number;
|
|
40
40
|
/**
|
|
41
|
-
* Optional passage extractor
|
|
41
|
+
* Optional passage extractor - replaces the default heuristic that
|
|
42
42
|
* walks `text → summary → value → label → id`. Useful when a custom
|
|
43
43
|
* `MemoryRecord` schema attaches the canonical text elsewhere.
|
|
44
44
|
*/
|
package/dist/reranker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reranker.js","names":["#revision","#cacheDir","#device","#pipelineFactory","#passageExtractor","#now","#invocationCount","#pipeline","#loading","#idleTimer","#getPipeline","#scoreInBatches","fused: MemoryHit<TInputRecord>[]","signals: Record<string, number>","out: number[]","#lastUsedAt","#scheduleIdleEviction"],"sources":["../src/reranker.ts"],"sourcesContent":["/**\n * Cross-encoder `ReRanker` implementation backed by\n * `@huggingface/transformers`. Lazy-loaded, optional idle eviction,\n * locale-aware default model.\n *\n * @packageDocumentation\n */\n\nimport type { MemoryHit, MemoryRecord } from '@graphorin/core';\nimport type { ReRanker, ReRankOptions } from '@graphorin/memory/search';\n\nimport {\n CrossEncoderLoadError,\n type CrossEncoderPipeline,\n type CrossEncoderPipelineFactory,\n extractPairScores,\n loadDefaultPipelineFactory,\n type RerankerDtype,\n} from './cross-encoder.js';\nimport {\n DEFAULT_ENGLISH_MODEL,\n DEFAULT_MULTILINGUAL_MODEL,\n type LocaleTag,\n pickRerankerModel,\n} from './model-selection.js';\nimport { defaultPassageExtractor, type PassageExtractor } from './text-extraction.js';\n\n/**\n * Options accepted by {@link createCrossEncoderReranker}.\n *\n * @stable\n */\nexport interface CrossEncoderRerankerOptions<TRecord extends MemoryRecord = MemoryRecord> {\n /** Override the auto-picked model. Default: derived from `locale`. */\n readonly model?: string;\n /** BCP 47 locale tag used to select the default model. Default `'en'`. */\n readonly locale?: LocaleTag;\n /** Default `'fp16'`. */\n readonly dtype?: RerankerDtype;\n /** Optional revision pin (`'main'` if unset). */\n readonly revision?: string;\n /** Optional cache directory. Honours `GRAPHORIN_CACHE_DIR` when unset. */\n readonly cacheDir?: string;\n /** Override device (`'cpu'`, `'webgpu'`, …). Default `'cpu'`. */\n readonly device?: string;\n /**\n * Maximum batch size sent to the cross-encoder per inference call.\n * Default `32`. Larger batches improve throughput at the cost of\n * resident memory.\n */\n readonly batchSize?: number;\n /**\n * Optional idle-eviction timeout in milliseconds. When the reranker\n * does not score a pair within this window, the loaded pipeline is\n * dropped so the OS can reclaim the underlying ONNX session. Default\n * `undefined` (eviction disabled).\n */\n readonly idleEvictionMs?: number;\n /**\n * Optional passage extractor — replaces the default heuristic that\n * walks `text → summary → value → label → id`. Useful when a custom\n * `MemoryRecord` schema attaches the canonical text elsewhere.\n */\n readonly passageExtractor?: PassageExtractor<TRecord>;\n /**\n * Inject a `pipelineFactory`. Used by tests to stub the underlying\n * `@huggingface/transformers` pipeline. Production callers leave\n * this unset so the package lazily loads the peer.\n */\n readonly pipelineFactory?: CrossEncoderPipelineFactory;\n /**\n * Override the wall-clock provider. Used by tests so the\n * idle-eviction timer can be exercised deterministically.\n *\n * @internal\n */\n readonly now?: () => number;\n}\n\n/** @stable */\nexport const RERANKER_ID = 'transformersjs-cross-encoder' as const;\n\n/**\n * Build a cross-encoder reranker. Lazy: the pipeline is constructed on\n * the first `rerank()` call so packaging the reranker pays no\n * model-load cost.\n *\n * @stable\n */\nexport function createCrossEncoderReranker<TRecord extends MemoryRecord = MemoryRecord>(\n options: CrossEncoderRerankerOptions<TRecord> = {},\n): TransformersJsReRanker<TRecord> {\n return new TransformersJsReRanker<TRecord>(options);\n}\n\n/**\n * `ReRanker` implementation. Matches the contract from\n * `@graphorin/memory/search`.\n *\n * @stable\n */\nexport class TransformersJsReRanker<TRecord extends MemoryRecord = MemoryRecord>\n implements ReRanker\n{\n readonly id = RERANKER_ID;\n readonly model: string;\n readonly locale: LocaleTag;\n readonly dtype: RerankerDtype;\n readonly batchSize: number;\n readonly idleEvictionMs: number | undefined;\n\n readonly #revision: string | undefined;\n readonly #cacheDir: string | undefined;\n readonly #device: string | undefined;\n readonly #pipelineFactory: CrossEncoderPipelineFactory | undefined;\n readonly #passageExtractor: PassageExtractor<TRecord>;\n readonly #now: () => number;\n #pipeline: CrossEncoderPipeline | null = null;\n #loading: Promise<CrossEncoderPipeline> | null = null;\n #lastUsedAt: number = 0;\n #idleTimer: ReturnType<typeof setTimeout> | null = null;\n #invocationCount = 0;\n\n constructor(options: CrossEncoderRerankerOptions<TRecord>) {\n this.locale = options.locale ?? 'en';\n this.model = options.model ?? pickRerankerModel(this.locale);\n this.dtype = options.dtype ?? 'fp16';\n this.batchSize = options.batchSize ?? 32;\n if (!Number.isInteger(this.batchSize) || this.batchSize <= 0) {\n throw new RangeError(\n `[graphorin/reranker-transformersjs] batchSize must be a positive integer; got ${String(\n options.batchSize,\n )}.`,\n );\n }\n this.idleEvictionMs = options.idleEvictionMs;\n this.#revision = options.revision;\n this.#cacheDir = options.cacheDir ?? process.env.GRAPHORIN_CACHE_DIR;\n this.#device = options.device;\n this.#pipelineFactory = options.pipelineFactory;\n this.#passageExtractor =\n options.passageExtractor ?? ((record: TRecord) => defaultPassageExtractor(record));\n this.#now = options.now ?? Date.now;\n }\n\n /**\n * Number of `rerank(...)` invocations since construction. Surfaced\n * for observability + the test suite.\n *\n * @stable\n */\n get invocationCount(): number {\n return this.#invocationCount;\n }\n\n /**\n * Whether the underlying ONNX pipeline is currently loaded in\n * memory. Surfaced for the idle-eviction integration test.\n *\n * @stable\n */\n get pipelineLoaded(): boolean {\n return this.#pipeline !== null;\n }\n\n /**\n * Drop the loaded pipeline. Equivalent to letting the idle-eviction\n * timer fire. Idempotent.\n *\n * @stable\n */\n unload(): void {\n this.#pipeline = null;\n this.#loading = null;\n if (this.#idleTimer !== null) {\n clearTimeout(this.#idleTimer);\n this.#idleTimer = null;\n }\n }\n\n async rerank<TInputRecord extends MemoryRecord>(\n query: string,\n lists: ReadonlyArray<ReadonlyArray<MemoryHit<TInputRecord>>>,\n options: ReRankOptions = {},\n ): Promise<ReadonlyArray<MemoryHit<TInputRecord>>> {\n if (options.signal?.aborted === true) {\n throw new DOMException('TransformersJsReRanker aborted', 'AbortError');\n }\n this.#invocationCount += 1;\n const merged = mergeAndDedupe(lists);\n if (merged.length === 0) return [];\n const pipeline = await this.#getPipeline();\n const passages = merged.map((entry) =>\n this.#passageExtractor(entry.hit.record as unknown as TRecord),\n );\n const scores = await this.#scoreInBatches(pipeline, query, passages, options.signal);\n const fused: MemoryHit<TInputRecord>[] = merged.map((entry, idx) => {\n const score = scores[idx] ?? 0;\n const baseSignals = entry.hit.signals ?? {};\n const signals: Record<string, number> = {\n ...baseSignals,\n cross_encoder: score,\n };\n return Object.freeze({\n record: entry.hit.record,\n score,\n signals: Object.freeze(signals),\n });\n });\n fused.sort((a, b) => b.score - a.score);\n const topK = options.topK ?? 10;\n return fused.slice(0, Math.max(0, topK));\n }\n\n async #scoreInBatches(\n pipeline: CrossEncoderPipeline,\n query: string,\n passages: ReadonlyArray<string>,\n signal: AbortSignal | undefined,\n ): Promise<number[]> {\n const out: number[] = [];\n for (let i = 0; i < passages.length; i += this.batchSize) {\n if (signal?.aborted === true) {\n throw new DOMException('TransformersJsReRanker aborted', 'AbortError');\n }\n const batch = passages.slice(i, i + this.batchSize);\n const pairs = batch.map((text_pair) => ({ text: query, text_pair }));\n const raw = await pipeline(pairs, {\n topk: 1,\n ...(signal !== undefined ? { signal } : {}),\n });\n const scores = extractPairScores(raw, batch.length);\n for (const s of scores) out.push(s);\n }\n return out;\n }\n\n async #getPipeline(): Promise<CrossEncoderPipeline> {\n this.#lastUsedAt = this.#now();\n this.#scheduleIdleEviction();\n if (this.#pipeline !== null) return this.#pipeline;\n if (this.#loading !== null) return this.#loading;\n const factory = this.#pipelineFactory ?? (await loadDefaultPipelineFactory());\n this.#loading = factory('text-classification', this.model, {\n ...(this.#revision !== undefined ? { revision: this.#revision } : {}),\n ...(this.#cacheDir !== undefined ? { cache_dir: this.#cacheDir } : {}),\n ...(this.dtype !== undefined ? { dtype: this.dtype } : {}),\n ...(this.#device !== undefined ? { device: this.#device } : {}),\n })\n .then((pipe) => {\n this.#pipeline = pipe;\n this.#loading = null;\n return pipe;\n })\n .catch((err) => {\n this.#loading = null;\n throw new CrossEncoderLoadError(\n `[graphorin/reranker-transformersjs] failed to load model '${this.model}'.`,\n { cause: err },\n );\n });\n return this.#loading;\n }\n\n #scheduleIdleEviction(): void {\n if (this.idleEvictionMs === undefined) return;\n if (this.#idleTimer !== null) {\n clearTimeout(this.#idleTimer);\n }\n this.#idleTimer = setTimeout(() => {\n const elapsed = this.#now() - this.#lastUsedAt;\n if (elapsed >= (this.idleEvictionMs ?? 0)) {\n this.#pipeline = null;\n this.#loading = null;\n }\n this.#idleTimer = null;\n }, this.idleEvictionMs);\n this.#idleTimer.unref?.();\n }\n}\n\ninterface MergedEntry<TRecord extends MemoryRecord> {\n readonly hit: MemoryHit<TRecord>;\n readonly firstSeenOrder: number;\n}\n\n/**\n * Merge the per-source lists into a single deduplicated array,\n * preserving the **highest** initial score per record id and the\n * **first-seen order** for stable tie-breaking. Pure function;\n * exported for the unit test fixture.\n *\n * @stable\n */\nexport function mergeAndDedupe<TRecord extends MemoryRecord>(\n lists: ReadonlyArray<ReadonlyArray<MemoryHit<TRecord>>>,\n): ReadonlyArray<MergedEntry<TRecord>> {\n const out = new Map<string, MergedEntry<TRecord>>();\n let order = 0;\n for (const list of lists) {\n for (const hit of list) {\n const existing = out.get(hit.record.id);\n if (existing === undefined) {\n out.set(hit.record.id, { hit, firstSeenOrder: order++ });\n } else if (hit.score > existing.hit.score) {\n out.set(hit.record.id, {\n hit,\n firstSeenOrder: existing.firstSeenOrder,\n });\n }\n }\n }\n const arr = Array.from(out.values());\n arr.sort((a, b) => a.firstSeenOrder - b.firstSeenOrder);\n return arr;\n}\n\nexport { DEFAULT_ENGLISH_MODEL, DEFAULT_MULTILINGUAL_MODEL };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reranker.js","names":["#revision","#cacheDir","#device","#pipelineFactory","#passageExtractor","#now","#invocationCount","#pipeline","#loading","#idleTimer","#getPipeline","#scoreInBatches","fused: MemoryHit<TInputRecord>[]","signals: Record<string, number>","out: number[]","#lastUsedAt","#scheduleIdleEviction"],"sources":["../src/reranker.ts"],"sourcesContent":["/**\n * Cross-encoder `ReRanker` implementation backed by\n * `@huggingface/transformers`. Lazy-loaded, optional idle eviction,\n * locale-aware default model.\n *\n * @packageDocumentation\n */\n\nimport type { MemoryHit, MemoryRecord } from '@graphorin/core';\nimport type { ReRanker, ReRankOptions } from '@graphorin/memory/search';\n\nimport {\n CrossEncoderLoadError,\n type CrossEncoderPipeline,\n type CrossEncoderPipelineFactory,\n extractPairScores,\n loadDefaultPipelineFactory,\n type RerankerDtype,\n} from './cross-encoder.js';\nimport {\n DEFAULT_ENGLISH_MODEL,\n DEFAULT_MULTILINGUAL_MODEL,\n type LocaleTag,\n pickRerankerModel,\n} from './model-selection.js';\nimport { defaultPassageExtractor, type PassageExtractor } from './text-extraction.js';\n\n/**\n * Options accepted by {@link createCrossEncoderReranker}.\n *\n * @stable\n */\nexport interface CrossEncoderRerankerOptions<TRecord extends MemoryRecord = MemoryRecord> {\n /** Override the auto-picked model. Default: derived from `locale`. */\n readonly model?: string;\n /** BCP 47 locale tag used to select the default model. Default `'en'`. */\n readonly locale?: LocaleTag;\n /** Default `'fp16'`. */\n readonly dtype?: RerankerDtype;\n /** Optional revision pin (`'main'` if unset). */\n readonly revision?: string;\n /** Optional cache directory. Honours `GRAPHORIN_CACHE_DIR` when unset. */\n readonly cacheDir?: string;\n /** Override device (`'cpu'`, `'webgpu'`, …). Default `'cpu'`. */\n readonly device?: string;\n /**\n * Maximum batch size sent to the cross-encoder per inference call.\n * Default `32`. Larger batches improve throughput at the cost of\n * resident memory.\n */\n readonly batchSize?: number;\n /**\n * Optional idle-eviction timeout in milliseconds. When the reranker\n * does not score a pair within this window, the loaded pipeline is\n * dropped so the OS can reclaim the underlying ONNX session. Default\n * `undefined` (eviction disabled).\n */\n readonly idleEvictionMs?: number;\n /**\n * Optional passage extractor - replaces the default heuristic that\n * walks `text → summary → value → label → id`. Useful when a custom\n * `MemoryRecord` schema attaches the canonical text elsewhere.\n */\n readonly passageExtractor?: PassageExtractor<TRecord>;\n /**\n * Inject a `pipelineFactory`. Used by tests to stub the underlying\n * `@huggingface/transformers` pipeline. Production callers leave\n * this unset so the package lazily loads the peer.\n */\n readonly pipelineFactory?: CrossEncoderPipelineFactory;\n /**\n * Override the wall-clock provider. Used by tests so the\n * idle-eviction timer can be exercised deterministically.\n *\n * @internal\n */\n readonly now?: () => number;\n}\n\n/** @stable */\nexport const RERANKER_ID = 'transformersjs-cross-encoder' as const;\n\n/**\n * Build a cross-encoder reranker. Lazy: the pipeline is constructed on\n * the first `rerank()` call so packaging the reranker pays no\n * model-load cost.\n *\n * @stable\n */\nexport function createCrossEncoderReranker<TRecord extends MemoryRecord = MemoryRecord>(\n options: CrossEncoderRerankerOptions<TRecord> = {},\n): TransformersJsReRanker<TRecord> {\n return new TransformersJsReRanker<TRecord>(options);\n}\n\n/**\n * `ReRanker` implementation. Matches the contract from\n * `@graphorin/memory/search`.\n *\n * @stable\n */\nexport class TransformersJsReRanker<TRecord extends MemoryRecord = MemoryRecord>\n implements ReRanker\n{\n readonly id = RERANKER_ID;\n readonly model: string;\n readonly locale: LocaleTag;\n readonly dtype: RerankerDtype;\n readonly batchSize: number;\n readonly idleEvictionMs: number | undefined;\n\n readonly #revision: string | undefined;\n readonly #cacheDir: string | undefined;\n readonly #device: string | undefined;\n readonly #pipelineFactory: CrossEncoderPipelineFactory | undefined;\n readonly #passageExtractor: PassageExtractor<TRecord>;\n readonly #now: () => number;\n #pipeline: CrossEncoderPipeline | null = null;\n #loading: Promise<CrossEncoderPipeline> | null = null;\n #lastUsedAt: number = 0;\n #idleTimer: ReturnType<typeof setTimeout> | null = null;\n #invocationCount = 0;\n\n constructor(options: CrossEncoderRerankerOptions<TRecord>) {\n this.locale = options.locale ?? 'en';\n this.model = options.model ?? pickRerankerModel(this.locale);\n this.dtype = options.dtype ?? 'fp16';\n this.batchSize = options.batchSize ?? 32;\n if (!Number.isInteger(this.batchSize) || this.batchSize <= 0) {\n throw new RangeError(\n `[graphorin/reranker-transformersjs] batchSize must be a positive integer; got ${String(\n options.batchSize,\n )}.`,\n );\n }\n this.idleEvictionMs = options.idleEvictionMs;\n this.#revision = options.revision;\n this.#cacheDir = options.cacheDir ?? process.env.GRAPHORIN_CACHE_DIR;\n this.#device = options.device;\n this.#pipelineFactory = options.pipelineFactory;\n this.#passageExtractor =\n options.passageExtractor ?? ((record: TRecord) => defaultPassageExtractor(record));\n this.#now = options.now ?? Date.now;\n }\n\n /**\n * Number of `rerank(...)` invocations since construction. Surfaced\n * for observability + the test suite.\n *\n * @stable\n */\n get invocationCount(): number {\n return this.#invocationCount;\n }\n\n /**\n * Whether the underlying ONNX pipeline is currently loaded in\n * memory. Surfaced for the idle-eviction integration test.\n *\n * @stable\n */\n get pipelineLoaded(): boolean {\n return this.#pipeline !== null;\n }\n\n /**\n * Drop the loaded pipeline. Equivalent to letting the idle-eviction\n * timer fire. Idempotent.\n *\n * @stable\n */\n unload(): void {\n this.#pipeline = null;\n this.#loading = null;\n if (this.#idleTimer !== null) {\n clearTimeout(this.#idleTimer);\n this.#idleTimer = null;\n }\n }\n\n async rerank<TInputRecord extends MemoryRecord>(\n query: string,\n lists: ReadonlyArray<ReadonlyArray<MemoryHit<TInputRecord>>>,\n options: ReRankOptions = {},\n ): Promise<ReadonlyArray<MemoryHit<TInputRecord>>> {\n if (options.signal?.aborted === true) {\n throw new DOMException('TransformersJsReRanker aborted', 'AbortError');\n }\n this.#invocationCount += 1;\n const merged = mergeAndDedupe(lists);\n if (merged.length === 0) return [];\n const pipeline = await this.#getPipeline();\n const passages = merged.map((entry) =>\n this.#passageExtractor(entry.hit.record as unknown as TRecord),\n );\n const scores = await this.#scoreInBatches(pipeline, query, passages, options.signal);\n const fused: MemoryHit<TInputRecord>[] = merged.map((entry, idx) => {\n const score = scores[idx] ?? 0;\n const baseSignals = entry.hit.signals ?? {};\n const signals: Record<string, number> = {\n ...baseSignals,\n cross_encoder: score,\n };\n return Object.freeze({\n record: entry.hit.record,\n score,\n signals: Object.freeze(signals),\n });\n });\n fused.sort((a, b) => b.score - a.score);\n const topK = options.topK ?? 10;\n return fused.slice(0, Math.max(0, topK));\n }\n\n async #scoreInBatches(\n pipeline: CrossEncoderPipeline,\n query: string,\n passages: ReadonlyArray<string>,\n signal: AbortSignal | undefined,\n ): Promise<number[]> {\n const out: number[] = [];\n for (let i = 0; i < passages.length; i += this.batchSize) {\n if (signal?.aborted === true) {\n throw new DOMException('TransformersJsReRanker aborted', 'AbortError');\n }\n const batch = passages.slice(i, i + this.batchSize);\n const pairs = batch.map((text_pair) => ({ text: query, text_pair }));\n const raw = await pipeline(pairs, {\n topk: 1,\n ...(signal !== undefined ? { signal } : {}),\n });\n const scores = extractPairScores(raw, batch.length);\n for (const s of scores) out.push(s);\n }\n return out;\n }\n\n async #getPipeline(): Promise<CrossEncoderPipeline> {\n this.#lastUsedAt = this.#now();\n this.#scheduleIdleEviction();\n if (this.#pipeline !== null) return this.#pipeline;\n if (this.#loading !== null) return this.#loading;\n const factory = this.#pipelineFactory ?? (await loadDefaultPipelineFactory());\n this.#loading = factory('text-classification', this.model, {\n ...(this.#revision !== undefined ? { revision: this.#revision } : {}),\n ...(this.#cacheDir !== undefined ? { cache_dir: this.#cacheDir } : {}),\n ...(this.dtype !== undefined ? { dtype: this.dtype } : {}),\n ...(this.#device !== undefined ? { device: this.#device } : {}),\n })\n .then((pipe) => {\n this.#pipeline = pipe;\n this.#loading = null;\n return pipe;\n })\n .catch((err) => {\n this.#loading = null;\n throw new CrossEncoderLoadError(\n `[graphorin/reranker-transformersjs] failed to load model '${this.model}'.`,\n { cause: err },\n );\n });\n return this.#loading;\n }\n\n #scheduleIdleEviction(): void {\n if (this.idleEvictionMs === undefined) return;\n if (this.#idleTimer !== null) {\n clearTimeout(this.#idleTimer);\n }\n this.#idleTimer = setTimeout(() => {\n const elapsed = this.#now() - this.#lastUsedAt;\n if (elapsed >= (this.idleEvictionMs ?? 0)) {\n this.#pipeline = null;\n this.#loading = null;\n }\n this.#idleTimer = null;\n }, this.idleEvictionMs);\n this.#idleTimer.unref?.();\n }\n}\n\ninterface MergedEntry<TRecord extends MemoryRecord> {\n readonly hit: MemoryHit<TRecord>;\n readonly firstSeenOrder: number;\n}\n\n/**\n * Merge the per-source lists into a single deduplicated array,\n * preserving the **highest** initial score per record id and the\n * **first-seen order** for stable tie-breaking. Pure function;\n * exported for the unit test fixture.\n *\n * @stable\n */\nexport function mergeAndDedupe<TRecord extends MemoryRecord>(\n lists: ReadonlyArray<ReadonlyArray<MemoryHit<TRecord>>>,\n): ReadonlyArray<MergedEntry<TRecord>> {\n const out = new Map<string, MergedEntry<TRecord>>();\n let order = 0;\n for (const list of lists) {\n for (const hit of list) {\n const existing = out.get(hit.record.id);\n if (existing === undefined) {\n out.set(hit.record.id, { hit, firstSeenOrder: order++ });\n } else if (hit.score > existing.hit.score) {\n out.set(hit.record.id, {\n hit,\n firstSeenOrder: existing.firstSeenOrder,\n });\n }\n }\n }\n const arr = Array.from(out.values());\n arr.sort((a, b) => a.firstSeenOrder - b.firstSeenOrder);\n return arr;\n}\n\nexport { DEFAULT_ENGLISH_MODEL, DEFAULT_MULTILINGUAL_MODEL };\n"],"mappings":"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"}
|
|
@@ -6,9 +6,9 @@ import { MemoryRecord } from "@graphorin/core";
|
|
|
6
6
|
* Returns the best-effort passage text for a {@link MemoryRecord}. The
|
|
7
7
|
* order of preference, top-down:
|
|
8
8
|
*
|
|
9
|
-
* 1. `text`
|
|
10
|
-
* 2. `summary`
|
|
11
|
-
* 3. `value`
|
|
9
|
+
* 1. `text` - facts, rules, generic text-bearing tiers.
|
|
10
|
+
* 2. `summary` - episodes.
|
|
11
|
+
* 3. `value` - working-memory blocks.
|
|
12
12
|
* 4. `id` fallback so the reranker never sees an empty passage.
|
|
13
13
|
*
|
|
14
14
|
* @stable
|
package/dist/text-extraction.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Returns the best-effort passage text for a {@link MemoryRecord}. The
|
|
4
4
|
* order of preference, top-down:
|
|
5
5
|
*
|
|
6
|
-
* 1. `text`
|
|
7
|
-
* 2. `summary`
|
|
8
|
-
* 3. `value`
|
|
6
|
+
* 1. `text` - facts, rules, generic text-bearing tiers.
|
|
7
|
+
* 2. `summary` - episodes.
|
|
8
|
+
* 3. `value` - working-memory blocks.
|
|
9
9
|
* 4. `id` fallback so the reranker never sees an empty passage.
|
|
10
10
|
*
|
|
11
11
|
* @stable
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-extraction.js","names":[],"sources":["../src/text-extraction.ts"],"sourcesContent":["/**\n * Heuristic text-extraction helpers. The reranker scores `(query,\n * passage)` pairs; this module turns a `MemoryRecord` of any concrete\n * tier into the best single-passage approximation.\n *\n * The defaults are conservative
|
|
1
|
+
{"version":3,"file":"text-extraction.js","names":[],"sources":["../src/text-extraction.ts"],"sourcesContent":["/**\n * Heuristic text-extraction helpers. The reranker scores `(query,\n * passage)` pairs; this module turns a `MemoryRecord` of any concrete\n * tier into the best single-passage approximation.\n *\n * The defaults are conservative - text-bearing fields take precedence\n * over IDs / labels - and operators may inject a custom\n * `passageExtractor` when their schema attaches the canonical text\n * elsewhere.\n *\n * @packageDocumentation\n */\n\nimport type { MemoryRecord } from '@graphorin/core';\n\n/**\n * Returns the best-effort passage text for a {@link MemoryRecord}. The\n * order of preference, top-down:\n *\n * 1. `text` - facts, rules, generic text-bearing tiers.\n * 2. `summary` - episodes.\n * 3. `value` - working-memory blocks.\n * 4. `id` fallback so the reranker never sees an empty passage.\n *\n * @stable\n */\nexport function defaultPassageExtractor(record: MemoryRecord): string {\n const obj = record as unknown as Record<string, unknown>;\n const text = obj.text;\n if (typeof text === 'string' && text.length > 0) return text;\n const summary = obj.summary;\n if (typeof summary === 'string' && summary.length > 0) return summary;\n const value = obj.value;\n if (typeof value === 'string' && value.length > 0) return value;\n const label = obj.label;\n if (typeof label === 'string' && label.length > 0) return label;\n return record.id;\n}\n\n/**\n * Caller-supplied passage extractor. Receives the record + the\n * surrounding metadata (kind, sensitivity, tags) and returns the\n * passage to feed into the cross-encoder.\n *\n * @stable\n */\nexport type PassageExtractor<TRecord extends MemoryRecord = MemoryRecord> = (\n record: TRecord,\n) => string;\n"],"mappings":";;;;;;;;;;;;AA0BA,SAAgB,wBAAwB,QAA8B;CACpE,MAAM,MAAM;CACZ,MAAM,OAAO,IAAI;AACjB,KAAI,OAAO,SAAS,YAAY,KAAK,SAAS,EAAG,QAAO;CACxD,MAAM,UAAU,IAAI;AACpB,KAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,EAAG,QAAO;CAC9D,MAAM,QAAQ,IAAI;AAClB,KAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;CAC1D,MAAM,QAAQ,IAAI;AAClB,KAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;AAC1D,QAAO,OAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphorin/reranker-transformersjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Cross-encoder reranker adapter for the Graphorin framework. Wraps `@huggingface/transformers@^4.1.0` to score query / passage pairs in-process via the BAAI BGE family of cross-encoder models. Automatic model selection from the agent locale: `'en'` defaults to `Xenova/bge-reranker-base` (278M); every other locale defaults to `BAAI/bge-reranker-v2-m3` (568M, multilingual). FP16 quantization default; FP32 / Q8 opt-in. Lazy-loaded with an optional idle-eviction timer to bound resident memory. Implements the `ReRanker` contract exposed by `@graphorin/memory/search`. Created and maintained by Oleksiy Stepurenko.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Oleksiy Stepurenko",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"LICENSE"
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@graphorin/core": "0.
|
|
52
|
-
"@graphorin/memory": "0.
|
|
51
|
+
"@graphorin/core": "0.6.0",
|
|
52
|
+
"@graphorin/memory": "0.6.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@huggingface/transformers": "^4.1.0"
|