@mnemoai/core 1.1.0 → 1.1.1
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/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.d.ts.map +1 -0
- package/{index.ts → dist/index.js} +526 -1333
- package/dist/index.js.map +7 -0
- package/dist/src/access-tracker.d.ts +97 -0
- package/dist/src/access-tracker.d.ts.map +1 -0
- package/dist/src/access-tracker.js +184 -0
- package/dist/src/access-tracker.js.map +7 -0
- package/dist/src/adapters/chroma.d.ts +31 -0
- package/dist/src/adapters/chroma.d.ts.map +1 -0
- package/{src/adapters/chroma.ts → dist/src/adapters/chroma.js} +45 -107
- package/dist/src/adapters/chroma.js.map +7 -0
- package/dist/src/adapters/lancedb.d.ts +29 -0
- package/dist/src/adapters/lancedb.d.ts.map +1 -0
- package/{src/adapters/lancedb.ts → dist/src/adapters/lancedb.js} +41 -109
- package/dist/src/adapters/lancedb.js.map +7 -0
- package/dist/src/adapters/pgvector.d.ts +33 -0
- package/dist/src/adapters/pgvector.d.ts.map +1 -0
- package/{src/adapters/pgvector.ts → dist/src/adapters/pgvector.js} +42 -104
- package/dist/src/adapters/pgvector.js.map +7 -0
- package/dist/src/adapters/qdrant.d.ts +34 -0
- package/dist/src/adapters/qdrant.d.ts.map +1 -0
- package/dist/src/adapters/qdrant.js +132 -0
- package/dist/src/adapters/qdrant.js.map +7 -0
- package/dist/src/adaptive-retrieval.d.ts +14 -0
- package/dist/src/adaptive-retrieval.d.ts.map +1 -0
- package/dist/src/adaptive-retrieval.js +52 -0
- package/dist/src/adaptive-retrieval.js.map +7 -0
- package/dist/src/audit-log.d.ts +56 -0
- package/dist/src/audit-log.d.ts.map +1 -0
- package/dist/src/audit-log.js +139 -0
- package/dist/src/audit-log.js.map +7 -0
- package/dist/src/chunker.d.ts +45 -0
- package/dist/src/chunker.d.ts.map +1 -0
- package/dist/src/chunker.js +157 -0
- package/dist/src/chunker.js.map +7 -0
- package/dist/src/config.d.ts +70 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +142 -0
- package/dist/src/config.js.map +7 -0
- package/dist/src/decay-engine.d.ts +73 -0
- package/dist/src/decay-engine.d.ts.map +1 -0
- package/dist/src/decay-engine.js +119 -0
- package/dist/src/decay-engine.js.map +7 -0
- package/dist/src/embedder.d.ts +94 -0
- package/dist/src/embedder.d.ts.map +1 -0
- package/{src/embedder.ts → dist/src/embedder.js} +119 -317
- package/dist/src/embedder.js.map +7 -0
- package/dist/src/extraction-prompts.d.ts +12 -0
- package/dist/src/extraction-prompts.d.ts.map +1 -0
- package/dist/src/extraction-prompts.js +311 -0
- package/dist/src/extraction-prompts.js.map +7 -0
- package/dist/src/license.d.ts +29 -0
- package/dist/src/license.d.ts.map +1 -0
- package/{src/license.ts → dist/src/license.js} +42 -113
- package/dist/src/license.js.map +7 -0
- package/dist/src/llm-client.d.ts +23 -0
- package/dist/src/llm-client.d.ts.map +1 -0
- package/{src/llm-client.ts → dist/src/llm-client.js} +22 -55
- package/dist/src/llm-client.js.map +7 -0
- package/dist/src/logger.d.ts +33 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +35 -0
- package/dist/src/logger.js.map +7 -0
- package/dist/src/mcp-server.d.ts +16 -0
- package/dist/src/mcp-server.d.ts.map +1 -0
- package/{src/mcp-server.ts → dist/src/mcp-server.js} +81 -181
- package/dist/src/mcp-server.js.map +7 -0
- package/dist/src/memory-categories.d.ts +40 -0
- package/dist/src/memory-categories.d.ts.map +1 -0
- package/dist/src/memory-categories.js +33 -0
- package/dist/src/memory-categories.js.map +7 -0
- package/dist/src/memory-upgrader.d.ts +71 -0
- package/dist/src/memory-upgrader.d.ts.map +1 -0
- package/dist/src/memory-upgrader.js +238 -0
- package/dist/src/memory-upgrader.js.map +7 -0
- package/dist/src/migrate.d.ts +47 -0
- package/dist/src/migrate.d.ts.map +1 -0
- package/{src/migrate.ts → dist/src/migrate.js} +57 -165
- package/dist/src/migrate.js.map +7 -0
- package/dist/src/mnemo.d.ts +67 -0
- package/dist/src/mnemo.d.ts.map +1 -0
- package/dist/src/mnemo.js +66 -0
- package/dist/src/mnemo.js.map +7 -0
- package/dist/src/noise-filter.d.ts +23 -0
- package/dist/src/noise-filter.d.ts.map +1 -0
- package/dist/src/noise-filter.js +62 -0
- package/dist/src/noise-filter.js.map +7 -0
- package/dist/src/noise-prototypes.d.ts +40 -0
- package/dist/src/noise-prototypes.d.ts.map +1 -0
- package/dist/src/noise-prototypes.js +116 -0
- package/dist/src/noise-prototypes.js.map +7 -0
- package/dist/src/observability.d.ts +16 -0
- package/dist/src/observability.d.ts.map +1 -0
- package/dist/src/observability.js +53 -0
- package/dist/src/observability.js.map +7 -0
- package/dist/src/query-tracker.d.ts +27 -0
- package/dist/src/query-tracker.d.ts.map +1 -0
- package/dist/src/query-tracker.js +32 -0
- package/dist/src/query-tracker.js.map +7 -0
- package/dist/src/reflection-event-store.d.ts +44 -0
- package/dist/src/reflection-event-store.d.ts.map +1 -0
- package/dist/src/reflection-event-store.js +50 -0
- package/dist/src/reflection-event-store.js.map +7 -0
- package/dist/src/reflection-item-store.d.ts +58 -0
- package/dist/src/reflection-item-store.d.ts.map +1 -0
- package/dist/src/reflection-item-store.js +69 -0
- package/dist/src/reflection-item-store.js.map +7 -0
- package/dist/src/reflection-mapped-metadata.d.ts +47 -0
- package/dist/src/reflection-mapped-metadata.d.ts.map +1 -0
- package/dist/src/reflection-mapped-metadata.js +40 -0
- package/dist/src/reflection-mapped-metadata.js.map +7 -0
- package/dist/src/reflection-metadata.d.ts +11 -0
- package/dist/src/reflection-metadata.d.ts.map +1 -0
- package/dist/src/reflection-metadata.js +24 -0
- package/dist/src/reflection-metadata.js.map +7 -0
- package/dist/src/reflection-ranking.d.ts +13 -0
- package/dist/src/reflection-ranking.d.ts.map +1 -0
- package/{src/reflection-ranking.ts → dist/src/reflection-ranking.js} +12 -21
- package/dist/src/reflection-ranking.js.map +7 -0
- package/dist/src/reflection-retry.d.ts +30 -0
- package/dist/src/reflection-retry.d.ts.map +1 -0
- package/{src/reflection-retry.ts → dist/src/reflection-retry.js} +24 -64
- package/dist/src/reflection-retry.js.map +7 -0
- package/dist/src/reflection-slices.d.ts +42 -0
- package/dist/src/reflection-slices.d.ts.map +1 -0
- package/{src/reflection-slices.ts → dist/src/reflection-slices.js} +60 -136
- package/dist/src/reflection-slices.js.map +7 -0
- package/dist/src/reflection-store.d.ts +85 -0
- package/dist/src/reflection-store.d.ts.map +1 -0
- package/dist/src/reflection-store.js +407 -0
- package/dist/src/reflection-store.js.map +7 -0
- package/dist/src/resonance-state.d.ts +19 -0
- package/dist/src/resonance-state.d.ts.map +1 -0
- package/{src/resonance-state.ts → dist/src/resonance-state.js} +13 -42
- package/dist/src/resonance-state.js.map +7 -0
- package/dist/src/retriever.d.ts +228 -0
- package/dist/src/retriever.d.ts.map +1 -0
- package/dist/src/retriever.js +1006 -0
- package/dist/src/retriever.js.map +7 -0
- package/dist/src/scopes.d.ts +58 -0
- package/dist/src/scopes.d.ts.map +1 -0
- package/dist/src/scopes.js +252 -0
- package/dist/src/scopes.js.map +7 -0
- package/dist/src/self-improvement-files.d.ts +20 -0
- package/dist/src/self-improvement-files.d.ts.map +1 -0
- package/{src/self-improvement-files.ts → dist/src/self-improvement-files.js} +24 -49
- package/dist/src/self-improvement-files.js.map +7 -0
- package/dist/src/semantic-gate.d.ts +24 -0
- package/dist/src/semantic-gate.d.ts.map +1 -0
- package/dist/src/semantic-gate.js +86 -0
- package/dist/src/semantic-gate.js.map +7 -0
- package/dist/src/session-recovery.d.ts +9 -0
- package/dist/src/session-recovery.d.ts.map +1 -0
- package/{src/session-recovery.ts → dist/src/session-recovery.js} +40 -57
- package/dist/src/session-recovery.js.map +7 -0
- package/dist/src/smart-extractor.d.ts +107 -0
- package/dist/src/smart-extractor.d.ts.map +1 -0
- package/{src/smart-extractor.ts → dist/src/smart-extractor.js} +130 -383
- package/dist/src/smart-extractor.js.map +7 -0
- package/dist/src/smart-metadata.d.ts +103 -0
- package/dist/src/smart-metadata.d.ts.map +1 -0
- package/dist/src/smart-metadata.js +361 -0
- package/dist/src/smart-metadata.js.map +7 -0
- package/dist/src/storage-adapter.d.ts +102 -0
- package/dist/src/storage-adapter.d.ts.map +1 -0
- package/dist/src/storage-adapter.js +22 -0
- package/dist/src/storage-adapter.js.map +7 -0
- package/dist/src/store.d.ts +108 -0
- package/dist/src/store.d.ts.map +1 -0
- package/dist/src/store.js +939 -0
- package/dist/src/store.js.map +7 -0
- package/dist/src/tier-manager.d.ts +57 -0
- package/dist/src/tier-manager.d.ts.map +1 -0
- package/dist/src/tier-manager.js +80 -0
- package/dist/src/tier-manager.js.map +7 -0
- package/dist/src/tools.d.ts +43 -0
- package/dist/src/tools.d.ts.map +1 -0
- package/dist/src/tools.js +1075 -0
- package/dist/src/tools.js.map +7 -0
- package/dist/src/wal-recovery.d.ts +30 -0
- package/dist/src/wal-recovery.d.ts.map +1 -0
- package/{src/wal-recovery.ts → dist/src/wal-recovery.js} +26 -79
- package/dist/src/wal-recovery.js.map +7 -0
- package/package.json +21 -2
- package/openclaw.plugin.json +0 -815
- package/src/access-tracker.ts +0 -341
- package/src/adapters/README.md +0 -78
- package/src/adapters/qdrant.ts +0 -191
- package/src/adaptive-retrieval.ts +0 -90
- package/src/audit-log.ts +0 -238
- package/src/chunker.ts +0 -254
- package/src/config.ts +0 -271
- package/src/decay-engine.ts +0 -238
- package/src/extraction-prompts.ts +0 -339
- package/src/memory-categories.ts +0 -71
- package/src/memory-upgrader.ts +0 -388
- package/src/mnemo.ts +0 -142
- package/src/noise-filter.ts +0 -97
- package/src/noise-prototypes.ts +0 -164
- package/src/observability.ts +0 -81
- package/src/query-tracker.ts +0 -57
- package/src/reflection-event-store.ts +0 -98
- package/src/reflection-item-store.ts +0 -112
- package/src/reflection-mapped-metadata.ts +0 -84
- package/src/reflection-metadata.ts +0 -23
- package/src/reflection-store.ts +0 -602
- package/src/retriever.ts +0 -1510
- package/src/scopes.ts +0 -375
- package/src/semantic-gate.ts +0 -121
- package/src/smart-metadata.ts +0 -561
- package/src/storage-adapter.ts +0 -153
- package/src/store.ts +0 -1330
- package/src/tier-manager.ts +0 -189
- package/src/tools.ts +0 -1292
- package/test/core.test.mjs +0 -301
|
@@ -1,109 +1,60 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
/**
|
|
3
|
-
* Embedding Abstraction Layer
|
|
4
|
-
* OpenAI-compatible API for various embedding providers.
|
|
5
|
-
* Supports automatic chunking for documents exceeding embedding context limits.
|
|
6
|
-
*
|
|
7
|
-
* Note: Some providers (e.g. Jina) support extra parameters like `task` and
|
|
8
|
-
* `normalized` on the embeddings endpoint. The OpenAI SDK types do not include
|
|
9
|
-
* these fields, so we pass them via a narrow `any` cast.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
1
|
import OpenAI from "openai";
|
|
13
2
|
import { createHash } from "node:crypto";
|
|
14
3
|
import { smartChunk } from "./chunker.js";
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Embedding Cache (LRU with TTL)
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
interface CacheEntry {
|
|
21
|
-
vector: number[];
|
|
22
|
-
createdAt: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
4
|
+
import { log } from "./logger.js";
|
|
25
5
|
class EmbeddingCache {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
6
|
+
cache = /* @__PURE__ */ new Map();
|
|
7
|
+
maxSize;
|
|
8
|
+
ttlMs;
|
|
9
|
+
hits = 0;
|
|
10
|
+
misses = 0;
|
|
32
11
|
constructor(maxSize = 256, ttlMinutes = 30) {
|
|
33
12
|
this.maxSize = maxSize;
|
|
34
|
-
this.ttlMs = ttlMinutes *
|
|
13
|
+
this.ttlMs = ttlMinutes * 6e4;
|
|
35
14
|
}
|
|
36
|
-
|
|
37
|
-
private key(text: string, task?: string): string {
|
|
15
|
+
key(text, task) {
|
|
38
16
|
const hash = createHash("sha256").update(`${task || ""}:${text}`).digest("hex").slice(0, 24);
|
|
39
17
|
return hash;
|
|
40
18
|
}
|
|
41
|
-
|
|
42
|
-
get(text: string, task?: string): number[] | undefined {
|
|
19
|
+
get(text, task) {
|
|
43
20
|
const k = this.key(text, task);
|
|
44
21
|
const entry = this.cache.get(k);
|
|
45
22
|
if (!entry) {
|
|
46
23
|
this.misses++;
|
|
47
|
-
return
|
|
24
|
+
return void 0;
|
|
48
25
|
}
|
|
49
26
|
if (Date.now() - entry.createdAt > this.ttlMs) {
|
|
50
27
|
this.cache.delete(k);
|
|
51
28
|
this.misses++;
|
|
52
|
-
return
|
|
29
|
+
return void 0;
|
|
53
30
|
}
|
|
54
|
-
// Move to end (most recently used)
|
|
55
31
|
this.cache.delete(k);
|
|
56
32
|
this.cache.set(k, entry);
|
|
57
33
|
this.hits++;
|
|
58
34
|
return entry.vector;
|
|
59
35
|
}
|
|
60
|
-
|
|
61
|
-
set(text: string, task: string | undefined, vector: number[]): void {
|
|
36
|
+
set(text, task, vector) {
|
|
62
37
|
const k = this.key(text, task);
|
|
63
|
-
// Evict oldest if full
|
|
64
38
|
if (this.cache.size >= this.maxSize) {
|
|
65
39
|
const firstKey = this.cache.keys().next().value;
|
|
66
|
-
if (firstKey !==
|
|
40
|
+
if (firstKey !== void 0) this.cache.delete(firstKey);
|
|
67
41
|
}
|
|
68
42
|
this.cache.set(k, { vector, createdAt: Date.now() });
|
|
69
43
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
44
|
+
get size() {
|
|
45
|
+
return this.cache.size;
|
|
46
|
+
}
|
|
47
|
+
get stats() {
|
|
73
48
|
const total = this.hits + this.misses;
|
|
74
49
|
return {
|
|
75
50
|
size: this.cache.size,
|
|
76
51
|
hits: this.hits,
|
|
77
52
|
misses: this.misses,
|
|
78
|
-
hitRate: total > 0 ? `${(
|
|
53
|
+
hitRate: total > 0 ? `${(this.hits / total * 100).toFixed(1)}%` : "N/A"
|
|
79
54
|
};
|
|
80
55
|
}
|
|
81
56
|
}
|
|
82
|
-
|
|
83
|
-
// ============================================================================
|
|
84
|
-
// Types & Configuration
|
|
85
|
-
// ============================================================================
|
|
86
|
-
|
|
87
|
-
export interface EmbeddingConfig {
|
|
88
|
-
provider: "openai-compatible";
|
|
89
|
-
/** Single API key or array of keys for round-robin rotation with failover. */
|
|
90
|
-
apiKey: string | string[];
|
|
91
|
-
model: string;
|
|
92
|
-
baseURL?: string;
|
|
93
|
-
dimensions?: number;
|
|
94
|
-
|
|
95
|
-
/** Optional task type for query embeddings (e.g. "retrieval.query") */
|
|
96
|
-
taskQuery?: string;
|
|
97
|
-
/** Optional task type for passage/document embeddings (e.g. "retrieval.passage") */
|
|
98
|
-
taskPassage?: string;
|
|
99
|
-
/** Optional flag to request normalized embeddings (provider-dependent, e.g. Jina v5) */
|
|
100
|
-
normalized?: boolean;
|
|
101
|
-
/** Enable automatic chunking for documents exceeding context limits (default: true) */
|
|
102
|
-
chunking?: boolean;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Known embedding model dimensions
|
|
106
|
-
const EMBEDDING_DIMENSIONS: Record<string, number> = {
|
|
57
|
+
const EMBEDDING_DIMENSIONS = {
|
|
107
58
|
"text-embedding-3-small": 1536,
|
|
108
59
|
"text-embedding-3-large": 3072,
|
|
109
60
|
"text-embedding-004": 768,
|
|
@@ -113,17 +64,11 @@ const EMBEDDING_DIMENSIONS: Record<string, number> = {
|
|
|
113
64
|
"BAAI/bge-m3": 1024,
|
|
114
65
|
"all-MiniLM-L6-v2": 384,
|
|
115
66
|
"all-mpnet-base-v2": 512,
|
|
116
|
-
|
|
117
67
|
// Jina v5
|
|
118
68
|
"jina-embeddings-v5-text-small": 1024,
|
|
119
|
-
"jina-embeddings-v5-text-nano": 768
|
|
69
|
+
"jina-embeddings-v5-text-nano": 768
|
|
120
70
|
};
|
|
121
|
-
|
|
122
|
-
// ============================================================================
|
|
123
|
-
// Utility Functions
|
|
124
|
-
// ============================================================================
|
|
125
|
-
|
|
126
|
-
function resolveEnvVars(value: string): string {
|
|
71
|
+
function resolveEnvVars(value) {
|
|
127
72
|
return value.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
|
|
128
73
|
const envValue = process.env[envVar];
|
|
129
74
|
if (!envValue) {
|
|
@@ -132,310 +77,231 @@ function resolveEnvVars(value: string): string {
|
|
|
132
77
|
return envValue;
|
|
133
78
|
});
|
|
134
79
|
}
|
|
135
|
-
|
|
136
|
-
function getErrorMessage(error: unknown): string {
|
|
80
|
+
function getErrorMessage(error) {
|
|
137
81
|
return error instanceof Error ? error.message : String(error);
|
|
138
82
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const err = error as Record<string, any>;
|
|
83
|
+
function getErrorStatus(error) {
|
|
84
|
+
if (!error || typeof error !== "object") return void 0;
|
|
85
|
+
const err = error;
|
|
143
86
|
if (typeof err.status === "number") return err.status;
|
|
144
87
|
if (typeof err.statusCode === "number") return err.statusCode;
|
|
145
88
|
if (err.error && typeof err.error === "object") {
|
|
146
89
|
if (typeof err.error.status === "number") return err.error.status;
|
|
147
90
|
if (typeof err.error.statusCode === "number") return err.error.statusCode;
|
|
148
91
|
}
|
|
149
|
-
return
|
|
92
|
+
return void 0;
|
|
150
93
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const err = error as Record<string, any>;
|
|
94
|
+
function getErrorCode(error) {
|
|
95
|
+
if (!error || typeof error !== "object") return void 0;
|
|
96
|
+
const err = error;
|
|
155
97
|
if (typeof err.code === "string") return err.code;
|
|
156
98
|
if (err.error && typeof err.error === "object" && typeof err.error.code === "string") {
|
|
157
99
|
return err.error.code;
|
|
158
100
|
}
|
|
159
|
-
return
|
|
101
|
+
return void 0;
|
|
160
102
|
}
|
|
161
|
-
|
|
162
|
-
function getProviderLabel(baseURL: string | undefined, model: string): string {
|
|
103
|
+
function getProviderLabel(baseURL, model) {
|
|
163
104
|
const base = baseURL || "";
|
|
164
|
-
|
|
165
105
|
if (base) {
|
|
166
106
|
if (/api\.jina\.ai/i.test(base)) return "Jina";
|
|
167
107
|
if (/localhost:11434|127\.0\.0\.1:11434|\/ollama\b/i.test(base)) return "Ollama";
|
|
168
108
|
if (/api\.openai\.com/i.test(base)) return "OpenAI";
|
|
169
|
-
|
|
170
109
|
try {
|
|
171
110
|
return new URL(base).host;
|
|
172
111
|
} catch {
|
|
173
112
|
return base;
|
|
174
113
|
}
|
|
175
114
|
}
|
|
176
|
-
|
|
177
115
|
if (/^jina-/i.test(model)) return "Jina";
|
|
178
|
-
|
|
179
116
|
return "embedding provider";
|
|
180
117
|
}
|
|
181
|
-
|
|
182
|
-
function isAuthError(error: unknown): boolean {
|
|
118
|
+
function isAuthError(error) {
|
|
183
119
|
const status = getErrorStatus(error);
|
|
184
120
|
if (status === 401 || status === 403) return true;
|
|
185
|
-
|
|
186
121
|
const code = getErrorCode(error);
|
|
187
122
|
if (code && /invalid.*key|auth|forbidden|unauthorized/i.test(code)) return true;
|
|
188
|
-
|
|
189
123
|
const msg = getErrorMessage(error);
|
|
190
124
|
return /\b401\b|\b403\b|invalid api key|api key expired|expired api key|forbidden|unauthorized|authentication failed|access denied/i.test(msg);
|
|
191
125
|
}
|
|
192
|
-
|
|
193
|
-
function isNetworkError(error: unknown): boolean {
|
|
126
|
+
function isNetworkError(error) {
|
|
194
127
|
const code = getErrorCode(error);
|
|
195
128
|
if (code && /ECONNREFUSED|ECONNRESET|ENOTFOUND|EHOSTUNREACH|ETIMEDOUT/i.test(code)) {
|
|
196
129
|
return true;
|
|
197
130
|
}
|
|
198
|
-
|
|
199
131
|
const msg = getErrorMessage(error);
|
|
200
132
|
return /ECONNREFUSED|ECONNRESET|ENOTFOUND|EHOSTUNREACH|ETIMEDOUT|fetch failed|network error|socket hang up|connection refused|getaddrinfo/i.test(msg);
|
|
201
133
|
}
|
|
202
|
-
|
|
203
|
-
export function formatEmbeddingProviderError(
|
|
204
|
-
error: unknown,
|
|
205
|
-
opts: { baseURL?: string; model: string; mode?: "single" | "batch" },
|
|
206
|
-
): string {
|
|
134
|
+
function formatEmbeddingProviderError(error, opts) {
|
|
207
135
|
const raw = getErrorMessage(error).trim();
|
|
208
|
-
if (
|
|
209
|
-
raw.startsWith("Embedding provider authentication failed") ||
|
|
210
|
-
raw.startsWith("Embedding provider unreachable") ||
|
|
211
|
-
raw.startsWith("Failed to generate embedding from ") ||
|
|
212
|
-
raw.startsWith("Failed to generate batch embeddings from ")
|
|
213
|
-
) {
|
|
136
|
+
if (raw.startsWith("Embedding provider authentication failed") || raw.startsWith("Embedding provider unreachable") || raw.startsWith("Failed to generate embedding from ") || raw.startsWith("Failed to generate batch embeddings from ")) {
|
|
214
137
|
return raw;
|
|
215
138
|
}
|
|
216
|
-
|
|
217
139
|
const status = getErrorStatus(error);
|
|
218
140
|
const code = getErrorCode(error);
|
|
219
141
|
const provider = getProviderLabel(opts.baseURL, opts.model);
|
|
220
142
|
const detail = raw.length > 0 ? raw : "unknown error";
|
|
221
143
|
const suffix = [status, code].filter(Boolean).join(" ");
|
|
222
144
|
const detailText = suffix ? `${suffix}: ${detail}` : detail;
|
|
223
|
-
const genericPrefix =
|
|
224
|
-
opts.mode === "batch"
|
|
225
|
-
? `Failed to generate batch embeddings from ${provider}: `
|
|
226
|
-
: `Failed to generate embedding from ${provider}: `;
|
|
227
|
-
|
|
145
|
+
const genericPrefix = opts.mode === "batch" ? `Failed to generate batch embeddings from ${provider}: ` : `Failed to generate embedding from ${provider}: `;
|
|
228
146
|
if (isAuthError(error)) {
|
|
229
147
|
let hint = `Check embedding.apiKey and endpoint for ${provider}.`;
|
|
230
148
|
if (provider === "Jina") {
|
|
231
|
-
hint +=
|
|
232
|
-
" If your Jina key expired or lost access, replace the key or switch to a local OpenAI-compatible endpoint such as Ollama (for example baseURL http://127.0.0.1:11434/v1, with a matching model and embedding.dimensions).";
|
|
149
|
+
hint += " If your Jina key expired or lost access, replace the key or switch to a local OpenAI-compatible endpoint such as Ollama (for example baseURL http://127.0.0.1:11434/v1, with a matching model and embedding.dimensions).";
|
|
233
150
|
} else if (provider === "Ollama") {
|
|
234
|
-
hint +=
|
|
235
|
-
" Ollama usually works with a dummy apiKey; verify the local server is running, the model is pulled, and embedding.dimensions matches the model output.";
|
|
151
|
+
hint += " Ollama usually works with a dummy apiKey; verify the local server is running, the model is pulled, and embedding.dimensions matches the model output.";
|
|
236
152
|
}
|
|
237
153
|
return `Embedding provider authentication failed (${detailText}). ${hint}`;
|
|
238
154
|
}
|
|
239
|
-
|
|
240
155
|
if (isNetworkError(error)) {
|
|
241
156
|
let hint = `Verify the endpoint is reachable`;
|
|
242
157
|
if (opts.baseURL) {
|
|
243
158
|
hint += ` at ${opts.baseURL}`;
|
|
244
159
|
}
|
|
245
|
-
hint += ` and that model
|
|
160
|
+
hint += ` and that model "${opts.model}" is available.`;
|
|
246
161
|
return `Embedding provider unreachable (${detailText}). ${hint}`;
|
|
247
162
|
}
|
|
248
|
-
|
|
249
163
|
return `${genericPrefix}${detailText}`;
|
|
250
164
|
}
|
|
251
|
-
|
|
252
|
-
export function getVectorDimensions(model: string, overrideDims?: number): number {
|
|
165
|
+
function getVectorDimensions(model, overrideDims) {
|
|
253
166
|
if (overrideDims && overrideDims > 0) {
|
|
254
167
|
return overrideDims;
|
|
255
168
|
}
|
|
256
|
-
|
|
257
169
|
const dims = EMBEDDING_DIMENSIONS[model];
|
|
258
170
|
if (!dims) {
|
|
259
171
|
throw new Error(
|
|
260
172
|
`Unsupported embedding model: ${model}. Either add it to EMBEDDING_DIMENSIONS or set embedding.dimensions in config.`
|
|
261
173
|
);
|
|
262
174
|
}
|
|
263
|
-
|
|
264
175
|
return dims;
|
|
265
176
|
}
|
|
266
|
-
|
|
267
|
-
// ============================================================================
|
|
268
|
-
// Embedder Class
|
|
269
|
-
// ============================================================================
|
|
270
|
-
|
|
271
|
-
export class Embedder {
|
|
177
|
+
class Embedder {
|
|
272
178
|
/** Pool of OpenAI clients — one per API key for round-robin rotation. */
|
|
273
|
-
|
|
179
|
+
clients;
|
|
274
180
|
/** Round-robin index for client rotation. */
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
private readonly _taskPassage?: string;
|
|
284
|
-
private readonly _normalized?: boolean;
|
|
285
|
-
|
|
181
|
+
_clientIndex = 0;
|
|
182
|
+
dimensions;
|
|
183
|
+
_cache;
|
|
184
|
+
_model;
|
|
185
|
+
_baseURL;
|
|
186
|
+
_taskQuery;
|
|
187
|
+
_taskPassage;
|
|
188
|
+
_normalized;
|
|
286
189
|
/** Optional requested dimensions to pass through to the embedding provider (OpenAI-compatible). */
|
|
287
|
-
|
|
190
|
+
_requestDimensions;
|
|
288
191
|
/** Enable automatic chunking for long documents (default: true) */
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
constructor(config: EmbeddingConfig & { chunking?: boolean }) {
|
|
292
|
-
// Normalize apiKey to array and resolve environment variables
|
|
192
|
+
_autoChunk;
|
|
193
|
+
constructor(config) {
|
|
293
194
|
const apiKeys = Array.isArray(config.apiKey) ? config.apiKey : [config.apiKey];
|
|
294
|
-
const resolvedKeys = apiKeys.map(k => resolveEnvVars(k));
|
|
295
|
-
|
|
195
|
+
const resolvedKeys = apiKeys.map((k) => resolveEnvVars(k));
|
|
296
196
|
this._model = config.model;
|
|
297
197
|
this._baseURL = config.baseURL;
|
|
298
198
|
this._taskQuery = config.taskQuery;
|
|
299
199
|
this._taskPassage = config.taskPassage;
|
|
300
200
|
this._normalized = config.normalized;
|
|
301
201
|
this._requestDimensions = config.dimensions;
|
|
302
|
-
// Enable auto-chunking by default for better handling of long documents
|
|
303
202
|
this._autoChunk = config.chunking !== false;
|
|
304
|
-
|
|
305
|
-
// Create a client pool — one OpenAI client per key
|
|
306
|
-
this.clients = resolvedKeys.map(key => new OpenAI({
|
|
203
|
+
this.clients = resolvedKeys.map((key) => new OpenAI({
|
|
307
204
|
apiKey: key,
|
|
308
|
-
...
|
|
205
|
+
...config.baseURL ? { baseURL: config.baseURL } : {}
|
|
309
206
|
}));
|
|
310
|
-
|
|
311
207
|
if (this.clients.length > 1) {
|
|
312
|
-
|
|
208
|
+
log.info(`Initialized ${this.clients.length} API keys for round-robin rotation`);
|
|
313
209
|
}
|
|
314
|
-
|
|
315
210
|
this.dimensions = getVectorDimensions(config.model, config.dimensions);
|
|
316
|
-
this._cache = new EmbeddingCache(256, 30);
|
|
211
|
+
this._cache = new EmbeddingCache(256, 30);
|
|
317
212
|
}
|
|
318
|
-
|
|
319
213
|
// --------------------------------------------------------------------------
|
|
320
214
|
// Multi-key rotation helpers
|
|
321
215
|
// --------------------------------------------------------------------------
|
|
322
|
-
|
|
323
216
|
/** Return the next client in round-robin order. */
|
|
324
|
-
|
|
217
|
+
nextClient() {
|
|
325
218
|
const client = this.clients[this._clientIndex % this.clients.length];
|
|
326
219
|
this._clientIndex = (this._clientIndex + 1) % this.clients.length;
|
|
327
220
|
return client;
|
|
328
221
|
}
|
|
329
|
-
|
|
330
222
|
/** Check whether an error is a rate-limit / quota-exceeded / overload error. */
|
|
331
|
-
|
|
223
|
+
isRateLimitError(error) {
|
|
332
224
|
if (!error || typeof error !== "object") return false;
|
|
333
|
-
|
|
334
|
-
const err = error as Record<string, any>;
|
|
335
|
-
|
|
336
|
-
// HTTP status: 429 (rate limit) or 503 (service overload)
|
|
225
|
+
const err = error;
|
|
337
226
|
if (err.status === 429 || err.status === 503) return true;
|
|
338
|
-
|
|
339
|
-
// OpenAI SDK structured error code
|
|
340
227
|
if (err.code === "rate_limit_exceeded" || err.code === "insufficient_quota") return true;
|
|
341
|
-
|
|
342
|
-
// Nested error object (some providers)
|
|
343
228
|
const nested = err.error;
|
|
344
229
|
if (nested && typeof nested === "object") {
|
|
345
230
|
if (nested.type === "rate_limit_exceeded" || nested.type === "insufficient_quota") return true;
|
|
346
231
|
if (nested.code === "rate_limit_exceeded" || nested.code === "insufficient_quota") return true;
|
|
347
232
|
}
|
|
348
|
-
|
|
349
|
-
// Fallback: message text matching
|
|
350
233
|
const msg = error instanceof Error ? error.message : String(error);
|
|
351
234
|
return /rate.limit|quota|too many requests|insufficient.*credit|429|503.*overload/i.test(msg);
|
|
352
235
|
}
|
|
353
|
-
|
|
354
236
|
/**
|
|
355
237
|
* Call embeddings.create with automatic key rotation on rate-limit errors.
|
|
356
238
|
* Tries each key in the pool at most once before giving up.
|
|
357
239
|
*/
|
|
358
|
-
|
|
240
|
+
// TODO: type payload as OpenAI.EmbeddingCreateParams & extra provider fields; type return as CreateEmbeddingResponse
|
|
241
|
+
async embedWithRetry(payload) {
|
|
359
242
|
const maxAttempts = this.clients.length;
|
|
360
|
-
let lastError
|
|
361
|
-
|
|
243
|
+
let lastError;
|
|
362
244
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
363
245
|
const client = this.nextClient();
|
|
364
246
|
try {
|
|
365
247
|
return await client.embeddings.create(payload);
|
|
366
248
|
} catch (error) {
|
|
367
249
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
368
|
-
|
|
369
250
|
if (this.isRateLimitError(error) && attempt < maxAttempts - 1) {
|
|
370
|
-
|
|
371
|
-
`
|
|
251
|
+
log.info(
|
|
252
|
+
`Attempt ${attempt + 1}/${maxAttempts} hit rate limit, rotating to next key...`
|
|
372
253
|
);
|
|
373
254
|
continue;
|
|
374
255
|
}
|
|
375
|
-
|
|
376
|
-
// Non-rate-limit error → don't retry, let caller handle (e.g. chunking)
|
|
377
256
|
if (!this.isRateLimitError(error)) {
|
|
378
257
|
throw error;
|
|
379
258
|
}
|
|
380
259
|
}
|
|
381
260
|
}
|
|
382
|
-
|
|
383
|
-
// All keys exhausted with rate-limit errors
|
|
384
261
|
throw new Error(
|
|
385
262
|
`All ${maxAttempts} API keys exhausted (rate limited). Last error: ${lastError?.message || "unknown"}`,
|
|
386
263
|
{ cause: lastError }
|
|
387
264
|
);
|
|
388
265
|
}
|
|
389
|
-
|
|
390
266
|
/** Number of API keys in the rotation pool. */
|
|
391
|
-
get keyCount()
|
|
267
|
+
get keyCount() {
|
|
392
268
|
return this.clients.length;
|
|
393
269
|
}
|
|
394
|
-
|
|
395
270
|
// --------------------------------------------------------------------------
|
|
396
271
|
// Backward-compatible API
|
|
397
272
|
// --------------------------------------------------------------------------
|
|
398
|
-
|
|
399
273
|
/**
|
|
400
274
|
* Backward-compatible embedding API.
|
|
401
275
|
*
|
|
402
276
|
* Historically the plugin used a single `embed()` method for both query and
|
|
403
277
|
* passage embeddings. With task-aware providers we treat this as passage.
|
|
404
278
|
*/
|
|
405
|
-
async embed(text
|
|
279
|
+
async embed(text) {
|
|
406
280
|
return this.embedPassage(text);
|
|
407
281
|
}
|
|
408
|
-
|
|
409
282
|
/** Backward-compatible batch embedding API (treated as passage). */
|
|
410
|
-
async embedBatch(texts
|
|
283
|
+
async embedBatch(texts) {
|
|
411
284
|
return this.embedBatchPassage(texts);
|
|
412
285
|
}
|
|
413
|
-
|
|
414
286
|
// --------------------------------------------------------------------------
|
|
415
287
|
// Task-aware API
|
|
416
288
|
// --------------------------------------------------------------------------
|
|
417
|
-
|
|
418
|
-
async embedQuery(text: string): Promise<number[]> {
|
|
289
|
+
async embedQuery(text) {
|
|
419
290
|
return this.embedSingle(text, this._taskQuery);
|
|
420
291
|
}
|
|
421
|
-
|
|
422
|
-
async embedPassage(text: string): Promise<number[]> {
|
|
292
|
+
async embedPassage(text) {
|
|
423
293
|
return this.embedSingle(text, this._taskPassage);
|
|
424
294
|
}
|
|
425
|
-
|
|
426
|
-
async embedBatchQuery(texts: string[]): Promise<number[][]> {
|
|
295
|
+
async embedBatchQuery(texts) {
|
|
427
296
|
return this.embedMany(texts, this._taskQuery);
|
|
428
297
|
}
|
|
429
|
-
|
|
430
|
-
async embedBatchPassage(texts: string[]): Promise<number[][]> {
|
|
298
|
+
async embedBatchPassage(texts) {
|
|
431
299
|
return this.embedMany(texts, this._taskPassage);
|
|
432
300
|
}
|
|
433
|
-
|
|
434
301
|
// --------------------------------------------------------------------------
|
|
435
302
|
// Internals
|
|
436
303
|
// --------------------------------------------------------------------------
|
|
437
|
-
|
|
438
|
-
private validateEmbedding(embedding: number[]): void {
|
|
304
|
+
validateEmbedding(embedding) {
|
|
439
305
|
if (!Array.isArray(embedding)) {
|
|
440
306
|
throw new Error(`Embedding is not an array (got ${typeof embedding})`);
|
|
441
307
|
}
|
|
@@ -445,91 +311,66 @@ export class Embedder {
|
|
|
445
311
|
);
|
|
446
312
|
}
|
|
447
313
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const payload
|
|
314
|
+
// TODO: type return as OpenAI.EmbeddingCreateParams & provider-specific fields
|
|
315
|
+
buildPayload(input, task) {
|
|
316
|
+
const payload = {
|
|
451
317
|
model: this.model,
|
|
452
|
-
input
|
|
318
|
+
input
|
|
453
319
|
};
|
|
454
|
-
|
|
455
|
-
// Force float output to avoid SDK default base64 decoding path.
|
|
456
|
-
// Skip for providers that reject this field (e.g. Voyage).
|
|
457
320
|
const isVoyage = this._baseURL?.includes("voyageai.com");
|
|
458
321
|
if (!isVoyage) {
|
|
459
322
|
payload.encoding_format = "float";
|
|
460
323
|
}
|
|
461
|
-
|
|
462
|
-
// Voyage uses "input_type" instead of "task"
|
|
463
324
|
if (task && isVoyage) {
|
|
464
|
-
// Map taskQuery/taskPassage to Voyage input_type
|
|
465
325
|
if (task.includes("query")) payload.input_type = "query";
|
|
466
326
|
else if (task.includes("passage") || task.includes("document")) payload.input_type = "document";
|
|
467
327
|
else payload.input_type = task;
|
|
468
328
|
} else if (task) {
|
|
469
329
|
payload.task = task;
|
|
470
330
|
}
|
|
471
|
-
if (this._normalized !==
|
|
472
|
-
|
|
473
|
-
// Some OpenAI-compatible providers support requesting a specific vector size.
|
|
474
|
-
// We only pass it through when explicitly configured to avoid breaking providers
|
|
475
|
-
// that reject unknown fields.
|
|
331
|
+
if (this._normalized !== void 0) payload.normalized = this._normalized;
|
|
476
332
|
if (this._requestDimensions && this._requestDimensions > 0 && !isVoyage) {
|
|
477
333
|
payload.dimensions = this._requestDimensions;
|
|
478
334
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
335
|
return payload;
|
|
483
336
|
}
|
|
484
|
-
|
|
485
|
-
private async embedSingle(text: string, task?: string): Promise<number[]> {
|
|
337
|
+
async embedSingle(text, task) {
|
|
486
338
|
if (!text || text.trim().length === 0) {
|
|
487
339
|
throw new Error("Cannot embed empty text");
|
|
488
340
|
}
|
|
489
|
-
|
|
490
|
-
// Check cache first
|
|
491
341
|
const cached = this._cache.get(text, task);
|
|
492
342
|
if (cached) return cached;
|
|
493
|
-
|
|
494
343
|
try {
|
|
495
344
|
const response = await this.embedWithRetry(this.buildPayload(text, task));
|
|
496
|
-
const embedding = response.data[0]?.embedding
|
|
345
|
+
const embedding = response.data[0]?.embedding;
|
|
497
346
|
if (!embedding) {
|
|
498
347
|
throw new Error("No embedding returned from provider");
|
|
499
348
|
}
|
|
500
|
-
|
|
501
349
|
this.validateEmbedding(embedding);
|
|
502
350
|
this._cache.set(text, task, embedding);
|
|
503
351
|
return embedding;
|
|
504
352
|
} catch (error) {
|
|
505
|
-
// Check if this is a context length exceeded error and try chunking
|
|
506
353
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
507
354
|
const isContextError = /context|too long|exceed|length/i.test(errorMsg);
|
|
508
|
-
|
|
509
355
|
if (isContextError && this._autoChunk) {
|
|
510
356
|
try {
|
|
511
|
-
|
|
357
|
+
log.info(`Document exceeded context limit (${errorMsg}), attempting chunking...`);
|
|
512
358
|
const chunkResult = smartChunk(text, this._model);
|
|
513
|
-
|
|
514
359
|
if (chunkResult.chunks.length === 0) {
|
|
515
360
|
throw new Error(`Failed to chunk document: ${errorMsg}`);
|
|
516
361
|
}
|
|
517
|
-
|
|
518
|
-
// Embed all chunks in parallel
|
|
519
|
-
console.log(`Split document into ${chunkResult.chunkCount} chunks for embedding`);
|
|
362
|
+
log.info(`Split document into ${chunkResult.chunkCount} chunks for embedding`);
|
|
520
363
|
const chunkEmbeddings = await Promise.all(
|
|
521
364
|
chunkResult.chunks.map(async (chunk, idx) => {
|
|
522
365
|
try {
|
|
523
366
|
const embedding = await this.embedSingle(chunk, task);
|
|
524
367
|
return { embedding };
|
|
525
368
|
} catch (chunkError) {
|
|
526
|
-
|
|
369
|
+
log.warn(`Failed to embed chunk ${idx}:`, chunkError);
|
|
527
370
|
throw chunkError;
|
|
528
371
|
}
|
|
529
372
|
})
|
|
530
373
|
);
|
|
531
|
-
|
|
532
|
-
// Compute average embedding across chunks
|
|
533
374
|
const avgEmbedding = chunkEmbeddings.reduce(
|
|
534
375
|
(sum, { embedding }) => {
|
|
535
376
|
for (let i = 0; i < embedding.length; i++) {
|
|
@@ -539,101 +380,75 @@ export class Embedder {
|
|
|
539
380
|
},
|
|
540
381
|
new Array(this.dimensions).fill(0)
|
|
541
382
|
);
|
|
542
|
-
|
|
543
|
-
const finalEmbedding = avgEmbedding.map(v => v / chunkEmbeddings.length);
|
|
544
|
-
|
|
545
|
-
// Cache the result for the original text (using its hash)
|
|
383
|
+
const finalEmbedding = avgEmbedding.map((v) => v / chunkEmbeddings.length);
|
|
546
384
|
this._cache.set(text, task, finalEmbedding);
|
|
547
|
-
|
|
548
|
-
|
|
385
|
+
log.info(`Successfully embedded long document as ${chunkEmbeddings.length} averaged chunks`);
|
|
549
386
|
return finalEmbedding;
|
|
550
387
|
} catch (chunkError) {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
const friendly = formatEmbeddingProviderError(error, {
|
|
388
|
+
log.warn(`Chunking failed, using original error:`, chunkError);
|
|
389
|
+
const friendly2 = formatEmbeddingProviderError(error, {
|
|
554
390
|
baseURL: this._baseURL,
|
|
555
391
|
model: this._model,
|
|
556
|
-
mode: "single"
|
|
392
|
+
mode: "single"
|
|
557
393
|
});
|
|
558
|
-
throw new Error(
|
|
394
|
+
throw new Error(friendly2, { cause: error });
|
|
559
395
|
}
|
|
560
396
|
}
|
|
561
|
-
|
|
562
397
|
const friendly = formatEmbeddingProviderError(error, {
|
|
563
398
|
baseURL: this._baseURL,
|
|
564
399
|
model: this._model,
|
|
565
|
-
mode: "single"
|
|
400
|
+
mode: "single"
|
|
566
401
|
});
|
|
567
|
-
throw new Error(friendly, { cause: error instanceof Error ? error :
|
|
402
|
+
throw new Error(friendly, { cause: error instanceof Error ? error : void 0 });
|
|
568
403
|
}
|
|
569
404
|
}
|
|
570
|
-
|
|
571
|
-
private async embedMany(texts: string[], task?: string): Promise<number[][]> {
|
|
405
|
+
async embedMany(texts, task) {
|
|
572
406
|
if (!texts || texts.length === 0) {
|
|
573
407
|
return [];
|
|
574
408
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const validTexts: string[] = [];
|
|
578
|
-
const validIndices: number[] = [];
|
|
579
|
-
|
|
409
|
+
const validTexts = [];
|
|
410
|
+
const validIndices = [];
|
|
580
411
|
texts.forEach((text, index) => {
|
|
581
412
|
if (text && text.trim().length > 0) {
|
|
582
413
|
validTexts.push(text);
|
|
583
414
|
validIndices.push(index);
|
|
584
415
|
}
|
|
585
416
|
});
|
|
586
|
-
|
|
587
417
|
if (validTexts.length === 0) {
|
|
588
418
|
return texts.map(() => []);
|
|
589
419
|
}
|
|
590
|
-
|
|
591
420
|
try {
|
|
592
421
|
const response = await this.embedWithRetry(
|
|
593
422
|
this.buildPayload(validTexts, task)
|
|
594
423
|
);
|
|
595
|
-
|
|
596
|
-
// Create result array with proper length
|
|
597
|
-
const results: number[][] = new Array(texts.length);
|
|
598
|
-
|
|
599
|
-
// Fill in embeddings for valid texts
|
|
424
|
+
const results = new Array(texts.length);
|
|
600
425
|
response.data.forEach((item, idx) => {
|
|
601
426
|
const originalIndex = validIndices[idx];
|
|
602
|
-
const embedding = item.embedding
|
|
603
|
-
|
|
427
|
+
const embedding = item.embedding;
|
|
604
428
|
this.validateEmbedding(embedding);
|
|
605
429
|
results[originalIndex] = embedding;
|
|
606
430
|
});
|
|
607
|
-
|
|
608
|
-
// Fill empty arrays for invalid texts
|
|
609
431
|
for (let i = 0; i < texts.length; i++) {
|
|
610
432
|
if (!results[i]) {
|
|
611
433
|
results[i] = [];
|
|
612
434
|
}
|
|
613
435
|
}
|
|
614
|
-
|
|
615
436
|
return results;
|
|
616
437
|
} catch (error) {
|
|
617
|
-
// Check if this is a context length exceeded error and try chunking each text
|
|
618
438
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
619
439
|
const isContextError = /context|too long|exceed|length/i.test(errorMsg);
|
|
620
|
-
|
|
621
440
|
if (isContextError && this._autoChunk) {
|
|
622
441
|
try {
|
|
623
|
-
|
|
624
|
-
|
|
442
|
+
log.info(`Batch embedding failed with context error, attempting chunking...`);
|
|
625
443
|
const chunkResults = await Promise.all(
|
|
626
444
|
validTexts.map(async (text, idx) => {
|
|
627
445
|
const chunkResult = smartChunk(text, this._model);
|
|
628
446
|
if (chunkResult.chunks.length === 0) {
|
|
629
447
|
throw new Error("Chunker produced no chunks");
|
|
630
448
|
}
|
|
631
|
-
|
|
632
|
-
// Embed all chunks in parallel, then average.
|
|
633
449
|
const embeddings = await Promise.all(
|
|
634
450
|
chunkResult.chunks.map((chunk) => this.embedSingle(chunk, task))
|
|
635
451
|
);
|
|
636
|
-
|
|
637
452
|
const avgEmbedding = embeddings.reduce(
|
|
638
453
|
(sum, emb) => {
|
|
639
454
|
for (let i = 0; i < emb.length; i++) {
|
|
@@ -643,20 +458,13 @@ export class Embedder {
|
|
|
643
458
|
},
|
|
644
459
|
new Array(this.dimensions).fill(0)
|
|
645
460
|
);
|
|
646
|
-
|
|
647
461
|
const finalEmbedding = avgEmbedding.map((v) => v / embeddings.length);
|
|
648
|
-
|
|
649
|
-
// Cache the averaged embedding for the original (long) text.
|
|
650
462
|
this._cache.set(text, task, finalEmbedding);
|
|
651
|
-
|
|
652
463
|
return { embedding: finalEmbedding, index: validIndices[idx] };
|
|
653
464
|
})
|
|
654
465
|
);
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
// Build results array
|
|
659
|
-
const results: number[][] = new Array(texts.length);
|
|
466
|
+
log.info(`Successfully chunked and embedded ${chunkResults.length} long documents`);
|
|
467
|
+
const results = new Array(texts.length);
|
|
660
468
|
chunkResults.forEach(({ embedding, index }) => {
|
|
661
469
|
if (embedding.length > 0) {
|
|
662
470
|
this.validateEmbedding(embedding);
|
|
@@ -665,71 +473,65 @@ export class Embedder {
|
|
|
665
473
|
results[index] = [];
|
|
666
474
|
}
|
|
667
475
|
});
|
|
668
|
-
|
|
669
|
-
// Fill empty arrays for invalid texts
|
|
670
476
|
for (let i = 0; i < texts.length; i++) {
|
|
671
477
|
if (!results[i]) {
|
|
672
478
|
results[i] = [];
|
|
673
479
|
}
|
|
674
480
|
}
|
|
675
|
-
|
|
676
481
|
return results;
|
|
677
482
|
} catch (chunkError) {
|
|
678
|
-
const
|
|
483
|
+
const friendly2 = formatEmbeddingProviderError(error, {
|
|
679
484
|
baseURL: this._baseURL,
|
|
680
485
|
model: this._model,
|
|
681
|
-
mode: "batch"
|
|
486
|
+
mode: "batch"
|
|
682
487
|
});
|
|
683
|
-
throw new Error(`Failed to embed documents after chunking attempt: ${
|
|
684
|
-
cause: error instanceof Error ? error :
|
|
488
|
+
throw new Error(`Failed to embed documents after chunking attempt: ${friendly2}`, {
|
|
489
|
+
cause: error instanceof Error ? error : void 0
|
|
685
490
|
});
|
|
686
491
|
}
|
|
687
492
|
}
|
|
688
|
-
|
|
689
493
|
const friendly = formatEmbeddingProviderError(error, {
|
|
690
494
|
baseURL: this._baseURL,
|
|
691
495
|
model: this._model,
|
|
692
|
-
mode: "batch"
|
|
496
|
+
mode: "batch"
|
|
693
497
|
});
|
|
694
498
|
throw new Error(friendly, {
|
|
695
|
-
cause: error instanceof Error ? error :
|
|
499
|
+
cause: error instanceof Error ? error : void 0
|
|
696
500
|
});
|
|
697
501
|
}
|
|
698
502
|
}
|
|
699
|
-
|
|
700
|
-
get model(): string {
|
|
503
|
+
get model() {
|
|
701
504
|
return this._model;
|
|
702
505
|
}
|
|
703
|
-
|
|
704
506
|
// Test connection and validate configuration
|
|
705
|
-
async test()
|
|
507
|
+
async test() {
|
|
706
508
|
try {
|
|
707
509
|
const testEmbedding = await this.embedPassage("test");
|
|
708
510
|
return {
|
|
709
511
|
success: true,
|
|
710
|
-
dimensions: testEmbedding.length
|
|
512
|
+
dimensions: testEmbedding.length
|
|
711
513
|
};
|
|
712
514
|
} catch (error) {
|
|
713
|
-
|
|
714
515
|
return {
|
|
715
516
|
success: false,
|
|
716
|
-
error: error instanceof Error ? error.message : String(error)
|
|
517
|
+
error: error instanceof Error ? error.message : String(error)
|
|
717
518
|
};
|
|
718
519
|
}
|
|
719
520
|
}
|
|
720
|
-
|
|
721
521
|
get cacheStats() {
|
|
722
522
|
return {
|
|
723
523
|
...this._cache.stats,
|
|
724
|
-
keyCount: this.clients.length
|
|
524
|
+
keyCount: this.clients.length
|
|
725
525
|
};
|
|
726
526
|
}
|
|
727
527
|
}
|
|
728
|
-
|
|
729
|
-
// ============================================================================
|
|
730
|
-
// Factory Function
|
|
731
|
-
// ============================================================================
|
|
732
|
-
|
|
733
|
-
export function createEmbedder(config: EmbeddingConfig): Embedder {
|
|
528
|
+
function createEmbedder(config) {
|
|
734
529
|
return new Embedder(config);
|
|
735
530
|
}
|
|
531
|
+
export {
|
|
532
|
+
Embedder,
|
|
533
|
+
createEmbedder,
|
|
534
|
+
formatEmbeddingProviderError,
|
|
535
|
+
getVectorDimensions
|
|
536
|
+
};
|
|
537
|
+
//# sourceMappingURL=embedder.js.map
|