@oracle/langchain-oracledb 0.1.1 → 0.2.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/vectorstores.cjs +379 -159
- package/dist/vectorstores.cjs.map +1 -1
- package/dist/vectorstores.d.cts +58 -7
- package/dist/vectorstores.d.ts +58 -7
- package/dist/vectorstores.js +377 -157
- package/dist/vectorstores.js.map +1 -1
- package/package.json +1 -1
package/dist/vectorstores.cjs
CHANGED
|
@@ -3,9 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.OracleVS = exports.dropTablePurge = exports.createIndex = exports.createTable = exports.DistanceStrategy = exports.generateWhereClause = void 0;
|
|
6
|
+
exports.OracleVS = exports.dropTablePurge = exports.createIndex = exports.createTable = exports.DistanceStrategy = exports.VectorElementFormat = exports.VectorType = exports.generateWhereClause = void 0;
|
|
7
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
7
8
|
const oracledb_1 = __importDefault(require("oracledb"));
|
|
8
|
-
const crypto_1 = require("crypto");
|
|
9
9
|
const vectorstores_1 = require("@langchain/core/vectorstores");
|
|
10
10
|
const documents_1 = require("@langchain/core/documents");
|
|
11
11
|
const math_1 = require("@langchain/core/utils/math");
|
|
@@ -97,6 +97,17 @@ function jsonCompare(column, op, value, bindValues) {
|
|
|
97
97
|
const operator = op === "=" ? "==" : op; // Oracle requires '==' for equality
|
|
98
98
|
return `JSON_EXISTS(metadata, '$.${column}?(@ ${operator} $${alias})' PASSING :${pos} AS "${alias}")`;
|
|
99
99
|
}
|
|
100
|
+
exports.VectorType = {
|
|
101
|
+
DENSE: "DENSE",
|
|
102
|
+
SPARSE: "SPARSE",
|
|
103
|
+
};
|
|
104
|
+
exports.VectorElementFormat = {
|
|
105
|
+
INT8: "INT8",
|
|
106
|
+
FLOAT32: "FLOAT32",
|
|
107
|
+
FLOAT64: "FLOAT64",
|
|
108
|
+
BINARY: "BINARY",
|
|
109
|
+
FLEX: "*",
|
|
110
|
+
};
|
|
100
111
|
exports.DistanceStrategy = {
|
|
101
112
|
COSINE: "COSINE",
|
|
102
113
|
DOT_PRODUCT: "DOT",
|
|
@@ -142,10 +153,78 @@ function quoteIdentifier(identifier) {
|
|
|
142
153
|
const quotedParts = groups.map((g) => `"${g}"`);
|
|
143
154
|
return quotedParts.join(".");
|
|
144
155
|
}
|
|
145
|
-
|
|
156
|
+
const isFloat32Array = (value) =>
|
|
157
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
158
|
+
value instanceof Float32Array;
|
|
159
|
+
const isFloat64Array = (value) =>
|
|
160
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
161
|
+
value instanceof Float64Array;
|
|
162
|
+
const isInt8Array = (value) =>
|
|
163
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
164
|
+
value instanceof Int8Array;
|
|
165
|
+
const isUint8Array = (value) =>
|
|
166
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
167
|
+
value instanceof Uint8Array;
|
|
168
|
+
const isSparseVector = (value) =>
|
|
169
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
170
|
+
value instanceof oracledb_1.default.SparseVector;
|
|
171
|
+
function normalizeVectorTypeValue(value) {
|
|
172
|
+
const normalized = value?.toUpperCase();
|
|
173
|
+
if (normalized === exports.VectorType.DENSE || normalized === exports.VectorType.SPARSE) {
|
|
174
|
+
return normalized;
|
|
175
|
+
}
|
|
176
|
+
return exports.VectorType.DENSE;
|
|
177
|
+
}
|
|
178
|
+
function normalizeVectorFormat(value, vectorType) {
|
|
179
|
+
if (!value) {
|
|
180
|
+
return exports.VectorElementFormat.FLOAT32;
|
|
181
|
+
}
|
|
182
|
+
const normalizedValue = value === exports.VectorElementFormat.FLEX
|
|
183
|
+
? exports.VectorElementFormat.FLEX
|
|
184
|
+
: value.toUpperCase();
|
|
185
|
+
const validFormats = Object.values(exports.VectorElementFormat);
|
|
186
|
+
const format = validFormats.includes(normalizedValue)
|
|
187
|
+
? normalizedValue
|
|
188
|
+
: exports.VectorElementFormat.FLOAT32;
|
|
189
|
+
if (vectorType === exports.VectorType.SPARSE && format === exports.VectorElementFormat.BINARY) {
|
|
190
|
+
throw new Error("BINARY format is not supported for SPARSE vectors.");
|
|
191
|
+
}
|
|
192
|
+
return format;
|
|
193
|
+
}
|
|
194
|
+
function buildVectorColumnDefinition(embeddingDim, customization) {
|
|
195
|
+
if (embeddingDim === undefined || embeddingDim === null) {
|
|
196
|
+
throw new Error("Embedding dimension is required to create the vector column.");
|
|
197
|
+
}
|
|
198
|
+
if (!Number.isInteger(embeddingDim) || embeddingDim <= 0) {
|
|
199
|
+
throw new Error("Embedding dimension must be a positive integer.");
|
|
200
|
+
}
|
|
201
|
+
const vectorType = normalizeVectorTypeValue(customization?.vectorType);
|
|
202
|
+
if (vectorType === exports.VectorType.SPARSE && customization?.format === undefined) {
|
|
203
|
+
throw new Error("Sparse vector type requires a vector format to be specified.");
|
|
204
|
+
}
|
|
205
|
+
const format = normalizeVectorFormat(customization?.format, vectorType);
|
|
206
|
+
if (format === exports.VectorElementFormat.BINARY && embeddingDim % 8 !== 0) {
|
|
207
|
+
throw new Error("BINARY vector format requires dimensions to be a multiple of 8.");
|
|
208
|
+
}
|
|
209
|
+
const dimensionSegment = String(embeddingDim);
|
|
210
|
+
const formatSegment = format ?? exports.VectorElementFormat.FLOAT32;
|
|
211
|
+
if (vectorType === exports.VectorType.SPARSE) {
|
|
212
|
+
return `VECTOR(${dimensionSegment}, ${formatSegment}, SPARSE)`;
|
|
213
|
+
}
|
|
214
|
+
return `VECTOR(${dimensionSegment}, ${formatSegment})`;
|
|
215
|
+
}
|
|
216
|
+
function escapeCommentText(comment) {
|
|
217
|
+
return comment.replace(/'/g, "''");
|
|
218
|
+
}
|
|
219
|
+
function escapePlSqlLiteral(sql) {
|
|
220
|
+
return sql.replace(/'/g, "''");
|
|
221
|
+
}
|
|
222
|
+
async function createTable(connection, tableName, embeddingDim, customization) {
|
|
223
|
+
const tableIdentifier = quoteIdentifier(tableName);
|
|
146
224
|
const colsDict = {
|
|
147
225
|
id: "RAW(16) DEFAULT SYS_GUID() PRIMARY KEY",
|
|
148
|
-
|
|
226
|
+
external_id: `VARCHAR2(36) UNIQUE`,
|
|
227
|
+
embedding: buildVectorColumnDefinition(embeddingDim, customization),
|
|
149
228
|
text: "CLOB",
|
|
150
229
|
metadata: "JSON",
|
|
151
230
|
};
|
|
@@ -153,11 +232,33 @@ async function createTable(connection, tableName, embeddingDim) {
|
|
|
153
232
|
const ddlBody = Object.entries(colsDict)
|
|
154
233
|
.map(([colName, colType]) => `${colName} ${colType}`)
|
|
155
234
|
.join(", ");
|
|
156
|
-
const
|
|
235
|
+
const createTableSql = `CREATE TABLE IF NOT EXISTS ${tableIdentifier}
|
|
157
236
|
(
|
|
158
237
|
${ddlBody}
|
|
159
238
|
)`;
|
|
160
|
-
|
|
239
|
+
const statements = [
|
|
240
|
+
`EXECUTE IMMEDIATE '${escapePlSqlLiteral(createTableSql)}';`,
|
|
241
|
+
];
|
|
242
|
+
const tableDescription = customization?.description?.trim();
|
|
243
|
+
if (tableDescription) {
|
|
244
|
+
const escapedDescription = escapeCommentText(tableDescription);
|
|
245
|
+
statements.push(`EXECUTE IMMEDIATE 'COMMENT ON TABLE ${tableIdentifier} IS ''${escapedDescription}''';`);
|
|
246
|
+
}
|
|
247
|
+
if (customization?.annotations) {
|
|
248
|
+
for (const [columnName, note] of Object.entries(customization.annotations)) {
|
|
249
|
+
const trimmedNote = note?.trim();
|
|
250
|
+
if (!trimmedNote)
|
|
251
|
+
continue;
|
|
252
|
+
const columnKey = columnName.trim().toLowerCase();
|
|
253
|
+
if (!(columnKey in colsDict))
|
|
254
|
+
continue;
|
|
255
|
+
const normalizedColumnIdentifier = `${tableIdentifier}.${quoteIdentifier(columnKey.toUpperCase())}`;
|
|
256
|
+
const escapedNote = escapeCommentText(trimmedNote);
|
|
257
|
+
statements.push(`EXECUTE IMMEDIATE 'COMMENT ON COLUMN ${normalizedColumnIdentifier} IS ''${escapedNote}''';`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const block = `BEGIN\n ${statements.join("\n ")}\nEND;`;
|
|
261
|
+
await connection.execute(block);
|
|
161
262
|
}
|
|
162
263
|
catch (error) {
|
|
163
264
|
handleError(error);
|
|
@@ -165,11 +266,63 @@ async function createTable(connection, tableName, embeddingDim) {
|
|
|
165
266
|
}
|
|
166
267
|
exports.createTable = createTable;
|
|
167
268
|
function _getIndexName(baseName) {
|
|
168
|
-
const uniqueId =
|
|
269
|
+
const uniqueId = node_crypto_1.default.randomUUID().replace(/-/g, "");
|
|
169
270
|
return `${baseName}_${uniqueId}`;
|
|
170
271
|
}
|
|
272
|
+
// Packs an array of 0/1 numbers into a big-endian Uint8Array for BINARY VECTOR storage.
|
|
273
|
+
function packBinaryVector(bits) {
|
|
274
|
+
const byteLength = Math.ceil(bits.length / 8);
|
|
275
|
+
const buffer = new Uint8Array(byteLength);
|
|
276
|
+
for (let i = 0; i < bits.length; i += 1) {
|
|
277
|
+
const bit = bits[i] > 0 ? 1 : 0;
|
|
278
|
+
if (bit === 1) {
|
|
279
|
+
const byteIndex = Math.floor(i / 8);
|
|
280
|
+
const bitOffset = 7 - (i % 8);
|
|
281
|
+
buffer[byteIndex] |= 1 << bitOffset;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return buffer;
|
|
285
|
+
}
|
|
286
|
+
// Converts a dense numeric vector into the typed array that matches the configured element format.
|
|
287
|
+
function convertDenseVectorForFormat(values, format) {
|
|
288
|
+
switch (format) {
|
|
289
|
+
case exports.VectorElementFormat.FLOAT32:
|
|
290
|
+
case undefined:
|
|
291
|
+
return new Float32Array(values);
|
|
292
|
+
case exports.VectorElementFormat.FLOAT64:
|
|
293
|
+
return new Float64Array(values);
|
|
294
|
+
case exports.VectorElementFormat.INT8: {
|
|
295
|
+
const clamped = new Int8Array(values.length);
|
|
296
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
297
|
+
const rounded = Math.round(values[i]);
|
|
298
|
+
if (rounded < -128 || rounded > 127) {
|
|
299
|
+
throw new Error("INT8 vector values must be within [-128, 127].");
|
|
300
|
+
}
|
|
301
|
+
clamped[i] = rounded;
|
|
302
|
+
}
|
|
303
|
+
return clamped;
|
|
304
|
+
}
|
|
305
|
+
case exports.VectorElementFormat.BINARY:
|
|
306
|
+
return packBinaryVector(values);
|
|
307
|
+
default:
|
|
308
|
+
return new Float64Array(values);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function unpackBinaryVector(bytesLike, dimension) {
|
|
312
|
+
const bytes = isUint8Array(bytesLike)
|
|
313
|
+
? bytesLike
|
|
314
|
+
: Uint8Array.from(bytesLike);
|
|
315
|
+
const result = new Float32Array(dimension);
|
|
316
|
+
for (let i = 0; i < dimension; i += 1) {
|
|
317
|
+
const byteIndex = Math.floor(i / 8);
|
|
318
|
+
const bitOffset = 7 - (i % 8);
|
|
319
|
+
const byte = bytes[byteIndex] ?? 0;
|
|
320
|
+
result[i] = (byte >> bitOffset) & 1;
|
|
321
|
+
}
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
171
324
|
async function createIndex(client, vectorStore, params) {
|
|
172
|
-
const idxType = params?.idxType
|
|
325
|
+
const idxType = (params?.idxType ?? "HNSW").toUpperCase();
|
|
173
326
|
if (idxType === "IVF") {
|
|
174
327
|
await createIVFIndex(client, vectorStore, params);
|
|
175
328
|
}
|
|
@@ -178,122 +331,48 @@ async function createIndex(client, vectorStore, params) {
|
|
|
178
331
|
}
|
|
179
332
|
}
|
|
180
333
|
exports.createIndex = createIndex;
|
|
181
|
-
async function createHNSWIndex(connection, oraclevs, params) {
|
|
334
|
+
async function createHNSWIndex(connection, oraclevs, params = {}) {
|
|
182
335
|
try {
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (key === "idxName") {
|
|
200
|
-
config[key] = _getIndexName(defaults[key]);
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
config[key] = defaults[key];
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
// Validate keys in config against defaults
|
|
208
|
-
for (const key of Object.keys(config)) {
|
|
209
|
-
if (!(key in defaults)) {
|
|
210
|
-
throw new Error(`Invalid parameter: ${key}`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
const { idxName } = config;
|
|
214
|
-
const baseSql = `CREATE VECTOR INDEX IF NOT EXISTS ${quoteIdentifier(idxName)}
|
|
215
|
-
ON ${oraclevs.tableName}(embedding)
|
|
216
|
-
ORGANIZATION INMEMORY NEIGHBOR GRAPH`;
|
|
217
|
-
const accuracyPart = config.accuracy
|
|
218
|
-
? ` WITH TARGET ACCURACY ${config.accuracy}`
|
|
219
|
-
: "";
|
|
220
|
-
const distancePart = ` DISTANCE ${oraclevs.distanceStrategy}`;
|
|
221
|
-
let parametersPart = "";
|
|
222
|
-
if ("neighbors" in config && "efConstruction" in config) {
|
|
223
|
-
parametersPart = ` parameters (type ${config.idxType},
|
|
224
|
-
neighbors ${config.neighbors},
|
|
225
|
-
efConstruction ${config.efConstruction})`;
|
|
226
|
-
}
|
|
227
|
-
else if ("neighbors" in config && !("efConstruction" in config)) {
|
|
228
|
-
config.efConstruction = defaults.efConstruction;
|
|
229
|
-
parametersPart = ` parameters (type ${config.idxType},
|
|
230
|
-
neighbors ${config.neighbors},
|
|
231
|
-
efConstruction ${config.efConstruction})`;
|
|
232
|
-
}
|
|
233
|
-
else if (!("neighbors" in config) && "efConstruction" in config) {
|
|
234
|
-
config.neighbors = defaults.neighbors;
|
|
235
|
-
parametersPart = ` parameters (type ${config.idxType},
|
|
236
|
-
neighbors ${config.neighbors},
|
|
237
|
-
efConstruction ${config.efConstruction})`;
|
|
238
|
-
}
|
|
239
|
-
const parallelPart = ` PARALLEL ${config.parallel}`;
|
|
240
|
-
const ddl = baseSql + accuracyPart + distancePart + parametersPart + parallelPart;
|
|
336
|
+
const { idxName = _getIndexName("HNSW"), idxType = "HNSW", neighbors = 32, efConstruction = 200, accuracy = 90, parallel = 8, ...extra } = params;
|
|
337
|
+
const invalidKeys = Object.keys(extra);
|
|
338
|
+
if (invalidKeys.length > 0) {
|
|
339
|
+
throw new Error(`Invalid parameter(s): ${invalidKeys.join(", ")}`);
|
|
340
|
+
}
|
|
341
|
+
const ddl = `
|
|
342
|
+
CREATE VECTOR INDEX IF NOT EXISTS ${quoteIdentifier(idxName)}
|
|
343
|
+
ON ${oraclevs.tableName}(embedding)
|
|
344
|
+
ORGANIZATION INMEMORY NEIGHBOR GRAPH
|
|
345
|
+
WITH TARGET ACCURACY ${accuracy}
|
|
346
|
+
DISTANCE ${oraclevs.distanceStrategy}
|
|
347
|
+
PARAMETERS (type ${idxType}, neighbors ${neighbors}, efConstruction ${efConstruction})
|
|
348
|
+
PARALLEL ${parallel}
|
|
349
|
+
`
|
|
350
|
+
.trim()
|
|
351
|
+
.replace(/\s+/g, " ");
|
|
241
352
|
await connection.execute(ddl);
|
|
242
353
|
}
|
|
243
354
|
catch (error) {
|
|
244
355
|
handleError(error);
|
|
245
356
|
}
|
|
246
357
|
}
|
|
247
|
-
async function createIVFIndex(connection, oraclevs, params) {
|
|
358
|
+
async function createIVFIndex(connection, oraclevs, params = {}) {
|
|
248
359
|
try {
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
config[key] = _getIndexName(defaults[key]);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
config[key] = defaults[key];
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
// Validate keys in config against defaults
|
|
273
|
-
for (const key of Object.keys(config)) {
|
|
274
|
-
if (!(key in defaults)) {
|
|
275
|
-
throw new Error(`Invalid parameter: ${key}`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
// Base SQL statement
|
|
279
|
-
const { idxName } = config;
|
|
280
|
-
const baseSql = `CREATE VECTOR INDEX IF NOT EXISTS ${quoteIdentifier(idxName)}
|
|
281
|
-
ON ${oraclevs.tableName}(embedding)
|
|
282
|
-
ORGANIZATION NEIGHBOR PARTITIONS`;
|
|
283
|
-
// Optional parts depending on parameters
|
|
284
|
-
const accuracyPart = config.accuracy
|
|
285
|
-
? ` WITH TARGET ACCURACY ${config.accuracy}`
|
|
286
|
-
: "";
|
|
287
|
-
const distancePart = ` DISTANCE ${oraclevs.distanceStrategy}`;
|
|
288
|
-
let parametersPart = "";
|
|
289
|
-
if ("idxType" in config && "neighborPart" in config) {
|
|
290
|
-
parametersPart = ` PARAMETERS (type ${config.idxType},
|
|
291
|
-
neighbor partitions ${config.neighborPart})`;
|
|
292
|
-
}
|
|
293
|
-
// Always included part for parallel - assuming parallel is compulsory and always included
|
|
294
|
-
const parallelPart = ` PARALLEL ${config.parallel}`;
|
|
295
|
-
// Combine all parts
|
|
296
|
-
const ddl = baseSql + accuracyPart + distancePart + parametersPart + parallelPart;
|
|
360
|
+
const { idxName = _getIndexName("IVF"), idxType = "IVF", neighborPart = 32, accuracy = 90, parallel = 8, ...extra } = params;
|
|
361
|
+
const invalidKeys = Object.keys(extra);
|
|
362
|
+
if (invalidKeys.length > 0) {
|
|
363
|
+
throw new Error(`Invalid parameter(s): ${invalidKeys.join(", ")}`);
|
|
364
|
+
}
|
|
365
|
+
const ddl = `
|
|
366
|
+
CREATE VECTOR INDEX IF NOT EXISTS ${quoteIdentifier(idxName)}
|
|
367
|
+
ON ${oraclevs.tableName}(embedding)
|
|
368
|
+
ORGANIZATION NEIGHBOR PARTITIONS
|
|
369
|
+
WITH TARGET ACCURACY ${accuracy}
|
|
370
|
+
DISTANCE ${oraclevs.distanceStrategy}
|
|
371
|
+
PARAMETERS (type ${idxType}, neighbor partitions ${neighborPart})
|
|
372
|
+
PARALLEL ${parallel}
|
|
373
|
+
`
|
|
374
|
+
.trim()
|
|
375
|
+
.replace(/\s+/g, " ");
|
|
297
376
|
await connection.execute(ddl);
|
|
298
377
|
}
|
|
299
378
|
catch (error) {
|
|
@@ -346,6 +425,30 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
346
425
|
writable: true,
|
|
347
426
|
value: void 0
|
|
348
427
|
});
|
|
428
|
+
Object.defineProperty(this, "description", {
|
|
429
|
+
enumerable: true,
|
|
430
|
+
configurable: true,
|
|
431
|
+
writable: true,
|
|
432
|
+
value: void 0
|
|
433
|
+
});
|
|
434
|
+
Object.defineProperty(this, "annotations", {
|
|
435
|
+
enumerable: true,
|
|
436
|
+
configurable: true,
|
|
437
|
+
writable: true,
|
|
438
|
+
value: void 0
|
|
439
|
+
});
|
|
440
|
+
Object.defineProperty(this, "vectorType", {
|
|
441
|
+
enumerable: true,
|
|
442
|
+
configurable: true,
|
|
443
|
+
writable: true,
|
|
444
|
+
value: void 0
|
|
445
|
+
});
|
|
446
|
+
Object.defineProperty(this, "vectorFormat", {
|
|
447
|
+
enumerable: true,
|
|
448
|
+
configurable: true,
|
|
449
|
+
writable: true,
|
|
450
|
+
value: void 0
|
|
451
|
+
});
|
|
349
452
|
Object.defineProperty(this, "query", {
|
|
350
453
|
enumerable: true,
|
|
351
454
|
configurable: true,
|
|
@@ -359,11 +462,120 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
359
462
|
dbConfig.distanceStrategy ?? this.distanceStrategy;
|
|
360
463
|
this.query = dbConfig.query;
|
|
361
464
|
this.filter = dbConfig.filter;
|
|
465
|
+
this.description = dbConfig.description;
|
|
466
|
+
this.annotations = dbConfig.annotations
|
|
467
|
+
? { ...dbConfig.annotations }
|
|
468
|
+
: undefined;
|
|
469
|
+
this.vectorType = dbConfig.vectorType;
|
|
470
|
+
this.vectorFormat = dbConfig.format;
|
|
362
471
|
}
|
|
363
472
|
catch (error) {
|
|
364
473
|
handleError(error);
|
|
365
474
|
}
|
|
366
475
|
}
|
|
476
|
+
getActiveVectorType() {
|
|
477
|
+
return this.vectorType ? normalizeVectorTypeValue(this.vectorType) : exports.VectorType.DENSE;
|
|
478
|
+
}
|
|
479
|
+
ensureEmbeddingDimension() {
|
|
480
|
+
if (this.embeddingDimension === undefined || this.embeddingDimension === null) {
|
|
481
|
+
throw new Error("Embedding dimension is not initialized for this vector store.");
|
|
482
|
+
}
|
|
483
|
+
return this.embeddingDimension;
|
|
484
|
+
}
|
|
485
|
+
prepareVectorForStorage(vector) {
|
|
486
|
+
if (this.embeddingDimension === undefined || this.embeddingDimension === null) {
|
|
487
|
+
this.embeddingDimension = vector.length;
|
|
488
|
+
}
|
|
489
|
+
const dimension = this.ensureEmbeddingDimension();
|
|
490
|
+
const vectorType = this.getActiveVectorType();
|
|
491
|
+
const format = this.vectorFormat
|
|
492
|
+
? normalizeVectorFormat(this.vectorFormat, vectorType)
|
|
493
|
+
: exports.VectorElementFormat.FLOAT32;
|
|
494
|
+
if (vectorType === exports.VectorType.SPARSE) {
|
|
495
|
+
if (vector.length !== dimension) {
|
|
496
|
+
throw new Error("Sparse vectors must supply full-dimension arrays for conversion.");
|
|
497
|
+
}
|
|
498
|
+
let sparseInput = vector;
|
|
499
|
+
if (format === exports.VectorElementFormat.INT8) {
|
|
500
|
+
const clamped = new Int8Array(vector.length);
|
|
501
|
+
for (let i = 0; i < vector.length; i += 1) {
|
|
502
|
+
const rounded = Math.round(vector[i]);
|
|
503
|
+
if (rounded < -128 || rounded > 127) {
|
|
504
|
+
throw new Error("INT8 sparse vector values must be within [-128, 127].");
|
|
505
|
+
}
|
|
506
|
+
clamped[i] = rounded;
|
|
507
|
+
}
|
|
508
|
+
sparseInput = clamped;
|
|
509
|
+
}
|
|
510
|
+
else if (format === exports.VectorElementFormat.FLOAT64) {
|
|
511
|
+
sparseInput = new Float64Array(vector);
|
|
512
|
+
}
|
|
513
|
+
else if (format === exports.VectorElementFormat.FLOAT32) {
|
|
514
|
+
sparseInput = new Float32Array(vector);
|
|
515
|
+
}
|
|
516
|
+
return new oracledb_1.default.SparseVector(sparseInput);
|
|
517
|
+
}
|
|
518
|
+
if (vector.length !== dimension) {
|
|
519
|
+
throw new Error("Vector length does not match the embedding dimension.");
|
|
520
|
+
}
|
|
521
|
+
return convertDenseVectorForFormat(vector, format);
|
|
522
|
+
}
|
|
523
|
+
prepareQueryVector(vector) {
|
|
524
|
+
return this.prepareVectorForStorage(vector);
|
|
525
|
+
}
|
|
526
|
+
// Preserves the native driver representation, only expanding sparse vectors to dense form.
|
|
527
|
+
// Preserves the native driver representation, only expanding sparse vectors to dense form.
|
|
528
|
+
normalizeReturnedEmbedding(value) {
|
|
529
|
+
if (isSparseVector(value)) {
|
|
530
|
+
const dense = value.dense?.();
|
|
531
|
+
if (!dense) {
|
|
532
|
+
throw new Error("Unable to expand sparse vector to dense representation.");
|
|
533
|
+
}
|
|
534
|
+
if (isFloat32Array(dense)) {
|
|
535
|
+
return dense;
|
|
536
|
+
}
|
|
537
|
+
if (isFloat64Array(dense)) {
|
|
538
|
+
return new Float32Array(dense);
|
|
539
|
+
}
|
|
540
|
+
if (isInt8Array(dense)) {
|
|
541
|
+
return Float32Array.from(dense);
|
|
542
|
+
}
|
|
543
|
+
if (Array.isArray(dense)) {
|
|
544
|
+
return Float32Array.from(dense);
|
|
545
|
+
}
|
|
546
|
+
return Float32Array.from(dense);
|
|
547
|
+
}
|
|
548
|
+
if (isFloat32Array(value) || isFloat64Array(value) || isInt8Array(value) || isUint8Array(value)) {
|
|
549
|
+
return value;
|
|
550
|
+
}
|
|
551
|
+
if (Array.isArray(value)) {
|
|
552
|
+
return value;
|
|
553
|
+
}
|
|
554
|
+
throw new Error("Received unsupported vector representation from the database.");
|
|
555
|
+
}
|
|
556
|
+
// Normalizes any supported vector representation into Float32Array so downstream math
|
|
557
|
+
// (MMR, distance calculations) can assume a consistent element type without forcing
|
|
558
|
+
// callers to give up the native shape returned by node-oracledb.
|
|
559
|
+
coerceEmbeddingToFloat32(value) {
|
|
560
|
+
if (isFloat32Array(value)) {
|
|
561
|
+
return value;
|
|
562
|
+
}
|
|
563
|
+
if (isFloat64Array(value)) {
|
|
564
|
+
return new Float32Array(value);
|
|
565
|
+
}
|
|
566
|
+
if (isInt8Array(value)) {
|
|
567
|
+
return Float32Array.from(value);
|
|
568
|
+
}
|
|
569
|
+
if (isUint8Array(value)) {
|
|
570
|
+
const byteLength = value.length;
|
|
571
|
+
const dimension = this.embeddingDimension ?? byteLength * 8;
|
|
572
|
+
return unpackBinaryVector(value, dimension);
|
|
573
|
+
}
|
|
574
|
+
if (Array.isArray(value)) {
|
|
575
|
+
return Float32Array.from(value);
|
|
576
|
+
}
|
|
577
|
+
return this.coerceEmbeddingToFloat32(this.normalizeReturnedEmbedding(value));
|
|
578
|
+
}
|
|
367
579
|
async getEmbeddingDimension(query) {
|
|
368
580
|
const embeddingVector = await this.embeddings.embedQuery(query);
|
|
369
581
|
return embeddingVector.length;
|
|
@@ -373,7 +585,12 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
373
585
|
try {
|
|
374
586
|
this.embeddingDimension = await this.getEmbeddingDimension(this.query);
|
|
375
587
|
connection = await this.getConnection();
|
|
376
|
-
await createTable(connection, this.tableName, this.embeddingDimension
|
|
588
|
+
await createTable(connection, this.tableName, this.embeddingDimension, {
|
|
589
|
+
description: this.description,
|
|
590
|
+
annotations: this.annotations,
|
|
591
|
+
vectorType: this.vectorType,
|
|
592
|
+
format: this.vectorFormat
|
|
593
|
+
});
|
|
377
594
|
}
|
|
378
595
|
catch (error) {
|
|
379
596
|
handleError(error);
|
|
@@ -412,73 +629,79 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
412
629
|
* @param vectors The vectors to add.
|
|
413
630
|
* @param documents The documents associated with the vectors.
|
|
414
631
|
* @param options
|
|
632
|
+
* ** Add { mutateOnDuplicate?: boolean } to do upsert
|
|
415
633
|
* @returns Promise that resolves when the vectors have been added.
|
|
416
634
|
*/
|
|
417
635
|
async addVectors(vectors, documents, options) {
|
|
418
636
|
if (vectors.length === 0) {
|
|
419
637
|
throw new Error("Vectors input null. Nothing to add...");
|
|
420
638
|
}
|
|
421
|
-
const
|
|
639
|
+
const inputIds = options?.ids;
|
|
422
640
|
let connection = null;
|
|
423
641
|
try {
|
|
424
642
|
// Ensure there are IDs for all documents
|
|
425
|
-
if (
|
|
643
|
+
if (inputIds !== undefined && inputIds.length !== vectors.length) {
|
|
426
644
|
throw new Error("The number of ids must match the number of vectors provided.");
|
|
427
645
|
}
|
|
428
646
|
connection = await this.getConnection();
|
|
647
|
+
const finalIds = [];
|
|
429
648
|
const binds = [];
|
|
430
649
|
for (let index = 0; index < documents.length; index += 1) {
|
|
431
650
|
const doc = documents[index];
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
.toUpperCase();
|
|
439
|
-
}
|
|
440
|
-
else {
|
|
441
|
-
const sourceId = doc.metadata.id ?? crypto.randomUUID();
|
|
442
|
-
processedId = (0, crypto_1.createHash)("sha256")
|
|
443
|
-
.update(sourceId)
|
|
444
|
-
.digest("hex")
|
|
445
|
-
.substring(0, 16)
|
|
446
|
-
.toUpperCase();
|
|
447
|
-
}
|
|
448
|
-
const idBuffer = Buffer.from(processedId, "hex");
|
|
449
|
-
const bind = {
|
|
450
|
-
id: idBuffer,
|
|
651
|
+
// Generate ID if not provided: Priority (Options -> Metadata -> Random)
|
|
652
|
+
const externalId = inputIds?.[index] ?? doc.metadata.id ?? node_crypto_1.default.randomUUID();
|
|
653
|
+
finalIds.push(externalId);
|
|
654
|
+
const preparedEmbedding = this.prepareVectorForStorage(vectors[index]);
|
|
655
|
+
const row = {
|
|
656
|
+
ext_id: externalId,
|
|
451
657
|
text: doc.pageContent,
|
|
452
658
|
metadata: doc.metadata,
|
|
453
|
-
embedding:
|
|
659
|
+
embedding: preparedEmbedding,
|
|
454
660
|
};
|
|
455
|
-
binds.push(
|
|
661
|
+
binds.push(row);
|
|
456
662
|
}
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
663
|
+
const isMutateOnDuplicate = options?.mutateOnDuplicate ?? false; // Default to false for better performance
|
|
664
|
+
const insertSql = `
|
|
665
|
+
INSERT INTO ${this.tableName} (external_id, embedding, text, metadata)
|
|
666
|
+
VALUES (:ext_id, :embedding, :text, :metadata)`;
|
|
667
|
+
const mergeSql = `
|
|
668
|
+
MERGE INTO ${this.tableName} t
|
|
669
|
+
USING (
|
|
670
|
+
SELECT :ext_id as external_id, :embedding as embedding,
|
|
671
|
+
:metadata as metadata, :text as text FROM DUAL
|
|
672
|
+
) s
|
|
673
|
+
ON (t.external_id = s.external_id)
|
|
674
|
+
WHEN MATCHED THEN
|
|
675
|
+
UPDATE SET
|
|
676
|
+
t.embedding = s.embedding,
|
|
677
|
+
t.metadata = s.metadata,
|
|
678
|
+
t.text = s.text
|
|
679
|
+
WHEN NOT MATCHED THEN
|
|
680
|
+
INSERT (external_id, embedding, metadata, text)
|
|
681
|
+
VALUES (s.external_id, s.embedding, s.metadata, s.text)`;
|
|
682
|
+
const sql = isMutateOnDuplicate ? mergeSql : insertSql;
|
|
683
|
+
const executeOptions = {
|
|
460
684
|
bindDefs: {
|
|
461
|
-
|
|
685
|
+
ext_id: { type: oracledb_1.default.STRING, maxSize: 255 },
|
|
462
686
|
text: { type: oracledb_1.default.STRING, maxSize: 10000000 },
|
|
463
687
|
metadata: { type: oracledb_1.default.DB_TYPE_JSON },
|
|
464
688
|
embedding: { type: oracledb_1.default.DB_TYPE_VECTOR },
|
|
465
689
|
},
|
|
466
690
|
autoCommit: false,
|
|
467
691
|
};
|
|
468
|
-
await connection.executeMany(sql, binds,
|
|
692
|
+
await connection.executeMany(sql, binds, executeOptions);
|
|
469
693
|
// Commit once all inserts are queued up
|
|
470
694
|
await connection.commit();
|
|
471
|
-
|
|
695
|
+
return finalIds;
|
|
472
696
|
}
|
|
473
697
|
catch (error) {
|
|
474
|
-
handleError(error);
|
|
698
|
+
return handleError(error);
|
|
475
699
|
}
|
|
476
700
|
finally {
|
|
477
701
|
if (connection) {
|
|
478
702
|
await this.retConnection(connection);
|
|
479
703
|
}
|
|
480
704
|
}
|
|
481
|
-
return ids;
|
|
482
705
|
}
|
|
483
706
|
async addDocuments(documents, options) {
|
|
484
707
|
const texts = documents.map(({ pageContent }) => pageContent);
|
|
@@ -495,10 +718,9 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
495
718
|
const docsScoresAndEmbeddings = [];
|
|
496
719
|
let connection = null;
|
|
497
720
|
try {
|
|
498
|
-
const
|
|
499
|
-
const bindValues = [convertedEmbedding];
|
|
721
|
+
const bindValues = [this.prepareQueryVector(query)];
|
|
500
722
|
let sqlQuery = `
|
|
501
|
-
SELECT
|
|
723
|
+
SELECT external_id,
|
|
502
724
|
text,
|
|
503
725
|
metadata,
|
|
504
726
|
vector_distance(embedding, :1, ${this.distanceStrategy}) as distance,
|
|
@@ -523,11 +745,11 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
523
745
|
const text = row[1];
|
|
524
746
|
const metadata = row[2];
|
|
525
747
|
const distance = row[3];
|
|
526
|
-
const embedding = row[4];
|
|
748
|
+
const embedding = this.normalizeReturnedEmbedding(row[4]);
|
|
527
749
|
const document = new documents_1.Document({
|
|
528
750
|
pageContent: text || "",
|
|
529
751
|
metadata: metadata || {},
|
|
530
|
-
id: row[0]
|
|
752
|
+
id: row[0],
|
|
531
753
|
});
|
|
532
754
|
docsScoresAndEmbeddings.push([document, distance, embedding]);
|
|
533
755
|
}
|
|
@@ -586,10 +808,8 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
586
808
|
// Split documents, scores, and embeddings
|
|
587
809
|
const documents = docsScoresEmbeddings.map(([document]) => document);
|
|
588
810
|
const scores = docsScoresEmbeddings.map(([, score]) => score);
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
const consistentEmbeddings = embeddings.map((embedding) => Array.from(embedding));
|
|
592
|
-
const queryEmbedding = Array.from(embedding);
|
|
811
|
+
const consistentEmbeddings = docsScoresEmbeddings.map(([, , emb]) => Array.from(this.coerceEmbeddingToFloat32(emb)));
|
|
812
|
+
const queryEmbedding = Array.from(this.coerceEmbeddingToFloat32(embedding));
|
|
593
813
|
// Ensure lambdaMult has a default value if not provided
|
|
594
814
|
const lambdaMult = options.lambda ?? 0.5;
|
|
595
815
|
const mmrSelectedIndices = (0, math_1.maximalMarginalRelevance)(queryEmbedding, consistentEmbeddings, lambdaMult, options.k);
|
|
@@ -626,7 +846,7 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
626
846
|
await connection.close();
|
|
627
847
|
}
|
|
628
848
|
}
|
|
629
|
-
static async fromDocuments(documents, embeddings, dbConfig) {
|
|
849
|
+
static async fromDocuments(documents, embeddings, dbConfig, options) {
|
|
630
850
|
const { client } = dbConfig;
|
|
631
851
|
if (!client)
|
|
632
852
|
throw new Error("client parameter is required...");
|
|
@@ -636,7 +856,7 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
636
856
|
const texts = documents.map(({ pageContent }) => pageContent);
|
|
637
857
|
const vectors = await embeddings.embedDocuments(texts);
|
|
638
858
|
// Assuming a method exists to handle adding texts and metadata appropriately
|
|
639
|
-
await vss.addVectors(vectors, documents);
|
|
859
|
+
await vss.addVectors(vectors, documents, options);
|
|
640
860
|
return vss;
|
|
641
861
|
}
|
|
642
862
|
catch (error) {
|