@oracle/langchain-oracledb 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/dist/document_loaders.cjs +211 -0
- package/dist/document_loaders.cjs.map +1 -0
- package/dist/document_loaders.d.cts +33 -0
- package/dist/document_loaders.d.ts +33 -0
- package/dist/document_loaders.js +181 -0
- package/dist/document_loaders.js.map +1 -0
- package/dist/embeddings.cjs +129 -0
- package/dist/embeddings.cjs.map +1 -0
- package/dist/embeddings.d.cts +35 -0
- package/dist/embeddings.d.ts +35 -0
- package/dist/embeddings.js +122 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/index.cjs +22 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/summary.cjs +63 -0
- package/dist/summary.cjs.map +1 -0
- package/dist/summary.d.cts +16 -0
- package/dist/summary.d.ts +16 -0
- package/dist/summary.js +56 -0
- package/dist/summary.js.map +1 -0
- package/dist/text_splitter.cjs +58 -0
- package/dist/text_splitter.cjs.map +1 -0
- package/dist/text_splitter.d.cts +17 -0
- package/dist/text_splitter.d.ts +17 -0
- package/dist/text_splitter.js +51 -0
- package/dist/text_splitter.js.map +1 -0
- package/dist/vectorstores.cjs +658 -0
- package/dist/vectorstores.cjs.map +1 -0
- package/dist/vectorstores.d.cts +98 -0
- package/dist/vectorstores.d.ts +98 -0
- package/dist/vectorstores.js +647 -0
- package/dist/vectorstores.js.map +1 -0
- package/index.cjs +1 -0
- package/index.d.cts +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +88 -0
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OracleVS = exports.dropTablePurge = exports.createIndex = exports.createTable = exports.DistanceStrategy = exports.generateWhereClause = void 0;
|
|
7
|
+
const oracledb_1 = __importDefault(require("oracledb"));
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
9
|
+
const vectorstores_1 = require("@langchain/core/vectorstores");
|
|
10
|
+
const documents_1 = require("@langchain/core/documents");
|
|
11
|
+
const math_1 = require("@langchain/core/utils/math");
|
|
12
|
+
function generateWhereClause(dbFilter, bindValues) {
|
|
13
|
+
// Handle $and
|
|
14
|
+
if ("$and" in dbFilter && Array.isArray(dbFilter.$and)) {
|
|
15
|
+
const andConditions = dbFilter.$and.map((cond) => generateWhereClause(cond, bindValues));
|
|
16
|
+
return `(${andConditions.join(" AND ")})`;
|
|
17
|
+
}
|
|
18
|
+
// Handle $or
|
|
19
|
+
if ("$or" in dbFilter && Array.isArray(dbFilter.$or)) {
|
|
20
|
+
const orConditions = dbFilter.$or.map((cond) => generateWhereClause(cond, bindValues));
|
|
21
|
+
return `(${orConditions.join(" OR ")})`;
|
|
22
|
+
}
|
|
23
|
+
// Otherwise, normal object with key: condition(s)
|
|
24
|
+
const conditions = [];
|
|
25
|
+
for (const [col, val] of Object.entries(dbFilter)) {
|
|
26
|
+
if (typeof val === "object" && val !== null && !Array.isArray(val)) {
|
|
27
|
+
// Operator-based filters ($eq, $lte, $in,...)
|
|
28
|
+
for (const [op, operand] of Object.entries(val)) {
|
|
29
|
+
conditions.push(generateOperatorCondition(col, op, operand, bindValues));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (Array.isArray(val)) {
|
|
33
|
+
// shorthand { tags: ["a","b"] } → IN
|
|
34
|
+
conditions.push(generateOperatorCondition(col, "$in", val, bindValues));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// shorthand { name: "John" } → equality
|
|
38
|
+
conditions.push(generateOperatorCondition(col, "$eq", val, bindValues));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return conditions.length > 1
|
|
42
|
+
? `(${conditions.join(" AND ")})`
|
|
43
|
+
: conditions[0];
|
|
44
|
+
}
|
|
45
|
+
exports.generateWhereClause = generateWhereClause;
|
|
46
|
+
function generateOperatorCondition(column, operator, value, bindValues) {
|
|
47
|
+
switch (operator) {
|
|
48
|
+
case "$eq":
|
|
49
|
+
return jsonCompare(column, "=", value, bindValues);
|
|
50
|
+
case "$ne":
|
|
51
|
+
return jsonCompare(column, "!=", value, bindValues);
|
|
52
|
+
case "$lt":
|
|
53
|
+
return jsonCompare(column, "<", value, bindValues);
|
|
54
|
+
case "$lte":
|
|
55
|
+
return jsonCompare(column, "<=", value, bindValues);
|
|
56
|
+
case "$gt":
|
|
57
|
+
return jsonCompare(column, ">", value, bindValues);
|
|
58
|
+
case "$gte":
|
|
59
|
+
return jsonCompare(column, ">=", value, bindValues);
|
|
60
|
+
case "$in": {
|
|
61
|
+
if (!Array.isArray(value))
|
|
62
|
+
throw new Error("$in requires array");
|
|
63
|
+
const inClauses = value.map((v) => jsonCompare(column, "=", v, bindValues));
|
|
64
|
+
return `(${inClauses.join(" OR ")})`;
|
|
65
|
+
}
|
|
66
|
+
case "$nin": {
|
|
67
|
+
if (!Array.isArray(value))
|
|
68
|
+
throw new Error("$nin requires array");
|
|
69
|
+
const ninClauses = value.map((v) => jsonCompare(column, "!=", v, bindValues));
|
|
70
|
+
return `(${ninClauses.join(" AND ")})`;
|
|
71
|
+
}
|
|
72
|
+
case "$between": {
|
|
73
|
+
if (!Array.isArray(value) || value.length !== 2)
|
|
74
|
+
throw new Error("$between requires [low, high]");
|
|
75
|
+
const [low, high] = value;
|
|
76
|
+
bindValues.push(low, high);
|
|
77
|
+
const posLow = bindValues.length - 1;
|
|
78
|
+
const posHigh = bindValues.length;
|
|
79
|
+
return `JSON_EXISTS(metadata, '$.${column}?(@ >= $low && @ <= $high)' PASSING :${posLow} AS "low", :${posHigh} AS "high")`;
|
|
80
|
+
}
|
|
81
|
+
case "$exists":
|
|
82
|
+
if (value) {
|
|
83
|
+
return `JSON_EXISTS(metadata, '$.${column}')`;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return `NOT JSON_EXISTS(metadata, '$.${column}')`;
|
|
87
|
+
}
|
|
88
|
+
default:
|
|
89
|
+
throw new Error(`Unsupported operator: ${operator}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Helper: build JSON_EXISTS clause for comparison
|
|
93
|
+
function jsonCompare(column, op, value, bindValues) {
|
|
94
|
+
bindValues.push(value);
|
|
95
|
+
const pos = bindValues.length;
|
|
96
|
+
const alias = `val${pos}`; // unique bind alias
|
|
97
|
+
const operator = op === "=" ? "==" : op; // Oracle requires '==' for equality
|
|
98
|
+
return `JSON_EXISTS(metadata, '$.${column}?(@ ${operator} $${alias})' PASSING :${pos} AS "${alias}")`;
|
|
99
|
+
}
|
|
100
|
+
exports.DistanceStrategy = {
|
|
101
|
+
COSINE: "COSINE",
|
|
102
|
+
DOT_PRODUCT: "DOT",
|
|
103
|
+
EUCLIDEAN: "EUCLIDEAN",
|
|
104
|
+
MANHATTAN: "MANHATTAN",
|
|
105
|
+
HAMMING: "HAMMING",
|
|
106
|
+
EUCLIDEAN_SQUARED: "EUCLIDEAN_SQUARED",
|
|
107
|
+
};
|
|
108
|
+
function handleError(error) {
|
|
109
|
+
// Type guard to check if the error is an object and has 'name' and 'message' properties
|
|
110
|
+
if (typeof error === "object" &&
|
|
111
|
+
error !== null &&
|
|
112
|
+
"name" in error &&
|
|
113
|
+
"message" in error) {
|
|
114
|
+
const err = error; // Type assertion based on guarded checks
|
|
115
|
+
// Handle specific error types based on the name property
|
|
116
|
+
switch (err.name) {
|
|
117
|
+
case "RuntimeError":
|
|
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}`);
|
|
126
|
+
}
|
|
127
|
+
function isPool(client) {
|
|
128
|
+
return "getConnection" in client;
|
|
129
|
+
}
|
|
130
|
+
function quoteIdentifier(identifier) {
|
|
131
|
+
const name = identifier.trim();
|
|
132
|
+
const validateRegex = /^(?:"[^"]+"|[^".]+)(?:\.(?:"[^"]+"|[^".]+))*$/;
|
|
133
|
+
if (!validateRegex.test(name)) {
|
|
134
|
+
throw new Error(`Identifier name ${identifier} is not valid.`);
|
|
135
|
+
}
|
|
136
|
+
// extracts parts of the identifier with quoted and unquoted.
|
|
137
|
+
const matchRegex = /"([^"]+)"|([^".]+)/g;
|
|
138
|
+
const groups = [];
|
|
139
|
+
for (const match of name.matchAll(matchRegex)) {
|
|
140
|
+
groups.push(match[1] || match[2]);
|
|
141
|
+
}
|
|
142
|
+
const quotedParts = groups.map((g) => `"${g}"`);
|
|
143
|
+
return quotedParts.join(".");
|
|
144
|
+
}
|
|
145
|
+
async function createTable(connection, tableName, embeddingDim) {
|
|
146
|
+
const colsDict = {
|
|
147
|
+
id: "RAW(16) DEFAULT SYS_GUID() PRIMARY KEY",
|
|
148
|
+
embedding: `vector(${embeddingDim}, FLOAT32)`,
|
|
149
|
+
text: "CLOB",
|
|
150
|
+
metadata: "JSON",
|
|
151
|
+
};
|
|
152
|
+
try {
|
|
153
|
+
const ddlBody = Object.entries(colsDict)
|
|
154
|
+
.map(([colName, colType]) => `${colName} ${colType}`)
|
|
155
|
+
.join(", ");
|
|
156
|
+
const ddl = `CREATE TABLE IF NOT EXISTS ${quoteIdentifier(tableName)}
|
|
157
|
+
(
|
|
158
|
+
${ddlBody}
|
|
159
|
+
)`;
|
|
160
|
+
await connection.execute(ddl);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
handleError(error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.createTable = createTable;
|
|
167
|
+
function _getIndexName(baseName) {
|
|
168
|
+
const uniqueId = crypto.randomUUID().replace(/-/g, "");
|
|
169
|
+
return `${baseName}_${uniqueId}`;
|
|
170
|
+
}
|
|
171
|
+
async function createIndex(client, vectorStore, params) {
|
|
172
|
+
const idxType = params?.idxType || "HNSW";
|
|
173
|
+
if (idxType === "IVF") {
|
|
174
|
+
await createIVFIndex(client, vectorStore, params);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
await createHNSWIndex(client, vectorStore, params);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
exports.createIndex = createIndex;
|
|
181
|
+
async function createHNSWIndex(connection, oraclevs, params) {
|
|
182
|
+
try {
|
|
183
|
+
const defaults = {
|
|
184
|
+
idxName: "HNSW",
|
|
185
|
+
idxType: "HNSW",
|
|
186
|
+
neighbors: 32,
|
|
187
|
+
efConstruction: 200,
|
|
188
|
+
accuracy: 90,
|
|
189
|
+
parallel: 8,
|
|
190
|
+
};
|
|
191
|
+
// if params then copy params to config
|
|
192
|
+
const config = params
|
|
193
|
+
? { ...params }
|
|
194
|
+
: { ...defaults };
|
|
195
|
+
// Ensure compulsory parts are included
|
|
196
|
+
const compulsoryKeys = ["idxName", "parallel"];
|
|
197
|
+
for (const key of compulsoryKeys) {
|
|
198
|
+
if (!(key in config)) {
|
|
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;
|
|
241
|
+
await connection.execute(ddl);
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
handleError(error);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function createIVFIndex(connection, oraclevs, params) {
|
|
248
|
+
try {
|
|
249
|
+
const defaults = {
|
|
250
|
+
idxName: "IVF",
|
|
251
|
+
idxType: "IVF",
|
|
252
|
+
neighborPart: 32,
|
|
253
|
+
accuracy: 90,
|
|
254
|
+
parallel: 8,
|
|
255
|
+
};
|
|
256
|
+
// Combine defaults with any provided params. Note: params could contain keys not explicitly declared in IndexConfig
|
|
257
|
+
const config = params
|
|
258
|
+
? { ...params }
|
|
259
|
+
: { ...defaults };
|
|
260
|
+
// Ensure compulsory parts are included
|
|
261
|
+
const compulsoryKeys = ["idxName", "parallel"];
|
|
262
|
+
for (const key of compulsoryKeys) {
|
|
263
|
+
if (!(key in config)) {
|
|
264
|
+
if (key === "idxName") {
|
|
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;
|
|
297
|
+
await connection.execute(ddl);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
handleError(error);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async function dropTablePurge(connection, tableName) {
|
|
304
|
+
try {
|
|
305
|
+
const ddl = `DROP TABLE IF EXISTS ${quoteIdentifier(tableName)} PURGE`;
|
|
306
|
+
await connection.execute(ddl);
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
handleError(error);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
exports.dropTablePurge = dropTablePurge;
|
|
313
|
+
class OracleVS extends vectorstores_1.VectorStore {
|
|
314
|
+
_vectorstoreType() {
|
|
315
|
+
return "oraclevs";
|
|
316
|
+
}
|
|
317
|
+
constructor(embeddings, dbConfig) {
|
|
318
|
+
super(embeddings, dbConfig);
|
|
319
|
+
Object.defineProperty(this, "client", {
|
|
320
|
+
enumerable: true,
|
|
321
|
+
configurable: true,
|
|
322
|
+
writable: true,
|
|
323
|
+
value: void 0
|
|
324
|
+
});
|
|
325
|
+
Object.defineProperty(this, "embeddingDimension", {
|
|
326
|
+
enumerable: true,
|
|
327
|
+
configurable: true,
|
|
328
|
+
writable: true,
|
|
329
|
+
value: void 0
|
|
330
|
+
});
|
|
331
|
+
Object.defineProperty(this, "tableName", {
|
|
332
|
+
enumerable: true,
|
|
333
|
+
configurable: true,
|
|
334
|
+
writable: true,
|
|
335
|
+
value: void 0
|
|
336
|
+
});
|
|
337
|
+
Object.defineProperty(this, "distanceStrategy", {
|
|
338
|
+
enumerable: true,
|
|
339
|
+
configurable: true,
|
|
340
|
+
writable: true,
|
|
341
|
+
value: exports.DistanceStrategy.COSINE
|
|
342
|
+
});
|
|
343
|
+
Object.defineProperty(this, "filter", {
|
|
344
|
+
enumerable: true,
|
|
345
|
+
configurable: true,
|
|
346
|
+
writable: true,
|
|
347
|
+
value: void 0
|
|
348
|
+
});
|
|
349
|
+
Object.defineProperty(this, "query", {
|
|
350
|
+
enumerable: true,
|
|
351
|
+
configurable: true,
|
|
352
|
+
writable: true,
|
|
353
|
+
value: void 0
|
|
354
|
+
});
|
|
355
|
+
try {
|
|
356
|
+
this.client = dbConfig.client;
|
|
357
|
+
this.tableName = quoteIdentifier(dbConfig.tableName);
|
|
358
|
+
this.distanceStrategy =
|
|
359
|
+
dbConfig.distanceStrategy ?? this.distanceStrategy;
|
|
360
|
+
this.query = dbConfig.query;
|
|
361
|
+
this.filter = dbConfig.filter;
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
handleError(error);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async getEmbeddingDimension(query) {
|
|
368
|
+
const embeddingVector = await this.embeddings.embedQuery(query);
|
|
369
|
+
return embeddingVector.length;
|
|
370
|
+
}
|
|
371
|
+
async initialize() {
|
|
372
|
+
let connection = null;
|
|
373
|
+
try {
|
|
374
|
+
this.embeddingDimension = await this.getEmbeddingDimension(this.query);
|
|
375
|
+
connection = await this.getConnection();
|
|
376
|
+
await createTable(connection, this.tableName, this.embeddingDimension);
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
handleError(error);
|
|
380
|
+
}
|
|
381
|
+
finally {
|
|
382
|
+
if (connection)
|
|
383
|
+
await this.retConnection(connection);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async getConnection() {
|
|
387
|
+
try {
|
|
388
|
+
if (isPool(this.client)) {
|
|
389
|
+
return await this.client.getConnection();
|
|
390
|
+
}
|
|
391
|
+
return this.client;
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
handleError(error);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// Close connection or return it to the pool
|
|
398
|
+
async retConnection(connection) {
|
|
399
|
+
try {
|
|
400
|
+
// If the client is a pool, close the connection (return it to the pool)
|
|
401
|
+
if (isPool(this.client)) {
|
|
402
|
+
await connection.close();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
console.error("Error in retConnection:", error);
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Method to add vectors to the Oracle database.
|
|
412
|
+
* @param vectors The vectors to add.
|
|
413
|
+
* @param documents The documents associated with the vectors.
|
|
414
|
+
* @param options
|
|
415
|
+
* @returns Promise that resolves when the vectors have been added.
|
|
416
|
+
*/
|
|
417
|
+
async addVectors(vectors, documents, options) {
|
|
418
|
+
if (vectors.length === 0) {
|
|
419
|
+
throw new Error("Vectors input null. Nothing to add...");
|
|
420
|
+
}
|
|
421
|
+
const ids = options?.ids;
|
|
422
|
+
let connection = null;
|
|
423
|
+
try {
|
|
424
|
+
// Ensure there are IDs for all documents
|
|
425
|
+
if (ids !== undefined && ids.length !== vectors.length) {
|
|
426
|
+
throw new Error("The number of ids must match the number of vectors provided.");
|
|
427
|
+
}
|
|
428
|
+
connection = await this.getConnection();
|
|
429
|
+
const binds = [];
|
|
430
|
+
for (let index = 0; index < documents.length; index += 1) {
|
|
431
|
+
const doc = documents[index];
|
|
432
|
+
let processedId;
|
|
433
|
+
if (ids) {
|
|
434
|
+
processedId = (0, crypto_1.createHash)("sha256")
|
|
435
|
+
.update(ids[index])
|
|
436
|
+
.digest("hex")
|
|
437
|
+
.substring(0, 16)
|
|
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,
|
|
451
|
+
text: doc.pageContent,
|
|
452
|
+
metadata: doc.metadata,
|
|
453
|
+
embedding: new Float32Array(vectors[index]),
|
|
454
|
+
};
|
|
455
|
+
binds.push(bind);
|
|
456
|
+
}
|
|
457
|
+
const sql = `INSERT INTO ${this.tableName} (id, embedding, text, metadata )
|
|
458
|
+
VALUES (:id, :embedding, :text, :metadata)`;
|
|
459
|
+
const options = {
|
|
460
|
+
bindDefs: {
|
|
461
|
+
id: { type: oracledb_1.default.BUFFER, maxSize: 24 },
|
|
462
|
+
text: { type: oracledb_1.default.STRING, maxSize: 10000000 },
|
|
463
|
+
metadata: { type: oracledb_1.default.DB_TYPE_JSON },
|
|
464
|
+
embedding: { type: oracledb_1.default.DB_TYPE_VECTOR },
|
|
465
|
+
},
|
|
466
|
+
autoCommit: false,
|
|
467
|
+
};
|
|
468
|
+
await connection.executeMany(sql, binds, options);
|
|
469
|
+
// Commit once all inserts are queued up
|
|
470
|
+
await connection.commit();
|
|
471
|
+
console.log("All documents have been inserted and committed.");
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
handleError(error);
|
|
475
|
+
}
|
|
476
|
+
finally {
|
|
477
|
+
if (connection) {
|
|
478
|
+
await this.retConnection(connection);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return ids;
|
|
482
|
+
}
|
|
483
|
+
async addDocuments(documents, options) {
|
|
484
|
+
const texts = documents.map(({ pageContent }) => pageContent);
|
|
485
|
+
return this.addVectors(await this.embeddings.embedDocuments(texts), documents, options);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Method to search for vectors that are similar to a given query vector.
|
|
489
|
+
* @param query The query vector.
|
|
490
|
+
* @param k The number of similar vectors to return.
|
|
491
|
+
* @param filter Optional filter for the search results.
|
|
492
|
+
* @returns Promise that resolves with an array of tuples, each containing a Document and a score.
|
|
493
|
+
*/
|
|
494
|
+
async similaritySearchByVectorReturningEmbeddings(query, k = 4, filter) {
|
|
495
|
+
const docsScoresAndEmbeddings = [];
|
|
496
|
+
let connection = null;
|
|
497
|
+
try {
|
|
498
|
+
const convertedEmbedding = new Float32Array(query);
|
|
499
|
+
const bindValues = [convertedEmbedding];
|
|
500
|
+
let sqlQuery = `
|
|
501
|
+
SELECT id,
|
|
502
|
+
text,
|
|
503
|
+
metadata,
|
|
504
|
+
vector_distance(embedding, :1, ${this.distanceStrategy}) as distance,
|
|
505
|
+
embedding
|
|
506
|
+
FROM ${this.tableName} `;
|
|
507
|
+
if (filter && Object.keys(filter).length > 0) {
|
|
508
|
+
sqlQuery += ` WHERE ${generateWhereClause(filter, bindValues)}`;
|
|
509
|
+
}
|
|
510
|
+
bindValues.push(k);
|
|
511
|
+
sqlQuery += ` ORDER BY distance FETCH APPROX FIRST :${bindValues.length} ROWS ONLY `;
|
|
512
|
+
// Execute the query
|
|
513
|
+
connection = await this.getConnection();
|
|
514
|
+
const resultSet = await connection.execute(sqlQuery, bindValues, {
|
|
515
|
+
fetchInfo: {
|
|
516
|
+
TEXT: { type: oracledb_1.default.STRING },
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
if (Array.isArray(resultSet.rows) && resultSet.rows.length > 0) {
|
|
520
|
+
const rows = resultSet.rows;
|
|
521
|
+
for (let idx = 0; idx < resultSet.rows.length; idx += 1) {
|
|
522
|
+
const row = rows[idx];
|
|
523
|
+
const text = row[1];
|
|
524
|
+
const metadata = row[2];
|
|
525
|
+
const distance = row[3];
|
|
526
|
+
const embedding = row[4];
|
|
527
|
+
const document = new documents_1.Document({
|
|
528
|
+
pageContent: text || "",
|
|
529
|
+
metadata: metadata || {},
|
|
530
|
+
id: row[0].toString("hex"),
|
|
531
|
+
});
|
|
532
|
+
docsScoresAndEmbeddings.push([document, distance, embedding]);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
// Throw an exception if no rows are found
|
|
537
|
+
throw new Error("No rows found.");
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
finally {
|
|
541
|
+
if (connection) {
|
|
542
|
+
await connection.close();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return docsScoresAndEmbeddings;
|
|
546
|
+
}
|
|
547
|
+
async similaritySearchVectorWithScore(query, k, filter) {
|
|
548
|
+
const docsScoresAndEmbeddings = await this.similaritySearchByVectorReturningEmbeddings(query, k, filter);
|
|
549
|
+
return docsScoresAndEmbeddings.map(([document, score]) => [
|
|
550
|
+
document,
|
|
551
|
+
score,
|
|
552
|
+
]);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Return documents selected using the maximal marginal relevance.
|
|
556
|
+
* Maximal marginal relevance optimizes for similarity to the query AND diversity
|
|
557
|
+
* among selected documents.
|
|
558
|
+
*
|
|
559
|
+
* @param {string} query - Text to look up documents similar to.
|
|
560
|
+
* @param options
|
|
561
|
+
* @param {number} options.k - Number of documents to return.
|
|
562
|
+
* @param {number} options.fetchK - Number of documents to fetch before passing to the MMR algorithm.
|
|
563
|
+
* @param {number} options.lambda - Number between 0 and 1 that determines the degree of diversity among the results,
|
|
564
|
+
* where 0 corresponds to maximum diversity and 1 to minimum diversity.
|
|
565
|
+
* @param {this["FilterType"]} options.filter - Optional filter
|
|
566
|
+
* @param _callbacks
|
|
567
|
+
*
|
|
568
|
+
* @returns {Promise<DocumentInterface[]>} - List of documents selected by maximal marginal relevance.
|
|
569
|
+
*/
|
|
570
|
+
async maxMarginalRelevanceSearch(query, options) {
|
|
571
|
+
const embedding = await this.embeddings.embedQuery(query);
|
|
572
|
+
return await this.maxMarginalRelevanceSearchByVector(embedding, options);
|
|
573
|
+
}
|
|
574
|
+
async maxMarginalRelevanceSearchByVector(embedding, options) {
|
|
575
|
+
// Fetch documents and their scores. This calls the previously adapted function.
|
|
576
|
+
const docsAndScores = await this.maxMarginalRelevanceSearchWithScoreByVector(embedding, options);
|
|
577
|
+
// Extract and return only the documents from the results
|
|
578
|
+
return docsAndScores.map((ds) => ds.document);
|
|
579
|
+
}
|
|
580
|
+
async maxMarginalRelevanceSearchWithScoreByVector(embedding, options) {
|
|
581
|
+
// Fetch documents and their scores.
|
|
582
|
+
const docsScoresEmbeddings = await this.similaritySearchByVectorReturningEmbeddings(embedding, options.fetchK, options.filter);
|
|
583
|
+
if (!docsScoresEmbeddings.length) {
|
|
584
|
+
return [];
|
|
585
|
+
}
|
|
586
|
+
// Split documents, scores, and embeddings
|
|
587
|
+
const documents = docsScoresEmbeddings.map(([document]) => document);
|
|
588
|
+
const scores = docsScoresEmbeddings.map(([, score]) => score);
|
|
589
|
+
const embeddings = docsScoresEmbeddings.map(([, , embedding]) => new Float32Array(embedding));
|
|
590
|
+
// Convert all embeddings to Float32Array for consistency
|
|
591
|
+
const consistentEmbeddings = embeddings.map((embedding) => Array.from(embedding));
|
|
592
|
+
const queryEmbedding = Array.from(embedding);
|
|
593
|
+
// Ensure lambdaMult has a default value if not provided
|
|
594
|
+
const lambdaMult = options.lambda ?? 0.5;
|
|
595
|
+
const mmrSelectedIndices = (0, math_1.maximalMarginalRelevance)(queryEmbedding, consistentEmbeddings, lambdaMult, options.k);
|
|
596
|
+
// Filter documents based on MMR-selected indices and map scores
|
|
597
|
+
return mmrSelectedIndices.map((index) => ({
|
|
598
|
+
document: documents[index],
|
|
599
|
+
score: scores[index],
|
|
600
|
+
}));
|
|
601
|
+
}
|
|
602
|
+
async delete(params) {
|
|
603
|
+
let connection = null;
|
|
604
|
+
try {
|
|
605
|
+
connection = await this.getConnection();
|
|
606
|
+
const options = { autoCommit: true };
|
|
607
|
+
if (params.ids && params.ids.length > 0) {
|
|
608
|
+
// Dynamically create placeholders
|
|
609
|
+
const placeholders = params.ids
|
|
610
|
+
.map((_, index) => `:${index + 1}`)
|
|
611
|
+
.join(",");
|
|
612
|
+
// Prepare the query
|
|
613
|
+
const query = `DELETE FROM ${this.tableName} WHERE id IN (${placeholders})`;
|
|
614
|
+
// Execute the query with the IDs as bind parameters
|
|
615
|
+
await connection.execute(query, [...params.ids], options);
|
|
616
|
+
}
|
|
617
|
+
else if (params.deleteAll) {
|
|
618
|
+
await connection.execute(`TRUNCATE TABLE ${this.tableName}`, [], options);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
handleError(error);
|
|
623
|
+
}
|
|
624
|
+
finally {
|
|
625
|
+
if (connection)
|
|
626
|
+
await connection.close();
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
static async fromDocuments(documents, embeddings, dbConfig) {
|
|
630
|
+
const { client } = dbConfig;
|
|
631
|
+
if (!client)
|
|
632
|
+
throw new Error("client parameter is required...");
|
|
633
|
+
try {
|
|
634
|
+
const vss = new OracleVS(embeddings, dbConfig);
|
|
635
|
+
await vss.initialize();
|
|
636
|
+
const texts = documents.map(({ pageContent }) => pageContent);
|
|
637
|
+
const vectors = await embeddings.embedDocuments(texts);
|
|
638
|
+
// Assuming a method exists to handle adding texts and metadata appropriately
|
|
639
|
+
await vss.addVectors(vectors, documents);
|
|
640
|
+
return vss;
|
|
641
|
+
}
|
|
642
|
+
catch (error) {
|
|
643
|
+
handleError(error);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
*
|
|
648
|
+
* @returns Promise that resolves when all connections
|
|
649
|
+
* inside the pool are terminated.
|
|
650
|
+
*/
|
|
651
|
+
async end() {
|
|
652
|
+
if (isPool(this.client)) {
|
|
653
|
+
await this.client?.close();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
exports.OracleVS = OracleVS;
|
|
658
|
+
//# sourceMappingURL=vectorstores.cjs.map
|