rag-lite-ts 2.0.5 → 2.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.
@@ -17,7 +17,7 @@
17
17
  import { readFileSync, writeFileSync } from 'fs';
18
18
  export class BinaryIndexFormat {
19
19
  /**
20
- * Save index data to binary format
20
+ * Save index data to binary format (original format for backward compatibility)
21
21
  *
22
22
  * File structure:
23
23
  * - Header (24 bytes): dimensions, maxElements, M, efConstruction, seed, currentSize
@@ -66,7 +66,115 @@ export class BinaryIndexFormat {
66
66
  writeFileSync(indexPath, Buffer.from(buffer));
67
67
  }
68
68
  /**
69
- * Load index data from binary format
69
+ * Save index data to grouped binary format
70
+ *
71
+ * File structure:
72
+ * - Extended Header (40 bytes):
73
+ * - Original 6 fields (24 bytes)
74
+ * - hasGroups flag (4 bytes)
75
+ * - textOffset (4 bytes)
76
+ * - textCount (4 bytes)
77
+ * - imageOffset (4 bytes)
78
+ * - imageCount (4 bytes)
79
+ * - Data section: [text vectors...][image vectors...]
80
+ *
81
+ * @param indexPath Path to save the binary index file
82
+ * @param data Index data to serialize
83
+ */
84
+ static async saveGrouped(indexPath, data) {
85
+ if (!data.hasContentTypeGroups || !data.textVectors || !data.imageVectors) {
86
+ // Fallback to original format
87
+ return this.save(indexPath, data);
88
+ }
89
+ const headerSize = 44; // Extended header: 24 + 20 bytes (hasGroups + textOffset + textCount + imageOffset + imageCount)
90
+ const vectorSize = 4 + (data.dimensions * 4); // id + vector
91
+ // Calculate offsets and total size
92
+ const textOffset = headerSize;
93
+ const imageOffset = textOffset + (data.textVectors.length * vectorSize);
94
+ const totalSize = imageOffset + (data.imageVectors.length * vectorSize);
95
+ const buffer = new ArrayBuffer(totalSize);
96
+ const view = new DataView(buffer);
97
+ let offset = 0;
98
+ // Write extended header (40 bytes, all little-endian)
99
+ if (offset + 40 > buffer.byteLength) {
100
+ throw new Error(`Header write would exceed buffer bounds: offset=${offset}, headerSize=40, bufferSize=${buffer.byteLength}`);
101
+ }
102
+ view.setUint32(offset, data.dimensions, true);
103
+ offset += 4;
104
+ view.setUint32(offset, data.maxElements, true);
105
+ offset += 4;
106
+ view.setUint32(offset, data.M, true);
107
+ offset += 4;
108
+ view.setUint32(offset, data.efConstruction, true);
109
+ offset += 4;
110
+ view.setUint32(offset, data.seed, true);
111
+ offset += 4;
112
+ view.setUint32(offset, data.currentSize, true);
113
+ offset += 4;
114
+ // Extended fields
115
+ view.setUint32(offset, 1, true);
116
+ offset += 4; // hasGroups = 1
117
+ view.setUint32(offset, textOffset, true);
118
+ offset += 4;
119
+ view.setUint32(offset, data.textVectors.length, true);
120
+ offset += 4;
121
+ view.setUint32(offset, imageOffset, true);
122
+ offset += 4;
123
+ view.setUint32(offset, data.imageVectors.length, true);
124
+ offset += 4;
125
+ // Write text vectors
126
+ for (const item of data.textVectors) {
127
+ // Ensure 4-byte alignment
128
+ if (offset % 4 !== 0) {
129
+ throw new Error(`Offset ${offset} is not 4-byte aligned`);
130
+ }
131
+ // Check bounds before writing
132
+ if (offset + 4 > buffer.byteLength) {
133
+ throw new Error(`ID write would exceed buffer bounds: offset=${offset}, bufferSize=${buffer.byteLength}`);
134
+ }
135
+ // Write vector ID
136
+ view.setUint32(offset, item.id, true);
137
+ offset += 4;
138
+ // Check bounds for vector data
139
+ const vectorDataSize = item.vector.length * 4;
140
+ if (offset + vectorDataSize > buffer.byteLength) {
141
+ throw new Error(`Vector data write would exceed buffer bounds: offset=${offset}, dataSize=${vectorDataSize}, bufferSize=${buffer.byteLength}`);
142
+ }
143
+ // Write vector data
144
+ for (let i = 0; i < item.vector.length; i++) {
145
+ view.setFloat32(offset, item.vector[i], true);
146
+ offset += 4;
147
+ }
148
+ }
149
+ // Write image vectors
150
+ for (const item of data.imageVectors) {
151
+ // Ensure 4-byte alignment
152
+ if (offset % 4 !== 0) {
153
+ throw new Error(`Offset ${offset} is not 4-byte aligned`);
154
+ }
155
+ // Check bounds before writing
156
+ if (offset + 4 > buffer.byteLength) {
157
+ throw new Error(`ID write would exceed buffer bounds: offset=${offset}, bufferSize=${buffer.byteLength}`);
158
+ }
159
+ // Write vector ID
160
+ view.setUint32(offset, item.id, true);
161
+ offset += 4;
162
+ // Check bounds for vector data
163
+ const vectorDataSize = item.vector.length * 4;
164
+ if (offset + vectorDataSize > buffer.byteLength) {
165
+ throw new Error(`Vector data write would exceed buffer bounds: offset=${offset}, dataSize=${vectorDataSize}, bufferSize=${buffer.byteLength}`);
166
+ }
167
+ // Write vector data
168
+ for (let i = 0; i < item.vector.length; i++) {
169
+ view.setFloat32(offset, item.vector[i], true);
170
+ offset += 4;
171
+ }
172
+ }
173
+ // Write to file
174
+ writeFileSync(indexPath, Buffer.from(buffer));
175
+ }
176
+ /**
177
+ * Load index data from binary format (supports both original and grouped formats)
70
178
  *
71
179
  * Uses zero-copy Float32Array views for efficient loading.
72
180
  * Copies the views to ensure data persistence after buffer lifecycle.
@@ -78,7 +186,7 @@ export class BinaryIndexFormat {
78
186
  const buffer = readFileSync(indexPath);
79
187
  const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
80
188
  let offset = 0;
81
- // Read header (24 bytes, all little-endian)
189
+ // Read basic header (24 bytes, all little-endian)
82
190
  const dimensions = view.getUint32(offset, true);
83
191
  offset += 4;
84
192
  const maxElements = view.getUint32(offset, true);
@@ -91,32 +199,93 @@ export class BinaryIndexFormat {
91
199
  offset += 4;
92
200
  const currentSize = view.getUint32(offset, true);
93
201
  offset += 4;
94
- // Read vectors
95
- const vectors = [];
96
- for (let i = 0; i < currentSize; i++) {
97
- // Ensure 4-byte alignment (should always be true with our format)
98
- if (offset % 4 !== 0) {
99
- throw new Error(`Offset ${offset} is not 4-byte aligned`);
202
+ // Check if this is the extended grouped format (40+ bytes header)
203
+ const hasGroups = buffer.byteLength >= 40 ? view.getUint32(offset, true) : 0;
204
+ if (hasGroups === 1 && buffer.byteLength >= 40) {
205
+ // Load grouped format
206
+ const textOffset = view.getUint32(offset + 4, true);
207
+ const textCount = view.getUint32(offset + 8, true);
208
+ const imageOffset = view.getUint32(offset + 12, true);
209
+ const imageCount = view.getUint32(offset + 16, true);
210
+ // Load text vectors
211
+ const textVectors = [];
212
+ offset = textOffset;
213
+ for (let i = 0; i < textCount; i++) {
214
+ // Ensure 4-byte alignment
215
+ if (offset % 4 !== 0) {
216
+ throw new Error(`Offset ${offset} is not 4-byte aligned`);
217
+ }
218
+ // Read vector ID
219
+ const id = view.getUint32(offset, true);
220
+ offset += 4;
221
+ // Zero-copy Float32Array view
222
+ const vectorView = new Float32Array(buffer.buffer, buffer.byteOffset + offset, dimensions);
223
+ // Copy to avoid buffer lifecycle issues
224
+ const vector = new Float32Array(vectorView);
225
+ offset += dimensions * 4;
226
+ textVectors.push({ id, vector });
100
227
  }
101
- // Read vector ID
102
- const id = view.getUint32(offset, true);
103
- offset += 4;
104
- // Zero-copy Float32Array view (fast!)
105
- const vectorView = new Float32Array(buffer.buffer, buffer.byteOffset + offset, dimensions);
106
- // Copy to avoid buffer lifecycle issues
107
- const vector = new Float32Array(vectorView);
108
- offset += dimensions * 4;
109
- vectors.push({ id, vector });
228
+ // Load image vectors
229
+ const imageVectors = [];
230
+ offset = imageOffset;
231
+ for (let i = 0; i < imageCount; i++) {
232
+ // Ensure 4-byte alignment
233
+ if (offset % 4 !== 0) {
234
+ throw new Error(`Offset ${offset} is not 4-byte aligned`);
235
+ }
236
+ // Read vector ID
237
+ const id = view.getUint32(offset, true);
238
+ offset += 4;
239
+ // Zero-copy Float32Array view
240
+ const vectorView = new Float32Array(buffer.buffer, buffer.byteOffset + offset, dimensions);
241
+ // Copy to avoid buffer lifecycle issues
242
+ const vector = new Float32Array(vectorView);
243
+ offset += dimensions * 4;
244
+ imageVectors.push({ id, vector });
245
+ }
246
+ // Combine all vectors for backward compatibility
247
+ const allVectors = [...textVectors, ...imageVectors];
248
+ return {
249
+ dimensions,
250
+ maxElements,
251
+ M,
252
+ efConstruction,
253
+ seed,
254
+ currentSize,
255
+ vectors: allVectors,
256
+ hasContentTypeGroups: true,
257
+ textVectors,
258
+ imageVectors
259
+ };
260
+ }
261
+ else {
262
+ // Load original format
263
+ const vectors = [];
264
+ for (let i = 0; i < currentSize; i++) {
265
+ // Ensure 4-byte alignment (should always be true with our format)
266
+ if (offset % 4 !== 0) {
267
+ throw new Error(`Offset ${offset} is not 4-byte aligned`);
268
+ }
269
+ // Read vector ID
270
+ const id = view.getUint32(offset, true);
271
+ offset += 4;
272
+ // Zero-copy Float32Array view (fast!)
273
+ const vectorView = new Float32Array(buffer.buffer, buffer.byteOffset + offset, dimensions);
274
+ // Copy to avoid buffer lifecycle issues
275
+ const vector = new Float32Array(vectorView);
276
+ offset += dimensions * 4;
277
+ vectors.push({ id, vector });
278
+ }
279
+ return {
280
+ dimensions,
281
+ maxElements,
282
+ M,
283
+ efConstruction,
284
+ seed,
285
+ currentSize,
286
+ vectors
287
+ };
110
288
  }
111
- return {
112
- dimensions,
113
- maxElements,
114
- M,
115
- efConstruction,
116
- seed,
117
- currentSize,
118
- vectors
119
- };
120
289
  }
121
290
  }
122
291
  //# sourceMappingURL=binary-index-format.js.map