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.
@@ -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.1.1",
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 .ts,.js",
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
 
@@ -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 768;
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 768; // default for Gemini embeddings
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
- logger.info(`Llama.cpp provider initialized with model: ${this.modelPath}`);
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
- // Check if llama-embedding exists and is executable
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 HTTP API instead of CLI arguments
73
- const requestBody = {
74
- input: text,
75
- model: await this.getModelPath(),
76
- pooling: 'mean',
77
- normalize: 2
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
- // Execute HTTP request to llama.cpp server
81
- const result = await this.executeLlamaEmbedding([JSON.stringify(requestBody)]);
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
- // Protected methods
144
- protected getModel(): string {
145
- return this.modelPath;
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
- await access(path, constants.F_OK);
161
- return resolve(path);
162
- } catch {
163
- continue;
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
- private async executeLlamaEmbedding(args: string[]): Promise<{stdout: string; stderr: string}> {
171
- return new Promise((resolve, reject) => {
172
- // Use HTTP API instead of CLI for cleaner output
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
  }
@@ -1,9 +1,7 @@
1
1
  export type ProviderType =
2
2
  | 'openai'
3
3
  | 'gemini'
4
- | 'claude'
5
4
  | 'mistral'
6
- | 'deepseek'
7
5
  | 'llamacpp';
8
6
 
9
7
  export interface EmbedConfig {
@@ -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
- }