11/**
2- * Embedder resolution — auto-selects the best available embedder.
3- *
4- * Provides `resolveEmbedder()` which tries TransformersEmbedder first,
5- * falling back to SimpleEmbedder on model-load failures. Also exports
6- * `isRecoverableError()` for distinguishing transient vs code-level errors.
2+ * Embedder resolution — resolves the TransformersEmbedder from the
3+ * configured model. Throws an error when the model cannot be loaded
4+ * or @huggingface/transformers is not installed.
75 */
86
97import { Embedder } from './types' ;
10- import { SimpleEmbedder } from './simple' ;
118import { TransformersEmbedder , loadTransformersModule } from './transformers' ;
129
1310// ---------------------------------------------------------------------------
1411// Embedder type info
1512// ---------------------------------------------------------------------------
1613
1714/** Describes the kind of embedder being used. */
18- export type EmbedderKind = 'transformers' | 'simple' ;
15+ export type EmbedderKind = 'transformers' ;
1916
2017/** Diagnostic information about the resolved embedder. */
2118export interface EmbedderInfo {
@@ -25,10 +22,6 @@ export interface EmbedderInfo {
2522 dimensions : number ;
2623 /** Model ID (only set for TransformersEmbedder). */
2724 modelId ?: string ;
28- /** Whether the resolution fell back from the preferred embedder. */
29- isFallback : boolean ;
30- /** Reason for fallback (if applicable). */
31- fallbackReason ?: string ;
3225}
3326
3427// ---------------------------------------------------------------------------
@@ -48,108 +41,41 @@ export interface ResolveResult {
4841/**
4942 * Resolve which Embedder to use based on the model option.
5043 *
51- * - If model is specified, try TransformersEmbedder first, fall back to
52- * SimpleEmbedder on failure (with a cooldown to allow retry).
53- * - If no model, try TransformersEmbedder with the default model.
54- *
55- * Returns both the embedder instance and diagnostic info so callers
56- * can detect fallbacks and report them to users.
44+ * Requires @huggingface/transformers to be installed and the model to be
45+ * loadable. Throws a descriptive error when either condition is not met.
5746 */
5847export async function resolveEmbedder ( model ?: string ) : Promise < ResolveResult > {
5948 const t = await loadTransformersModule ( ) ;
6049
61- if ( t ) {
62- try {
63- const embedder = new TransformersEmbedder ( undefined , { modelId : model } ) ;
64- await embedder . embed ( 'probe' ) ;
65- return {
66- embedder,
67- info : {
68- kind : 'transformers' ,
69- dimensions : embedder . dimensions ,
70- modelId : model ?? 'onnx-community/bge-small-zh-v1.5-ONNX' ,
71- isFallback : false ,
72- } ,
73- } ;
74- } catch ( err ) {
75- // Only fallback for network/model-load errors, not for code bugs
76- if ( isRecoverableError ( err ) ) {
77- const reason = ( err as Error ) . message ?. split ( '\n' ) [ 0 ] ?? 'unknown' ;
78- console . warn (
79- `[context] Model (${ model ?? 'bge-small-zh-v1.5' } ) load failed, falling back to basic mode (lower recall quality).\n` +
80- ` To fix model download:\n` +
81- ` 1. Set mirror: export HF_ENDPOINT=https://hf-mirror.com\n` +
82- ` 2. Manual download: node scripts/download-model.mjs\n`
83- ) ;
84- const fallback = new SimpleEmbedder ( ) ;
85- return {
86- embedder : fallback ,
87- info : {
88- kind : 'simple' ,
89- dimensions : fallback . dimensions ,
90- isFallback : true ,
91- fallbackReason : reason ,
92- } ,
93- } ;
94- }
95- // Unrecoverable errors should propagate
96- throw err ;
97- }
50+ if ( ! t ) {
51+ throw new Error (
52+ '@huggingface/transformers is not installed. Semantic search requires a model-based embedder.\n' +
53+ ' Install it with:\n' +
54+ ' npm install @huggingface/transformers\n' +
55+ ' Then download the model:\n' +
56+ ' node scripts/download-model.mjs\n' +
57+ ' Or set mirror for China:\n' +
58+ ' export HF_ENDPOINT=https://hf-mirror.com'
59+ ) ;
9860 }
9961
100- // Transformers not installed — fallback
101- console . warn (
102- '[context] @huggingface/transformers not installed, using basic mode (lower recall quality).\n' +
103- ' Install it for better retrieval:\n' +
104- ' npm install @huggingface/transformers\n'
105- ) ;
106- const fallback = new SimpleEmbedder ( ) ;
107- return {
108- embedder : fallback ,
109- info : {
110- kind : 'simple' ,
111- dimensions : fallback . dimensions ,
112- isFallback : true ,
113- fallbackReason : '@huggingface/transformers not installed' ,
114- } ,
115- } ;
116- }
117-
118- /**
119- * Determine if an error is recoverable (network, model-not-found, etc.)
120- * vs a code-level bug that should not be silently swallowed.
121- */
122- export function isRecoverableError ( err : unknown ) : boolean {
123- if ( ! ( err instanceof Error ) ) return true ; // unknown errors → fallback
124-
125- const message = err . message ?? '' ;
126-
127- // Network / download failures
128- if ( message . includes ( 'fetch' ) || message . includes ( 'network' ) ||
129- message . includes ( 'ENOTFOUND' ) || message . includes ( 'ECONNREFUSED' ) ||
130- message . includes ( 'timeout' ) || message . includes ( 'Failed to fetch' ) ) {
131- return true ;
62+ try {
63+ const embedder = new TransformersEmbedder ( undefined , { modelId : model } ) ;
64+ await embedder . embed ( 'probe' ) ;
65+ return {
66+ embedder,
67+ info : {
68+ kind : 'transformers' ,
69+ dimensions : embedder . dimensions ,
70+ modelId : model ?? 'onnx-community/bge-small-zh-v1.5-ONNX' ,
71+ } ,
72+ } ;
73+ } catch ( err ) {
74+ throw new Error (
75+ `Failed to load embedding model (${ model ?? 'bge-small-zh-v1.5' } ): ${ ( err as Error ) . message ?. split ( '\n' ) [ 0 ] ?? 'unknown' } \n` +
76+ ' To fix model download:\n' +
77+ ' 1. Set mirror: export HF_ENDPOINT=https://hf-mirror.com\n' +
78+ ' 2. Manual download: node scripts/download-model.mjs'
79+ ) ;
13280 }
133-
134- // Model not found or invalid
135- if ( message . includes ( 'not found' ) || message . includes ( '404' ) ||
136- message . includes ( 'model' ) || message . includes ( 'shape' ) ||
137- message . includes ( 'dimension' ) || message . includes ( 'size' ) ) {
138- return true ;
139- }
140-
141- // WASM / native binding issues
142- if ( message . includes ( 'wasm' ) || message . includes ( 'native' ) ||
143- message . includes ( 'binding' ) ) {
144- return true ;
145- }
146-
147- // SyntaxError, TypeError, ReferenceError are code bugs — don't swallow
148- if ( err instanceof SyntaxError || err instanceof TypeError ||
149- err instanceof ReferenceError || err instanceof RangeError ) {
150- return false ;
151- }
152-
153- // Default: recoverable (most runtime errors in model loading are transient)
154- return true ;
15581}
0 commit comments