@1mbrain/core 0.1.0

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,78 @@
1
+ /**
2
+ * Ollama Embedding Provider
3
+ *
4
+ * Uses a local Ollama instance for generating embeddings.
5
+ * No API key required — runs entirely on-device.
6
+ */
7
+
8
+ import type { EmbeddingProvider } from '../types.js';
9
+ import { createChildLogger } from '../logger.js';
10
+
11
+ const log = createChildLogger('ollama-embedding');
12
+
13
+ const MODEL_DIMENSIONS: Record<string, number> = {
14
+ 'nomic-embed-text': 768,
15
+ 'all-minilm': 384,
16
+ 'mxbai-embed-large': 1024,
17
+ 'snowflake-arctic-embed': 1024,
18
+ };
19
+
20
+ export class OllamaEmbeddingProvider implements EmbeddingProvider {
21
+ readonly name = 'ollama';
22
+ readonly model: string;
23
+ readonly dimensions: number;
24
+ private readonly baseUrl: string;
25
+
26
+ constructor(baseUrl = 'http://localhost:11434', model = 'nomic-embed-text') {
27
+ this.baseUrl = baseUrl.replace(/\/$/, '');
28
+ this.model = model;
29
+ this.dimensions = MODEL_DIMENSIONS[model] ?? 768;
30
+ log.info({ model, dimensions: this.dimensions, baseUrl: this.baseUrl }, 'Ollama embedding provider initialized');
31
+ }
32
+
33
+ async embed(text: string): Promise<number[]> {
34
+ const response = await fetch(`${this.baseUrl}/api/embed`, {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/json' },
37
+ body: JSON.stringify({
38
+ model: this.model,
39
+ input: text,
40
+ }),
41
+ });
42
+
43
+ if (!response.ok) {
44
+ const error = await response.text();
45
+ throw new Error(`Ollama embedding error (${response.status}): ${error}`);
46
+ }
47
+
48
+ const data = (await response.json()) as OllamaEmbedResponse;
49
+ return data.embeddings[0];
50
+ }
51
+
52
+ async embedBatch(texts: string[]): Promise<number[][]> {
53
+ log.debug({ count: texts.length }, 'Embedding batch');
54
+
55
+ // Ollama's /api/embed supports batch input
56
+ const response = await fetch(`${this.baseUrl}/api/embed`, {
57
+ method: 'POST',
58
+ headers: { 'Content-Type': 'application/json' },
59
+ body: JSON.stringify({
60
+ model: this.model,
61
+ input: texts,
62
+ }),
63
+ });
64
+
65
+ if (!response.ok) {
66
+ const error = await response.text();
67
+ throw new Error(`Ollama embedding error (${response.status}): ${error}`);
68
+ }
69
+
70
+ const data = (await response.json()) as OllamaEmbedResponse;
71
+ return data.embeddings;
72
+ }
73
+ }
74
+
75
+ interface OllamaEmbedResponse {
76
+ model: string;
77
+ embeddings: number[][];
78
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * OpenAI Embedding Provider
3
+ *
4
+ * Uses OpenAI's text-embedding API for generating embeddings.
5
+ * Supports batching for efficiency.
6
+ */
7
+
8
+ import type { EmbeddingProvider } from '../types.js';
9
+ import { createChildLogger } from '../logger.js';
10
+
11
+ const log = createChildLogger('openai-embedding');
12
+
13
+ const MODEL_DIMENSIONS: Record<string, number> = {
14
+ 'text-embedding-3-small': 1536,
15
+ 'text-embedding-3-large': 3072,
16
+ 'text-embedding-ada-002': 1536,
17
+ };
18
+
19
+ export class OpenAIEmbeddingProvider implements EmbeddingProvider {
20
+ readonly name = 'openai';
21
+ readonly model: string;
22
+ readonly dimensions: number;
23
+ private readonly apiKey: string;
24
+ private readonly baseUrl = 'https://api.openai.com/v1';
25
+
26
+ constructor(apiKey: string, model = 'text-embedding-3-small') {
27
+ this.apiKey = apiKey;
28
+ this.model = model;
29
+ this.dimensions = MODEL_DIMENSIONS[model] ?? 1536;
30
+ log.info({ model, dimensions: this.dimensions }, 'OpenAI embedding provider initialized');
31
+ }
32
+
33
+ async embed(text: string): Promise<number[]> {
34
+ const results = await this.embedBatch([text]);
35
+ return results[0];
36
+ }
37
+
38
+ async embedBatch(texts: string[]): Promise<number[][]> {
39
+ log.debug({ count: texts.length }, 'Embedding batch');
40
+
41
+ let retries = 3;
42
+ let delay = 1000;
43
+
44
+ while (retries >= 0) {
45
+ try {
46
+ const response = await fetch(`${this.baseUrl}/embeddings`, {
47
+ method: 'POST',
48
+ headers: {
49
+ 'Content-Type': 'application/json',
50
+ Authorization: `Bearer ${this.apiKey}`,
51
+ },
52
+ body: JSON.stringify({
53
+ model: this.model,
54
+ input: texts,
55
+ }),
56
+ signal: AbortSignal.timeout(15000), // 15 second timeout to prevent indefinite hang
57
+ });
58
+
59
+ if (!response.ok) {
60
+ const error = await response.text();
61
+ if (response.status === 429 && retries > 0) {
62
+ log.warn(`Rate limited (429). Retrying in ${delay}ms...`);
63
+ await new Promise((r) => setTimeout(r, delay));
64
+ retries--;
65
+ delay *= 2;
66
+ continue;
67
+ }
68
+ throw new Error(`OpenAI embedding error (${response.status}): ${error}`);
69
+ }
70
+
71
+ const data = (await response.json()) as OpenAIEmbeddingResponse;
72
+ return data.data
73
+ .sort((a, b) => a.index - b.index)
74
+ .map((item) => item.embedding);
75
+ } catch (err: any) {
76
+ if ((err.name === 'TimeoutError' || err.message.includes('fetch')) && retries > 0) {
77
+ log.warn(`Fetch timeout or network error. Retrying in ${delay}ms...`);
78
+ await new Promise((r) => setTimeout(r, delay));
79
+ retries--;
80
+ delay *= 2;
81
+ continue;
82
+ }
83
+ throw err;
84
+ }
85
+ }
86
+ throw new Error('Failed to embed batch after multiple retries');
87
+ }
88
+ }
89
+
90
+ interface OpenAIEmbeddingResponse {
91
+ data: Array<{
92
+ embedding: number[];
93
+ index: number;
94
+ }>;
95
+ usage: {
96
+ prompt_tokens: number;
97
+ total_tokens: number;
98
+ };
99
+ }