@graphorin/reranker-transformersjs 0.6.0 → 0.7.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 CHANGED
@@ -1,5 +1,31 @@
1
1
  # @graphorin/reranker-transformersjs
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-072: every export map's `import` condition becomes `default`, and the Node floor rises to `>=22.12.0`.
8
+
9
+ CJS consumers previously hit a bewildering `ERR_PACKAGE_PATH_NOT_EXPORTED` instead of a clear ESM-only signal. With the `default` condition, plain `require('@graphorin/core')` works via Node's stable `require(esm)` - which shipped in 22.12, hence the engines bump across every workspace manifest (packages, examples, benchmarks, docs; enforced by the widened mvp-readiness sweep). No dual-instance hazard: there is no CJS build, `require()` returns the same ESM module instance. ESM consumers are unaffected (`default` serves both paths; `types` stays first). The pack gate now runs attw under the full `node16` profile (was `esm-only`) and adds a runtime `require(esm)` smoke against the packed tarballs. Installs on Node 22.0-22.11 with `engine-strict` will refuse - upgrade Node (see the migration guide).
10
+
11
+ ### Patch Changes
12
+
13
+ - [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Tarballs now ship `src/` so the published `dist/**/*.d.ts.map` files actually work (W-136): the maps referenced `../src/*.ts` that the `files` whitelist excluded, so go-to-definition fell back into `.d.ts` and the shipped maps were dead weight. The pack gate gains a `map-integrity` leg: every source referenced by a shipped map must resolve inside the tarball (or be embedded via `sourcesContent`), with an anti-vacuous guard - a package whose tsdown config emits declaration maps must contain a non-zero number of `.d.ts.map` files, so a cache-restored dist that silently dropped maps fails the gate instead of passing vacuously. `mvp-readiness` now requires `src` in every publishable `files` array.
14
+
15
+ - Updated dependencies [[`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`c1af9c7`](https://github.com/o-stepper/graphorin/commit/c1af9c790757fbe82da6dd2b6c1fdc497b5c605e), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`73b19ca`](https://github.com/o-stepper/graphorin/commit/73b19caeda388bda628a48138cb7d70b1db839a3), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04)]:
16
+ - @graphorin/core@0.7.0
17
+ - @graphorin/memory@0.7.0
18
+
19
+ ## 0.6.1
20
+
21
+ ### Patch Changes
22
+
23
+ - [#142](https://github.com/o-stepper/graphorin/pull/142) [`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430) Thanks [@o-stepper](https://github.com/o-stepper)! - Version constants and version-bearing strings now derive from each package's manifest at build time (`VERSION = pkg.version`; writer ids, client/server info, OTLP framework attributes, build-info metrics interpolate it). No behavioral change at the current version: the rendered strings are byte-identical. A release bump no longer edits source; the new `check-version-consistency` gate fails any reintroduced hardcoded framework version.
24
+
25
+ - Updated dependencies [[`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430)]:
26
+ - @graphorin/core@0.6.1
27
+ - @graphorin/memory@0.6.1
28
+
3
29
  ## 0.6.0
4
30
 
5
31
  ### Patch 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.6.0 · MIT License · © 2026 Oleksiy Stepurenko ·
10
+ > Project Graphorin · v0.7.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.6.0 (optional sub-pack)
17
+ - **Published:** v0.7.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).
@@ -120,4 +120,4 @@ MIT © 2026 Oleksiy Stepurenko
120
120
 
121
121
  ---
122
122
 
123
- **Project Graphorin** · v0.6.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
123
+ **Project Graphorin** · v0.7.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
package/dist/index.d.ts CHANGED
@@ -37,8 +37,7 @@ import { CrossEncoderRerankerOptions, RERANKER_ID, TransformersJsReRanker, creat
37
37
  *
38
38
  * @packageDocumentation
39
39
  */
40
- /** Canonical version constant. Mirrors the `package.json` version. */
41
- declare const VERSION = "0.6.0";
40
+ declare const VERSION: string;
42
41
  //#endregion
43
42
  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
43
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AAkCA;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,OAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AAoCA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { version } from "./package.js";
1
2
  import { CrossEncoderLoadError, _resetPipelineFactoryCacheForTesting, extractPairScores, loadDefaultPipelineFactory } from "./cross-encoder.js";
2
3
  import { DEFAULT_ENGLISH_MODEL, DEFAULT_MULTILINGUAL_MODEL, pickRerankerModel } from "./model-selection.js";
3
4
  import { defaultPassageExtractor } from "./text-extraction.js";
@@ -36,8 +37,8 @@ import { RERANKER_ID, TransformersJsReRanker, createCrossEncoderReranker, mergeA
36
37
  *
37
38
  * @packageDocumentation
38
39
  */
39
- /** Canonical version constant. Mirrors the `package.json` version. */
40
- const VERSION = "0.6.0";
40
+ /** Canonical version constant, derived from `package.json` at build time. */
41
+ const VERSION = version;
41
42
 
42
43
  //#endregion
43
44
  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 - 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"}
1
+ {"version":3,"file":"index.js","names":["VERSION: string","pkg.version"],"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, derived from `package.json` at build time. */\nimport pkg from '../package.json' with { type: 'json' };\n\nexport const VERSION: string = pkg.version;\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,MAAaA,UAAkBC"}
@@ -0,0 +1,6 @@
1
+ //#region package.json
2
+ var version = "0.7.0";
3
+
4
+ //#endregion
5
+ export { version };
6
+ //# sourceMappingURL=package.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.js","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@graphorin/reranker-transformersjs\",\n \"version\": \"0.7.0\",\n \"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.\",\n \"license\": \"MIT\",\n \"author\": \"Oleksiy Stepurenko\",\n \"homepage\": \"https://github.com/o-stepper/graphorin/tree/main/packages/reranker-transformersjs\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/o-stepper/graphorin.git\",\n \"directory\": \"packages/reranker-transformersjs\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/o-stepper/graphorin/issues\"\n },\n \"keywords\": [\n \"graphorin\",\n \"ai\",\n \"agents\",\n \"framework\",\n \"rerank\",\n \"reranker\",\n \"cross-encoder\",\n \"huggingface\",\n \"transformersjs\",\n \"bge\",\n \"bge-reranker\"\n ],\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"sideEffects\": false,\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"src\",\n \"README.md\",\n \"CHANGELOG.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"typecheck\": \"tsc --noEmit && tsc -p tsconfig.tests.json\",\n \"test\": \"vitest run\",\n \"lint\": \"biome check .\",\n \"clean\": \"rimraf dist .turbo *.tsbuildinfo\"\n },\n \"dependencies\": {\n \"@graphorin/core\": \"workspace:*\",\n \"@graphorin/memory\": \"workspace:*\"\n },\n \"peerDependencies\": {\n \"@huggingface/transformers\": \"^4.1.0\"\n },\n \"peerDependenciesMeta\": {\n \"@huggingface/transformers\": {\n \"optional\": false\n }\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n }\n}\n"],"mappings":";cAEa"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphorin/reranker-transformersjs",
3
- "version": "0.6.0",
3
+ "version": "0.7.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",
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "type": "module",
30
30
  "engines": {
31
- "node": ">=22.0.0"
31
+ "node": ">=22.12.0"
32
32
  },
33
33
  "main": "./dist/index.js",
34
34
  "module": "./dist/index.js",
@@ -37,19 +37,20 @@
37
37
  "exports": {
38
38
  ".": {
39
39
  "types": "./dist/index.d.ts",
40
- "import": "./dist/index.js"
40
+ "default": "./dist/index.js"
41
41
  },
42
42
  "./package.json": "./package.json"
43
43
  },
44
44
  "files": [
45
45
  "dist",
46
+ "src",
46
47
  "README.md",
47
48
  "CHANGELOG.md",
48
49
  "LICENSE"
49
50
  ],
50
51
  "dependencies": {
51
- "@graphorin/core": "0.6.0",
52
- "@graphorin/memory": "0.6.0"
52
+ "@graphorin/core": "0.7.0",
53
+ "@graphorin/memory": "0.7.0"
53
54
  },
54
55
  "peerDependencies": {
55
56
  "@huggingface/transformers": "^4.1.0"
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Lazy `@huggingface/transformers` text-classification pipeline used to
3
+ * score query / passage pairs. The runner caches the first-loaded
4
+ * pipeline per `(model, dtype, device, revision, cacheDir)` tuple so
5
+ * repeated `rerank(...)` calls inside the same process do not pay the
6
+ * model-load cost twice.
7
+ *
8
+ * Tests inject a stub `pipelineFactory`; the production path lazily
9
+ * imports `@huggingface/transformers` so plain inspection of the
10
+ * package (e.g. for documentation generation) does not pull in the
11
+ * native ONNX runtime.
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+
16
+ /**
17
+ * Numeric dtype hint. Default `'fp16'` per Phase 16 §
18
+ * `@graphorin/reranker-transformersjs`.
19
+ *
20
+ * @stable
21
+ */
22
+ export type RerankerDtype = 'fp32' | 'fp16' | 'q8' | 'q4';
23
+
24
+ /**
25
+ * Output shape returned by `@huggingface/transformers`'
26
+ * text-classification pipeline. Each pair returns either a single
27
+ * `{ label, score }` object (top-k = 1) or an array of them. We
28
+ * normalise on the array form upstream so the cross-encoder always
29
+ * sees a consistent shape.
30
+ *
31
+ * @internal
32
+ */
33
+ export interface ClassifierResult {
34
+ readonly label: string;
35
+ readonly score: number;
36
+ }
37
+
38
+ /** @internal */
39
+ export type CrossEncoderPipeline = (
40
+ pairs: ReadonlyArray<{ text: string; text_pair: string }>,
41
+ options?: { topk?: number; signal?: AbortSignal },
42
+ ) => Promise<ReadonlyArray<ClassifierResult> | ReadonlyArray<ReadonlyArray<ClassifierResult>>>;
43
+
44
+ /** @internal */
45
+ export type CrossEncoderPipelineFactory = (
46
+ task: 'text-classification',
47
+ model: string,
48
+ options: {
49
+ revision?: string;
50
+ cache_dir?: string;
51
+ dtype?: RerankerDtype;
52
+ device?: string;
53
+ },
54
+ ) => Promise<CrossEncoderPipeline>;
55
+
56
+ /**
57
+ * Raised when the `@huggingface/transformers` peer is missing or the
58
+ * configured cross-encoder model fails to load.
59
+ *
60
+ * @stable
61
+ */
62
+ export class CrossEncoderLoadError extends Error {
63
+ override readonly name = 'CrossEncoderLoadError';
64
+ }
65
+
66
+ /** @internal */
67
+ let CACHED_PIPELINE_FACTORY: CrossEncoderPipelineFactory | null = null;
68
+
69
+ /** @internal */
70
+ export async function loadDefaultPipelineFactory(): Promise<CrossEncoderPipelineFactory> {
71
+ if (CACHED_PIPELINE_FACTORY !== null) return CACHED_PIPELINE_FACTORY;
72
+ try {
73
+ const mod = (await import('@huggingface/transformers')) as unknown as {
74
+ pipeline: CrossEncoderPipelineFactory;
75
+ };
76
+ CACHED_PIPELINE_FACTORY = mod.pipeline;
77
+ return CACHED_PIPELINE_FACTORY;
78
+ } catch (err) {
79
+ throw new CrossEncoderLoadError(
80
+ "[graphorin/reranker-transformersjs] required peer '@huggingface/transformers' is not installed.",
81
+ { cause: err },
82
+ );
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Test-only helper. Drops the cached pipeline factory so the next
88
+ * loader call re-imports the peer.
89
+ *
90
+ * @internal
91
+ */
92
+ export function _resetPipelineFactoryCacheForTesting(): void {
93
+ CACHED_PIPELINE_FACTORY = null;
94
+ }
95
+
96
+ /**
97
+ * True when a classifier label names the *positive* (relevant) class. Matches
98
+ * the conventional binary forms - `LABEL_1`, `positive`, `relevant`,
99
+ * `entailment`, `true`, `yes` - using exact words so it does not mis-fire on a
100
+ * negative label that merely contains one (`irrelevant` ⊃ `relevant`).
101
+ */
102
+ function isPositiveLabel(label: string): boolean {
103
+ const l = label.toLowerCase().trim();
104
+ if (l === '1' || /(?:^|[_-])1$/.test(l)) return true; // '1', 'label_1', 'class-1'
105
+ return /^(?:positive|relevant|entailment|true|yes|pos)$/.test(l);
106
+ }
107
+
108
+ /**
109
+ * Normalises the raw pipeline output to a flat `score[]` aligned with the input
110
+ * pair order. Cross-encoder classifiers return either a single-best
111
+ * `{label, score}` per pair (the default single-logit bge exports) or an array
112
+ * of `topk` entries. For the array shape we read the POSITIVE label's
113
+ * confidence - NOT the max of any label (PS-16): an irrelevant pair's most
114
+ * confident class is the *negative* one, so taking the max would invert the
115
+ * ranking for any 2-label classifier. When no label looks positive (single-logit
116
+ * or unrecognised labels) we fall back to the top score.
117
+ *
118
+ * @internal
119
+ */
120
+ export function extractPairScores(
121
+ raw: ReadonlyArray<ClassifierResult> | ReadonlyArray<ReadonlyArray<ClassifierResult>>,
122
+ pairCount: number,
123
+ ): number[] {
124
+ const out: number[] = new Array(pairCount).fill(0) as number[];
125
+ for (let i = 0; i < pairCount; i++) {
126
+ const cell = raw[i];
127
+ if (cell === undefined) continue;
128
+ if (Array.isArray(cell)) {
129
+ let best = Number.NEGATIVE_INFINITY;
130
+ let positive = Number.NEGATIVE_INFINITY;
131
+ let sawPositive = false;
132
+ for (const entry of cell) {
133
+ if (entry === undefined) continue;
134
+ if (entry.score > best) best = entry.score;
135
+ if (isPositiveLabel(entry.label)) {
136
+ sawPositive = true;
137
+ if (entry.score > positive) positive = entry.score;
138
+ }
139
+ }
140
+ const chosen = sawPositive ? positive : best;
141
+ out[i] = Number.isFinite(chosen) ? chosen : 0;
142
+ } else {
143
+ const single = cell as ClassifierResult;
144
+ out[i] = single.score;
145
+ }
146
+ }
147
+ return out;
148
+ }
package/src/index.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @graphorin/reranker-transformersjs - cross-encoder reranker adapter
3
+ * for the Graphorin framework.
4
+ *
5
+ * Wraps `@huggingface/transformers@^4.1.0` to score `(query, passage)`
6
+ * pairs in-process. Plug into the memory hybrid-search pipeline as a
7
+ * drop-in replacement for the built-in `RRFReranker`:
8
+ *
9
+ * ```ts
10
+ * import { createMemory } from '@graphorin/memory';
11
+ * import { createCrossEncoderReranker } from '@graphorin/reranker-transformersjs';
12
+ *
13
+ * const memory = createMemory({
14
+ * store,
15
+ * embedder,
16
+ * reranker: createCrossEncoderReranker({ locale: 'en' }),
17
+ * });
18
+ * ```
19
+ *
20
+ * Locale-aware default model:
21
+ *
22
+ * - `'en'` / `'en-*'` → `Xenova/bge-reranker-base` (278M parameters,
23
+ * FP16 quantized).
24
+ * - Every other locale → `BAAI/bge-reranker-v2-m3` (568M parameters,
25
+ * multilingual baseline).
26
+ *
27
+ * Operators that want a narrower / language-specific cross-encoder
28
+ * pass an explicit `model` option - the package's defaults
29
+ * deliberately avoid privileging any single language pair.
30
+ *
31
+ * @packageDocumentation
32
+ */
33
+
34
+ /** Canonical version constant, derived from `package.json` at build time. */
35
+ import pkg from '../package.json' with { type: 'json' };
36
+
37
+ export const VERSION: string = pkg.version;
38
+
39
+ export {
40
+ _resetPipelineFactoryCacheForTesting,
41
+ type ClassifierResult,
42
+ CrossEncoderLoadError,
43
+ type CrossEncoderPipeline,
44
+ type CrossEncoderPipelineFactory,
45
+ extractPairScores,
46
+ loadDefaultPipelineFactory,
47
+ type RerankerDtype,
48
+ } from './cross-encoder.js';
49
+ export {
50
+ DEFAULT_ENGLISH_MODEL,
51
+ DEFAULT_MULTILINGUAL_MODEL,
52
+ type LocaleTag,
53
+ pickRerankerModel,
54
+ } from './model-selection.js';
55
+ export {
56
+ type CrossEncoderRerankerOptions,
57
+ createCrossEncoderReranker,
58
+ mergeAndDedupe,
59
+ RERANKER_ID,
60
+ TransformersJsReRanker,
61
+ } from './reranker.js';
62
+ export {
63
+ defaultPassageExtractor,
64
+ type PassageExtractor,
65
+ } from './text-extraction.js';
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Locale-aware default model selector. Mirrors ADR-024 / DEC-120 § BGE
3
+ * model family:
4
+ *
5
+ * - English-only deployments → `Xenova/bge-reranker-base` (278M, FP16).
6
+ * - Every other locale → `BAAI/bge-reranker-v2-m3` (568M, multilingual).
7
+ *
8
+ * The selector deliberately stays language-pair-agnostic outside the
9
+ * English fast path; operators that want a narrower / language-specific
10
+ * cross-encoder pass an explicit `model` to {@link
11
+ * createCrossEncoderReranker}.
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+
16
+ /**
17
+ * BCP 47 locale tag (e.g. `'en'`, `'en-GB'`, `'pt-BR'`, `'zh-Hans-CN'`).
18
+ *
19
+ * @stable
20
+ */
21
+ export type LocaleTag = string;
22
+
23
+ /** @stable */
24
+ export const DEFAULT_ENGLISH_MODEL = 'Xenova/bge-reranker-base';
25
+ /** @stable */
26
+ export const DEFAULT_MULTILINGUAL_MODEL = 'BAAI/bge-reranker-v2-m3';
27
+
28
+ /**
29
+ * Pick a reranker model from the agent locale. Pure function so callers
30
+ * (and tests) can pre-resolve the choice without constructing the
31
+ * reranker.
32
+ *
33
+ * @stable
34
+ */
35
+ export function pickRerankerModel(locale: LocaleTag | undefined): string {
36
+ if (locale === undefined || locale.length === 0) return DEFAULT_MULTILINGUAL_MODEL;
37
+ const normalized = locale.toLowerCase();
38
+ if (normalized === 'en' || normalized.startsWith('en-')) return DEFAULT_ENGLISH_MODEL;
39
+ return DEFAULT_MULTILINGUAL_MODEL;
40
+ }
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Cross-encoder `ReRanker` implementation backed by
3
+ * `@huggingface/transformers`. Lazy-loaded, optional idle eviction,
4
+ * locale-aware default model.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ import type { MemoryHit, MemoryRecord } from '@graphorin/core';
10
+ import type { ReRanker, ReRankOptions } from '@graphorin/memory/search';
11
+
12
+ import {
13
+ CrossEncoderLoadError,
14
+ type CrossEncoderPipeline,
15
+ type CrossEncoderPipelineFactory,
16
+ extractPairScores,
17
+ loadDefaultPipelineFactory,
18
+ type RerankerDtype,
19
+ } from './cross-encoder.js';
20
+ import {
21
+ DEFAULT_ENGLISH_MODEL,
22
+ DEFAULT_MULTILINGUAL_MODEL,
23
+ type LocaleTag,
24
+ pickRerankerModel,
25
+ } from './model-selection.js';
26
+ import { defaultPassageExtractor, type PassageExtractor } from './text-extraction.js';
27
+
28
+ /**
29
+ * Options accepted by {@link createCrossEncoderReranker}.
30
+ *
31
+ * @stable
32
+ */
33
+ export interface CrossEncoderRerankerOptions<TRecord extends MemoryRecord = MemoryRecord> {
34
+ /** Override the auto-picked model. Default: derived from `locale`. */
35
+ readonly model?: string;
36
+ /** BCP 47 locale tag used to select the default model. Default `'en'`. */
37
+ readonly locale?: LocaleTag;
38
+ /** Default `'fp16'`. */
39
+ readonly dtype?: RerankerDtype;
40
+ /** Optional revision pin (`'main'` if unset). */
41
+ readonly revision?: string;
42
+ /** Optional cache directory. Honours `GRAPHORIN_CACHE_DIR` when unset. */
43
+ readonly cacheDir?: string;
44
+ /** Override device (`'cpu'`, `'webgpu'`, …). Default `'cpu'`. */
45
+ readonly device?: string;
46
+ /**
47
+ * Maximum batch size sent to the cross-encoder per inference call.
48
+ * Default `32`. Larger batches improve throughput at the cost of
49
+ * resident memory.
50
+ */
51
+ readonly batchSize?: number;
52
+ /**
53
+ * Optional idle-eviction timeout in milliseconds. When the reranker
54
+ * does not score a pair within this window, the loaded pipeline is
55
+ * dropped so the OS can reclaim the underlying ONNX session. Default
56
+ * `undefined` (eviction disabled).
57
+ */
58
+ readonly idleEvictionMs?: number;
59
+ /**
60
+ * Optional passage extractor - replaces the default heuristic that
61
+ * walks `text → summary → value → label → id`. Useful when a custom
62
+ * `MemoryRecord` schema attaches the canonical text elsewhere.
63
+ */
64
+ readonly passageExtractor?: PassageExtractor<TRecord>;
65
+ /**
66
+ * Inject a `pipelineFactory`. Used by tests to stub the underlying
67
+ * `@huggingface/transformers` pipeline. Production callers leave
68
+ * this unset so the package lazily loads the peer.
69
+ */
70
+ readonly pipelineFactory?: CrossEncoderPipelineFactory;
71
+ /**
72
+ * Override the wall-clock provider. Used by tests so the
73
+ * idle-eviction timer can be exercised deterministically.
74
+ *
75
+ * @internal
76
+ */
77
+ readonly now?: () => number;
78
+ }
79
+
80
+ /** @stable */
81
+ export const RERANKER_ID = 'transformersjs-cross-encoder' as const;
82
+
83
+ /**
84
+ * Build a cross-encoder reranker. Lazy: the pipeline is constructed on
85
+ * the first `rerank()` call so packaging the reranker pays no
86
+ * model-load cost.
87
+ *
88
+ * @stable
89
+ */
90
+ export function createCrossEncoderReranker<TRecord extends MemoryRecord = MemoryRecord>(
91
+ options: CrossEncoderRerankerOptions<TRecord> = {},
92
+ ): TransformersJsReRanker<TRecord> {
93
+ return new TransformersJsReRanker<TRecord>(options);
94
+ }
95
+
96
+ /**
97
+ * `ReRanker` implementation. Matches the contract from
98
+ * `@graphorin/memory/search`.
99
+ *
100
+ * @stable
101
+ */
102
+ export class TransformersJsReRanker<TRecord extends MemoryRecord = MemoryRecord>
103
+ implements ReRanker
104
+ {
105
+ readonly id = RERANKER_ID;
106
+ readonly model: string;
107
+ readonly locale: LocaleTag;
108
+ readonly dtype: RerankerDtype;
109
+ readonly batchSize: number;
110
+ readonly idleEvictionMs: number | undefined;
111
+
112
+ readonly #revision: string | undefined;
113
+ readonly #cacheDir: string | undefined;
114
+ readonly #device: string | undefined;
115
+ readonly #pipelineFactory: CrossEncoderPipelineFactory | undefined;
116
+ readonly #passageExtractor: PassageExtractor<TRecord>;
117
+ readonly #now: () => number;
118
+ #pipeline: CrossEncoderPipeline | null = null;
119
+ #loading: Promise<CrossEncoderPipeline> | null = null;
120
+ #lastUsedAt: number = 0;
121
+ #idleTimer: ReturnType<typeof setTimeout> | null = null;
122
+ #invocationCount = 0;
123
+
124
+ constructor(options: CrossEncoderRerankerOptions<TRecord>) {
125
+ this.locale = options.locale ?? 'en';
126
+ this.model = options.model ?? pickRerankerModel(this.locale);
127
+ this.dtype = options.dtype ?? 'fp16';
128
+ this.batchSize = options.batchSize ?? 32;
129
+ if (!Number.isInteger(this.batchSize) || this.batchSize <= 0) {
130
+ throw new RangeError(
131
+ `[graphorin/reranker-transformersjs] batchSize must be a positive integer; got ${String(
132
+ options.batchSize,
133
+ )}.`,
134
+ );
135
+ }
136
+ this.idleEvictionMs = options.idleEvictionMs;
137
+ this.#revision = options.revision;
138
+ this.#cacheDir = options.cacheDir ?? process.env.GRAPHORIN_CACHE_DIR;
139
+ this.#device = options.device;
140
+ this.#pipelineFactory = options.pipelineFactory;
141
+ this.#passageExtractor =
142
+ options.passageExtractor ?? ((record: TRecord) => defaultPassageExtractor(record));
143
+ this.#now = options.now ?? Date.now;
144
+ }
145
+
146
+ /**
147
+ * Number of `rerank(...)` invocations since construction. Surfaced
148
+ * for observability + the test suite.
149
+ *
150
+ * @stable
151
+ */
152
+ get invocationCount(): number {
153
+ return this.#invocationCount;
154
+ }
155
+
156
+ /**
157
+ * Whether the underlying ONNX pipeline is currently loaded in
158
+ * memory. Surfaced for the idle-eviction integration test.
159
+ *
160
+ * @stable
161
+ */
162
+ get pipelineLoaded(): boolean {
163
+ return this.#pipeline !== null;
164
+ }
165
+
166
+ /**
167
+ * Drop the loaded pipeline. Equivalent to letting the idle-eviction
168
+ * timer fire. Idempotent.
169
+ *
170
+ * @stable
171
+ */
172
+ unload(): void {
173
+ this.#pipeline = null;
174
+ this.#loading = null;
175
+ if (this.#idleTimer !== null) {
176
+ clearTimeout(this.#idleTimer);
177
+ this.#idleTimer = null;
178
+ }
179
+ }
180
+
181
+ async rerank<TInputRecord extends MemoryRecord>(
182
+ query: string,
183
+ lists: ReadonlyArray<ReadonlyArray<MemoryHit<TInputRecord>>>,
184
+ options: ReRankOptions = {},
185
+ ): Promise<ReadonlyArray<MemoryHit<TInputRecord>>> {
186
+ if (options.signal?.aborted === true) {
187
+ throw new DOMException('TransformersJsReRanker aborted', 'AbortError');
188
+ }
189
+ this.#invocationCount += 1;
190
+ const merged = mergeAndDedupe(lists);
191
+ if (merged.length === 0) return [];
192
+ const pipeline = await this.#getPipeline();
193
+ const passages = merged.map((entry) =>
194
+ this.#passageExtractor(entry.hit.record as unknown as TRecord),
195
+ );
196
+ const scores = await this.#scoreInBatches(pipeline, query, passages, options.signal);
197
+ const fused: MemoryHit<TInputRecord>[] = merged.map((entry, idx) => {
198
+ const score = scores[idx] ?? 0;
199
+ const baseSignals = entry.hit.signals ?? {};
200
+ const signals: Record<string, number> = {
201
+ ...baseSignals,
202
+ cross_encoder: score,
203
+ };
204
+ return Object.freeze({
205
+ record: entry.hit.record,
206
+ score,
207
+ signals: Object.freeze(signals),
208
+ });
209
+ });
210
+ fused.sort((a, b) => b.score - a.score);
211
+ const topK = options.topK ?? 10;
212
+ return fused.slice(0, Math.max(0, topK));
213
+ }
214
+
215
+ async #scoreInBatches(
216
+ pipeline: CrossEncoderPipeline,
217
+ query: string,
218
+ passages: ReadonlyArray<string>,
219
+ signal: AbortSignal | undefined,
220
+ ): Promise<number[]> {
221
+ const out: number[] = [];
222
+ for (let i = 0; i < passages.length; i += this.batchSize) {
223
+ if (signal?.aborted === true) {
224
+ throw new DOMException('TransformersJsReRanker aborted', 'AbortError');
225
+ }
226
+ const batch = passages.slice(i, i + this.batchSize);
227
+ const pairs = batch.map((text_pair) => ({ text: query, text_pair }));
228
+ const raw = await pipeline(pairs, {
229
+ topk: 1,
230
+ ...(signal !== undefined ? { signal } : {}),
231
+ });
232
+ const scores = extractPairScores(raw, batch.length);
233
+ for (const s of scores) out.push(s);
234
+ }
235
+ return out;
236
+ }
237
+
238
+ async #getPipeline(): Promise<CrossEncoderPipeline> {
239
+ this.#lastUsedAt = this.#now();
240
+ this.#scheduleIdleEviction();
241
+ if (this.#pipeline !== null) return this.#pipeline;
242
+ if (this.#loading !== null) return this.#loading;
243
+ const factory = this.#pipelineFactory ?? (await loadDefaultPipelineFactory());
244
+ this.#loading = factory('text-classification', this.model, {
245
+ ...(this.#revision !== undefined ? { revision: this.#revision } : {}),
246
+ ...(this.#cacheDir !== undefined ? { cache_dir: this.#cacheDir } : {}),
247
+ ...(this.dtype !== undefined ? { dtype: this.dtype } : {}),
248
+ ...(this.#device !== undefined ? { device: this.#device } : {}),
249
+ })
250
+ .then((pipe) => {
251
+ this.#pipeline = pipe;
252
+ this.#loading = null;
253
+ return pipe;
254
+ })
255
+ .catch((err) => {
256
+ this.#loading = null;
257
+ throw new CrossEncoderLoadError(
258
+ `[graphorin/reranker-transformersjs] failed to load model '${this.model}'.`,
259
+ { cause: err },
260
+ );
261
+ });
262
+ return this.#loading;
263
+ }
264
+
265
+ #scheduleIdleEviction(): void {
266
+ if (this.idleEvictionMs === undefined) return;
267
+ if (this.#idleTimer !== null) {
268
+ clearTimeout(this.#idleTimer);
269
+ }
270
+ this.#idleTimer = setTimeout(() => {
271
+ const elapsed = this.#now() - this.#lastUsedAt;
272
+ if (elapsed >= (this.idleEvictionMs ?? 0)) {
273
+ this.#pipeline = null;
274
+ this.#loading = null;
275
+ }
276
+ this.#idleTimer = null;
277
+ }, this.idleEvictionMs);
278
+ this.#idleTimer.unref?.();
279
+ }
280
+ }
281
+
282
+ interface MergedEntry<TRecord extends MemoryRecord> {
283
+ readonly hit: MemoryHit<TRecord>;
284
+ readonly firstSeenOrder: number;
285
+ }
286
+
287
+ /**
288
+ * Merge the per-source lists into a single deduplicated array,
289
+ * preserving the **highest** initial score per record id and the
290
+ * **first-seen order** for stable tie-breaking. Pure function;
291
+ * exported for the unit test fixture.
292
+ *
293
+ * @stable
294
+ */
295
+ export function mergeAndDedupe<TRecord extends MemoryRecord>(
296
+ lists: ReadonlyArray<ReadonlyArray<MemoryHit<TRecord>>>,
297
+ ): ReadonlyArray<MergedEntry<TRecord>> {
298
+ const out = new Map<string, MergedEntry<TRecord>>();
299
+ let order = 0;
300
+ for (const list of lists) {
301
+ for (const hit of list) {
302
+ const existing = out.get(hit.record.id);
303
+ if (existing === undefined) {
304
+ out.set(hit.record.id, { hit, firstSeenOrder: order++ });
305
+ } else if (hit.score > existing.hit.score) {
306
+ out.set(hit.record.id, {
307
+ hit,
308
+ firstSeenOrder: existing.firstSeenOrder,
309
+ });
310
+ }
311
+ }
312
+ }
313
+ const arr = Array.from(out.values());
314
+ arr.sort((a, b) => a.firstSeenOrder - b.firstSeenOrder);
315
+ return arr;
316
+ }
317
+
318
+ export { DEFAULT_ENGLISH_MODEL, DEFAULT_MULTILINGUAL_MODEL };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Heuristic text-extraction helpers. The reranker scores `(query,
3
+ * passage)` pairs; this module turns a `MemoryRecord` of any concrete
4
+ * tier into the best single-passage approximation.
5
+ *
6
+ * The defaults are conservative - text-bearing fields take precedence
7
+ * over IDs / labels - and operators may inject a custom
8
+ * `passageExtractor` when their schema attaches the canonical text
9
+ * elsewhere.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+
14
+ import type { MemoryRecord } from '@graphorin/core';
15
+
16
+ /**
17
+ * Returns the best-effort passage text for a {@link MemoryRecord}. The
18
+ * order of preference, top-down:
19
+ *
20
+ * 1. `text` - facts, rules, generic text-bearing tiers.
21
+ * 2. `summary` - episodes.
22
+ * 3. `value` - working-memory blocks.
23
+ * 4. `id` fallback so the reranker never sees an empty passage.
24
+ *
25
+ * @stable
26
+ */
27
+ export function defaultPassageExtractor(record: MemoryRecord): string {
28
+ const obj = record as unknown as Record<string, unknown>;
29
+ const text = obj.text;
30
+ if (typeof text === 'string' && text.length > 0) return text;
31
+ const summary = obj.summary;
32
+ if (typeof summary === 'string' && summary.length > 0) return summary;
33
+ const value = obj.value;
34
+ if (typeof value === 'string' && value.length > 0) return value;
35
+ const label = obj.label;
36
+ if (typeof label === 'string' && label.length > 0) return label;
37
+ return record.id;
38
+ }
39
+
40
+ /**
41
+ * Caller-supplied passage extractor. Receives the record + the
42
+ * surrounding metadata (kind, sensitivity, tags) and returns the
43
+ * passage to feed into the cross-encoder.
44
+ *
45
+ * @stable
46
+ */
47
+ export type PassageExtractor<TRecord extends MemoryRecord = MemoryRecord> = (
48
+ record: TRecord,
49
+ ) => string;