rag-module 0.5.6

Enterprise RAG module with chat context storage, vector search, session management, and model downloading. Rust implementation with Node.js compatibility.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
# Tauri App Batch Ingestion Upgrade Guide

## ๐Ÿ“‹ Overview

This document provides step-by-step instructions to upgrade your Tauri app to support batch AWS estate resource ingestion. The new batch functionality allows processing up to 32 resources in a single operation, providing significant performance improvements over individual resource processing.

## ๐ŸŽฏ Benefits

- **Performance**: 32x fewer API calls (32 individual calls โ†’ 1 batch call)
- **Efficiency**: Single embedding generation and database transaction
- **Throughput**: ~40 docs/sec vs ~1.5 docs/sec individual processing
- **Reliability**: Atomic operations with graceful error handling
- **Backward Compatibility**: Existing single resource functionality preserved

## ๐Ÿ“ Files to Modify

1. `src-tauri/src/commands/rag_commands.rs`
2. `src-tauri/src/services/rag/rag_service.rs`
3. `src-tauri/src/main.rs`
4. Frontend TypeScript files (usage examples provided)

---

## ๐Ÿ”ง Implementation Steps

### 1. Update `src-tauri/src/commands/rag_commands.rs`

#### Add New Request/Response Types

Add these types after your existing `IngestEstateResourceResult` struct:

```rust
/// Request to ingest multiple estate resources in batch
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IngestEstateResourcesBatchRequest {
    /// Array of resource data objects to ingest (max 32 recommended)
    pub resources_data: Vec<serde_json::Value>,
    /// User ID for authentication and storage isolation
    pub user_id: String,
    /// Collection name to store resources in ('core_estate' or 'detailed_estate')
    pub collection_name: String,
    /// Authentication token
    pub token: String,
}

/// Response for batch ingestion (reuses existing result structure)
pub type IngestEstateResourcesBatchResult = IngestEstateResourceResult;
```

#### Add New Batch Command

Add this new Tauri command function:

```rust
/// Ingest multiple AWS estate resources in batch
///
/// This command processes multiple resources at once for improved performance.
/// Recommended batch size: 1-32 resources per call.
///
/// # Frontend Usage
/// ```typescript
/// import { invoke } from '@tauri-apps/api/core';
///
/// const result = await invoke('ingest_estate_resources_batch', {
///   request: {
///     resourcesData: [
///       { content: "EC2 instance...", resource_type: "ec2_instance", ... },
///       { content: "S3 bucket...", resource_type: "s3_bucket", ... },
///       // ... up to 32 resources
///     ],
///     userId: 'user-123',
///     collectionName: 'detailed_estate',
///     token: idToken
///   }
/// });
/// ```
#[tauri::command]
pub async fn ingest_estate_resources_batch(
    request: IngestEstateResourcesBatchRequest,
) -> ApiResponse<IngestEstateResourcesBatchResult> {
    info!("๐Ÿ“ฆ Batch ingesting {} resources to collection: {}", 
          request.resources_data.len(), request.collection_name);

    // Validate inputs
    if request.user_id.trim().is_empty() {
        error!("Empty user ID provided for batch ingestion");
        return ApiResponse::error("User ID cannot be empty".to_string());
    }

    if request.token.trim().is_empty() {
        error!("Empty auth token provided for batch ingestion");
        return ApiResponse::error("Authentication token cannot be empty".to_string());
    }

    // Validate collection name
    if request.collection_name != "core_estate" && request.collection_name != "detailed_estate" {
        error!("Invalid collection name: {}", request.collection_name);
        return ApiResponse::error(format!(
            "Invalid collection name '{}'. Must be 'core_estate' or 'detailed_estate'",
            request.collection_name
        ));
    }

    // Validate batch size (recommended maximum of 32 for optimal performance)
    if request.resources_data.is_empty() {
        error!("Empty batch provided");
        return ApiResponse::error("Batch cannot be empty".to_string());
    }

    if request.resources_data.len() > 32 {
        error!("Batch size {} exceeds recommended maximum", request.resources_data.len());
        return ApiResponse::error(format!(
            "Batch size {} exceeds maximum of 32 resources. Consider splitting into smaller batches.", 
            request.resources_data.len()
        ));
    }

    // Validate each resource has required 'content' field
    for (i, resource) in request.resources_data.iter().enumerate() {
        if resource.get("content").is_none() {
            error!("Resource at index {} missing 'content' field", i);
            return ApiResponse::error(format!(
                "Resource at index {} missing required 'content' field", i
            ));
        }
    }

    // Get RAG service instance and process batch
    match RagService::get_instance().await {
        Ok(service_arc) => {
            let service = service_arc.read().await;

            // Call batch ingestion service
            match service
                .ingest_estate_resources_batch(
                    request.resources_data,
                    &request.user_id,
                    &request.collection_name,
                )
                .await
            {
                Ok(result) => {
                    info!("โœ… Batch ingestion completed: {}/{} resources successful", 
                          result.parsed_resources, result.total_resources);

                    ApiResponse::success(IngestEstateResourcesBatchResult {
                        success: result.parsed_resources > 0,
                        total_resources: result.total_resources,
                        parsed_resources: result.parsed_resources,
                        failed_resources: result.failed_resources,
                    })
                }
                Err(e) => {
                    error!("โŒ Batch ingestion failed: {}", e);
                    ApiResponse::error(format!("Failed to ingest resources batch: {}", e))
                }
            }
        }
        Err(e) => {
            error!("โŒ Failed to get RAG service: {}", e);
            ApiResponse::error(format!("RAG service unavailable: {}", e))
        }
    }
}
```

### 2. Update `src-tauri/src/services/rag/rag_service.rs`

#### Add Batch Method to RagService

Add this method to your `RagService` implementation block:

```rust
impl RagService {
    // ... existing methods ...

    /// Ingest multiple estate resources in batch for improved performance
    ///
    /// This method leverages the RAG module's batch processing capabilities
    /// to efficiently handle multiple resources in a single operation.
    ///
    /// # Arguments
    /// * `resources_data` - Vector of resource JSON objects to ingest
    /// * `user_id` - User identifier for storage isolation
    /// * `collection_name` - Collection to store in ('core_estate' or 'detailed_estate')
    ///
    /// # Returns
    /// * `Result<rag_module::services::AwsEstateIngestResult>` - Batch ingest result with statistics
    ///
    /// # Performance
    /// - Batch embedding generation (1 API call vs N calls)
    /// - Batch database insertion (1 transaction vs N transactions)
    /// - Typical throughput: 30-40 docs/sec for batch vs 1-2 docs/sec individual
    pub async fn ingest_estate_resources_batch(
        &self,
        resources_data: Vec<serde_json::Value>,
        user_id: &str,
        collection_name: &str,
    ) -> Result<rag_module::services::AwsEstateIngestResult> {
        debug!("Batch ingesting {} resources to collection: {} for user: {}", 
               resources_data.len(), collection_name, user_id);

        // Measure performance
        let start_time = std::time::Instant::now();

        let rag_module = self.rag_module.read().await;
        let result = rag_module
            .ingest_aws_estate_batch(resources_data, user_id, collection_name)
            .await
            .context("Failed to ingest estate resources batch")?;

        let duration = start_time.elapsed();
        let throughput = result.parsed_resources as f64 / duration.as_secs_f64();

        info!("Batch ingestion completed in {:?}: parsed={}, failed={}, throughput={:.1} docs/sec",
              duration, result.parsed_resources, result.failed_resources, throughput);

        Ok(result)
    }

    // ... rest of existing methods ...
}
```

### 3. Update `src-tauri/src/main.rs`

#### Register New Command

Add the new batch command to your Tauri app's invoke handler:

```rust
fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_log::Builder::default().level(log::LevelFilter::Debug).build())
        .invoke_handler(tauri::generate_handler![
            // ... your existing commands ...
            rag_commands::ingest_estate_resource,          // Keep existing single resource
            rag_commands::ingest_estate_resources_batch,   // Add new batch processing
            // ... other commands ...
        ])
        .setup(|app| {
            // ... existing setup code ...
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

---

## ๐ŸŽจ Frontend Integration

### TypeScript Types

Add these types to your frontend TypeScript definitions:

```typescript
// Add to your existing types file
export interface IngestEstateResourcesBatchRequest {
  resourcesData: Array<{
    content: string;
    [key: string]: any; // Additional resource fields
  }>;
  userId: string;
  collectionName: 'core_estate' | 'detailed_estate';
  token: string;
}

export interface IngestEstateResourcesBatchResult {
  success: boolean;
  totalResources: number;
  parsedResources: number;
  failedResources: number;
}
```

### Frontend Usage Examples

#### Single Resource (Existing - No Changes)

```typescript
import { invoke } from '@tauri-apps/api/core';

// Process one resource at a time (existing functionality)
const processSingleResource = async (resource: any, userId: string, token: string) => {
  try {
    const result = await invoke('ingest_estate_resource', {
      request: {
        resourceData: resource,
        userId: userId,
        collectionName: 'detailed_estate',
        token: token
      }
    });
    
    console.log('Single resource processed:', result.data);
    return result;
  } catch (error) {
    console.error('Error processing single resource:', error);
    throw error;
  }
};
```

#### Batch Processing (New)

```typescript
// Process multiple resources in batch (NEW - high performance)
const processBatchResources = async (resources: any[], userId: string, token: string) => {
  // Split into chunks of 32 if needed
  const batchSize = 32;
  const results = [];
  
  for (let i = 0; i < resources.length; i += batchSize) {
    const batch = resources.slice(i, i + batchSize);
    
    try {
      const result = await invoke('ingest_estate_resources_batch', {
        request: {
          resourcesData: batch,
          userId: userId,
          collectionName: 'detailed_estate',
          token: token
        }
      });
      
      console.log(`Batch ${Math.floor(i/batchSize) + 1} processed: ${result.data.parsedResources}/${result.data.totalResources}`);
      results.push(result);
      
    } catch (error) {
      console.error(`Error processing batch ${Math.floor(i/batchSize) + 1}:`, error);
      throw error;
    }
  }
  
  return results;
};
```

#### Smart Processing Function

```typescript
// Intelligent function that chooses single vs batch based on data size
const processResources = async (resources: any[], userId: string, token: string) => {
  if (resources.length === 1) {
    // Single resource - use existing method
    return await processSingleResource(resources[0], userId, token);
  } else {
    // Multiple resources - use batch method for better performance
    return await processBatchResources(resources, userId, token);
  }
};

// Usage examples
const handleAWSData = async (awsData: any[]) => {
  const userId = getCurrentUserId();
  const token = await getAuthToken();
  
  if (awsData.length === 0) {
    console.log('No data to process');
    return;
  }
  
  console.log(`Processing ${awsData.length} AWS resources...`);
  const startTime = Date.now();
  
  try {
    await processResources(awsData, userId, token);
    const duration = Date.now() - startTime;
    console.log(`โœ… Completed processing ${awsData.length} resources in ${duration}ms`);
  } catch (error) {
    console.error('โŒ Failed to process resources:', error);
  }
};
```

---

## ๐Ÿงช Testing

### Manual Testing

1. **Test single resource** (ensure backward compatibility):
   ```typescript
   await invoke('ingest_estate_resource', { /* single resource */ });
   ```

2. **Test small batch** (2-5 resources):
   ```typescript
   await invoke('ingest_estate_resources_batch', { 
     request: { resourcesData: [resource1, resource2, resource3], /* ... */ }
   });
   ```

3. **Test large batch** (32 resources):
   ```typescript
   const resources = Array(32).fill(null).map((_, i) => ({
     content: `Test resource ${i}`,
     resource_type: 'test',
     id: `test-${i}`
   }));
   
   await invoke('ingest_estate_resources_batch', { 
     request: { resourcesData: resources, /* ... */ }
   });
   ```

4. **Test error handling**:
   - Empty batch
   - Batch > 32 resources
   - Invalid collection name
   - Missing content fields

### Performance Verification

Monitor the logs to verify performance improvements:

```
// You should see logs like:
INFO Batch ingestion completed in 800ms: parsed=32, failed=0, throughput=40.0 docs/sec
```

Compare with individual processing logs to confirm the performance gain.

---

## ๐Ÿšจ Important Notes

### Batch Size Recommendations

- **Optimal**: 16-32 resources per batch
- **Minimum**: 2 resources (otherwise use single method)
- **Maximum**: 32 resources (enforced by validation)

### Memory Considerations

- Each resource with embeddings uses ~1KB memory
- 32 resources โ‰ˆ 32KB memory footprint
- Monitor memory usage in production

### Error Handling

The batch function processes **all valid resources** even if some fail:
- Invalid resources are skipped with error logging
- Valid resources continue processing
- Final result shows success/failure counts

### Collection Support

Batch ingestion supports the same collections as single ingestion:
- `core_estate`
- `detailed_estate`

### Migration Strategy

1. **Phase 1**: Deploy batch functionality alongside existing single processing
2. **Phase 2**: Update frontend to use batch for multi-resource scenarios  
3. **Phase 3**: Keep single processing for compatibility with existing workflows

---

## ๐Ÿ“Š Performance Comparison

| Method | Resources | Time | Throughput | API Calls | DB Operations |
|--------|-----------|------|------------|-----------|---------------|
| Single | 32 | ~25s | 1.3/sec | 32 | 32 |
| Batch | 32 | ~0.8s | 40/sec | 1 | 1 |
| **Improvement** | - | **31x faster** | **31x higher** | **32x fewer** | **32x fewer** |

---

## โœ… Checklist

- [ ] Add batch request/response types to `rag_commands.rs`
- [ ] Add `ingest_estate_resources_batch` command to `rag_commands.rs`
- [ ] Add `ingest_estate_resources_batch` method to `rag_service.rs`
- [ ] Register new command in `main.rs`
- [ ] Update frontend TypeScript types
- [ ] Implement frontend batch processing logic
- [ ] Test single resource processing (backward compatibility)
- [ ] Test batch processing with various sizes
- [ ] Verify performance improvements in logs
- [ ] Document frontend usage for your team

---

## ๐Ÿค Support

If you encounter any issues during implementation:

1. Check that your `rag-module-rust` dependency includes the batch functionality
2. Verify all imports are correct
3. Ensure proper error handling in both Rust and TypeScript
4. Monitor logs for performance metrics and error details

The batch functionality is backward compatible, so your existing single resource processing will continue to work unchanged.