@utaba/deep-memory-embeddings-openai 0.8.1 → 0.9.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.
- package/dist/index.cjs +53 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +54 -20
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -26,12 +26,14 @@ module.exports = __toCommonJS(index_exports);
|
|
|
26
26
|
|
|
27
27
|
// src/OpenAIEmbeddingProvider.ts
|
|
28
28
|
var import_deep_memory = require("@utaba/deep-memory");
|
|
29
|
+
var PROVIDER_NAME = "openai";
|
|
29
30
|
var OpenAIEmbeddingProvider = class {
|
|
30
31
|
_baseUrl;
|
|
31
32
|
_model;
|
|
32
33
|
_apiKey;
|
|
33
34
|
_timeoutMs;
|
|
34
35
|
_maxBatchSize;
|
|
36
|
+
_reportUsage;
|
|
35
37
|
_dimensions;
|
|
36
38
|
constructor(config) {
|
|
37
39
|
this._baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
@@ -40,34 +42,66 @@ var OpenAIEmbeddingProvider = class {
|
|
|
40
42
|
this._timeoutMs = config.timeoutMs ?? 3e4;
|
|
41
43
|
this._maxBatchSize = config.maxBatchSize ?? 64;
|
|
42
44
|
this._dimensions = config.dimensions;
|
|
45
|
+
this._reportUsage = (0, import_deep_memory.createSafeSink)(config.reportUsage);
|
|
43
46
|
}
|
|
44
47
|
async embed(text) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
let totalPromptTokens = 0;
|
|
49
|
+
let totalTokens = 0;
|
|
50
|
+
let calls = 0;
|
|
51
|
+
try {
|
|
52
|
+
const response = await this._request([text]);
|
|
53
|
+
totalPromptTokens += response.usage?.prompt_tokens ?? 0;
|
|
54
|
+
totalTokens += response.usage?.total_tokens ?? 0;
|
|
55
|
+
calls++;
|
|
56
|
+
const vector = response.data[0]?.embedding;
|
|
57
|
+
if (!vector) {
|
|
58
|
+
throw new import_deep_memory.ProviderError(
|
|
59
|
+
"OpenAI embeddings API returned no data",
|
|
60
|
+
"Check that the model is loaded and the endpoint is responding correctly."
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
this._resolveDimensions(vector);
|
|
64
|
+
return vector;
|
|
65
|
+
} finally {
|
|
66
|
+
this._emitUsage("embed", totalPromptTokens, totalTokens, calls);
|
|
52
67
|
}
|
|
53
|
-
this._resolveDimensions(vector);
|
|
54
|
-
return vector;
|
|
55
68
|
}
|
|
56
69
|
async embedBatch(texts) {
|
|
57
70
|
if (texts.length === 0) return [];
|
|
58
71
|
const results = [];
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
for (
|
|
64
|
-
|
|
72
|
+
let totalPromptTokens = 0;
|
|
73
|
+
let totalTokens = 0;
|
|
74
|
+
let calls = 0;
|
|
75
|
+
try {
|
|
76
|
+
for (let i = 0; i < texts.length; i += this._maxBatchSize) {
|
|
77
|
+
const chunk = texts.slice(i, i + this._maxBatchSize);
|
|
78
|
+
const response = await this._request(chunk);
|
|
79
|
+
totalPromptTokens += response.usage?.prompt_tokens ?? 0;
|
|
80
|
+
totalTokens += response.usage?.total_tokens ?? 0;
|
|
81
|
+
calls++;
|
|
82
|
+
const sorted = response.data.sort((a, b) => a.index - b.index);
|
|
83
|
+
for (const item of sorted) {
|
|
84
|
+
results.push(item.embedding);
|
|
85
|
+
}
|
|
65
86
|
}
|
|
87
|
+
if (results.length > 0 && results[0]) {
|
|
88
|
+
this._resolveDimensions(results[0]);
|
|
89
|
+
}
|
|
90
|
+
return results;
|
|
91
|
+
} finally {
|
|
92
|
+
this._emitUsage("embedBatch", totalPromptTokens, totalTokens, calls);
|
|
66
93
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
94
|
+
}
|
|
95
|
+
_emitUsage(operation, promptTokens, totalTokens, calls) {
|
|
96
|
+
if (!this._reportUsage || calls === 0) return;
|
|
97
|
+
this._reportUsage({
|
|
98
|
+
provider: PROVIDER_NAME,
|
|
99
|
+
operation,
|
|
100
|
+
unit: "tokens",
|
|
101
|
+
value: totalTokens,
|
|
102
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
103
|
+
details: { promptTokens, totalTokens, calls }
|
|
104
|
+
});
|
|
71
105
|
}
|
|
72
106
|
dimensions() {
|
|
73
107
|
if (this._dimensions === void 0) {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/OpenAIEmbeddingProvider.ts"],"sourcesContent":["// @utaba/deep-memory-embeddings-openai — OpenAI-compatible EmbeddingProvider\n\nexport {\n OpenAIEmbeddingProvider,\n type OpenAIEmbeddingProviderConfig,\n} from './OpenAIEmbeddingProvider.js';\n","// OpenAI-compatible EmbeddingProvider — works with vLLM, OpenAI, Azure, Ollama, etc.\n\nimport type { EmbeddingProvider } from '@utaba/deep-memory/providers';\nimport { ProviderError } from '@utaba/deep-memory';\n\n/** Configuration for the OpenAI-compatible embedding provider */\nexport interface OpenAIEmbeddingProviderConfig {\n /** Base URL of the embeddings API (e.g. \"http://localhost:8010\") */\n baseUrl: string;\n\n /** Model identifier sent in requests (e.g. \"Qwen/Qwen3-Embedding-8B\") */\n model: string;\n\n /** Dimensionality of the embedding vectors. Auto-detected on first call if omitted. */\n dimensions?: number;\n\n /** API key for authenticated endpoints. Optional for local servers. */\n apiKey?: string;\n\n /** Request timeout in milliseconds. Default: 30000 */\n timeoutMs?: number;\n\n /** Maximum texts per batch request. Default: 64 */\n maxBatchSize?: number;\n}\n\n/** Response shape from the OpenAI /v1/embeddings endpoint */\ninterface EmbeddingsResponse {\n object: string;\n data: Array<{ object: string; index: number; embedding: number[] }>;\n model: string;\n usage: { prompt_tokens: number; total_tokens: number };\n}\n\nexport class OpenAIEmbeddingProvider implements EmbeddingProvider {\n private readonly _baseUrl: string;\n private readonly _model: string;\n private readonly _apiKey: string | undefined;\n private readonly _timeoutMs: number;\n private readonly _maxBatchSize: number;\n private _dimensions: number | undefined;\n\n constructor(config: OpenAIEmbeddingProviderConfig) {\n this._baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this._model = config.model;\n this._apiKey = config.apiKey;\n this._timeoutMs = config.timeoutMs ?? 30_000;\n this._maxBatchSize = config.maxBatchSize ?? 64;\n this._dimensions = config.dimensions;\n }\n\n async embed(text: string): Promise<number[]> {\n const response = await this._request([text]);\n const vector = response.data[0]?.embedding;\n if (!vector) {\n throw new ProviderError(\n 'OpenAI embeddings API returned no data',\n 'Check that the model is loaded and the endpoint is responding correctly.',\n );\n }\n this._resolveDimensions(vector);\n return vector;\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n\n const results: number[][] = [];\n\n // Chunk into maxBatchSize to avoid overloading the server\n for (let i = 0; i < texts.length; i += this._maxBatchSize) {\n const chunk = texts.slice(i, i + this._maxBatchSize);\n const response = await this._request(chunk);\n\n // Sort by index to guarantee order matches input\n const sorted = response.data.sort((a, b) => a.index - b.index);\n for (const item of sorted) {\n results.push(item.embedding);\n }\n }\n\n if (results.length > 0 && results[0]) {\n this._resolveDimensions(results[0]);\n }\n\n return results;\n }\n\n dimensions(): number {\n if (this._dimensions === undefined) {\n throw new ProviderError(\n 'Embedding dimensions not yet known. Call embed() first or provide dimensions in config.',\n 'Either set dimensions in OpenAIEmbeddingProviderConfig, or call embed() once before calling dimensions().',\n );\n }\n return this._dimensions;\n }\n\n modelId(): string {\n return this._model;\n }\n\n similarity(a: number[], b: number[]): number {\n return cosineSimilarity(a, b);\n }\n\n private async _request(input: string[]): Promise<EmbeddingsResponse> {\n const url = `${this._baseUrl}/v1/embeddings`;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this._apiKey) {\n headers['Authorization'] = `Bearer ${this._apiKey}`;\n }\n\n const requestBody: { input: string[]; model: string; dimensions?: number } = {\n input,\n model: this._model,\n };\n if (this._dimensions !== undefined) {\n requestBody.dimensions = this._dimensions;\n }\n const body = JSON.stringify(requestBody);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers,\n body,\n signal: AbortSignal.timeout(this._timeoutMs),\n });\n } catch (error: unknown) {\n if (error instanceof DOMException && error.name === 'TimeoutError') {\n throw new ProviderError(\n `Embedding request timed out after ${this._timeoutMs}ms`,\n `Increase timeoutMs in config, reduce batch size, or check server health at ${this._baseUrl}/health`,\n );\n }\n throw new ProviderError(\n `Failed to connect to embedding server: ${error instanceof Error ? error.message : String(error)}`,\n `Check that the server is running at ${this._baseUrl}`,\n );\n }\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => '');\n throw new ProviderError(\n `Embedding API returned ${response.status}: ${errorBody}`,\n response.status === 401\n ? 'Check your API key in OpenAIEmbeddingProviderConfig.'\n : `Check the embedding server logs. URL: ${url}`,\n );\n }\n\n return (await response.json()) as EmbeddingsResponse;\n }\n\n private _resolveDimensions(vector: number[]): void {\n if (this._dimensions === undefined) {\n this._dimensions = vector.length;\n }\n }\n}\n\n/** Cosine similarity between two vectors */\nfunction cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n const ai = a[i]!;\n const bi = b[i]!;\n dot += ai * bi;\n normA += ai * ai;\n normB += bi * bi;\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n return denom === 0 ? 0 : dot / denom;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/OpenAIEmbeddingProvider.ts"],"sourcesContent":["// @utaba/deep-memory-embeddings-openai — OpenAI-compatible EmbeddingProvider\n\nexport {\n OpenAIEmbeddingProvider,\n type OpenAIEmbeddingProviderConfig,\n} from './OpenAIEmbeddingProvider.js';\n","// OpenAI-compatible EmbeddingProvider — works with vLLM, OpenAI, Azure, Ollama, etc.\n\nimport type { EmbeddingProvider } from '@utaba/deep-memory/providers';\nimport type { UsageSink } from '@utaba/deep-memory/types';\nimport { ProviderError, createSafeSink } from '@utaba/deep-memory';\n\nconst PROVIDER_NAME = 'openai';\n\n/** Configuration for the OpenAI-compatible embedding provider */\nexport interface OpenAIEmbeddingProviderConfig {\n /** Base URL of the embeddings API (e.g. \"http://localhost:8010\") */\n baseUrl: string;\n\n /** Model identifier sent in requests (e.g. \"Qwen/Qwen3-Embedding-8B\") */\n model: string;\n\n /** Dimensionality of the embedding vectors. Auto-detected on first call if omitted. */\n dimensions?: number;\n\n /** API key for authenticated endpoints. Optional for local servers. */\n apiKey?: string;\n\n /** Request timeout in milliseconds. Default: 30000 */\n timeoutMs?: number;\n\n /** Maximum texts per batch request. Default: 64 */\n maxBatchSize?: number;\n\n /**\n * Optional usage sink. When provided, the provider emits one\n * {@link OperationUsage} record per `embed`/`embedBatch` call reporting\n * prompt tokens consumed. Multi-chunk `embedBatch` aggregates across\n * chunks into a single record.\n */\n reportUsage?: UsageSink;\n}\n\n/** Response shape from the OpenAI /v1/embeddings endpoint */\ninterface EmbeddingsResponse {\n object: string;\n data: Array<{ object: string; index: number; embedding: number[] }>;\n model: string;\n usage: { prompt_tokens: number; total_tokens: number };\n}\n\nexport class OpenAIEmbeddingProvider implements EmbeddingProvider {\n private readonly _baseUrl: string;\n private readonly _model: string;\n private readonly _apiKey: string | undefined;\n private readonly _timeoutMs: number;\n private readonly _maxBatchSize: number;\n private readonly _reportUsage: UsageSink | undefined;\n private _dimensions: number | undefined;\n\n constructor(config: OpenAIEmbeddingProviderConfig) {\n this._baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this._model = config.model;\n this._apiKey = config.apiKey;\n this._timeoutMs = config.timeoutMs ?? 30_000;\n this._maxBatchSize = config.maxBatchSize ?? 64;\n this._dimensions = config.dimensions;\n this._reportUsage = createSafeSink(config.reportUsage);\n }\n\n async embed(text: string): Promise<number[]> {\n let totalPromptTokens = 0;\n let totalTokens = 0;\n let calls = 0;\n try {\n const response = await this._request([text]);\n totalPromptTokens += response.usage?.prompt_tokens ?? 0;\n totalTokens += response.usage?.total_tokens ?? 0;\n calls++;\n const vector = response.data[0]?.embedding;\n if (!vector) {\n throw new ProviderError(\n 'OpenAI embeddings API returned no data',\n 'Check that the model is loaded and the endpoint is responding correctly.',\n );\n }\n this._resolveDimensions(vector);\n return vector;\n } finally {\n this._emitUsage('embed', totalPromptTokens, totalTokens, calls);\n }\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n\n const results: number[][] = [];\n let totalPromptTokens = 0;\n let totalTokens = 0;\n let calls = 0;\n\n try {\n // Chunk into maxBatchSize to avoid overloading the server\n for (let i = 0; i < texts.length; i += this._maxBatchSize) {\n const chunk = texts.slice(i, i + this._maxBatchSize);\n const response = await this._request(chunk);\n totalPromptTokens += response.usage?.prompt_tokens ?? 0;\n totalTokens += response.usage?.total_tokens ?? 0;\n calls++;\n\n // Sort by index to guarantee order matches input\n const sorted = response.data.sort((a, b) => a.index - b.index);\n for (const item of sorted) {\n results.push(item.embedding);\n }\n }\n\n if (results.length > 0 && results[0]) {\n this._resolveDimensions(results[0]);\n }\n\n return results;\n } finally {\n this._emitUsage('embedBatch', totalPromptTokens, totalTokens, calls);\n }\n }\n\n private _emitUsage(operation: string, promptTokens: number, totalTokens: number, calls: number): void {\n if (!this._reportUsage || calls === 0) return;\n this._reportUsage({\n provider: PROVIDER_NAME,\n operation,\n unit: 'tokens',\n value: totalTokens,\n timestamp: new Date(),\n details: { promptTokens, totalTokens, calls },\n });\n }\n\n dimensions(): number {\n if (this._dimensions === undefined) {\n throw new ProviderError(\n 'Embedding dimensions not yet known. Call embed() first or provide dimensions in config.',\n 'Either set dimensions in OpenAIEmbeddingProviderConfig, or call embed() once before calling dimensions().',\n );\n }\n return this._dimensions;\n }\n\n modelId(): string {\n return this._model;\n }\n\n similarity(a: number[], b: number[]): number {\n return cosineSimilarity(a, b);\n }\n\n private async _request(input: string[]): Promise<EmbeddingsResponse> {\n const url = `${this._baseUrl}/v1/embeddings`;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this._apiKey) {\n headers['Authorization'] = `Bearer ${this._apiKey}`;\n }\n\n const requestBody: { input: string[]; model: string; dimensions?: number } = {\n input,\n model: this._model,\n };\n if (this._dimensions !== undefined) {\n requestBody.dimensions = this._dimensions;\n }\n const body = JSON.stringify(requestBody);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers,\n body,\n signal: AbortSignal.timeout(this._timeoutMs),\n });\n } catch (error: unknown) {\n if (error instanceof DOMException && error.name === 'TimeoutError') {\n throw new ProviderError(\n `Embedding request timed out after ${this._timeoutMs}ms`,\n `Increase timeoutMs in config, reduce batch size, or check server health at ${this._baseUrl}/health`,\n );\n }\n throw new ProviderError(\n `Failed to connect to embedding server: ${error instanceof Error ? error.message : String(error)}`,\n `Check that the server is running at ${this._baseUrl}`,\n );\n }\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => '');\n throw new ProviderError(\n `Embedding API returned ${response.status}: ${errorBody}`,\n response.status === 401\n ? 'Check your API key in OpenAIEmbeddingProviderConfig.'\n : `Check the embedding server logs. URL: ${url}`,\n );\n }\n\n return (await response.json()) as EmbeddingsResponse;\n }\n\n private _resolveDimensions(vector: number[]): void {\n if (this._dimensions === undefined) {\n this._dimensions = vector.length;\n }\n }\n}\n\n/** Cosine similarity between two vectors */\nfunction cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n const ai = a[i]!;\n const bi = b[i]!;\n dot += ai * bi;\n normA += ai * ai;\n normB += bi * bi;\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n return denom === 0 ? 0 : dot / denom;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EmbeddingProvider } from '@utaba/deep-memory/providers';
|
|
2
|
+
import { UsageSink } from '@utaba/deep-memory/types';
|
|
2
3
|
|
|
3
4
|
/** Configuration for the OpenAI-compatible embedding provider */
|
|
4
5
|
interface OpenAIEmbeddingProviderConfig {
|
|
@@ -14,6 +15,13 @@ interface OpenAIEmbeddingProviderConfig {
|
|
|
14
15
|
timeoutMs?: number;
|
|
15
16
|
/** Maximum texts per batch request. Default: 64 */
|
|
16
17
|
maxBatchSize?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Optional usage sink. When provided, the provider emits one
|
|
20
|
+
* {@link OperationUsage} record per `embed`/`embedBatch` call reporting
|
|
21
|
+
* prompt tokens consumed. Multi-chunk `embedBatch` aggregates across
|
|
22
|
+
* chunks into a single record.
|
|
23
|
+
*/
|
|
24
|
+
reportUsage?: UsageSink;
|
|
17
25
|
}
|
|
18
26
|
declare class OpenAIEmbeddingProvider implements EmbeddingProvider {
|
|
19
27
|
private readonly _baseUrl;
|
|
@@ -21,10 +29,12 @@ declare class OpenAIEmbeddingProvider implements EmbeddingProvider {
|
|
|
21
29
|
private readonly _apiKey;
|
|
22
30
|
private readonly _timeoutMs;
|
|
23
31
|
private readonly _maxBatchSize;
|
|
32
|
+
private readonly _reportUsage;
|
|
24
33
|
private _dimensions;
|
|
25
34
|
constructor(config: OpenAIEmbeddingProviderConfig);
|
|
26
35
|
embed(text: string): Promise<number[]>;
|
|
27
36
|
embedBatch(texts: string[]): Promise<number[][]>;
|
|
37
|
+
private _emitUsage;
|
|
28
38
|
dimensions(): number;
|
|
29
39
|
modelId(): string;
|
|
30
40
|
similarity(a: number[], b: number[]): number;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EmbeddingProvider } from '@utaba/deep-memory/providers';
|
|
2
|
+
import { UsageSink } from '@utaba/deep-memory/types';
|
|
2
3
|
|
|
3
4
|
/** Configuration for the OpenAI-compatible embedding provider */
|
|
4
5
|
interface OpenAIEmbeddingProviderConfig {
|
|
@@ -14,6 +15,13 @@ interface OpenAIEmbeddingProviderConfig {
|
|
|
14
15
|
timeoutMs?: number;
|
|
15
16
|
/** Maximum texts per batch request. Default: 64 */
|
|
16
17
|
maxBatchSize?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Optional usage sink. When provided, the provider emits one
|
|
20
|
+
* {@link OperationUsage} record per `embed`/`embedBatch` call reporting
|
|
21
|
+
* prompt tokens consumed. Multi-chunk `embedBatch` aggregates across
|
|
22
|
+
* chunks into a single record.
|
|
23
|
+
*/
|
|
24
|
+
reportUsage?: UsageSink;
|
|
17
25
|
}
|
|
18
26
|
declare class OpenAIEmbeddingProvider implements EmbeddingProvider {
|
|
19
27
|
private readonly _baseUrl;
|
|
@@ -21,10 +29,12 @@ declare class OpenAIEmbeddingProvider implements EmbeddingProvider {
|
|
|
21
29
|
private readonly _apiKey;
|
|
22
30
|
private readonly _timeoutMs;
|
|
23
31
|
private readonly _maxBatchSize;
|
|
32
|
+
private readonly _reportUsage;
|
|
24
33
|
private _dimensions;
|
|
25
34
|
constructor(config: OpenAIEmbeddingProviderConfig);
|
|
26
35
|
embed(text: string): Promise<number[]>;
|
|
27
36
|
embedBatch(texts: string[]): Promise<number[][]>;
|
|
37
|
+
private _emitUsage;
|
|
28
38
|
dimensions(): number;
|
|
29
39
|
modelId(): string;
|
|
30
40
|
similarity(a: number[], b: number[]): number;
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// src/OpenAIEmbeddingProvider.ts
|
|
2
|
-
import { ProviderError } from "@utaba/deep-memory";
|
|
2
|
+
import { ProviderError, createSafeSink } from "@utaba/deep-memory";
|
|
3
|
+
var PROVIDER_NAME = "openai";
|
|
3
4
|
var OpenAIEmbeddingProvider = class {
|
|
4
5
|
_baseUrl;
|
|
5
6
|
_model;
|
|
6
7
|
_apiKey;
|
|
7
8
|
_timeoutMs;
|
|
8
9
|
_maxBatchSize;
|
|
10
|
+
_reportUsage;
|
|
9
11
|
_dimensions;
|
|
10
12
|
constructor(config) {
|
|
11
13
|
this._baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
@@ -14,34 +16,66 @@ var OpenAIEmbeddingProvider = class {
|
|
|
14
16
|
this._timeoutMs = config.timeoutMs ?? 3e4;
|
|
15
17
|
this._maxBatchSize = config.maxBatchSize ?? 64;
|
|
16
18
|
this._dimensions = config.dimensions;
|
|
19
|
+
this._reportUsage = createSafeSink(config.reportUsage);
|
|
17
20
|
}
|
|
18
21
|
async embed(text) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
let totalPromptTokens = 0;
|
|
23
|
+
let totalTokens = 0;
|
|
24
|
+
let calls = 0;
|
|
25
|
+
try {
|
|
26
|
+
const response = await this._request([text]);
|
|
27
|
+
totalPromptTokens += response.usage?.prompt_tokens ?? 0;
|
|
28
|
+
totalTokens += response.usage?.total_tokens ?? 0;
|
|
29
|
+
calls++;
|
|
30
|
+
const vector = response.data[0]?.embedding;
|
|
31
|
+
if (!vector) {
|
|
32
|
+
throw new ProviderError(
|
|
33
|
+
"OpenAI embeddings API returned no data",
|
|
34
|
+
"Check that the model is loaded and the endpoint is responding correctly."
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
this._resolveDimensions(vector);
|
|
38
|
+
return vector;
|
|
39
|
+
} finally {
|
|
40
|
+
this._emitUsage("embed", totalPromptTokens, totalTokens, calls);
|
|
26
41
|
}
|
|
27
|
-
this._resolveDimensions(vector);
|
|
28
|
-
return vector;
|
|
29
42
|
}
|
|
30
43
|
async embedBatch(texts) {
|
|
31
44
|
if (texts.length === 0) return [];
|
|
32
45
|
const results = [];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
for (
|
|
38
|
-
|
|
46
|
+
let totalPromptTokens = 0;
|
|
47
|
+
let totalTokens = 0;
|
|
48
|
+
let calls = 0;
|
|
49
|
+
try {
|
|
50
|
+
for (let i = 0; i < texts.length; i += this._maxBatchSize) {
|
|
51
|
+
const chunk = texts.slice(i, i + this._maxBatchSize);
|
|
52
|
+
const response = await this._request(chunk);
|
|
53
|
+
totalPromptTokens += response.usage?.prompt_tokens ?? 0;
|
|
54
|
+
totalTokens += response.usage?.total_tokens ?? 0;
|
|
55
|
+
calls++;
|
|
56
|
+
const sorted = response.data.sort((a, b) => a.index - b.index);
|
|
57
|
+
for (const item of sorted) {
|
|
58
|
+
results.push(item.embedding);
|
|
59
|
+
}
|
|
39
60
|
}
|
|
61
|
+
if (results.length > 0 && results[0]) {
|
|
62
|
+
this._resolveDimensions(results[0]);
|
|
63
|
+
}
|
|
64
|
+
return results;
|
|
65
|
+
} finally {
|
|
66
|
+
this._emitUsage("embedBatch", totalPromptTokens, totalTokens, calls);
|
|
40
67
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
68
|
+
}
|
|
69
|
+
_emitUsage(operation, promptTokens, totalTokens, calls) {
|
|
70
|
+
if (!this._reportUsage || calls === 0) return;
|
|
71
|
+
this._reportUsage({
|
|
72
|
+
provider: PROVIDER_NAME,
|
|
73
|
+
operation,
|
|
74
|
+
unit: "tokens",
|
|
75
|
+
value: totalTokens,
|
|
76
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
77
|
+
details: { promptTokens, totalTokens, calls }
|
|
78
|
+
});
|
|
45
79
|
}
|
|
46
80
|
dimensions() {
|
|
47
81
|
if (this._dimensions === void 0) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/OpenAIEmbeddingProvider.ts"],"sourcesContent":["// OpenAI-compatible EmbeddingProvider — works with vLLM, OpenAI, Azure, Ollama, etc.\n\nimport type { EmbeddingProvider } from '@utaba/deep-memory/providers';\nimport { ProviderError } from '@utaba/deep-memory';\n\n/** Configuration for the OpenAI-compatible embedding provider */\nexport interface OpenAIEmbeddingProviderConfig {\n /** Base URL of the embeddings API (e.g. \"http://localhost:8010\") */\n baseUrl: string;\n\n /** Model identifier sent in requests (e.g. \"Qwen/Qwen3-Embedding-8B\") */\n model: string;\n\n /** Dimensionality of the embedding vectors. Auto-detected on first call if omitted. */\n dimensions?: number;\n\n /** API key for authenticated endpoints. Optional for local servers. */\n apiKey?: string;\n\n /** Request timeout in milliseconds. Default: 30000 */\n timeoutMs?: number;\n\n /** Maximum texts per batch request. Default: 64 */\n maxBatchSize?: number;\n}\n\n/** Response shape from the OpenAI /v1/embeddings endpoint */\ninterface EmbeddingsResponse {\n object: string;\n data: Array<{ object: string; index: number; embedding: number[] }>;\n model: string;\n usage: { prompt_tokens: number; total_tokens: number };\n}\n\nexport class OpenAIEmbeddingProvider implements EmbeddingProvider {\n private readonly _baseUrl: string;\n private readonly _model: string;\n private readonly _apiKey: string | undefined;\n private readonly _timeoutMs: number;\n private readonly _maxBatchSize: number;\n private _dimensions: number | undefined;\n\n constructor(config: OpenAIEmbeddingProviderConfig) {\n this._baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this._model = config.model;\n this._apiKey = config.apiKey;\n this._timeoutMs = config.timeoutMs ?? 30_000;\n this._maxBatchSize = config.maxBatchSize ?? 64;\n this._dimensions = config.dimensions;\n }\n\n async embed(text: string): Promise<number[]> {\n const response = await this._request([text]);\n const vector = response.data[0]?.embedding;\n if (!vector) {\n throw new ProviderError(\n 'OpenAI embeddings API returned no data',\n 'Check that the model is loaded and the endpoint is responding correctly.',\n );\n }\n this._resolveDimensions(vector);\n return vector;\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n\n const results: number[][] = [];\n\n // Chunk into maxBatchSize to avoid overloading the server\n for (let i = 0; i < texts.length; i += this._maxBatchSize) {\n const chunk = texts.slice(i, i + this._maxBatchSize);\n const response = await this._request(chunk);\n\n // Sort by index to guarantee order matches input\n const sorted = response.data.sort((a, b) => a.index - b.index);\n for (const item of sorted) {\n results.push(item.embedding);\n }\n }\n\n if (results.length > 0 && results[0]) {\n this._resolveDimensions(results[0]);\n }\n\n return results;\n }\n\n dimensions(): number {\n if (this._dimensions === undefined) {\n throw new ProviderError(\n 'Embedding dimensions not yet known. Call embed() first or provide dimensions in config.',\n 'Either set dimensions in OpenAIEmbeddingProviderConfig, or call embed() once before calling dimensions().',\n );\n }\n return this._dimensions;\n }\n\n modelId(): string {\n return this._model;\n }\n\n similarity(a: number[], b: number[]): number {\n return cosineSimilarity(a, b);\n }\n\n private async _request(input: string[]): Promise<EmbeddingsResponse> {\n const url = `${this._baseUrl}/v1/embeddings`;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this._apiKey) {\n headers['Authorization'] = `Bearer ${this._apiKey}`;\n }\n\n const requestBody: { input: string[]; model: string; dimensions?: number } = {\n input,\n model: this._model,\n };\n if (this._dimensions !== undefined) {\n requestBody.dimensions = this._dimensions;\n }\n const body = JSON.stringify(requestBody);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers,\n body,\n signal: AbortSignal.timeout(this._timeoutMs),\n });\n } catch (error: unknown) {\n if (error instanceof DOMException && error.name === 'TimeoutError') {\n throw new ProviderError(\n `Embedding request timed out after ${this._timeoutMs}ms`,\n `Increase timeoutMs in config, reduce batch size, or check server health at ${this._baseUrl}/health`,\n );\n }\n throw new ProviderError(\n `Failed to connect to embedding server: ${error instanceof Error ? error.message : String(error)}`,\n `Check that the server is running at ${this._baseUrl}`,\n );\n }\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => '');\n throw new ProviderError(\n `Embedding API returned ${response.status}: ${errorBody}`,\n response.status === 401\n ? 'Check your API key in OpenAIEmbeddingProviderConfig.'\n : `Check the embedding server logs. URL: ${url}`,\n );\n }\n\n return (await response.json()) as EmbeddingsResponse;\n }\n\n private _resolveDimensions(vector: number[]): void {\n if (this._dimensions === undefined) {\n this._dimensions = vector.length;\n }\n }\n}\n\n/** Cosine similarity between two vectors */\nfunction cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n const ai = a[i]!;\n const bi = b[i]!;\n dot += ai * bi;\n normA += ai * ai;\n normB += bi * bi;\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n return denom === 0 ? 0 : dot / denom;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/OpenAIEmbeddingProvider.ts"],"sourcesContent":["// OpenAI-compatible EmbeddingProvider — works with vLLM, OpenAI, Azure, Ollama, etc.\n\nimport type { EmbeddingProvider } from '@utaba/deep-memory/providers';\nimport type { UsageSink } from '@utaba/deep-memory/types';\nimport { ProviderError, createSafeSink } from '@utaba/deep-memory';\n\nconst PROVIDER_NAME = 'openai';\n\n/** Configuration for the OpenAI-compatible embedding provider */\nexport interface OpenAIEmbeddingProviderConfig {\n /** Base URL of the embeddings API (e.g. \"http://localhost:8010\") */\n baseUrl: string;\n\n /** Model identifier sent in requests (e.g. \"Qwen/Qwen3-Embedding-8B\") */\n model: string;\n\n /** Dimensionality of the embedding vectors. Auto-detected on first call if omitted. */\n dimensions?: number;\n\n /** API key for authenticated endpoints. Optional for local servers. */\n apiKey?: string;\n\n /** Request timeout in milliseconds. Default: 30000 */\n timeoutMs?: number;\n\n /** Maximum texts per batch request. Default: 64 */\n maxBatchSize?: number;\n\n /**\n * Optional usage sink. When provided, the provider emits one\n * {@link OperationUsage} record per `embed`/`embedBatch` call reporting\n * prompt tokens consumed. Multi-chunk `embedBatch` aggregates across\n * chunks into a single record.\n */\n reportUsage?: UsageSink;\n}\n\n/** Response shape from the OpenAI /v1/embeddings endpoint */\ninterface EmbeddingsResponse {\n object: string;\n data: Array<{ object: string; index: number; embedding: number[] }>;\n model: string;\n usage: { prompt_tokens: number; total_tokens: number };\n}\n\nexport class OpenAIEmbeddingProvider implements EmbeddingProvider {\n private readonly _baseUrl: string;\n private readonly _model: string;\n private readonly _apiKey: string | undefined;\n private readonly _timeoutMs: number;\n private readonly _maxBatchSize: number;\n private readonly _reportUsage: UsageSink | undefined;\n private _dimensions: number | undefined;\n\n constructor(config: OpenAIEmbeddingProviderConfig) {\n this._baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this._model = config.model;\n this._apiKey = config.apiKey;\n this._timeoutMs = config.timeoutMs ?? 30_000;\n this._maxBatchSize = config.maxBatchSize ?? 64;\n this._dimensions = config.dimensions;\n this._reportUsage = createSafeSink(config.reportUsage);\n }\n\n async embed(text: string): Promise<number[]> {\n let totalPromptTokens = 0;\n let totalTokens = 0;\n let calls = 0;\n try {\n const response = await this._request([text]);\n totalPromptTokens += response.usage?.prompt_tokens ?? 0;\n totalTokens += response.usage?.total_tokens ?? 0;\n calls++;\n const vector = response.data[0]?.embedding;\n if (!vector) {\n throw new ProviderError(\n 'OpenAI embeddings API returned no data',\n 'Check that the model is loaded and the endpoint is responding correctly.',\n );\n }\n this._resolveDimensions(vector);\n return vector;\n } finally {\n this._emitUsage('embed', totalPromptTokens, totalTokens, calls);\n }\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n\n const results: number[][] = [];\n let totalPromptTokens = 0;\n let totalTokens = 0;\n let calls = 0;\n\n try {\n // Chunk into maxBatchSize to avoid overloading the server\n for (let i = 0; i < texts.length; i += this._maxBatchSize) {\n const chunk = texts.slice(i, i + this._maxBatchSize);\n const response = await this._request(chunk);\n totalPromptTokens += response.usage?.prompt_tokens ?? 0;\n totalTokens += response.usage?.total_tokens ?? 0;\n calls++;\n\n // Sort by index to guarantee order matches input\n const sorted = response.data.sort((a, b) => a.index - b.index);\n for (const item of sorted) {\n results.push(item.embedding);\n }\n }\n\n if (results.length > 0 && results[0]) {\n this._resolveDimensions(results[0]);\n }\n\n return results;\n } finally {\n this._emitUsage('embedBatch', totalPromptTokens, totalTokens, calls);\n }\n }\n\n private _emitUsage(operation: string, promptTokens: number, totalTokens: number, calls: number): void {\n if (!this._reportUsage || calls === 0) return;\n this._reportUsage({\n provider: PROVIDER_NAME,\n operation,\n unit: 'tokens',\n value: totalTokens,\n timestamp: new Date(),\n details: { promptTokens, totalTokens, calls },\n });\n }\n\n dimensions(): number {\n if (this._dimensions === undefined) {\n throw new ProviderError(\n 'Embedding dimensions not yet known. Call embed() first or provide dimensions in config.',\n 'Either set dimensions in OpenAIEmbeddingProviderConfig, or call embed() once before calling dimensions().',\n );\n }\n return this._dimensions;\n }\n\n modelId(): string {\n return this._model;\n }\n\n similarity(a: number[], b: number[]): number {\n return cosineSimilarity(a, b);\n }\n\n private async _request(input: string[]): Promise<EmbeddingsResponse> {\n const url = `${this._baseUrl}/v1/embeddings`;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this._apiKey) {\n headers['Authorization'] = `Bearer ${this._apiKey}`;\n }\n\n const requestBody: { input: string[]; model: string; dimensions?: number } = {\n input,\n model: this._model,\n };\n if (this._dimensions !== undefined) {\n requestBody.dimensions = this._dimensions;\n }\n const body = JSON.stringify(requestBody);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers,\n body,\n signal: AbortSignal.timeout(this._timeoutMs),\n });\n } catch (error: unknown) {\n if (error instanceof DOMException && error.name === 'TimeoutError') {\n throw new ProviderError(\n `Embedding request timed out after ${this._timeoutMs}ms`,\n `Increase timeoutMs in config, reduce batch size, or check server health at ${this._baseUrl}/health`,\n );\n }\n throw new ProviderError(\n `Failed to connect to embedding server: ${error instanceof Error ? error.message : String(error)}`,\n `Check that the server is running at ${this._baseUrl}`,\n );\n }\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => '');\n throw new ProviderError(\n `Embedding API returned ${response.status}: ${errorBody}`,\n response.status === 401\n ? 'Check your API key in OpenAIEmbeddingProviderConfig.'\n : `Check the embedding server logs. URL: ${url}`,\n );\n }\n\n return (await response.json()) as EmbeddingsResponse;\n }\n\n private _resolveDimensions(vector: number[]): void {\n if (this._dimensions === undefined) {\n this._dimensions = vector.length;\n }\n }\n}\n\n/** Cosine similarity between two vectors */\nfunction cosineSimilarity(a: number[], b: number[]): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n const ai = a[i]!;\n const bi = b[i]!;\n dot += ai * bi;\n normA += ai * ai;\n normB += bi * bi;\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n return denom === 0 ? 0 : dot / denom;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utaba/deep-memory-embeddings-openai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "OpenAI-compatible embeddings provider for @utaba/deep-memory",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"agents"
|
|
53
53
|
],
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@utaba/deep-memory": "0.
|
|
55
|
+
"@utaba/deep-memory": "0.9.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/node": "^22.0.0",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"vitest": "^4.1.2"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@utaba/deep-memory": "^0.
|
|
64
|
+
"@utaba/deep-memory": "^0.9.0"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
67
|
"build": "tsup",
|