@utaba/deep-memory-embeddings-openai 0.20.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,14 +34,23 @@ var OpenAIEmbeddingProvider = class {
34
34
  _timeoutMs;
35
35
  _maxBatchSize;
36
36
  _reportUsage;
37
- _dimensions;
37
+ /**
38
+ * Caller-requested output size. The ONLY value ever sent on the wire as the
39
+ * `dimensions` request param, and only when the caller opted into truncation.
40
+ */
41
+ _requestedDimensions;
42
+ /**
43
+ * Native dimension learned from the first response. Powers dimensions() and
44
+ * length-consistency validation. Never sent on a request.
45
+ */
46
+ _observedDimensions;
38
47
  constructor(config) {
39
48
  this._baseUrl = config.baseUrl.replace(/\/+$/, "");
40
49
  this._model = config.model;
41
50
  this._apiKey = config.apiKey;
42
51
  this._timeoutMs = config.timeoutMs ?? 3e4;
43
52
  this._maxBatchSize = config.maxBatchSize ?? 64;
44
- this._dimensions = config.dimensions;
53
+ this._requestedDimensions = config.dimensions;
45
54
  this._reportUsage = (0, import_deep_memory.createSafeSink)(config.reportUsage);
46
55
  }
47
56
  async embed(text) {
@@ -104,13 +113,14 @@ var OpenAIEmbeddingProvider = class {
104
113
  });
105
114
  }
106
115
  dimensions() {
107
- if (this._dimensions === void 0) {
116
+ const known = this._requestedDimensions ?? this._observedDimensions;
117
+ if (known === void 0) {
108
118
  throw new import_deep_memory.ProviderError(
109
119
  "Embedding dimensions not yet known. Call embed() first or provide dimensions in config.",
110
120
  "Either set dimensions in OpenAIEmbeddingProviderConfig, or call embed() once before calling dimensions()."
111
121
  );
112
122
  }
113
- return this._dimensions;
123
+ return known;
114
124
  }
115
125
  modelId() {
116
126
  return this._model;
@@ -130,8 +140,8 @@ var OpenAIEmbeddingProvider = class {
130
140
  input,
131
141
  model: this._model
132
142
  };
133
- if (this._dimensions !== void 0) {
134
- requestBody.dimensions = this._dimensions;
143
+ if (this._requestedDimensions !== void 0) {
144
+ requestBody.dimensions = this._requestedDimensions;
135
145
  }
136
146
  const body = JSON.stringify(requestBody);
137
147
  let response;
@@ -164,8 +174,15 @@ var OpenAIEmbeddingProvider = class {
164
174
  return await response.json();
165
175
  }
166
176
  _resolveDimensions(vector) {
167
- if (this._dimensions === void 0) {
168
- this._dimensions = vector.length;
177
+ const expected = this._requestedDimensions ?? this._observedDimensions;
178
+ if (expected !== void 0 && vector.length !== expected) {
179
+ throw new import_deep_memory.ProviderError(
180
+ `Embedding server returned a ${vector.length}-dimension vector but ${expected} was expected`,
181
+ this._requestedDimensions !== void 0 ? `The server ignored the requested dimensions (${this._requestedDimensions}). Remove dimensions from config, or use a model that supports output truncation.` : "The server returned inconsistent vector sizes across calls. Check that the model and endpoint are stable."
182
+ );
183
+ }
184
+ if (this._observedDimensions === void 0) {
185
+ this._observedDimensions = vector.length;
169
186
  }
170
187
  }
171
188
  };
@@ -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 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":[]}
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 /**\n * Requested output dimensionality. When set, this is sent as the OpenAI\n * `dimensions` request parameter — a deliberate opt-in to matryoshka output\n * truncation. Leave undefined (the default) to let the server return its\n * native dimension; the native size is then detected from the first response.\n *\n * Only set this when you actually want the server to shorten its output.\n * Many OpenAI-compatible servers (e.g. vLLM without matryoshka support)\n * reject the `dimensions` param outright, so it must never be sent unless\n * the caller has opted in.\n */\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\n /**\n * Caller-requested output size. The ONLY value ever sent on the wire as the\n * `dimensions` request param, and only when the caller opted into truncation.\n */\n private readonly _requestedDimensions: number | undefined;\n\n /**\n * Native dimension learned from the first response. Powers dimensions() and\n * length-consistency validation. Never sent on a request.\n */\n private _observedDimensions: 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._requestedDimensions = 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 const known = this._requestedDimensions ?? this._observedDimensions;\n if (known === 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 known;\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 // Send `dimensions` only when the caller deliberately requested truncation.\n // The native size learned from responses is never echoed back — that is a\n // no-op that strict servers (e.g. vLLM without matryoshka) reject outright.\n if (this._requestedDimensions !== undefined) {\n requestBody.dimensions = this._requestedDimensions;\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 const expected = this._requestedDimensions ?? this._observedDimensions;\n if (expected !== undefined && vector.length !== expected) {\n throw new ProviderError(\n `Embedding server returned a ${vector.length}-dimension vector but ${expected} was expected`,\n this._requestedDimensions !== undefined\n ? `The server ignored the requested dimensions (${this._requestedDimensions}). Remove dimensions from config, or use a model that supports output truncation.`\n : 'The server returned inconsistent vector sizes across calls. Check that the model and endpoint are stable.',\n );\n }\n if (this._observedDimensions === undefined) {\n this._observedDimensions = 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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACIA,yBAA8C;AAE9C,IAAM,gBAAgB;AAiDf,IAAM,0BAAN,MAA2D;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMT;AAAA,EAER,YAAY,QAAuC;AACjD,SAAK,WAAW,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AACjD,SAAK,SAAS,OAAO;AACrB,SAAK,UAAU,OAAO;AACtB,SAAK,aAAa,OAAO,aAAa;AACtC,SAAK,gBAAgB,OAAO,gBAAgB;AAC5C,SAAK,uBAAuB,OAAO;AACnC,SAAK,mBAAe,mCAAe,OAAO,WAAW;AAAA,EACvD;AAAA,EAEA,MAAM,MAAM,MAAiC;AAC3C,QAAI,oBAAoB;AACxB,QAAI,cAAc;AAClB,QAAI,QAAQ;AACZ,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,SAAS,CAAC,IAAI,CAAC;AAC3C,2BAAqB,SAAS,OAAO,iBAAiB;AACtD,qBAAe,SAAS,OAAO,gBAAgB;AAC/C;AACA,YAAM,SAAS,SAAS,KAAK,CAAC,GAAG;AACjC,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,WAAK,mBAAmB,MAAM;AAC9B,aAAO;AAAA,IACT,UAAE;AACA,WAAK,WAAW,SAAS,mBAAmB,aAAa,KAAK;AAAA,IAChE;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,OAAsC;AACrD,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,UAAM,UAAsB,CAAC;AAC7B,QAAI,oBAAoB;AACxB,QAAI,cAAc;AAClB,QAAI,QAAQ;AAEZ,QAAI;AAEF,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,KAAK,eAAe;AACzD,cAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,KAAK,aAAa;AACnD,cAAM,WAAW,MAAM,KAAK,SAAS,KAAK;AAC1C,6BAAqB,SAAS,OAAO,iBAAiB;AACtD,uBAAe,SAAS,OAAO,gBAAgB;AAC/C;AAGA,cAAM,SAAS,SAAS,KAAK,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAC7D,mBAAW,QAAQ,QAAQ;AACzB,kBAAQ,KAAK,KAAK,SAAS;AAAA,QAC7B;AAAA,MACF;AAEA,UAAI,QAAQ,SAAS,KAAK,QAAQ,CAAC,GAAG;AACpC,aAAK,mBAAmB,QAAQ,CAAC,CAAC;AAAA,MACpC;AAEA,aAAO;AAAA,IACT,UAAE;AACA,WAAK,WAAW,cAAc,mBAAmB,aAAa,KAAK;AAAA,IACrE;AAAA,EACF;AAAA,EAEQ,WAAW,WAAmB,cAAsB,aAAqB,OAAqB;AACpG,QAAI,CAAC,KAAK,gBAAgB,UAAU,EAAG;AACvC,SAAK,aAAa;AAAA,MAChB,UAAU;AAAA,MACV;AAAA,MACA,MAAM;AAAA,MACN,OAAO;AAAA,MACP,WAAW,oBAAI,KAAK;AAAA,MACpB,SAAS,EAAE,cAAc,aAAa,MAAM;AAAA,IAC9C,CAAC;AAAA,EACH;AAAA,EAEA,aAAqB;AACnB,UAAM,QAAQ,KAAK,wBAAwB,KAAK;AAChD,QAAI,UAAU,QAAW;AACvB,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,UAAkB;AAChB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,WAAW,GAAa,GAAqB;AAC3C,WAAO,iBAAiB,GAAG,CAAC;AAAA,EAC9B;AAAA,EAEA,MAAc,SAAS,OAA8C;AACnE,UAAM,MAAM,GAAG,KAAK,QAAQ;AAE5B,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,IAClB;AACA,QAAI,KAAK,SAAS;AAChB,cAAQ,eAAe,IAAI,UAAU,KAAK,OAAO;AAAA,IACnD;AAEA,UAAM,cAAuE;AAAA,MAC3E;AAAA,MACA,OAAO,KAAK;AAAA,IACd;AAIA,QAAI,KAAK,yBAAyB,QAAW;AAC3C,kBAAY,aAAa,KAAK;AAAA,IAChC;AACA,UAAM,OAAO,KAAK,UAAU,WAAW;AAEvC,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,KAAK;AAAA,QAC1B,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,QACA,QAAQ,YAAY,QAAQ,KAAK,UAAU;AAAA,MAC7C,CAAC;AAAA,IACH,SAAS,OAAgB;AACvB,UAAI,iBAAiB,gBAAgB,MAAM,SAAS,gBAAgB;AAClE,cAAM,IAAI;AAAA,UACR,qCAAqC,KAAK,UAAU;AAAA,UACpD,8EAA8E,KAAK,QAAQ;AAAA,QAC7F;AAAA,MACF;AACA,YAAM,IAAI;AAAA,QACR,0CAA0C,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QAChG,uCAAuC,KAAK,QAAQ;AAAA,MACtD;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACtD,YAAM,IAAI;AAAA,QACR,0BAA0B,SAAS,MAAM,KAAK,SAAS;AAAA,QACvD,SAAS,WAAW,MAChB,yDACA,yCAAyC,GAAG;AAAA,MAClD;AAAA,IACF;AAEA,WAAQ,MAAM,SAAS,KAAK;AAAA,EAC9B;AAAA,EAEQ,mBAAmB,QAAwB;AACjD,UAAM,WAAW,KAAK,wBAAwB,KAAK;AACnD,QAAI,aAAa,UAAa,OAAO,WAAW,UAAU;AACxD,YAAM,IAAI;AAAA,QACR,+BAA+B,OAAO,MAAM,yBAAyB,QAAQ;AAAA,QAC7E,KAAK,yBAAyB,SAC1B,gDAAgD,KAAK,oBAAoB,sFACzE;AAAA,MACN;AAAA,IACF;AACA,QAAI,KAAK,wBAAwB,QAAW;AAC1C,WAAK,sBAAsB,OAAO;AAAA,IACpC;AAAA,EACF;AACF;AAGA,SAAS,iBAAiB,GAAa,GAAqB;AAC1D,MAAI,MAAM;AACV,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,UAAM,KAAK,EAAE,CAAC;AACd,UAAM,KAAK,EAAE,CAAC;AACd,WAAO,KAAK;AACZ,aAAS,KAAK;AACd,aAAS,KAAK;AAAA,EAChB;AACA,QAAM,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK;AAChD,SAAO,UAAU,IAAI,IAAI,MAAM;AACjC;","names":[]}
package/dist/index.d.cts CHANGED
@@ -7,7 +7,17 @@ interface OpenAIEmbeddingProviderConfig {
7
7
  baseUrl: string;
8
8
  /** Model identifier sent in requests (e.g. "Qwen/Qwen3-Embedding-8B") */
9
9
  model: string;
10
- /** Dimensionality of the embedding vectors. Auto-detected on first call if omitted. */
10
+ /**
11
+ * Requested output dimensionality. When set, this is sent as the OpenAI
12
+ * `dimensions` request parameter — a deliberate opt-in to matryoshka output
13
+ * truncation. Leave undefined (the default) to let the server return its
14
+ * native dimension; the native size is then detected from the first response.
15
+ *
16
+ * Only set this when you actually want the server to shorten its output.
17
+ * Many OpenAI-compatible servers (e.g. vLLM without matryoshka support)
18
+ * reject the `dimensions` param outright, so it must never be sent unless
19
+ * the caller has opted in.
20
+ */
11
21
  dimensions?: number;
12
22
  /** API key for authenticated endpoints. Optional for local servers. */
13
23
  apiKey?: string;
@@ -30,7 +40,16 @@ declare class OpenAIEmbeddingProvider implements EmbeddingProvider {
30
40
  private readonly _timeoutMs;
31
41
  private readonly _maxBatchSize;
32
42
  private readonly _reportUsage;
33
- private _dimensions;
43
+ /**
44
+ * Caller-requested output size. The ONLY value ever sent on the wire as the
45
+ * `dimensions` request param, and only when the caller opted into truncation.
46
+ */
47
+ private readonly _requestedDimensions;
48
+ /**
49
+ * Native dimension learned from the first response. Powers dimensions() and
50
+ * length-consistency validation. Never sent on a request.
51
+ */
52
+ private _observedDimensions;
34
53
  constructor(config: OpenAIEmbeddingProviderConfig);
35
54
  embed(text: string): Promise<number[]>;
36
55
  embedBatch(texts: string[]): Promise<number[][]>;
package/dist/index.d.ts CHANGED
@@ -7,7 +7,17 @@ interface OpenAIEmbeddingProviderConfig {
7
7
  baseUrl: string;
8
8
  /** Model identifier sent in requests (e.g. "Qwen/Qwen3-Embedding-8B") */
9
9
  model: string;
10
- /** Dimensionality of the embedding vectors. Auto-detected on first call if omitted. */
10
+ /**
11
+ * Requested output dimensionality. When set, this is sent as the OpenAI
12
+ * `dimensions` request parameter — a deliberate opt-in to matryoshka output
13
+ * truncation. Leave undefined (the default) to let the server return its
14
+ * native dimension; the native size is then detected from the first response.
15
+ *
16
+ * Only set this when you actually want the server to shorten its output.
17
+ * Many OpenAI-compatible servers (e.g. vLLM without matryoshka support)
18
+ * reject the `dimensions` param outright, so it must never be sent unless
19
+ * the caller has opted in.
20
+ */
11
21
  dimensions?: number;
12
22
  /** API key for authenticated endpoints. Optional for local servers. */
13
23
  apiKey?: string;
@@ -30,7 +40,16 @@ declare class OpenAIEmbeddingProvider implements EmbeddingProvider {
30
40
  private readonly _timeoutMs;
31
41
  private readonly _maxBatchSize;
32
42
  private readonly _reportUsage;
33
- private _dimensions;
43
+ /**
44
+ * Caller-requested output size. The ONLY value ever sent on the wire as the
45
+ * `dimensions` request param, and only when the caller opted into truncation.
46
+ */
47
+ private readonly _requestedDimensions;
48
+ /**
49
+ * Native dimension learned from the first response. Powers dimensions() and
50
+ * length-consistency validation. Never sent on a request.
51
+ */
52
+ private _observedDimensions;
34
53
  constructor(config: OpenAIEmbeddingProviderConfig);
35
54
  embed(text: string): Promise<number[]>;
36
55
  embedBatch(texts: string[]): Promise<number[][]>;
package/dist/index.js CHANGED
@@ -8,14 +8,23 @@ var OpenAIEmbeddingProvider = class {
8
8
  _timeoutMs;
9
9
  _maxBatchSize;
10
10
  _reportUsage;
11
- _dimensions;
11
+ /**
12
+ * Caller-requested output size. The ONLY value ever sent on the wire as the
13
+ * `dimensions` request param, and only when the caller opted into truncation.
14
+ */
15
+ _requestedDimensions;
16
+ /**
17
+ * Native dimension learned from the first response. Powers dimensions() and
18
+ * length-consistency validation. Never sent on a request.
19
+ */
20
+ _observedDimensions;
12
21
  constructor(config) {
13
22
  this._baseUrl = config.baseUrl.replace(/\/+$/, "");
14
23
  this._model = config.model;
15
24
  this._apiKey = config.apiKey;
16
25
  this._timeoutMs = config.timeoutMs ?? 3e4;
17
26
  this._maxBatchSize = config.maxBatchSize ?? 64;
18
- this._dimensions = config.dimensions;
27
+ this._requestedDimensions = config.dimensions;
19
28
  this._reportUsage = createSafeSink(config.reportUsage);
20
29
  }
21
30
  async embed(text) {
@@ -78,13 +87,14 @@ var OpenAIEmbeddingProvider = class {
78
87
  });
79
88
  }
80
89
  dimensions() {
81
- if (this._dimensions === void 0) {
90
+ const known = this._requestedDimensions ?? this._observedDimensions;
91
+ if (known === void 0) {
82
92
  throw new ProviderError(
83
93
  "Embedding dimensions not yet known. Call embed() first or provide dimensions in config.",
84
94
  "Either set dimensions in OpenAIEmbeddingProviderConfig, or call embed() once before calling dimensions()."
85
95
  );
86
96
  }
87
- return this._dimensions;
97
+ return known;
88
98
  }
89
99
  modelId() {
90
100
  return this._model;
@@ -104,8 +114,8 @@ var OpenAIEmbeddingProvider = class {
104
114
  input,
105
115
  model: this._model
106
116
  };
107
- if (this._dimensions !== void 0) {
108
- requestBody.dimensions = this._dimensions;
117
+ if (this._requestedDimensions !== void 0) {
118
+ requestBody.dimensions = this._requestedDimensions;
109
119
  }
110
120
  const body = JSON.stringify(requestBody);
111
121
  let response;
@@ -138,8 +148,15 @@ var OpenAIEmbeddingProvider = class {
138
148
  return await response.json();
139
149
  }
140
150
  _resolveDimensions(vector) {
141
- if (this._dimensions === void 0) {
142
- this._dimensions = vector.length;
151
+ const expected = this._requestedDimensions ?? this._observedDimensions;
152
+ if (expected !== void 0 && vector.length !== expected) {
153
+ throw new ProviderError(
154
+ `Embedding server returned a ${vector.length}-dimension vector but ${expected} was expected`,
155
+ this._requestedDimensions !== void 0 ? `The server ignored the requested dimensions (${this._requestedDimensions}). Remove dimensions from config, or use a model that supports output truncation.` : "The server returned inconsistent vector sizes across calls. Check that the model and endpoint are stable."
156
+ );
157
+ }
158
+ if (this._observedDimensions === void 0) {
159
+ this._observedDimensions = vector.length;
143
160
  }
144
161
  }
145
162
  };
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 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":";AAIA,SAAS,eAAe,sBAAsB;AAE9C,IAAM,gBAAgB;AAuCf,IAAM,0BAAN,MAA2D;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EAER,YAAY,QAAuC;AACjD,SAAK,WAAW,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AACjD,SAAK,SAAS,OAAO;AACrB,SAAK,UAAU,OAAO;AACtB,SAAK,aAAa,OAAO,aAAa;AACtC,SAAK,gBAAgB,OAAO,gBAAgB;AAC5C,SAAK,cAAc,OAAO;AAC1B,SAAK,eAAe,eAAe,OAAO,WAAW;AAAA,EACvD;AAAA,EAEA,MAAM,MAAM,MAAiC;AAC3C,QAAI,oBAAoB;AACxB,QAAI,cAAc;AAClB,QAAI,QAAQ;AACZ,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,SAAS,CAAC,IAAI,CAAC;AAC3C,2BAAqB,SAAS,OAAO,iBAAiB;AACtD,qBAAe,SAAS,OAAO,gBAAgB;AAC/C;AACA,YAAM,SAAS,SAAS,KAAK,CAAC,GAAG;AACjC,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,WAAK,mBAAmB,MAAM;AAC9B,aAAO;AAAA,IACT,UAAE;AACA,WAAK,WAAW,SAAS,mBAAmB,aAAa,KAAK;AAAA,IAChE;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,OAAsC;AACrD,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,UAAM,UAAsB,CAAC;AAC7B,QAAI,oBAAoB;AACxB,QAAI,cAAc;AAClB,QAAI,QAAQ;AAEZ,QAAI;AAEF,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,KAAK,eAAe;AACzD,cAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,KAAK,aAAa;AACnD,cAAM,WAAW,MAAM,KAAK,SAAS,KAAK;AAC1C,6BAAqB,SAAS,OAAO,iBAAiB;AACtD,uBAAe,SAAS,OAAO,gBAAgB;AAC/C;AAGA,cAAM,SAAS,SAAS,KAAK,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAC7D,mBAAW,QAAQ,QAAQ;AACzB,kBAAQ,KAAK,KAAK,SAAS;AAAA,QAC7B;AAAA,MACF;AAEA,UAAI,QAAQ,SAAS,KAAK,QAAQ,CAAC,GAAG;AACpC,aAAK,mBAAmB,QAAQ,CAAC,CAAC;AAAA,MACpC;AAEA,aAAO;AAAA,IACT,UAAE;AACA,WAAK,WAAW,cAAc,mBAAmB,aAAa,KAAK;AAAA,IACrE;AAAA,EACF;AAAA,EAEQ,WAAW,WAAmB,cAAsB,aAAqB,OAAqB;AACpG,QAAI,CAAC,KAAK,gBAAgB,UAAU,EAAG;AACvC,SAAK,aAAa;AAAA,MAChB,UAAU;AAAA,MACV;AAAA,MACA,MAAM;AAAA,MACN,OAAO;AAAA,MACP,WAAW,oBAAI,KAAK;AAAA,MACpB,SAAS,EAAE,cAAc,aAAa,MAAM;AAAA,IAC9C,CAAC;AAAA,EACH;AAAA,EAEA,aAAqB;AACnB,QAAI,KAAK,gBAAgB,QAAW;AAClC,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAkB;AAChB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,WAAW,GAAa,GAAqB;AAC3C,WAAO,iBAAiB,GAAG,CAAC;AAAA,EAC9B;AAAA,EAEA,MAAc,SAAS,OAA8C;AACnE,UAAM,MAAM,GAAG,KAAK,QAAQ;AAE5B,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,IAClB;AACA,QAAI,KAAK,SAAS;AAChB,cAAQ,eAAe,IAAI,UAAU,KAAK,OAAO;AAAA,IACnD;AAEA,UAAM,cAAuE;AAAA,MAC3E;AAAA,MACA,OAAO,KAAK;AAAA,IACd;AACA,QAAI,KAAK,gBAAgB,QAAW;AAClC,kBAAY,aAAa,KAAK;AAAA,IAChC;AACA,UAAM,OAAO,KAAK,UAAU,WAAW;AAEvC,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,KAAK;AAAA,QAC1B,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,QACA,QAAQ,YAAY,QAAQ,KAAK,UAAU;AAAA,MAC7C,CAAC;AAAA,IACH,SAAS,OAAgB;AACvB,UAAI,iBAAiB,gBAAgB,MAAM,SAAS,gBAAgB;AAClE,cAAM,IAAI;AAAA,UACR,qCAAqC,KAAK,UAAU;AAAA,UACpD,8EAA8E,KAAK,QAAQ;AAAA,QAC7F;AAAA,MACF;AACA,YAAM,IAAI;AAAA,QACR,0CAA0C,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QAChG,uCAAuC,KAAK,QAAQ;AAAA,MACtD;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACtD,YAAM,IAAI;AAAA,QACR,0BAA0B,SAAS,MAAM,KAAK,SAAS;AAAA,QACvD,SAAS,WAAW,MAChB,yDACA,yCAAyC,GAAG;AAAA,MAClD;AAAA,IACF;AAEA,WAAQ,MAAM,SAAS,KAAK;AAAA,EAC9B;AAAA,EAEQ,mBAAmB,QAAwB;AACjD,QAAI,KAAK,gBAAgB,QAAW;AAClC,WAAK,cAAc,OAAO;AAAA,IAC5B;AAAA,EACF;AACF;AAGA,SAAS,iBAAiB,GAAa,GAAqB;AAC1D,MAAI,MAAM;AACV,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,UAAM,KAAK,EAAE,CAAC;AACd,UAAM,KAAK,EAAE,CAAC;AACd,WAAO,KAAK;AACZ,aAAS,KAAK;AACd,aAAS,KAAK;AAAA,EAChB;AACA,QAAM,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK;AAChD,SAAO,UAAU,IAAI,IAAI,MAAM;AACjC;","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 /**\n * Requested output dimensionality. When set, this is sent as the OpenAI\n * `dimensions` request parameter — a deliberate opt-in to matryoshka output\n * truncation. Leave undefined (the default) to let the server return its\n * native dimension; the native size is then detected from the first response.\n *\n * Only set this when you actually want the server to shorten its output.\n * Many OpenAI-compatible servers (e.g. vLLM without matryoshka support)\n * reject the `dimensions` param outright, so it must never be sent unless\n * the caller has opted in.\n */\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\n /**\n * Caller-requested output size. The ONLY value ever sent on the wire as the\n * `dimensions` request param, and only when the caller opted into truncation.\n */\n private readonly _requestedDimensions: number | undefined;\n\n /**\n * Native dimension learned from the first response. Powers dimensions() and\n * length-consistency validation. Never sent on a request.\n */\n private _observedDimensions: 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._requestedDimensions = 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 const known = this._requestedDimensions ?? this._observedDimensions;\n if (known === 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 known;\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 // Send `dimensions` only when the caller deliberately requested truncation.\n // The native size learned from responses is never echoed back — that is a\n // no-op that strict servers (e.g. vLLM without matryoshka) reject outright.\n if (this._requestedDimensions !== undefined) {\n requestBody.dimensions = this._requestedDimensions;\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 const expected = this._requestedDimensions ?? this._observedDimensions;\n if (expected !== undefined && vector.length !== expected) {\n throw new ProviderError(\n `Embedding server returned a ${vector.length}-dimension vector but ${expected} was expected`,\n this._requestedDimensions !== undefined\n ? `The server ignored the requested dimensions (${this._requestedDimensions}). Remove dimensions from config, or use a model that supports output truncation.`\n : 'The server returned inconsistent vector sizes across calls. Check that the model and endpoint are stable.',\n );\n }\n if (this._observedDimensions === undefined) {\n this._observedDimensions = 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.20.0",
3
+ "version": "0.20.1",
4
4
  "description": "OpenAI-compatible embeddings provider for @utaba/deep-memory",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -51,13 +51,13 @@
51
51
  "agents"
52
52
  ],
53
53
  "dependencies": {
54
- "@utaba/deep-memory": "^0.20.0"
54
+ "@utaba/deep-memory": "^0.20.1"
55
55
  },
56
56
  "devDependencies": {
57
- "@types/node": "^22.0.0",
57
+ "@types/node": "^22.20.0",
58
58
  "tsup": "^8.5.1",
59
59
  "typescript": "^6.0.2",
60
- "vitest": "^4.1.8"
60
+ "vitest": "^4.1.10"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "tsup",