Skip to content

Commit 7f82427

Browse files
author
福晋
committed
chore: remove sample embedder
1 parent 5fdc318 commit 7f82427

9 files changed

Lines changed: 73 additions & 471 deletions

File tree

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ A local context retrieval library that enables semantic search over your documen
1414
- 🔍 **Hybrid Retrieval**: Combines vector similarity + FTS text matching via RRF fusion for better recall
1515
- 🔄 **Deduplication**: Automatically skip already-loaded documents; content-hash change detection for re-embedding updated files
1616
- ⚖️ **Weight Configuration**: Per-field FTS boost weights and RRF rank constant tuning
17-
- 🛡️ **Graceful Degradation**: Falls back to SimpleEmbedder when Transformers model unavailable
17+
- 🛡️ **Clear Error Messages**: Throws descriptive errors when Transformers model is unavailable, guiding users to fix the issue
1818
- 🧩 **Document Chunking**: Split documents into semantic chunks (heading-aware for Markdown, fixed-size for plain text) for finer-grained retrieval
1919
- 🔁 **Two-stage Reranking**: KeywordReranker boosts candidates with exact query term matches after coarse vector/hybrid search
2020
- 🌐 **Query Expansion**: SynonymExpander uses user-provided synonym maps to bridge CN↔EN terminology gaps
@@ -79,7 +79,6 @@ await ctx.close();
7979
| `model` | `string` | auto | Transformers model name for embedding. Skipped when custom `embedder` is provided. |
8080
| `loaders` | `Loader[]` | built-in | Custom loaders (default: MarkdownLoader, JsonLoader, TextLoader) |
8181
| `embedder` | `Embedder` | auto-resolved | Custom embedder. Skips auto-resolution when provided. |
82-
| `onEmbedderFallback` | `(info: EmbedderInfo) => void` || Callback invoked when the embedder falls back to SimpleEmbedder. |
8382
| `onProgress` | `(phase, detail) => void` || Progress callback for `load()` phases: `'load'``'chunk'``'embed'``'insert'`. |
8483
| `chunking` | `ChunkingOptions | false` | `{ strategy: 'auto', maxChunkSize: 1024, chunkOverlap: 128 }` | Document chunking config. `false` disables chunking. |
8584
| `queryExpansion` | `QueryExpansionOptions | false` | `false` (no-op) | Query expansion with user-provided synonym map. `false` disables. Without `synonyms`, expansion is a no-op. |
@@ -356,7 +355,7 @@ await ctx.close();
356355
- **Chunking**: `MarkdownChunker`, `FixedSizeChunker`, `createChunker`, `ChunkingOptions`, `Chunk`, `Chunker`
357356
- **Reranking**: `KeywordReranker`, `createReranker`, `Reranker`, `RerankCandidate`, `RerankResult`, `RerankOptions`
358357
- **Query Expansion**: `SynonymExpander`, `NoopExpander`, `QueryExpander`, `QueryExpansionOptions`
359-
- **Advanced API**: `Embedder`, `SimpleEmbedder`, `TransformersEmbedder`, `EmbedderManager`, `IZvecStore`, `MemoryZvecStore`, `ActualZvecStore`, `DocumentRegistry`, `StoreManager`
358+
- **Advanced API**: `Embedder`, `TransformersEmbedder`, `EmbedderManager`, `IZvecStore`, `MemoryZvecStore`, `ActualZvecStore`, `DocumentRegistry`, `StoreManager`
360359

361360

362361
## License

src/context.ts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -77,22 +77,15 @@ export class Context {
7777

7878
if (options.embedder) {
7979
// User provided a custom embedder — infer info from its class
80-
const isTransformers = options.embedder.constructor.name === 'TransformersEmbedder';
8180
embedder = options.embedder;
8281
embedderInfo = {
83-
kind: isTransformers ? 'transformers' : 'simple',
82+
kind: 'transformers',
8483
dimensions: embedder.dimensions,
85-
isFallback: false,
8684
};
8785
} else {
8886
const result = await resolveEmbedder(options.model);
8987
embedder = result.embedder;
9088
embedderInfo = result.info;
91-
92-
// Notify user if a fallback occurred (via callback if provided)
93-
if (embedderInfo.isFallback && options.onEmbedderFallback) {
94-
options.onEmbedderFallback(embedderInfo);
95-
}
9689
}
9790

9891
// Ensure vectors directory exists
@@ -142,17 +135,6 @@ export class Context {
142135

143136
/**
144137
* Diagnostic information about the active embedder.
145-
*
146-
* Use this to detect when a fallback to SimpleEmbedder occurred, so
147-
* you can warn your users or log the event for troubleshooting.
148-
*
149-
* Example:
150-
* ```ts
151-
* const ctx = await Context.create({ vectorsDir: './vectors' });
152-
* if (ctx.embedderInfo.isFallback) {
153-
* console.warn(`Using fallback embedder: ${ctx.embedderInfo.fallbackReason}`);
154-
* }
155-
* ```
156138
*/
157139
get embedderInfo(): EmbedderInfo {
158140
return this._embedderInfo;

src/embedder/index.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
/**
22
* embedder — aggregate entry point for all embedding modules.
33
*
4-
* Re-exports from split files for backward compatibility:
4+
* Re-exports from split files:
55
* types.ts → Embedder interface
66
* language.ts → isCJK, detectLanguage, tokenizerForLanguage, detectTokenizer
7-
* simple.ts → SimpleEmbedder
87
* transformers.ts → TransformersEmbedder
98
* manager.ts → EmbedderManager, getEmbedder, resetEmbedder
10-
* resolve.ts → resolveEmbedder, isRecoverableError
9+
* resolve.ts → resolveEmbedder
1110
*/
1211

1312
// Types
@@ -17,15 +16,12 @@ export type { Embedder } from './types';
1716
export { isCJK, splitMixed, detectLanguage, tokenizerForLanguage, detectTokenizer } from './language';
1817
export type { LanguageHint } from './language';
1918

20-
// SimpleEmbedder — lightweight fallback
21-
export { SimpleEmbedder } from './simple';
22-
2319
// TransformersEmbedder — production-quality model embedder
2420
export { TransformersEmbedder } from './transformers';
2521

2622
// EmbedderManager & global convenience functions
2723
export { EmbedderManager, getEmbedder, resetEmbedder } from './manager';
2824

29-
// Embedder resolution & error classification
30-
export { resolveEmbedder, isRecoverableError } from './resolve';
25+
// Embedder resolution
26+
export { resolveEmbedder } from './resolve';
3127
export type { EmbedderInfo, EmbedderKind, ResolveResult } from './resolve';

src/embedder/manager.ts

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88

99
import { Embedder } from './types';
10-
import { SimpleEmbedder } from './simple';
1110
import {
1211
TransformersEmbedder,
1312
loadTransformersModule,
@@ -61,38 +60,36 @@ export class EmbedderManager {
6160
/**
6261
* Return a shared Embedder instance (async).
6362
*
64-
* Tries TransformersEmbedder first (needs @huggingface/transformers installed
65-
* AND the model downloadable from HuggingFace Hub). If either fails, falls
66-
* back to SimpleEmbedder gracefully.
63+
* Requires @huggingface/transformers to be installed and the model to be
64+
* loadable. Throws an error when either condition is not met.
6765
*/
68-
async getEmbedder(synonymMap?: Map<string, string[]>): Promise<Embedder> {
66+
async getEmbedder(): Promise<Embedder> {
6967
if (this._defaultEmbedder) return this._defaultEmbedder;
7068

7169
const t = await this._loader.load();
72-
if (t) {
73-
try {
74-
const probe = new TransformersEmbedder(() => this._loader.load());
75-
await probe.embed('probe'); // triggers lazy model download
76-
this._defaultEmbedder = probe;
77-
} catch (err) {
78-
console.warn(
79-
`[embedder] Bilingual model (bge-small-zh-v1.5) load failed, falling back to SimpleEmbedder.\n` +
80-
` Error: ${(err as Error).message?.split('\n')[0]}\n` +
81-
`\n` +
82-
` SimpleEmbedder has lower recall quality. To fix model download:\n` +
83-
` 1. Set mirror: export HF_ENDPOINT=https://hf-mirror.com\n` +
84-
` 2. Manual download: node scripts/download-model.mjs\n`
85-
);
86-
this._defaultEmbedder = new SimpleEmbedder(synonymMap);
87-
}
88-
} else {
89-
console.warn(
90-
'[embedder] @huggingface/transformers not installed, using SimpleEmbedder.\n' +
91-
' Install it to enable bilingual model for better recall:\n' +
70+
if (!t) {
71+
throw new Error(
72+
'@huggingface/transformers is not installed. Semantic search requires a model-based embedder.\n' +
73+
' Install it with:\n' +
9274
' npm install @huggingface/transformers\n' +
93-
' node scripts/download-model.mjs\n'
75+
' Then download the model:\n' +
76+
' node scripts/download-model.mjs\n' +
77+
' Or set mirror for China:\n' +
78+
' export HF_ENDPOINT=https://hf-mirror.com'
79+
);
80+
}
81+
82+
try {
83+
const probe = new TransformersEmbedder(() => this._loader.load());
84+
await probe.embed('probe');
85+
this._defaultEmbedder = probe;
86+
} catch (err) {
87+
throw new Error(
88+
`Failed to load embedding model (bge-small-zh-v1.5): ${(err as Error).message?.split('\n')[0] ?? 'unknown'}\n` +
89+
' To fix model download:\n' +
90+
' 1. Set mirror: export HF_ENDPOINT=https://hf-mirror.com\n' +
91+
' 2. Manual download: node scripts/download-model.mjs'
9492
);
95-
this._defaultEmbedder = new SimpleEmbedder(synonymMap);
9693
}
9794
return this._defaultEmbedder;
9895
}
@@ -123,10 +120,9 @@ const _globalManager = new EmbedderManager();
123120
* explicit embedder to `Context.create({ embedder })` to avoid hidden
124121
* global state. This function remains for backward compatibility.
125122
*
126-
* @param synonymMap Optional synonym map to inject into SimpleEmbedder fallback.
127123
*/
128-
export async function getEmbedder(synonymMap?: Map<string, string[]>): Promise<Embedder> {
129-
return _globalManager.getEmbedder(synonymMap);
124+
export async function getEmbedder(): Promise<Embedder> {
125+
return _globalManager.getEmbedder();
130126
}
131127

132128
/**

src/embedder/resolve.ts

Lines changed: 34 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
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

97
import { Embedder } from './types';
10-
import { SimpleEmbedder } from './simple';
118
import { 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. */
2118
export 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
*/
5847
export 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

Comments
 (0)