@oracle/langchain-oracledb 0.1.1 → 1.0.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/README.md +7 -1
- package/dist/document_loaders.cjs +4 -3
- package/dist/document_loaders.cjs.map +1 -1
- package/dist/document_loaders.js +4 -3
- package/dist/document_loaders.js.map +1 -1
- package/dist/errors.cjs +99 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.cts +50 -0
- package/dist/errors.d.ts +50 -0
- package/dist/errors.js +90 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/vectorstores.cjs +463 -203
- package/dist/vectorstores.cjs.map +1 -1
- package/dist/vectorstores.d.cts +68 -10
- package/dist/vectorstores.d.ts +68 -10
- package/dist/vectorstores.js +459 -201
- package/dist/vectorstores.js.map +1 -1
- package/package.json +24 -23
package/dist/vectorstores.cjs
CHANGED
|
@@ -3,12 +3,24 @@ 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 = exports.isLangChainOracleError = exports.OracleVSErrorCode = exports.OracleVSError = exports.ErrorCode = exports.LangChainOracleError = 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");
|
|
12
|
+
const errors_js_1 = require("./errors.cjs");
|
|
13
|
+
Object.defineProperty(exports, "LangChainOracleError", { enumerable: true, get: function () { return errors_js_1.LangChainOracleError; } });
|
|
14
|
+
Object.defineProperty(exports, "OracleVSError", { enumerable: true, get: function () { return errors_js_1.LangChainOracleError; } });
|
|
15
|
+
Object.defineProperty(exports, "ErrorCode", { enumerable: true, get: function () { return errors_js_1.ErrorCode; } });
|
|
16
|
+
Object.defineProperty(exports, "OracleVSErrorCode", { enumerable: true, get: function () { return errors_js_1.ErrorCode; } });
|
|
17
|
+
Object.defineProperty(exports, "isLangChainOracleError", { enumerable: true, get: function () { return errors_js_1.isLangChainOracleError; } });
|
|
18
|
+
function validateMetadataKey(column) {
|
|
19
|
+
const pattern = /^[a-zA-Z0-9_.[\],\s*]*$/;
|
|
20
|
+
if (!column || !pattern.test(column)) {
|
|
21
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.FILTER_INVALID_METADATA_KEY, column);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
12
24
|
function generateWhereClause(dbFilter, bindValues) {
|
|
13
25
|
// Handle $and
|
|
14
26
|
if ("$and" in dbFilter && Array.isArray(dbFilter.$and)) {
|
|
@@ -44,6 +56,7 @@ function generateWhereClause(dbFilter, bindValues) {
|
|
|
44
56
|
}
|
|
45
57
|
exports.generateWhereClause = generateWhereClause;
|
|
46
58
|
function generateOperatorCondition(column, operator, value, bindValues) {
|
|
59
|
+
validateMetadataKey(column);
|
|
47
60
|
switch (operator) {
|
|
48
61
|
case "$eq":
|
|
49
62
|
return jsonCompare(column, "=", value, bindValues);
|
|
@@ -58,20 +71,23 @@ function generateOperatorCondition(column, operator, value, bindValues) {
|
|
|
58
71
|
case "$gte":
|
|
59
72
|
return jsonCompare(column, ">=", value, bindValues);
|
|
60
73
|
case "$in": {
|
|
61
|
-
if (!Array.isArray(value))
|
|
62
|
-
|
|
74
|
+
if (!Array.isArray(value)) {
|
|
75
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.FILTER_INVALID_VALUE, "$in requires array");
|
|
76
|
+
}
|
|
63
77
|
const inClauses = value.map((v) => jsonCompare(column, "=", v, bindValues));
|
|
64
78
|
return `(${inClauses.join(" OR ")})`;
|
|
65
79
|
}
|
|
66
80
|
case "$nin": {
|
|
67
|
-
if (!Array.isArray(value))
|
|
68
|
-
|
|
81
|
+
if (!Array.isArray(value)) {
|
|
82
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.FILTER_INVALID_VALUE, "$nin requires array");
|
|
83
|
+
}
|
|
69
84
|
const ninClauses = value.map((v) => jsonCompare(column, "!=", v, bindValues));
|
|
70
85
|
return `(${ninClauses.join(" AND ")})`;
|
|
71
86
|
}
|
|
72
87
|
case "$between": {
|
|
73
|
-
if (!Array.isArray(value) || value.length !== 2)
|
|
74
|
-
|
|
88
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
89
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.FILTER_INVALID_VALUE, "$between requires [low, high]");
|
|
90
|
+
}
|
|
75
91
|
const [low, high] = value;
|
|
76
92
|
bindValues.push(low, high);
|
|
77
93
|
const posLow = bindValues.length - 1;
|
|
@@ -86,7 +102,7 @@ function generateOperatorCondition(column, operator, value, bindValues) {
|
|
|
86
102
|
return `NOT JSON_EXISTS(metadata, '$.${column}')`;
|
|
87
103
|
}
|
|
88
104
|
default:
|
|
89
|
-
|
|
105
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.FILTER_UNSUPPORTED_OPERATOR, operator);
|
|
90
106
|
}
|
|
91
107
|
}
|
|
92
108
|
// Helper: build JSON_EXISTS clause for comparison
|
|
@@ -97,6 +113,17 @@ function jsonCompare(column, op, value, bindValues) {
|
|
|
97
113
|
const operator = op === "=" ? "==" : op; // Oracle requires '==' for equality
|
|
98
114
|
return `JSON_EXISTS(metadata, '$.${column}?(@ ${operator} $${alias})' PASSING :${pos} AS "${alias}")`;
|
|
99
115
|
}
|
|
116
|
+
exports.VectorType = {
|
|
117
|
+
DENSE: "DENSE",
|
|
118
|
+
SPARSE: "SPARSE",
|
|
119
|
+
};
|
|
120
|
+
exports.VectorElementFormat = {
|
|
121
|
+
INT8: "INT8",
|
|
122
|
+
FLOAT32: "FLOAT32",
|
|
123
|
+
FLOAT64: "FLOAT64",
|
|
124
|
+
BINARY: "BINARY",
|
|
125
|
+
FLEX: "*",
|
|
126
|
+
};
|
|
100
127
|
exports.DistanceStrategy = {
|
|
101
128
|
COSINE: "COSINE",
|
|
102
129
|
DOT_PRODUCT: "DOT",
|
|
@@ -106,32 +133,33 @@ exports.DistanceStrategy = {
|
|
|
106
133
|
EUCLIDEAN_SQUARED: "EUCLIDEAN_SQUARED",
|
|
107
134
|
};
|
|
108
135
|
function handleError(error) {
|
|
109
|
-
//
|
|
110
|
-
|
|
136
|
+
// Preserve LangChainOracleError instances created by this package instead of
|
|
137
|
+
// wrapping them again as generic SYSTEM_ERROR failures.
|
|
138
|
+
if ((0, errors_js_1.isLangChainOracleError)(error)) {
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
// Preserve a useful message for both real Error instances and thrown
|
|
142
|
+
// object literals shaped like { message: string }.
|
|
143
|
+
const details = typeof error === "object" &&
|
|
111
144
|
error !== null &&
|
|
112
|
-
"
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
throw new Error("Database operation failed due to a runtime error.");
|
|
119
|
-
case "ValidationError":
|
|
120
|
-
throw new Error("Operation failed due to a validation error.");
|
|
121
|
-
default:
|
|
122
|
-
throw new Error(`An unexpected error occurred during the operation. ${error}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
throw new Error(`An unknown and unexpected error occurred. ${error}`);
|
|
145
|
+
"message" in error &&
|
|
146
|
+
typeof error.message === "string"
|
|
147
|
+
? error.message
|
|
148
|
+
: String(error);
|
|
149
|
+
// Normalize all underlying anomalies into a unified telemetry payload.
|
|
150
|
+
throw (0, errors_js_1.createErrorFromCodeWithCause)(errors_js_1.ErrorCode.SYSTEM_ERROR, error, `An unexpected error occurred during the operation. ${details}`);
|
|
126
151
|
}
|
|
127
152
|
function isPool(client) {
|
|
128
153
|
return "getConnection" in client;
|
|
129
154
|
}
|
|
155
|
+
function isClientProvider(client) {
|
|
156
|
+
return typeof client === "function";
|
|
157
|
+
}
|
|
130
158
|
function quoteIdentifier(identifier) {
|
|
131
159
|
const name = identifier.trim();
|
|
132
160
|
const validateRegex = /^(?:"[^"]+"|[^".]+)(?:\.(?:"[^"]+"|[^".]+))*$/;
|
|
133
161
|
if (!validateRegex.test(name)) {
|
|
134
|
-
|
|
162
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VALIDATION_INVALID_IDENTIFIER, identifier);
|
|
135
163
|
}
|
|
136
164
|
// extracts parts of the identifier with quoted and unquoted.
|
|
137
165
|
const matchRegex = /"([^"]+)"|([^".]+)/g;
|
|
@@ -142,10 +170,78 @@ function quoteIdentifier(identifier) {
|
|
|
142
170
|
const quotedParts = groups.map((g) => `"${g}"`);
|
|
143
171
|
return quotedParts.join(".");
|
|
144
172
|
}
|
|
145
|
-
|
|
173
|
+
const isFloat32Array = (value) =>
|
|
174
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
175
|
+
value instanceof Float32Array;
|
|
176
|
+
const isFloat64Array = (value) =>
|
|
177
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
178
|
+
value instanceof Float64Array;
|
|
179
|
+
const isInt8Array = (value) =>
|
|
180
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
181
|
+
value instanceof Int8Array;
|
|
182
|
+
const isUint8Array = (value) =>
|
|
183
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
184
|
+
value instanceof Uint8Array;
|
|
185
|
+
const isSparseVector = (value) =>
|
|
186
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
187
|
+
value instanceof oracledb_1.default.SparseVector;
|
|
188
|
+
function normalizeVectorTypeValue(value) {
|
|
189
|
+
const normalized = value?.toUpperCase();
|
|
190
|
+
if (normalized === exports.VectorType.DENSE || normalized === exports.VectorType.SPARSE) {
|
|
191
|
+
return normalized;
|
|
192
|
+
}
|
|
193
|
+
return exports.VectorType.DENSE;
|
|
194
|
+
}
|
|
195
|
+
function normalizeVectorFormat(value, vectorType) {
|
|
196
|
+
if (!value) {
|
|
197
|
+
return exports.VectorElementFormat.FLOAT32;
|
|
198
|
+
}
|
|
199
|
+
const normalizedValue = value === exports.VectorElementFormat.FLEX
|
|
200
|
+
? exports.VectorElementFormat.FLEX
|
|
201
|
+
: value.toUpperCase();
|
|
202
|
+
const validFormats = Object.values(exports.VectorElementFormat);
|
|
203
|
+
const format = validFormats.includes(normalizedValue)
|
|
204
|
+
? normalizedValue
|
|
205
|
+
: exports.VectorElementFormat.FLOAT32;
|
|
206
|
+
if (vectorType === exports.VectorType.SPARSE && format === exports.VectorElementFormat.BINARY) {
|
|
207
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_INVALID_CONFIGURATION, "BINARY format is not supported for SPARSE vectors.");
|
|
208
|
+
}
|
|
209
|
+
return format;
|
|
210
|
+
}
|
|
211
|
+
function buildVectorColumnDefinition(embeddingDim, customization) {
|
|
212
|
+
if (embeddingDim === undefined || embeddingDim === null) {
|
|
213
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_INVALID_CONFIGURATION, "Embedding dimension is required to create the vector column.");
|
|
214
|
+
}
|
|
215
|
+
if (!Number.isInteger(embeddingDim) || embeddingDim <= 0) {
|
|
216
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_INVALID_CONFIGURATION, "Embedding dimension must be a positive integer.");
|
|
217
|
+
}
|
|
218
|
+
const vectorType = normalizeVectorTypeValue(customization?.vectorType);
|
|
219
|
+
if (vectorType === exports.VectorType.SPARSE && customization?.format === undefined) {
|
|
220
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_INVALID_CONFIGURATION, "Sparse vector type requires a vector format to be specified.");
|
|
221
|
+
}
|
|
222
|
+
const format = normalizeVectorFormat(customization?.format, vectorType);
|
|
223
|
+
if (format === exports.VectorElementFormat.BINARY && embeddingDim % 8 !== 0) {
|
|
224
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_INVALID_CONFIGURATION, "BINARY vector format requires dimensions to be a multiple of 8.");
|
|
225
|
+
}
|
|
226
|
+
const dimensionSegment = String(embeddingDim);
|
|
227
|
+
const formatSegment = format ?? exports.VectorElementFormat.FLOAT32;
|
|
228
|
+
if (vectorType === exports.VectorType.SPARSE) {
|
|
229
|
+
return `VECTOR(${dimensionSegment}, ${formatSegment}, SPARSE)`;
|
|
230
|
+
}
|
|
231
|
+
return `VECTOR(${dimensionSegment}, ${formatSegment})`;
|
|
232
|
+
}
|
|
233
|
+
function escapeCommentText(comment) {
|
|
234
|
+
return comment.replace(/'/g, "''");
|
|
235
|
+
}
|
|
236
|
+
function escapePlSqlLiteral(sql) {
|
|
237
|
+
return sql.replace(/'/g, "''");
|
|
238
|
+
}
|
|
239
|
+
async function createTable(connection, tableName, embeddingDim, customization) {
|
|
240
|
+
const tableIdentifier = quoteIdentifier(tableName);
|
|
146
241
|
const colsDict = {
|
|
147
242
|
id: "RAW(16) DEFAULT SYS_GUID() PRIMARY KEY",
|
|
148
|
-
|
|
243
|
+
external_id: `VARCHAR2(36) UNIQUE`,
|
|
244
|
+
embedding: buildVectorColumnDefinition(embeddingDim, customization),
|
|
149
245
|
text: "CLOB",
|
|
150
246
|
metadata: "JSON",
|
|
151
247
|
};
|
|
@@ -153,11 +249,33 @@ async function createTable(connection, tableName, embeddingDim) {
|
|
|
153
249
|
const ddlBody = Object.entries(colsDict)
|
|
154
250
|
.map(([colName, colType]) => `${colName} ${colType}`)
|
|
155
251
|
.join(", ");
|
|
156
|
-
const
|
|
252
|
+
const createTableSql = `CREATE TABLE IF NOT EXISTS ${tableIdentifier}
|
|
157
253
|
(
|
|
158
254
|
${ddlBody}
|
|
159
255
|
)`;
|
|
160
|
-
|
|
256
|
+
const statements = [
|
|
257
|
+
`EXECUTE IMMEDIATE '${escapePlSqlLiteral(createTableSql)}';`,
|
|
258
|
+
];
|
|
259
|
+
const tableDescription = customization?.description?.trim();
|
|
260
|
+
if (tableDescription) {
|
|
261
|
+
const escapedDescription = escapeCommentText(tableDescription);
|
|
262
|
+
statements.push(`EXECUTE IMMEDIATE 'COMMENT ON TABLE ${tableIdentifier} IS ''${escapedDescription}''';`);
|
|
263
|
+
}
|
|
264
|
+
if (customization?.annotations) {
|
|
265
|
+
for (const [columnName, note] of Object.entries(customization.annotations)) {
|
|
266
|
+
const trimmedNote = note?.trim();
|
|
267
|
+
if (!trimmedNote)
|
|
268
|
+
continue;
|
|
269
|
+
const columnKey = columnName.trim().toLowerCase();
|
|
270
|
+
if (!(columnKey in colsDict))
|
|
271
|
+
continue;
|
|
272
|
+
const normalizedColumnIdentifier = `${tableIdentifier}.${quoteIdentifier(columnKey.toUpperCase())}`;
|
|
273
|
+
const escapedNote = escapeCommentText(trimmedNote);
|
|
274
|
+
statements.push(`EXECUTE IMMEDIATE 'COMMENT ON COLUMN ${normalizedColumnIdentifier} IS ''${escapedNote}''';`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const block = `BEGIN\n ${statements.join("\n ")}\nEND;`;
|
|
278
|
+
await connection.execute(block);
|
|
161
279
|
}
|
|
162
280
|
catch (error) {
|
|
163
281
|
handleError(error);
|
|
@@ -165,11 +283,63 @@ async function createTable(connection, tableName, embeddingDim) {
|
|
|
165
283
|
}
|
|
166
284
|
exports.createTable = createTable;
|
|
167
285
|
function _getIndexName(baseName) {
|
|
168
|
-
const uniqueId =
|
|
286
|
+
const uniqueId = node_crypto_1.default.randomUUID().replace(/-/g, "");
|
|
169
287
|
return `${baseName}_${uniqueId}`;
|
|
170
288
|
}
|
|
289
|
+
// Packs an array of 0/1 numbers into a big-endian Uint8Array for BINARY VECTOR storage.
|
|
290
|
+
function packBinaryVector(bits) {
|
|
291
|
+
const byteLength = Math.ceil(bits.length / 8);
|
|
292
|
+
const buffer = new Uint8Array(byteLength);
|
|
293
|
+
for (let i = 0; i < bits.length; i += 1) {
|
|
294
|
+
const bit = bits[i] > 0 ? 1 : 0;
|
|
295
|
+
if (bit === 1) {
|
|
296
|
+
const byteIndex = Math.floor(i / 8);
|
|
297
|
+
const bitOffset = 7 - (i % 8);
|
|
298
|
+
buffer[byteIndex] |= 1 << bitOffset;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return buffer;
|
|
302
|
+
}
|
|
303
|
+
// Converts a dense numeric vector into the typed array that matches the configured element format.
|
|
304
|
+
function convertDenseVectorForFormat(values, format) {
|
|
305
|
+
switch (format) {
|
|
306
|
+
case exports.VectorElementFormat.FLOAT32:
|
|
307
|
+
case undefined:
|
|
308
|
+
return new Float32Array(values);
|
|
309
|
+
case exports.VectorElementFormat.FLOAT64:
|
|
310
|
+
return new Float64Array(values);
|
|
311
|
+
case exports.VectorElementFormat.INT8: {
|
|
312
|
+
const clamped = new Int8Array(values.length);
|
|
313
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
314
|
+
const rounded = Math.round(values[i]);
|
|
315
|
+
if (rounded < -128 || rounded > 127) {
|
|
316
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_INVALID_VALUE, "INT8 vector values must be within [-128, 127].");
|
|
317
|
+
}
|
|
318
|
+
clamped[i] = rounded;
|
|
319
|
+
}
|
|
320
|
+
return clamped;
|
|
321
|
+
}
|
|
322
|
+
case exports.VectorElementFormat.BINARY:
|
|
323
|
+
return packBinaryVector(values);
|
|
324
|
+
default:
|
|
325
|
+
return new Float64Array(values);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function unpackBinaryVector(bytesLike, dimension) {
|
|
329
|
+
const bytes = isUint8Array(bytesLike)
|
|
330
|
+
? bytesLike
|
|
331
|
+
: Uint8Array.from(bytesLike);
|
|
332
|
+
const result = new Float32Array(dimension);
|
|
333
|
+
for (let i = 0; i < dimension; i += 1) {
|
|
334
|
+
const byteIndex = Math.floor(i / 8);
|
|
335
|
+
const bitOffset = 7 - (i % 8);
|
|
336
|
+
const byte = bytes[byteIndex] ?? 0;
|
|
337
|
+
result[i] = (byte >> bitOffset) & 1;
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
171
341
|
async function createIndex(client, vectorStore, params) {
|
|
172
|
-
const idxType = params?.idxType
|
|
342
|
+
const idxType = (params?.idxType ?? "HNSW").toUpperCase();
|
|
173
343
|
if (idxType === "IVF") {
|
|
174
344
|
await createIVFIndex(client, vectorStore, params);
|
|
175
345
|
}
|
|
@@ -178,122 +348,48 @@ async function createIndex(client, vectorStore, params) {
|
|
|
178
348
|
}
|
|
179
349
|
}
|
|
180
350
|
exports.createIndex = createIndex;
|
|
181
|
-
async function createHNSWIndex(connection, oraclevs, params) {
|
|
351
|
+
async function createHNSWIndex(connection, oraclevs, params = {}) {
|
|
182
352
|
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;
|
|
353
|
+
const { idxName = _getIndexName("HNSW"), idxType = "HNSW", neighbors = 32, efConstruction = 200, accuracy = 90, parallel = 8, ...extra } = params;
|
|
354
|
+
const invalidKeys = Object.keys(extra);
|
|
355
|
+
if (invalidKeys.length > 0) {
|
|
356
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_INVALID_INDEX_PARAMETERS, invalidKeys);
|
|
357
|
+
}
|
|
358
|
+
const ddl = `
|
|
359
|
+
CREATE VECTOR INDEX IF NOT EXISTS ${quoteIdentifier(idxName)}
|
|
360
|
+
ON ${oraclevs.tableName}(embedding)
|
|
361
|
+
ORGANIZATION INMEMORY NEIGHBOR GRAPH
|
|
362
|
+
WITH TARGET ACCURACY ${accuracy}
|
|
363
|
+
DISTANCE ${oraclevs.distanceStrategy}
|
|
364
|
+
PARAMETERS (type ${idxType}, neighbors ${neighbors}, efConstruction ${efConstruction})
|
|
365
|
+
PARALLEL ${parallel}
|
|
366
|
+
`
|
|
367
|
+
.trim()
|
|
368
|
+
.replace(/\s+/g, " ");
|
|
241
369
|
await connection.execute(ddl);
|
|
242
370
|
}
|
|
243
371
|
catch (error) {
|
|
244
372
|
handleError(error);
|
|
245
373
|
}
|
|
246
374
|
}
|
|
247
|
-
async function createIVFIndex(connection, oraclevs, params) {
|
|
375
|
+
async function createIVFIndex(connection, oraclevs, params = {}) {
|
|
248
376
|
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;
|
|
377
|
+
const { idxName = _getIndexName("IVF"), idxType = "IVF", neighborPart = 32, accuracy = 90, parallel = 8, ...extra } = params;
|
|
378
|
+
const invalidKeys = Object.keys(extra);
|
|
379
|
+
if (invalidKeys.length > 0) {
|
|
380
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_INVALID_INDEX_PARAMETERS, invalidKeys);
|
|
381
|
+
}
|
|
382
|
+
const ddl = `
|
|
383
|
+
CREATE VECTOR INDEX IF NOT EXISTS ${quoteIdentifier(idxName)}
|
|
384
|
+
ON ${oraclevs.tableName}(embedding)
|
|
385
|
+
ORGANIZATION NEIGHBOR PARTITIONS
|
|
386
|
+
WITH TARGET ACCURACY ${accuracy}
|
|
387
|
+
DISTANCE ${oraclevs.distanceStrategy}
|
|
388
|
+
PARAMETERS (type ${idxType}, neighbor partitions ${neighborPart})
|
|
389
|
+
PARALLEL ${parallel}
|
|
390
|
+
`
|
|
391
|
+
.trim()
|
|
392
|
+
.replace(/\s+/g, " ");
|
|
297
393
|
await connection.execute(ddl);
|
|
298
394
|
}
|
|
299
395
|
catch (error) {
|
|
@@ -322,6 +418,14 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
322
418
|
writable: true,
|
|
323
419
|
value: void 0
|
|
324
420
|
});
|
|
421
|
+
// Tracks connections borrowed from a pool so retConnection() can return them
|
|
422
|
+
// without closing direct connections owned by the caller.
|
|
423
|
+
Object.defineProperty(this, "poolConnections", {
|
|
424
|
+
enumerable: true,
|
|
425
|
+
configurable: true,
|
|
426
|
+
writable: true,
|
|
427
|
+
value: new WeakSet()
|
|
428
|
+
});
|
|
325
429
|
Object.defineProperty(this, "embeddingDimension", {
|
|
326
430
|
enumerable: true,
|
|
327
431
|
configurable: true,
|
|
@@ -346,6 +450,30 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
346
450
|
writable: true,
|
|
347
451
|
value: void 0
|
|
348
452
|
});
|
|
453
|
+
Object.defineProperty(this, "description", {
|
|
454
|
+
enumerable: true,
|
|
455
|
+
configurable: true,
|
|
456
|
+
writable: true,
|
|
457
|
+
value: void 0
|
|
458
|
+
});
|
|
459
|
+
Object.defineProperty(this, "annotations", {
|
|
460
|
+
enumerable: true,
|
|
461
|
+
configurable: true,
|
|
462
|
+
writable: true,
|
|
463
|
+
value: void 0
|
|
464
|
+
});
|
|
465
|
+
Object.defineProperty(this, "vectorType", {
|
|
466
|
+
enumerable: true,
|
|
467
|
+
configurable: true,
|
|
468
|
+
writable: true,
|
|
469
|
+
value: void 0
|
|
470
|
+
});
|
|
471
|
+
Object.defineProperty(this, "vectorFormat", {
|
|
472
|
+
enumerable: true,
|
|
473
|
+
configurable: true,
|
|
474
|
+
writable: true,
|
|
475
|
+
value: void 0
|
|
476
|
+
});
|
|
349
477
|
Object.defineProperty(this, "query", {
|
|
350
478
|
enumerable: true,
|
|
351
479
|
configurable: true,
|
|
@@ -359,11 +487,120 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
359
487
|
dbConfig.distanceStrategy ?? this.distanceStrategy;
|
|
360
488
|
this.query = dbConfig.query;
|
|
361
489
|
this.filter = dbConfig.filter;
|
|
490
|
+
this.description = dbConfig.description;
|
|
491
|
+
this.annotations = dbConfig.annotations
|
|
492
|
+
? { ...dbConfig.annotations }
|
|
493
|
+
: undefined;
|
|
494
|
+
this.vectorType = dbConfig.vectorType;
|
|
495
|
+
this.vectorFormat = dbConfig.format;
|
|
362
496
|
}
|
|
363
497
|
catch (error) {
|
|
364
498
|
handleError(error);
|
|
365
499
|
}
|
|
366
500
|
}
|
|
501
|
+
getActiveVectorType() {
|
|
502
|
+
return this.vectorType ? normalizeVectorTypeValue(this.vectorType) : exports.VectorType.DENSE;
|
|
503
|
+
}
|
|
504
|
+
ensureEmbeddingDimension() {
|
|
505
|
+
if (this.embeddingDimension === undefined || this.embeddingDimension === null) {
|
|
506
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.STATE_INVALID, "Embedding dimension is not initialized for this vector store.");
|
|
507
|
+
}
|
|
508
|
+
return this.embeddingDimension;
|
|
509
|
+
}
|
|
510
|
+
prepareVectorForStorage(vector) {
|
|
511
|
+
if (this.embeddingDimension === undefined || this.embeddingDimension === null) {
|
|
512
|
+
this.embeddingDimension = vector.length;
|
|
513
|
+
}
|
|
514
|
+
const dimension = this.ensureEmbeddingDimension();
|
|
515
|
+
const vectorType = this.getActiveVectorType();
|
|
516
|
+
const format = this.vectorFormat
|
|
517
|
+
? normalizeVectorFormat(this.vectorFormat, vectorType)
|
|
518
|
+
: exports.VectorElementFormat.FLOAT32;
|
|
519
|
+
if (vectorType === exports.VectorType.SPARSE) {
|
|
520
|
+
if (vector.length !== dimension) {
|
|
521
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_INVALID_VALUE, "Sparse vectors must supply full-dimension arrays for conversion.");
|
|
522
|
+
}
|
|
523
|
+
let sparseInput = vector;
|
|
524
|
+
if (format === exports.VectorElementFormat.INT8) {
|
|
525
|
+
const clamped = new Int8Array(vector.length);
|
|
526
|
+
for (let i = 0; i < vector.length; i += 1) {
|
|
527
|
+
const rounded = Math.round(vector[i]);
|
|
528
|
+
if (rounded < -128 || rounded > 127) {
|
|
529
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_INVALID_VALUE, "INT8 sparse vector values must be within [-128, 127].");
|
|
530
|
+
}
|
|
531
|
+
clamped[i] = rounded;
|
|
532
|
+
}
|
|
533
|
+
sparseInput = clamped;
|
|
534
|
+
}
|
|
535
|
+
else if (format === exports.VectorElementFormat.FLOAT64) {
|
|
536
|
+
sparseInput = new Float64Array(vector);
|
|
537
|
+
}
|
|
538
|
+
else if (format === exports.VectorElementFormat.FLOAT32) {
|
|
539
|
+
sparseInput = new Float32Array(vector);
|
|
540
|
+
}
|
|
541
|
+
return new oracledb_1.default.SparseVector(sparseInput);
|
|
542
|
+
}
|
|
543
|
+
if (vector.length !== dimension) {
|
|
544
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_INVALID_VALUE, "Vector length does not match the embedding dimension.");
|
|
545
|
+
}
|
|
546
|
+
return convertDenseVectorForFormat(vector, format);
|
|
547
|
+
}
|
|
548
|
+
prepareQueryVector(vector) {
|
|
549
|
+
return this.prepareVectorForStorage(vector);
|
|
550
|
+
}
|
|
551
|
+
// Preserves the native driver representation, only expanding sparse vectors to dense form.
|
|
552
|
+
// Preserves the native driver representation, only expanding sparse vectors to dense form.
|
|
553
|
+
normalizeReturnedEmbedding(value) {
|
|
554
|
+
if (isSparseVector(value)) {
|
|
555
|
+
const dense = value.dense?.();
|
|
556
|
+
if (!dense) {
|
|
557
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_UNSUPPORTED_REPRESENTATION, "Unable to expand sparse vector to dense representation.");
|
|
558
|
+
}
|
|
559
|
+
if (isFloat32Array(dense)) {
|
|
560
|
+
return dense;
|
|
561
|
+
}
|
|
562
|
+
if (isFloat64Array(dense)) {
|
|
563
|
+
return new Float32Array(dense);
|
|
564
|
+
}
|
|
565
|
+
if (isInt8Array(dense)) {
|
|
566
|
+
return Float32Array.from(dense);
|
|
567
|
+
}
|
|
568
|
+
if (Array.isArray(dense)) {
|
|
569
|
+
return Float32Array.from(dense);
|
|
570
|
+
}
|
|
571
|
+
return Float32Array.from(dense);
|
|
572
|
+
}
|
|
573
|
+
if (isFloat32Array(value) || isFloat64Array(value) || isInt8Array(value) || isUint8Array(value)) {
|
|
574
|
+
return value;
|
|
575
|
+
}
|
|
576
|
+
if (Array.isArray(value)) {
|
|
577
|
+
return value;
|
|
578
|
+
}
|
|
579
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VECTOR_UNSUPPORTED_REPRESENTATION, "Received unsupported vector representation from the database.");
|
|
580
|
+
}
|
|
581
|
+
// Normalizes any supported vector representation into Float32Array so downstream math
|
|
582
|
+
// (MMR, distance calculations) can assume a consistent element type without forcing
|
|
583
|
+
// callers to give up the native shape returned by node-oracledb.
|
|
584
|
+
coerceEmbeddingToFloat32(value) {
|
|
585
|
+
if (isFloat32Array(value)) {
|
|
586
|
+
return value;
|
|
587
|
+
}
|
|
588
|
+
if (isFloat64Array(value)) {
|
|
589
|
+
return new Float32Array(value);
|
|
590
|
+
}
|
|
591
|
+
if (isInt8Array(value)) {
|
|
592
|
+
return Float32Array.from(value);
|
|
593
|
+
}
|
|
594
|
+
if (isUint8Array(value)) {
|
|
595
|
+
const byteLength = value.length;
|
|
596
|
+
const dimension = this.embeddingDimension ?? byteLength * 8;
|
|
597
|
+
return unpackBinaryVector(value, dimension);
|
|
598
|
+
}
|
|
599
|
+
if (Array.isArray(value)) {
|
|
600
|
+
return Float32Array.from(value);
|
|
601
|
+
}
|
|
602
|
+
return this.coerceEmbeddingToFloat32(this.normalizeReturnedEmbedding(value));
|
|
603
|
+
}
|
|
367
604
|
async getEmbeddingDimension(query) {
|
|
368
605
|
const embeddingVector = await this.embeddings.embedQuery(query);
|
|
369
606
|
return embeddingVector.length;
|
|
@@ -373,7 +610,12 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
373
610
|
try {
|
|
374
611
|
this.embeddingDimension = await this.getEmbeddingDimension(this.query);
|
|
375
612
|
connection = await this.getConnection();
|
|
376
|
-
await createTable(connection, this.tableName, this.embeddingDimension
|
|
613
|
+
await createTable(connection, this.tableName, this.embeddingDimension, {
|
|
614
|
+
description: this.description,
|
|
615
|
+
annotations: this.annotations,
|
|
616
|
+
vectorType: this.vectorType,
|
|
617
|
+
format: this.vectorFormat
|
|
618
|
+
});
|
|
377
619
|
}
|
|
378
620
|
catch (error) {
|
|
379
621
|
handleError(error);
|
|
@@ -383,12 +625,20 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
383
625
|
await this.retConnection(connection);
|
|
384
626
|
}
|
|
385
627
|
}
|
|
628
|
+
async resolveClient() {
|
|
629
|
+
return isClientProvider(this.client)
|
|
630
|
+
? await this.client()
|
|
631
|
+
: this.client;
|
|
632
|
+
}
|
|
386
633
|
async getConnection() {
|
|
387
634
|
try {
|
|
388
|
-
|
|
389
|
-
|
|
635
|
+
const client = await this.resolveClient();
|
|
636
|
+
if (isPool(client)) {
|
|
637
|
+
const connection = await client.getConnection();
|
|
638
|
+
this.poolConnections.add(connection);
|
|
639
|
+
return connection;
|
|
390
640
|
}
|
|
391
|
-
return
|
|
641
|
+
return client;
|
|
392
642
|
}
|
|
393
643
|
catch (error) {
|
|
394
644
|
handleError(error);
|
|
@@ -397,9 +647,10 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
397
647
|
// Close connection or return it to the pool
|
|
398
648
|
async retConnection(connection) {
|
|
399
649
|
try {
|
|
400
|
-
// If
|
|
401
|
-
if (
|
|
650
|
+
// If this public connection came from any pool, close it to return it to the pool.
|
|
651
|
+
if (this.poolConnections.has(connection)) {
|
|
402
652
|
await connection.close();
|
|
653
|
+
this.poolConnections.delete(connection);
|
|
403
654
|
}
|
|
404
655
|
}
|
|
405
656
|
catch (error) {
|
|
@@ -412,73 +663,79 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
412
663
|
* @param vectors The vectors to add.
|
|
413
664
|
* @param documents The documents associated with the vectors.
|
|
414
665
|
* @param options
|
|
666
|
+
* ** Add { mutateOnDuplicate?: boolean } to do upsert
|
|
415
667
|
* @returns Promise that resolves when the vectors have been added.
|
|
416
668
|
*/
|
|
417
669
|
async addVectors(vectors, documents, options) {
|
|
418
670
|
if (vectors.length === 0) {
|
|
419
|
-
|
|
671
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VALIDATION_INVALID_INPUT, "Vectors input null. Nothing to add...");
|
|
420
672
|
}
|
|
421
|
-
const
|
|
673
|
+
const inputIds = options?.ids;
|
|
422
674
|
let connection = null;
|
|
423
675
|
try {
|
|
424
676
|
// Ensure there are IDs for all documents
|
|
425
|
-
if (
|
|
426
|
-
|
|
677
|
+
if (inputIds !== undefined && inputIds.length !== vectors.length) {
|
|
678
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VALIDATION_INVALID_INPUT, "The number of ids must match the number of vectors provided.");
|
|
427
679
|
}
|
|
428
|
-
|
|
680
|
+
const finalIds = [];
|
|
429
681
|
const binds = [];
|
|
430
682
|
for (let index = 0; index < documents.length; index += 1) {
|
|
431
683
|
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,
|
|
684
|
+
// Generate ID if not provided: Priority (Options -> Metadata -> Random)
|
|
685
|
+
const externalId = inputIds?.[index] ?? doc.metadata.id ?? node_crypto_1.default.randomUUID();
|
|
686
|
+
finalIds.push(externalId);
|
|
687
|
+
const preparedEmbedding = this.prepareVectorForStorage(vectors[index]);
|
|
688
|
+
const row = {
|
|
689
|
+
ext_id: externalId,
|
|
451
690
|
text: doc.pageContent,
|
|
452
691
|
metadata: doc.metadata,
|
|
453
|
-
embedding:
|
|
692
|
+
embedding: preparedEmbedding,
|
|
454
693
|
};
|
|
455
|
-
binds.push(
|
|
694
|
+
binds.push(row);
|
|
456
695
|
}
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
696
|
+
const isMutateOnDuplicate = options?.mutateOnDuplicate ?? false; // Default to false for better performance
|
|
697
|
+
const insertSql = `
|
|
698
|
+
INSERT INTO ${this.tableName} (external_id, embedding, text, metadata)
|
|
699
|
+
VALUES (:ext_id, :embedding, :text, :metadata)`;
|
|
700
|
+
const mergeSql = `
|
|
701
|
+
MERGE INTO ${this.tableName} t
|
|
702
|
+
USING (
|
|
703
|
+
SELECT :ext_id as external_id, :embedding as embedding,
|
|
704
|
+
:metadata as metadata, :text as text FROM DUAL
|
|
705
|
+
) s
|
|
706
|
+
ON (t.external_id = s.external_id)
|
|
707
|
+
WHEN MATCHED THEN
|
|
708
|
+
UPDATE SET
|
|
709
|
+
t.embedding = s.embedding,
|
|
710
|
+
t.metadata = s.metadata,
|
|
711
|
+
t.text = s.text
|
|
712
|
+
WHEN NOT MATCHED THEN
|
|
713
|
+
INSERT (external_id, embedding, metadata, text)
|
|
714
|
+
VALUES (s.external_id, s.embedding, s.metadata, s.text)`;
|
|
715
|
+
const sql = isMutateOnDuplicate ? mergeSql : insertSql;
|
|
716
|
+
const executeOptions = {
|
|
460
717
|
bindDefs: {
|
|
461
|
-
|
|
718
|
+
ext_id: { type: oracledb_1.default.STRING, maxSize: 255 },
|
|
462
719
|
text: { type: oracledb_1.default.STRING, maxSize: 10000000 },
|
|
463
720
|
metadata: { type: oracledb_1.default.DB_TYPE_JSON },
|
|
464
721
|
embedding: { type: oracledb_1.default.DB_TYPE_VECTOR },
|
|
465
722
|
},
|
|
466
723
|
autoCommit: false,
|
|
467
724
|
};
|
|
468
|
-
await
|
|
725
|
+
connection = await this.getConnection();
|
|
726
|
+
await connection.executeMany(sql, binds, executeOptions);
|
|
469
727
|
// Commit once all inserts are queued up
|
|
470
728
|
await connection.commit();
|
|
471
|
-
|
|
729
|
+
return finalIds;
|
|
472
730
|
}
|
|
473
731
|
catch (error) {
|
|
474
|
-
handleError(error);
|
|
732
|
+
return handleError(error);
|
|
475
733
|
}
|
|
476
734
|
finally {
|
|
477
735
|
if (connection) {
|
|
478
736
|
await this.retConnection(connection);
|
|
479
737
|
}
|
|
480
738
|
}
|
|
481
|
-
return ids;
|
|
482
739
|
}
|
|
483
740
|
async addDocuments(documents, options) {
|
|
484
741
|
const texts = documents.map(({ pageContent }) => pageContent);
|
|
@@ -495,16 +752,23 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
495
752
|
const docsScoresAndEmbeddings = [];
|
|
496
753
|
let connection = null;
|
|
497
754
|
try {
|
|
498
|
-
const
|
|
499
|
-
const
|
|
755
|
+
const bindValues = [this.prepareQueryVector(query)];
|
|
756
|
+
const hasFilter = !!filter && Object.keys(filter).length > 0;
|
|
757
|
+
// Keep the vector index hint for unfiltered searches. When a JSON filter
|
|
758
|
+
// is present, forcing VECTOR_INDEX_TRANSFORM can change result semantics
|
|
759
|
+
// by doing approximate top-k before the filter is applied.
|
|
760
|
+
const selectClause = hasFilter
|
|
761
|
+
? "SELECT"
|
|
762
|
+
: `SELECT /*+ VECTOR_INDEX_TRANSFORM(${this.tableName}) */`;
|
|
500
763
|
let sqlQuery = `
|
|
501
|
-
|
|
764
|
+
${selectClause}
|
|
765
|
+
external_id,
|
|
502
766
|
text,
|
|
503
767
|
metadata,
|
|
504
768
|
vector_distance(embedding, :1, ${this.distanceStrategy}) as distance,
|
|
505
769
|
embedding
|
|
506
770
|
FROM ${this.tableName} `;
|
|
507
|
-
if (
|
|
771
|
+
if (hasFilter) {
|
|
508
772
|
sqlQuery += ` WHERE ${generateWhereClause(filter, bindValues)}`;
|
|
509
773
|
}
|
|
510
774
|
bindValues.push(k);
|
|
@@ -523,23 +787,19 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
523
787
|
const text = row[1];
|
|
524
788
|
const metadata = row[2];
|
|
525
789
|
const distance = row[3];
|
|
526
|
-
const embedding = row[4];
|
|
790
|
+
const embedding = this.normalizeReturnedEmbedding(row[4]);
|
|
527
791
|
const document = new documents_1.Document({
|
|
528
792
|
pageContent: text || "",
|
|
529
793
|
metadata: metadata || {},
|
|
530
|
-
id: row[0]
|
|
794
|
+
id: row[0],
|
|
531
795
|
});
|
|
532
796
|
docsScoresAndEmbeddings.push([document, distance, embedding]);
|
|
533
797
|
}
|
|
534
798
|
}
|
|
535
|
-
else {
|
|
536
|
-
// Throw an exception if no rows are found
|
|
537
|
-
throw new Error("No rows found.");
|
|
538
|
-
}
|
|
539
799
|
}
|
|
540
800
|
finally {
|
|
541
801
|
if (connection) {
|
|
542
|
-
await
|
|
802
|
+
await this.retConnection(connection);
|
|
543
803
|
}
|
|
544
804
|
}
|
|
545
805
|
return docsScoresAndEmbeddings;
|
|
@@ -586,10 +846,8 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
586
846
|
// Split documents, scores, and embeddings
|
|
587
847
|
const documents = docsScoresEmbeddings.map(([document]) => document);
|
|
588
848
|
const scores = docsScoresEmbeddings.map(([, score]) => score);
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
const consistentEmbeddings = embeddings.map((embedding) => Array.from(embedding));
|
|
592
|
-
const queryEmbedding = Array.from(embedding);
|
|
849
|
+
const consistentEmbeddings = docsScoresEmbeddings.map(([, , emb]) => Array.from(this.coerceEmbeddingToFloat32(emb)));
|
|
850
|
+
const queryEmbedding = Array.from(this.coerceEmbeddingToFloat32(embedding));
|
|
593
851
|
// Ensure lambdaMult has a default value if not provided
|
|
594
852
|
const lambdaMult = options.lambda ?? 0.5;
|
|
595
853
|
const mmrSelectedIndices = (0, math_1.maximalMarginalRelevance)(queryEmbedding, consistentEmbeddings, lambdaMult, options.k);
|
|
@@ -622,21 +880,23 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
622
880
|
handleError(error);
|
|
623
881
|
}
|
|
624
882
|
finally {
|
|
625
|
-
if (connection)
|
|
626
|
-
await
|
|
883
|
+
if (connection) {
|
|
884
|
+
await this.retConnection(connection);
|
|
885
|
+
}
|
|
627
886
|
}
|
|
628
887
|
}
|
|
629
|
-
static async fromDocuments(documents, embeddings, dbConfig) {
|
|
888
|
+
static async fromDocuments(documents, embeddings, dbConfig, options) {
|
|
630
889
|
const { client } = dbConfig;
|
|
631
|
-
if (!client)
|
|
632
|
-
|
|
890
|
+
if (!client) {
|
|
891
|
+
(0, errors_js_1.throwError)(errors_js_1.ErrorCode.VALIDATION_MISSING_REQUIRED_PARAMETER, "client");
|
|
892
|
+
}
|
|
633
893
|
try {
|
|
634
894
|
const vss = new OracleVS(embeddings, dbConfig);
|
|
635
895
|
await vss.initialize();
|
|
636
896
|
const texts = documents.map(({ pageContent }) => pageContent);
|
|
637
897
|
const vectors = await embeddings.embedDocuments(texts);
|
|
638
898
|
// Assuming a method exists to handle adding texts and metadata appropriately
|
|
639
|
-
await vss.addVectors(vectors, documents);
|
|
899
|
+
await vss.addVectors(vectors, documents, options);
|
|
640
900
|
return vss;
|
|
641
901
|
}
|
|
642
902
|
catch (error) {
|
|
@@ -649,8 +909,8 @@ class OracleVS extends vectorstores_1.VectorStore {
|
|
|
649
909
|
* inside the pool are terminated.
|
|
650
910
|
*/
|
|
651
911
|
async end() {
|
|
652
|
-
if (isPool(this.client)) {
|
|
653
|
-
await this.client
|
|
912
|
+
if (!isClientProvider(this.client) && isPool(this.client)) {
|
|
913
|
+
await this.client.close();
|
|
654
914
|
}
|
|
655
915
|
}
|
|
656
916
|
}
|