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