vecbox 0.1.1 → 0.2.2
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 +40 -0
- package/README.md +163 -228
- package/dist/index.cjs +189 -332
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -36
- package/dist/index.d.ts +26 -36
- package/dist/index.js +196 -333
- package/dist/index.js.map +1 -1
- package/dist/llama_embedding-EC3MWSUZ.node +0 -0
- package/native/binding.gyp +65 -0
- package/native/index.js +39 -0
- package/native/llama_embedding_simple.cpp +111 -0
- package/native/package-lock.json +1277 -0
- package/native/package.json +26 -0
- package/package.json +11 -19
- package/src/factory/EmbeddingFactory.ts +0 -4
- package/src/providers/gemini.ts +2 -2
- package/src/providers/llamacpp.ts +118 -170
- package/src/types/index.ts +0 -2
- package/src/providers/claude.ts +0 -78
- package/src/providers/deepseek.ts +0 -115
- package/src/types/deepseek.d.ts +0 -15
- package/src/types/index.d.ts +0 -43
- package/src/types/transformers.d.ts +0 -7
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "llama-embedding-native",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Native Node.js bindings for Llama.cpp embeddings",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"install": "node-gyp rebuild",
|
|
8
|
+
"build": "node-gyp rebuild"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"node-addon-api": "^7.1.0"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"node-gyp": "^10.1.0"
|
|
15
|
+
},
|
|
16
|
+
"gypfile": true,
|
|
17
|
+
"keywords": [
|
|
18
|
+
"llama.cpp",
|
|
19
|
+
"embedding",
|
|
20
|
+
"native",
|
|
21
|
+
"n-api",
|
|
22
|
+
"node.js"
|
|
23
|
+
],
|
|
24
|
+
"author": "Vecbox Team",
|
|
25
|
+
"license": "MIT"
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vecbox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "A minimal and powerful embedding library that supports multiple providers with automatic detection and fallback capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,19 +8,24 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
10
10
|
"src",
|
|
11
|
+
"native",
|
|
11
12
|
"README.md",
|
|
12
13
|
"CHANGELOG.md",
|
|
13
14
|
"LICENSE"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
16
17
|
"build": "tsup",
|
|
18
|
+
"build:native": "node scripts/build-native.cjs",
|
|
19
|
+
"build:all": "npm run build && npm run build:native",
|
|
17
20
|
"dev": "tsx watch main.ts",
|
|
18
21
|
"test": "vitest",
|
|
19
22
|
"test:run": "vitest run",
|
|
23
|
+
"test:native": "vitest test/native.test.js",
|
|
24
|
+
"test:integration": "vitest test/integration.test.js",
|
|
20
25
|
"example": "tsx examples/basic-usage.ts",
|
|
21
|
-
"lint": "eslint . --ext .
|
|
26
|
+
"lint": "eslint . --ext .js",
|
|
22
27
|
"lint:fix": "eslint . --ext .ts,.js --fix",
|
|
23
|
-
"prepublishOnly": "npm run build"
|
|
28
|
+
"prepublishOnly": "npm run build:all"
|
|
24
29
|
},
|
|
25
30
|
"keywords": [
|
|
26
31
|
"embeddings",
|
|
@@ -29,9 +34,7 @@
|
|
|
29
34
|
"machine-learning",
|
|
30
35
|
"openai",
|
|
31
36
|
"gemini",
|
|
32
|
-
"claude",
|
|
33
37
|
"mistral",
|
|
34
|
-
"deepseek",
|
|
35
38
|
"llamacpp",
|
|
36
39
|
"semantic-search",
|
|
37
40
|
"similarity",
|
|
@@ -56,24 +59,13 @@
|
|
|
56
59
|
"node": ">=16.0.0"
|
|
57
60
|
},
|
|
58
61
|
"packageManager": "pnpm@10.28.2",
|
|
59
|
-
"devDependencies": {
|
|
60
|
-
"@eslint/js": "^10.0.1",
|
|
61
|
-
"@types/node": "^25.2.3",
|
|
62
|
-
"eslint": "^10.0.0",
|
|
63
|
-
"globals": "^17.3.0",
|
|
64
|
-
"jiti": "^2.6.1",
|
|
65
|
-
"tsup": "^8.5.1",
|
|
66
|
-
"tsx": "^4.21.0",
|
|
67
|
-
"typescript": "^5.9.3",
|
|
68
|
-
"typescript-eslint": "^8.55.0",
|
|
69
|
-
"vitest": "^4.0.18"
|
|
70
|
-
},
|
|
71
62
|
"dependencies": {
|
|
72
|
-
"@anthropic-ai/sdk": "^0.74.0",
|
|
73
63
|
"@google/generative-ai": "^0.24.1",
|
|
74
64
|
"@mistralai/mistralai": "^1.14.0",
|
|
75
|
-
"deepseek": "^0.0.2",
|
|
76
65
|
"dotenv": "^17.3.1",
|
|
77
66
|
"openai": "^6.21.0"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"tsup": "^8.5.1"
|
|
78
70
|
}
|
|
79
71
|
}
|
|
@@ -2,9 +2,7 @@ import type { EmbedConfig, ProviderType } from '@src/types/index';
|
|
|
2
2
|
import { EmbeddingProvider } from '@providers/base/EmbeddingProvider';
|
|
3
3
|
import { OpenAIProvider } from '@providers/openai';
|
|
4
4
|
import { GeminiProvider } from '@providers/gemini';
|
|
5
|
-
import { ClaudeProvider } from '@providers/claude';
|
|
6
5
|
import { MistralProvider } from '@providers/mistral';
|
|
7
|
-
import { DeepSeekProvider } from '@providers/deepseek';
|
|
8
6
|
import { LlamaCppProvider } from '@providers/llamacpp';
|
|
9
7
|
import { Logger } from '@src/util/logger';
|
|
10
8
|
|
|
@@ -14,9 +12,7 @@ export class EmbeddingFactory {
|
|
|
14
12
|
private static providers = new Map<ProviderType, new (config: EmbedConfig) => EmbeddingProvider>([
|
|
15
13
|
['openai', OpenAIProvider],
|
|
16
14
|
['gemini', GeminiProvider],
|
|
17
|
-
['claude', ClaudeProvider],
|
|
18
15
|
['mistral', MistralProvider],
|
|
19
|
-
['deepseek', DeepSeekProvider],
|
|
20
16
|
['llamacpp', LlamaCppProvider], // Local embeddings with llama.cpp
|
|
21
17
|
]);
|
|
22
18
|
|
package/src/providers/gemini.ts
CHANGED
|
@@ -74,11 +74,11 @@ export class GeminiProvider extends EmbeddingProvider {
|
|
|
74
74
|
|
|
75
75
|
getDimensions(): number {
|
|
76
76
|
const model = this.getModel();
|
|
77
|
-
if (model.includes('gemini-embedding-001')) return
|
|
77
|
+
if (model.includes('gemini-embedding-001')) return 3072;
|
|
78
78
|
if (model.includes('text-embedding-004')) return 768;
|
|
79
79
|
if (model.includes('embedding-001')) return 768;
|
|
80
80
|
if (model.includes('multimodalembedding')) return 768;
|
|
81
|
-
return
|
|
81
|
+
return 3072; // default for Gemini embeddings
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
getProviderName(): string {
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Llama.cpp Provider - Local embeddings using llama.cpp directly
|
|
3
|
-
* Uses llama-embedding binary without any external dependencies
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import { access, constants } from 'fs/promises';
|
|
7
2
|
import { join, resolve } from 'path';
|
|
8
3
|
import { EmbeddingProvider } from '@providers/base/EmbeddingProvider';
|
|
@@ -10,6 +5,20 @@ import type { EmbedConfig, EmbedInput, EmbedResult, BatchEmbedResult } from '@sr
|
|
|
10
5
|
import { logger } from '@src/util/logger';
|
|
11
6
|
import * as http from 'http';
|
|
12
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Llama.cpp Provider - Local embeddings using llama.cpp directly
|
|
10
|
+
* Uses native N-API module for better performance
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Try to import native module
|
|
14
|
+
let nativeModule: any = null;
|
|
15
|
+
try {
|
|
16
|
+
nativeModule = require('../../native');
|
|
17
|
+
logger.info('Using native Llama.cpp module');
|
|
18
|
+
} catch (error) {
|
|
19
|
+
logger.warn('Native module not available, falling back to HTTP');
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
// Extend EmbedConfig to include llamaPath
|
|
14
23
|
interface LlamaCppConfig extends EmbedConfig {
|
|
15
24
|
llamaPath?: string;
|
|
@@ -18,12 +27,26 @@ interface LlamaCppConfig extends EmbedConfig {
|
|
|
18
27
|
export class LlamaCppProvider extends EmbeddingProvider {
|
|
19
28
|
private llamaPath: string;
|
|
20
29
|
private modelPath: string;
|
|
30
|
+
private useNative: boolean;
|
|
31
|
+
private nativeModel: any = null;
|
|
21
32
|
|
|
22
33
|
constructor(config: LlamaCppConfig) {
|
|
23
34
|
super({ ...config, provider: 'llamacpp' });
|
|
24
35
|
this.modelPath = config.model || 'nomic-embed-text-v1.5.Q4_K_M.gguf';
|
|
25
36
|
this.llamaPath = config.llamaPath || './llama.cpp/build/bin/llama-embedding';
|
|
26
|
-
|
|
37
|
+
this.useNative = !!nativeModule;
|
|
38
|
+
|
|
39
|
+
if (this.useNative) {
|
|
40
|
+
try {
|
|
41
|
+
this.nativeModel = nativeModule.create(this.modelPath);
|
|
42
|
+
logger.info(`Llama.cpp provider initialized with native module: ${this.modelPath}`);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
logger.error(`Failed to initialize native module: ${error}`);
|
|
45
|
+
this.useNative = false;
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
logger.info(`Llama.cpp provider initialized with HTTP fallback: ${this.modelPath}`);
|
|
49
|
+
}
|
|
27
50
|
}
|
|
28
51
|
|
|
29
52
|
// Public API methods
|
|
@@ -44,7 +67,12 @@ export class LlamaCppProvider extends EmbeddingProvider {
|
|
|
44
67
|
|
|
45
68
|
async isReady(): Promise<boolean> {
|
|
46
69
|
try {
|
|
47
|
-
|
|
70
|
+
if (this.useNative && this.nativeModel) {
|
|
71
|
+
// Native module is ready if model was loaded successfully
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fallback to HTTP check
|
|
48
76
|
await access(this.llamaPath, constants.F_OK);
|
|
49
77
|
await access(this.llamaPath, constants.X_OK);
|
|
50
78
|
|
|
@@ -60,6 +88,39 @@ export class LlamaCppProvider extends EmbeddingProvider {
|
|
|
60
88
|
}
|
|
61
89
|
}
|
|
62
90
|
|
|
91
|
+
private async loadGGUFModel(modelPath: string): Promise<Buffer> {
|
|
92
|
+
try {
|
|
93
|
+
logger.debug(`Loading GGUF model from: ${modelPath}`);
|
|
94
|
+
|
|
95
|
+
// Read model file
|
|
96
|
+
const modelBuffer = await fs.readFile(modelPath);
|
|
97
|
+
|
|
98
|
+
if (!modelBuffer) {
|
|
99
|
+
throw new Error(`Failed to read model file: ${modelPath}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
logger.debug(`Model file loaded, size: ${modelBuffer.length} bytes`);
|
|
103
|
+
return modelBuffer;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logger.error(`Failed to load GGUF model: ${error instanceof Error ? error.message : String(error)}`);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private generateEmbedding(modelBuffer: Buffer, text: string): number[] {
|
|
111
|
+
// Use the loaded model to generate embedding
|
|
112
|
+
logger.debug(`Generating embedding with model buffer (${modelBuffer.length} bytes)`);
|
|
113
|
+
|
|
114
|
+
// TODO: Implement actual Llama.cpp embedding generation
|
|
115
|
+
// For now, return mock embedding based on text length
|
|
116
|
+
const embedding = [];
|
|
117
|
+
for (let i = 0; i < Math.min(text.length, 768); i++) {
|
|
118
|
+
embedding.push(Math.sin(i * 0.1) * (i % 10));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return embedding;
|
|
122
|
+
}
|
|
123
|
+
|
|
63
124
|
async embed(input: EmbedInput): Promise<EmbedResult> {
|
|
64
125
|
try {
|
|
65
126
|
logger.debug(`Embedding text with llama.cpp: ${this.getModel()}`);
|
|
@@ -69,26 +130,20 @@ export class LlamaCppProvider extends EmbeddingProvider {
|
|
|
69
130
|
throw new Error('Text input cannot be empty');
|
|
70
131
|
}
|
|
71
132
|
|
|
72
|
-
// Use
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
133
|
+
// Use native module for now
|
|
134
|
+
if (this.useNative && this.nativeModel) {
|
|
135
|
+
const embedding = this.nativeModel.embed(text);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
embedding,
|
|
139
|
+
dimensions: embedding.length,
|
|
140
|
+
model: this.getModel(),
|
|
141
|
+
provider: 'llamacpp',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
79
144
|
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// Parse output to extract embedding
|
|
84
|
-
const embedding = this.parseRawOutput(result.stdout);
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
embedding,
|
|
88
|
-
dimensions: embedding.length,
|
|
89
|
-
model: this.getModel(),
|
|
90
|
-
provider: 'llamacpp',
|
|
91
|
-
};
|
|
145
|
+
// TODO: Implement direct Llama.cpp core usage in future
|
|
146
|
+
throw new Error('Direct Llama.cpp core integration not yet implemented. Please use HTTP fallback or wait for next version.');
|
|
92
147
|
} catch (error: unknown) {
|
|
93
148
|
logger.error(`Llama.cpp embedding failed: ${(error instanceof Error ? error.message : String(error))}`);
|
|
94
149
|
throw error;
|
|
@@ -99,6 +154,31 @@ export class LlamaCppProvider extends EmbeddingProvider {
|
|
|
99
154
|
try {
|
|
100
155
|
logger.debug(`Batch embedding ${inputs.length} texts with llama.cpp`);
|
|
101
156
|
|
|
157
|
+
if (this.useNative && this.nativeModel) {
|
|
158
|
+
// Use native module for batch
|
|
159
|
+
const embeddings: number[][] = [];
|
|
160
|
+
|
|
161
|
+
for (const input of inputs) {
|
|
162
|
+
const text = await this.readInput(input);
|
|
163
|
+
if (text.trim()) {
|
|
164
|
+
const embedding = this.nativeModel.embed(text);
|
|
165
|
+
embeddings.push(embedding);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (embeddings.length === 0) {
|
|
170
|
+
throw new Error('No valid texts to embed');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
embeddings,
|
|
175
|
+
dimensions: embeddings[0]?.length || 0,
|
|
176
|
+
model: this.getModel(),
|
|
177
|
+
provider: 'llamacpp',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Fallback to HTTP batch processing
|
|
102
182
|
const texts = [];
|
|
103
183
|
for (const input of inputs) {
|
|
104
184
|
const text = await this.readInput(input);
|
|
@@ -113,7 +193,7 @@ export class LlamaCppProvider extends EmbeddingProvider {
|
|
|
113
193
|
|
|
114
194
|
// For batch processing, use HTTP API
|
|
115
195
|
const modelPath = await this.getModelPath();
|
|
116
|
-
const requests = inputs.map(input => ({
|
|
196
|
+
const requests = inputs.map((input, v) => ({
|
|
117
197
|
input: input.text || '',
|
|
118
198
|
model: modelPath,
|
|
119
199
|
pooling: 'mean',
|
|
@@ -140,153 +220,21 @@ export class LlamaCppProvider extends EmbeddingProvider {
|
|
|
140
220
|
}
|
|
141
221
|
}
|
|
142
222
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Private helper methods
|
|
149
|
-
private async getModelPath(): Promise<string> {
|
|
150
|
-
// Try different model paths
|
|
151
|
-
const possiblePaths = [
|
|
152
|
-
this.modelPath, // As provided
|
|
153
|
-
join('./llama.cpp/models', this.modelPath), // In llama.cpp/models
|
|
154
|
-
join('./llama.cpp', this.modelPath), // In llama.cpp root
|
|
155
|
-
this.modelPath // Fallback
|
|
156
|
-
];
|
|
157
|
-
|
|
158
|
-
for (const path of possiblePaths) {
|
|
223
|
+
// Cleanup method
|
|
224
|
+
async cleanup(): Promise<void> {
|
|
225
|
+
if (this.useNative && this.nativeModel) {
|
|
159
226
|
try {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
227
|
+
this.nativeModel.close();
|
|
228
|
+
this.nativeModel = null;
|
|
229
|
+
logger.info('Native Llama.cpp model closed');
|
|
230
|
+
} catch (error) {
|
|
231
|
+
logger.error(`Error closing native model: ${error}`);
|
|
164
232
|
}
|
|
165
233
|
}
|
|
166
|
-
|
|
167
|
-
throw new Error(`Model file not found: ${this.modelPath}`);
|
|
168
234
|
}
|
|
169
235
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const port = 8080; // Default llama.cpp server port
|
|
174
|
-
|
|
175
|
-
// Parse the request body from args[0] (JSON string)
|
|
176
|
-
let requestBody;
|
|
177
|
-
try {
|
|
178
|
-
requestBody = JSON.parse(args[0] || '{}');
|
|
179
|
-
} catch {
|
|
180
|
-
reject(new Error('Invalid request body for HTTP API'));
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const postData = JSON.stringify(requestBody);
|
|
185
|
-
|
|
186
|
-
const options = {
|
|
187
|
-
hostname: 'localhost',
|
|
188
|
-
port: port,
|
|
189
|
-
path: '/embedding',
|
|
190
|
-
method: 'POST',
|
|
191
|
-
headers: {
|
|
192
|
-
'Content-Type': 'application/json',
|
|
193
|
-
'Content-Length': Buffer.byteLength(postData)
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
const req = http.request(options, (res: http.IncomingMessage) => {
|
|
198
|
-
let data = '';
|
|
199
|
-
|
|
200
|
-
res.on('data', (chunk: Buffer | string) => {
|
|
201
|
-
data += chunk;
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
res.on('end', () => {
|
|
205
|
-
if (res.statusCode === 200) {
|
|
206
|
-
resolve({ stdout: data, stderr: '' });
|
|
207
|
-
} else {
|
|
208
|
-
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
req.on('error', (error: Error) => {
|
|
214
|
-
reject(new Error(`Failed to connect to llama.cpp server: ${(error instanceof Error ? error.message : String(error))}`));
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
req.write(postData);
|
|
218
|
-
req.end();
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
private parseRawOutput(output: string): number[] {
|
|
223
|
-
try {
|
|
224
|
-
const response = JSON.parse(output);
|
|
225
|
-
|
|
226
|
-
logger.debug(`PARSE DEBUG: Response type: ${typeof response}`);
|
|
227
|
-
logger.debug(`PARSE DEBUG: Is Array: ${Array.isArray(response)}`);
|
|
228
|
-
|
|
229
|
-
// CASE 1: Array of objects with nested embedding
|
|
230
|
-
// Format: [{index: 0, embedding: [[...]]}]
|
|
231
|
-
if (Array.isArray(response) && response.length > 0) {
|
|
232
|
-
const first = response[0];
|
|
233
|
-
|
|
234
|
-
if (first && first.embedding && Array.isArray(first.embedding)) {
|
|
235
|
-
const emb = first.embedding;
|
|
236
|
-
|
|
237
|
-
// Check if nested: [[...]]
|
|
238
|
-
if (Array.isArray(emb[0])) {
|
|
239
|
-
const flat = emb[0]; // ← Take the inner array
|
|
240
|
-
logger.debug(`Parsed ${flat.length} dimensions (nested)`);
|
|
241
|
-
return flat;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Not nested: [...]
|
|
245
|
-
logger.debug(`Parsed ${emb.length} dimensions (direct)`);
|
|
246
|
-
return emb;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// CASE 2: Direct object {embedding: [...]}
|
|
251
|
-
if (response.embedding && Array.isArray(response.embedding)) {
|
|
252
|
-
const emb = response.embedding;
|
|
253
|
-
|
|
254
|
-
// Check nested
|
|
255
|
-
if (Array.isArray(emb[0])) {
|
|
256
|
-
return emb[0];
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return emb;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// CASE 3: Direct array of numbers
|
|
263
|
-
if (Array.isArray(response) && typeof response[0] === 'number') {
|
|
264
|
-
logger.debug(`Parsed ${response.length} dimensions (flat array)`);
|
|
265
|
-
return response;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
throw new Error(`Unexpected format: ${JSON.stringify(Object.keys(response))}`);
|
|
269
|
-
|
|
270
|
-
} catch (error: unknown) {
|
|
271
|
-
const errorMessage = error instanceof Error ? (error instanceof Error ? error.message : String(error)) : 'Unknown error';
|
|
272
|
-
throw new Error(`Parse failed: ${errorMessage}`, { cause: error });
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private parseArrayOutput(output: string): number[][] {
|
|
277
|
-
// Parse array format: [[val1,val2,...], [val1,val2,...], ...]
|
|
278
|
-
const arrayPattern = /\[([^\]]+)\]/g;
|
|
279
|
-
const matches = [...output.matchAll(arrayPattern)];
|
|
280
|
-
|
|
281
|
-
if (matches.length === 0) {
|
|
282
|
-
throw new Error('No array embeddings found in output');
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const embeddings = matches.map(match => {
|
|
286
|
-
const values = match[1]?.split(',').map(v => v.trim()) || [];
|
|
287
|
-
return values.map(v => parseFloat(v)).filter(v => !isNaN(v));
|
|
288
|
-
}).filter(embedding => embedding.length > 0);
|
|
289
|
-
|
|
290
|
-
return embeddings;
|
|
236
|
+
// Protected methods
|
|
237
|
+
protected getModel(): string {
|
|
238
|
+
return this.modelPath;
|
|
291
239
|
}
|
|
292
240
|
}
|
package/src/types/index.ts
CHANGED
package/src/providers/claude.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
-
import { EmbeddingProvider } from '@providers/base/EmbeddingProvider';
|
|
3
|
-
import type { EmbedConfig, EmbedResult, BatchEmbedResult } from '@src/types/index';
|
|
4
|
-
import { Logger } from '@src/util/logger';
|
|
5
|
-
|
|
6
|
-
const logger = Logger.createModuleLogger('claude');
|
|
7
|
-
|
|
8
|
-
export class ClaudeProvider extends EmbeddingProvider {
|
|
9
|
-
private client: Anthropic;
|
|
10
|
-
|
|
11
|
-
constructor(config: EmbedConfig) {
|
|
12
|
-
super(config);
|
|
13
|
-
|
|
14
|
-
if (!config.apiKey) {
|
|
15
|
-
throw new Error('Anthropic API key is required');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
this.client = new Anthropic({
|
|
19
|
-
apiKey: config.apiKey,
|
|
20
|
-
baseURL: config.baseUrl,
|
|
21
|
-
timeout: config.timeout || 30000,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
logger.info('Claude provider initialized');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async embed(): Promise<EmbedResult> {
|
|
28
|
-
try {
|
|
29
|
-
logger.debug(`Embedding text with model: ${this.getModel()}`);
|
|
30
|
-
|
|
31
|
-
// Note: Claude doesn't have native embeddings API yet
|
|
32
|
-
// This is a placeholder implementation
|
|
33
|
-
throw new Error('Claude embeddings API not yet available. Please use another provider.');
|
|
34
|
-
|
|
35
|
-
} catch (error: unknown) {
|
|
36
|
-
const errorMessage = error instanceof Error ? (error instanceof Error ? error.message : String(error)) : 'Unknown error';
|
|
37
|
-
logger.error(`Claude embedding failed: ${errorMessage}`);
|
|
38
|
-
throw error;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async embedBatch(): Promise<BatchEmbedResult> {
|
|
43
|
-
try {
|
|
44
|
-
// Note: Claude doesn't have native embeddings API yet
|
|
45
|
-
throw new Error('Claude embeddings API not yet available. Please use another provider.');
|
|
46
|
-
|
|
47
|
-
} catch (error: unknown) {
|
|
48
|
-
const errorMessage = error instanceof Error ? (error instanceof Error ? error.message : String(error)) : 'Unknown error';
|
|
49
|
-
logger.error(`Claude batch embedding failed: ${errorMessage}`);
|
|
50
|
-
throw error;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
getDimensions(): number {
|
|
55
|
-
// Claude doesn't have embeddings yet
|
|
56
|
-
return 0;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
getProviderName(): string {
|
|
60
|
-
return 'Anthropic Claude';
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async isReady(): Promise<boolean> {
|
|
64
|
-
try {
|
|
65
|
-
// Test API access
|
|
66
|
-
await this.client.messages.create({
|
|
67
|
-
model: 'claude-3-haiku-20240307',
|
|
68
|
-
max_tokens: 10,
|
|
69
|
-
messages: [{ role: 'user', content: 'test' }]
|
|
70
|
-
});
|
|
71
|
-
return true;
|
|
72
|
-
} catch (error: unknown) {
|
|
73
|
-
const errorMessage = error instanceof Error ? (error instanceof Error ? error.message : String(error)) : 'Unknown error';
|
|
74
|
-
logger.error(`Claude readiness check failed: ${errorMessage}`);
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { DeepSeek } from 'deepseek';
|
|
2
|
-
import { EmbeddingProvider } from '@providers/base/EmbeddingProvider';
|
|
3
|
-
import type { EmbedConfig, EmbedInput, EmbedResult, BatchEmbedResult } from '@src/types/index';
|
|
4
|
-
import { Logger } from '@src/util/logger';
|
|
5
|
-
|
|
6
|
-
const logger = Logger.createModuleLogger('deepseek');
|
|
7
|
-
|
|
8
|
-
export class DeepSeekProvider extends EmbeddingProvider {
|
|
9
|
-
private client: DeepSeek;
|
|
10
|
-
|
|
11
|
-
constructor(config: EmbedConfig) {
|
|
12
|
-
super(config);
|
|
13
|
-
|
|
14
|
-
if (!config.apiKey) {
|
|
15
|
-
throw new Error('DeepSeek API key is required');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const clientOptions: { apiKey: string; timeout: number; baseURL?: string } = {
|
|
19
|
-
apiKey: config.apiKey,
|
|
20
|
-
timeout: config.timeout || 30000,
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
if (config.baseUrl) {
|
|
24
|
-
clientOptions.baseURL = config.baseUrl;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
this.client = new DeepSeek(clientOptions);
|
|
28
|
-
|
|
29
|
-
logger.info('DeepSeek provider initialized');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async embed(input: EmbedInput): Promise<EmbedResult> {
|
|
33
|
-
try {
|
|
34
|
-
const text = await this.readInput(input);
|
|
35
|
-
logger.debug(`Embedding text with model: ${this.getModel()}`);
|
|
36
|
-
|
|
37
|
-
const response = await this.client.embeddings.create({
|
|
38
|
-
model: this.getModel(),
|
|
39
|
-
input: text,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const embedding = response.data[0];
|
|
43
|
-
if (!embedding) {
|
|
44
|
-
throw new Error('No embedding returned from DeepSeek API');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
embedding: embedding.embedding || [],
|
|
49
|
-
dimensions: embedding.embedding?.length || 0,
|
|
50
|
-
model: embedding.model || this.getModel(),
|
|
51
|
-
provider: 'deepseek',
|
|
52
|
-
usage: response.usage ? {
|
|
53
|
-
promptTokens: response.usage.prompt_tokens,
|
|
54
|
-
totalTokens: response.usage.total_tokens,
|
|
55
|
-
} : undefined,
|
|
56
|
-
};
|
|
57
|
-
} catch (error: unknown) {
|
|
58
|
-
logger.error(`DeepSeek embedding failed: ${(error instanceof Error ? error.message : String(error))}`);
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async embedBatch(inputs: EmbedInput[]): Promise<BatchEmbedResult> {
|
|
64
|
-
try {
|
|
65
|
-
const texts = await Promise.all(inputs.map(input => this.readInput(input)));
|
|
66
|
-
logger.debug(`Batch embedding ${texts.length} texts with model: ${this.getModel()}`);
|
|
67
|
-
|
|
68
|
-
const response = await this.client.embeddings.create({
|
|
69
|
-
model: this.getModel(),
|
|
70
|
-
input: texts,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const embeddings = response.data.map((item: { embedding: number[] }) => item.embedding);
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
embeddings,
|
|
77
|
-
dimensions: embeddings[0]?.length || 0,
|
|
78
|
-
model: response.model,
|
|
79
|
-
provider: 'deepseek',
|
|
80
|
-
usage: response.usage ? {
|
|
81
|
-
promptTokens: response.usage.prompt_tokens,
|
|
82
|
-
totalTokens: response.usage.total_tokens,
|
|
83
|
-
} : undefined,
|
|
84
|
-
};
|
|
85
|
-
} catch (error: unknown) {
|
|
86
|
-
logger.error(`DeepSeek batch embedding failed: ${(error instanceof Error ? error.message : String(error))}`);
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
getDimensions(): number {
|
|
92
|
-
// DeepSeek embedding dimensions
|
|
93
|
-
const model = this.getModel();
|
|
94
|
-
if (model.includes('deepseek-chat')) return 4096;
|
|
95
|
-
return 4096; // default for DeepSeek
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
getProviderName(): string {
|
|
99
|
-
return 'DeepSeek';
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async isReady(): Promise<boolean> {
|
|
103
|
-
try {
|
|
104
|
-
// Test with a simple embedding request
|
|
105
|
-
await this.client.embeddings.create({
|
|
106
|
-
model: this.getModel(),
|
|
107
|
-
input: 'test',
|
|
108
|
-
});
|
|
109
|
-
return true;
|
|
110
|
-
} catch (error: unknown) {
|
|
111
|
-
logger.error(`DeepSeek readiness check failed: ${(error instanceof Error ? error.message : String(error))}`);
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|