@dcyfr/ai-rag 0.1.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +588 -0
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +10 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/loaders/html/index.d.ts +26 -0
  8. package/dist/loaders/html/index.d.ts.map +1 -0
  9. package/dist/loaders/html/index.js +106 -0
  10. package/dist/loaders/html/index.js.map +1 -0
  11. package/dist/loaders/index.d.ts +8 -0
  12. package/dist/loaders/index.d.ts.map +1 -0
  13. package/dist/loaders/index.js +7 -0
  14. package/dist/loaders/index.js.map +1 -0
  15. package/dist/loaders/markdown/index.d.ts +33 -0
  16. package/dist/loaders/markdown/index.d.ts.map +1 -0
  17. package/dist/loaders/markdown/index.js +150 -0
  18. package/dist/loaders/markdown/index.js.map +1 -0
  19. package/dist/loaders/text/index.d.ts +21 -0
  20. package/dist/loaders/text/index.d.ts.map +1 -0
  21. package/dist/loaders/text/index.js +78 -0
  22. package/dist/loaders/text/index.js.map +1 -0
  23. package/dist/pipeline/embedding/generator.d.ts +24 -0
  24. package/dist/pipeline/embedding/generator.d.ts.map +1 -0
  25. package/dist/pipeline/embedding/generator.js +42 -0
  26. package/dist/pipeline/embedding/generator.js.map +1 -0
  27. package/dist/pipeline/embedding/index.d.ts +8 -0
  28. package/dist/pipeline/embedding/index.d.ts.map +1 -0
  29. package/dist/pipeline/embedding/index.js +6 -0
  30. package/dist/pipeline/embedding/index.js.map +1 -0
  31. package/dist/pipeline/embedding/pipeline.d.ts +26 -0
  32. package/dist/pipeline/embedding/pipeline.d.ts.map +1 -0
  33. package/dist/pipeline/embedding/pipeline.js +59 -0
  34. package/dist/pipeline/embedding/pipeline.js.map +1 -0
  35. package/dist/pipeline/index.d.ts +7 -0
  36. package/dist/pipeline/index.d.ts.map +1 -0
  37. package/dist/pipeline/index.js +7 -0
  38. package/dist/pipeline/index.js.map +1 -0
  39. package/dist/pipeline/ingestion/index.d.ts +5 -0
  40. package/dist/pipeline/ingestion/index.d.ts.map +1 -0
  41. package/dist/pipeline/ingestion/index.js +5 -0
  42. package/dist/pipeline/ingestion/index.js.map +1 -0
  43. package/dist/pipeline/ingestion/pipeline.d.ts +27 -0
  44. package/dist/pipeline/ingestion/pipeline.d.ts.map +1 -0
  45. package/dist/pipeline/ingestion/pipeline.js +118 -0
  46. package/dist/pipeline/ingestion/pipeline.js.map +1 -0
  47. package/dist/pipeline/retrieval/index.d.ts +5 -0
  48. package/dist/pipeline/retrieval/index.d.ts.map +1 -0
  49. package/dist/pipeline/retrieval/index.js +5 -0
  50. package/dist/pipeline/retrieval/index.js.map +1 -0
  51. package/dist/pipeline/retrieval/pipeline.d.ts +29 -0
  52. package/dist/pipeline/retrieval/pipeline.d.ts.map +1 -0
  53. package/dist/pipeline/retrieval/pipeline.js +109 -0
  54. package/dist/pipeline/retrieval/pipeline.js.map +1 -0
  55. package/dist/stores/index.d.ts +5 -0
  56. package/dist/stores/index.d.ts.map +1 -0
  57. package/dist/stores/index.js +5 -0
  58. package/dist/stores/index.js.map +1 -0
  59. package/dist/stores/vector/in-memory.d.ts +52 -0
  60. package/dist/stores/vector/in-memory.d.ts.map +1 -0
  61. package/dist/stores/vector/in-memory.js +172 -0
  62. package/dist/stores/vector/in-memory.js.map +1 -0
  63. package/dist/stores/vector/index.d.ts +6 -0
  64. package/dist/stores/vector/index.d.ts.map +1 -0
  65. package/dist/stores/vector/index.js +5 -0
  66. package/dist/stores/vector/index.js.map +1 -0
  67. package/dist/types/index.d.ts +259 -0
  68. package/dist/types/index.d.ts.map +1 -0
  69. package/dist/types/index.js +5 -0
  70. package/dist/types/index.js.map +1 -0
  71. package/docs/DOCUMENT_LOADERS.md +621 -0
  72. package/docs/EMBEDDINGS.md +733 -0
  73. package/docs/PIPELINES.md +771 -0
  74. package/docs/VECTOR_STORES.md +754 -0
  75. package/package.json +100 -0
@@ -0,0 +1,771 @@
1
+ # Pipelines - End-to-End RAG Workflows
2
+
3
+ **Target Audience:** Developers building complete RAG systems
4
+ **Prerequisites:** Understanding of loaders, embeddings, vector stores
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ Pipelines orchestrate the complete RAG workflow by connecting document loaders, embedding generators, and vector stores into seamless ingestion and retrieval flows.
11
+
12
+ **Key Components:**
13
+ - **Ingestion Pipeline** - Load documents → chunk → embed → store
14
+ - **Retrieval Pipeline** - Query → embed → search → assemble context
15
+ - Progress tracking and error handling
16
+ - Batch processing for efficiency
17
+ - Metadata filtering and ranking
18
+
19
+ ---
20
+
21
+ ## Ingestion Pipeline
22
+
23
+ ### Basic Setup
24
+
25
+ ```typescript
26
+ import {
27
+ TextLoader,
28
+ OpenAIEmbeddingGenerator,
29
+ Ingest InMemoryVectorStore,
30
+ IngestionPipeline,
31
+ } from '@dcyfr/ai-rag';
32
+
33
+ // 1. Setup components
34
+ const loader = new TextLoader();
35
+ const embedder = new OpenAIEmbeddingGenerator({
36
+ apiKey: process.env.OPENAI_API_KEY!,
37
+ });
38
+ const store = new InMemoryVectorStore({
39
+ collectionName: 'my-docs',
40
+ embeddingDimensions: 1536,
41
+ });
42
+
43
+ // 2. Create pipeline
44
+ const pipeline = new IngestionPipeline(loader, embedder, store);
45
+
46
+ // 3. Ingest documents
47
+ const result = await pipeline.ingest(['./docs/file1.txt', './docs/file2.txt']);
48
+
49
+ console.log(`Documents processed: ${result.documentsProcessed}`);
50
+ console.log(`Chunks generated: ${result.chunksGenerated}`);
51
+ console.log(`Embeddings created: ${result.embeddingsCreated}`);
52
+ console.log(`Duration: ${result.durationMs}ms`);
53
+ ```
54
+
55
+ ### Single File Ingestion
56
+
57
+ ```typescript
58
+ const result = await pipeline.ingestFile('./document.txt', {
59
+ chunkSize: 1000,
60
+ chunkOverlap: 200,
61
+ metadata: {
62
+ category: 'technical',
63
+ priority: 'high',
64
+ },
65
+ });
66
+ ```
67
+
68
+ ### Batch Ingestion
69
+
70
+ ```typescript
71
+ import { glob } from 'glob';
72
+
73
+ // Find all markdown files
74
+ const files = await glob('./docs/**/*.md');
75
+
76
+ const result = await pipeline.ingest(files, {
77
+ batchSize: 32, // Process 32 documents at a time
78
+ chunkSize: 800,
79
+ chunkOverlap: 150,
80
+ });
81
+
82
+ console.log(`Processed ${result.documentsProcessed} files`);
83
+ console.log(`Generated ${result.chunksGenerated} chunks`);
84
+ ```
85
+
86
+ ### Progress Tracking
87
+
88
+ ```typescript
89
+ const result = await pipeline.ingest(files, {
90
+ batchSize: 50,
91
+ onProgress: (current, total, details) => {
92
+ const percent = ((current / total) * 100).toFixed(1);
93
+ console.log(`Progress: ${current}/${total} (${percent}%)`);
94
+ console.log(`Current file: ${details.currentFile}`);
95
+ console.log(`Chunks so far: ${details.chunksProcessed}`);
96
+ console.log(`---`);
97
+ },
98
+ });
99
+ ```
100
+
101
+ **Output:**
102
+ ```
103
+ Progress: 10/100 (10.0%)
104
+ Current file: ./docs/guide-10.md
105
+ Chunks so far: 147
106
+ ---
107
+ Progress: 20/100 (20.0%)
108
+ Current file: ./docs/guide-20.md
109
+ Chunks so far: 289
110
+ ---
111
+ ```
112
+
113
+ ### Error Handling
114
+
115
+ ```typescript
116
+ const result = await pipeline.ingest(files, {
117
+ batchSize: 32,
118
+ continueOnError: true, // Don't stop on individual file errors
119
+ onError: (error, file) => {
120
+ console.error(`Failed to process ${file}:`, error.message);
121
+ // Log to monitoring service
122
+ logger.error({ error, file }, 'Ingestion error');
123
+ },
124
+ });
125
+
126
+ console.log(`Successful: ${result.documentsProcessed}`);
127
+ console.log(`Failed: ${result.errors.length}`);
128
+
129
+ // Review errors
130
+ result.errors.forEach(err => {
131
+ console.log(`${err.file}: ${err.error.message}`);
132
+ });
133
+ ```
134
+
135
+ ### Custom Metadata
136
+
137
+ ```typescript
138
+ await pipeline.ingest(files, {
139
+ metadata: (filePath) => ({
140
+ source: filePath,
141
+ category: filePath.includes('/api/') ? 'api-docs' : 'user-guide',
142
+ ingestedAt: new Date().toISOString(),
143
+ version: '2.0',
144
+ }),
145
+ });
146
+
147
+ // Metadata function receives file path and returns custom metadata
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Retrieval Pipeline
153
+
154
+ ### Basic Setup
155
+
156
+ ```typescript
157
+ import {
158
+ InMemoryVectorStore,
159
+ OpenAIEmbeddingGenerator,
160
+ RetrievalPipeline,
161
+ } from '@dcyfr/ai-rag';
162
+
163
+ const store = new InMemoryVectorStore({
164
+ collectionName: 'my-docs',
165
+ embeddingDimensions: 1536,
166
+ });
167
+
168
+ const embedder = new OpenAIEmbeddingGenerator({
169
+ apiKey: process.env.OPENAI_API_KEY!,
170
+ });
171
+
172
+ const pipeline = new RetrievalPipeline(store, embedder);
173
+ ```
174
+
175
+ ### Semantic Search
176
+
177
+ ```typescript
178
+ const result = await pipeline.query('What is machine learning?', {
179
+ limit: 5, // Return top 5 results
180
+ threshold: 0.7, // Minimum similarity score
181
+ includeMetadata: true,
182
+ });
183
+
184
+ // Assembled context from top results
185
+ console.log(result.context);
186
+
187
+ // Individual results with scores
188
+ result.results.forEach(r => {
189
+ console.log(`Score: ${r.score.toFixed(3)}`);
190
+ console.log(`Content: ${r.document.content.substring(0, 100)}...`);
191
+ console.log('---');
192
+ });
193
+
194
+ // Query metadata
195
+ console.log(`Query time: ${result.metadata.queryTimeMs}ms`);
196
+ console.log(`Total matches: ${result.metadata.totalMatches}`);
197
+ ```
198
+
199
+ ### Context Assembly
200
+
201
+ **Strategies:**
202
+
203
+ ```typescript
204
+ // 1. Simple concatenation (default)
205
+ const result = await pipeline.query('search query', {
206
+ contextAssembly: 'concatenate',
207
+ });
208
+
209
+ // Context: "chunk1\n\nchunk2\n\nchunk3..."
210
+
211
+ // 2. Ranked by relevance
212
+ const result = await pipeline.query('search query', {
213
+ contextAssembly: 'ranked',
214
+ includeScores: true,
215
+ });
216
+
217
+ // Context: "[Score: 0.95] chunk1\n\n[Score: 0.87] chunk2..."
218
+
219
+ // 3. Deduplicated
220
+ const result = await pipeline.query('search query', {
221
+ contextAssembly: 'deduplicated',
222
+ similarityThreshold: 0.95, // Merge very similar chunks
223
+ });
224
+
225
+ // 4. Custom assembly
226
+ const result = await pipeline.query('search query', {
227
+ contextAssembly: 'custom',
228
+ assembler: (results) => {
229
+ return results
230
+ .map(r => `[Source: ${r.document.metadata.source}]\n${r.document.content}`)
231
+ .join('\n\n---\n\n');
232
+ },
233
+ });
234
+ ```
235
+
236
+ ### Metadata Filtering
237
+
238
+ ```typescript
239
+ // Filter by category
240
+ const result = await pipeline.query('search query', {
241
+ limit: 10,
242
+ filter: {
243
+ field: 'category',
244
+ operator: 'eq',
245
+ value: 'api-docs',
246
+ },
247
+ });
248
+
249
+ // Only searches within API documentation
250
+
251
+ // Complex filters
252
+ const result = await pipeline.query('search query', {
253
+ limit: 10,
254
+ filter: {
255
+ operator: 'and',
256
+ filters: [
257
+ { field: 'category', operator: 'eq', value: 'technical' },
258
+ { field: 'priority', operator: 'gte', value: 7 },
259
+ { field: 'tags', operator: 'contains', value: 'ai' },
260
+ ],
261
+ },
262
+ });
263
+ ```
264
+
265
+ ### Re-ranking
266
+
267
+ ```typescript
268
+ // Re-rank results using different embedding model
269
+ const result = await pipeline.query('search query', {
270
+ limit: 10,
271
+ rerank: true,
272
+ rerankModel: 'cohere-rerank-v2',
273
+ rerankTopK: 5, // Re-rank top 5 from initial 10
274
+ });
275
+
276
+ // Results are semantically re-ordered for better relevance
277
+ ```
278
+
279
+ ### Hybrid Search
280
+
281
+ ```typescript
282
+ // Combine semantic search with keyword search
283
+ const result = await pipeline.query('search query', {
284
+ limit: 10,
285
+ hybrid: true,
286
+ keywordWeight: 0.3, // 30% keyword, 70% semantic
287
+ bm25Params: {
288
+ k1: 1.5,
289
+ b: 0.75,
290
+ },
291
+ });
292
+
293
+ // Captures both semantic meaning and keyword matches
294
+ ```
295
+
296
+ ---
297
+
298
+ ## Advanced Pipelines
299
+
300
+ ### Multi-Source Ingestion
301
+
302
+ ```typescript
303
+ import { IngestionPipeline, TextLoader, MarkdownLoader, HTMLLoader } from '@dcyfr/ai-rag';
304
+
305
+ class MultiSourcePipeline {
306
+ private pipelines: Map<string, IngestionPipeline>;
307
+
308
+ constructor(embedder, store) {
309
+ this.pipelines = new Map([
310
+ ['txt', new IngestionPipeline(new TextLoader(), embedder, store)],
311
+ ['md', new IngestionPipeline(new MarkdownLoader(), embedder, store)],
312
+ ['html', new IngestionPipeline(new HTMLLoader(), embedder, store)],
313
+ ]);
314
+ }
315
+
316
+ async ingest(files: string[]) {
317
+ const byExtension = new Map<string, string[]>();
318
+
319
+ // Group files by extension
320
+ files.forEach(file => {
321
+ const ext = file.split('.').pop() || '';
322
+ const group = byExtension.get(ext) || [];
323
+ group.push(file);
324
+ byExtension.set(ext, group);
325
+ });
326
+
327
+ // Process each group with appropriate pipeline
328
+ const results = [];
329
+ for (const [ext, fileList] of byExtension) {
330
+ const pipeline = this.pipelines.get(ext);
331
+ if (pipeline) {
332
+ const result = await pipeline.ingest(fileList);
333
+ results.push(result);
334
+ }
335
+ }
336
+
337
+ return results;
338
+ }
339
+ }
340
+
341
+ // Usage
342
+ const multiPipeline = new MultiSourcePipeline(embedder, store);
343
+ await multiPipeline.ingest([
344
+ './docs/guide.txt',
345
+ './docs/README.md',
346
+ './docs/page.html',
347
+ ]);
348
+ ```
349
+
350
+ ### Incremental Updates
351
+
352
+ ```typescript
353
+ class IncrementalPipeline {
354
+ private pipeline: IngestionPipeline;
355
+ private processedFiles: Map<string, string>; // file -> hash
356
+
357
+ async ingest(files: string[]) {
358
+ const toProcess = [];
359
+
360
+ for (const file of files) {
361
+ const content = await fs.readFile(file, 'utf-8');
362
+ const hash = this.hash(content);
363
+ const previousHash = this.processedFiles.get(file);
364
+
365
+ if (hash !== previousHash) {
366
+ // File changed or new
367
+ toProcess.push(file);
368
+
369
+ // Remove old version from store
370
+ if (previousHash) {
371
+ await this.removeFileDocuments(file);
372
+ }
373
+
374
+ this.processedFiles.set(file, hash);
375
+ }
376
+ }
377
+
378
+ if (toProcess.length > 0) {
379
+ console.log(`Processing ${toProcess.length} changed/new files`);
380
+ await this.pipeline.ingest(toProcess);
381
+ } else {
382
+ console.log('No changes detected');
383
+ }
384
+ }
385
+
386
+ private hash(content: string): string {
387
+ return crypto.createHash('sha256').update(content).digest('hex');
388
+ }
389
+
390
+ private async removeFileDocuments(file: string) {
391
+ // Remove documents with matching source metadata
392
+ const allDocs = await this.store.getAll();
393
+ const toDelete = allDocs
394
+ .filter(doc => doc.metadata.source === file)
395
+ .map(doc => doc.id);
396
+ await this.store.deleteDocuments(toDelete);
397
+ }
398
+ }
399
+ ```
400
+
401
+ ### Streaming Ingestion
402
+
403
+ ```typescript
404
+ import { Readable } from 'stream';
405
+
406
+ class StreamingPipeline {
407
+ async ingestStream(stream: Readable, options: IngestOptions) {
408
+ const chunks: Document[] = [];
409
+ let buffer = '';
410
+
411
+ stream.on('data', async (chunk) => {
412
+ buffer += chunk.toString();
413
+
414
+ // Split into documents when buffer exceeds threshold
415
+ if (buffer.length >= options.chunkSize) {
416
+ const docs = await this.processBuffer(buffer, options);
417
+ chunks.push(...docs);
418
+ buffer = '';
419
+
420
+ // Process batch
421
+ if (chunks.length >= options.batchSize) {
422
+ await this.processBatch(chunks);
423
+ chunks.length = 0;
424
+ }
425
+ }
426
+ });
427
+
428
+ stream.on('end', async () => {
429
+ // Process remaining buffer
430
+ if (buffer) {
431
+ const docs = await this.processBuffer(buffer, options);
432
+ chunks.push(...docs);
433
+ }
434
+
435
+ // Process final batch
436
+ if (chunks.length > 0) {
437
+ await this.processBatch(chunks);
438
+ }
439
+ });
440
+ }
441
+
442
+ private async processBuffer(text: string, options): Promise<Document[]> {
443
+ const [embedding] = await this.embedder.embed([text]);
444
+ return [{
445
+ id: crypto.randomUUID(),
446
+ content: text,
447
+ embedding,
448
+ metadata: options.metadata || {},
449
+ }];
450
+ }
451
+
452
+ private async processBatch(docs: Document[]) {
453
+ await this.store.addDocuments(docs);
454
+ console.log(`Processed batch of ${docs.length} documents`);
455
+ }
456
+ }
457
+ ```
458
+
459
+ ---
460
+
461
+ ## RAG Patterns
462
+
463
+ ### Question Answering
464
+
465
+ ```typescript
466
+ import OpenAI from 'openai';
467
+
468
+ async function answerQuestion(question: string): Promise<string> {
469
+ // 1. Retrieve relevant context
470
+ const retrieval = await pipeline.query(question, {
471
+ limit: 5,
472
+ threshold: 0.7,
473
+ });
474
+
475
+ // 2. Generate answer using context
476
+ const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });
477
+
478
+ const response = await openai.chat.completions.create({
479
+ model: 'gpt-4',
480
+ messages: [
481
+ {
482
+ role: 'system',
483
+ content: 'Answer the question based only on the provided context. If the context doesn\'t contain enough information, say "I don\'t have enough information to answer that."',
484
+ },
485
+ {
486
+ role: 'user',
487
+ content: `Context:\n${retrieval.context}\n\nQuestion: ${question}`,
488
+ },
489
+ ],
490
+ });
491
+
492
+ return response.choices[0].message.content || '';
493
+ }
494
+
495
+ // Usage
496
+ const answer = await answerQuestion('What is machine learning?');
497
+ console.log(answer);
498
+ ```
499
+
500
+ ### Conversational RAG
501
+
502
+ ```typescript
503
+ class ConversationalRAG {
504
+ private pipeline: RetrievalPipeline;
505
+ private openai: OpenAI;
506
+ private history: Array<{ role: string; content: string }> = [];
507
+
508
+ async chat(message: string): Promise<string> {
509
+ // 1. Generate search query from conversation history
510
+ const searchQuery = await this.generateSearchQuery(message);
511
+
512
+ // 2. Retrieve relevant context
513
+ const retrieval = await this.pipeline.query(searchQuery, {
514
+ limit: 5,
515
+ threshold: 0.7,
516
+ });
517
+
518
+ // 3. Generate response with context + history
519
+ this.history.push({ role: 'user', content: message });
520
+
521
+ const response = await this.openai.chat.completions.create({
522
+ model: 'gpt-4',
523
+ messages: [
524
+ {
525
+ role: 'system',
526
+ content: `You are a helpful assistant. Use the provided context to answer questions. Context:\n${retrieval.context}`,
527
+ },
528
+ ...this.history,
529
+ ],
530
+ });
531
+
532
+ const assistantMessage = response.choices[0].message.content || '';
533
+ this.history.push({ role: 'assistant', content: assistantMessage });
534
+
535
+ return assistantMessage;
536
+ }
537
+
538
+ private async generateSearchQuery(message: string): Promise<string> {
539
+ // Use conversation history to generate better search query
540
+ const response = await this.openai.chat.completions.create({
541
+ model: 'gpt-3.5-turbo',
542
+ messages: [
543
+ { role: 'system', content: 'Generate a semantic search query from the conversation.' },
544
+ ...this.history.slice(-4), // Last 2 turns
545
+ { role: 'user', content: message },
546
+ ],
547
+ });
548
+
549
+ return response.choices[0].message.content || message;
550
+ }
551
+ }
552
+
553
+ // Usage
554
+ const rag = new ConversationalRAG(pipeline, openai);
555
+
556
+ console.log(await rag.chat('What is RAG?'));
557
+ console.log(await rag.chat('How does it work?')); // Uses conversation context
558
+ console.log(await rag.chat('What are the benefits?'));
559
+ ```
560
+
561
+ ### Summarization with RAG
562
+
563
+ ```typescript
564
+ async function summarizeDocuments(topic: string): Promise<string> {
565
+ // 1. Retrieve all relevant documents
566
+ const retrieval = await pipeline.query(topic, {
567
+ limit: 20, // Get more documents for comprehensive summary
568
+ threshold: 0.6,
569
+ });
570
+
571
+ // 2. Generate summary
572
+ const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });
573
+
574
+ const response = await openai.chat.completions.create({
575
+ model: 'gpt-4',
576
+ messages: [
577
+ {
578
+ role: 'system',
579
+ content: 'Summarize the key points from the provided documents.',
580
+ },
581
+ {
582
+ role: 'user',
583
+ content: `Topic: ${topic}\n\nDocuments:\n${retrieval.context}`,
584
+ },
585
+ ],
586
+ });
587
+
588
+ return response.choices[0].message.content || '';
589
+ }
590
+
591
+ // Usage
592
+ const summary = await summarizeDocuments('machine learning best practices');
593
+ console.log(summary);
594
+ ```
595
+
596
+ ---
597
+
598
+ ## Best Practices
599
+
600
+ ### 1. Batch Processing for Efficiency
601
+
602
+ ```typescript
603
+ // ✅ GOOD - Process in batches
604
+ await pipeline.ingest(files, {
605
+ batchSize: 50, // Balance throughput and memory
606
+ });
607
+
608
+ // ❌ BAD - Process one by one
609
+ for (const file of files) {
610
+ await pipeline.ingestFile(file);
611
+ }
612
+ ```
613
+
614
+ ### 2. Add Progress Tracking
615
+
616
+ ```typescript
617
+ // ✅ GOOD - Monitor progress
618
+ await pipeline.ingest(files, {
619
+ onProgress: (current, total, details) => {
620
+ metrics.gauge('ingestion.progress', current / total);
621
+ logger.info({ current, total }, 'Ingestion progress');
622
+ },
623
+ });
624
+ ```
625
+
626
+ ### 3. Handle Errors Gracefully
627
+
628
+ ```typescript
629
+ // ✅ GOOD - Continue on errors, track failures
630
+ const result = await pipeline.ingest(files, {
631
+ continueOnError: true,
632
+ onError: (error, file) => {
633
+ logger.error({ error, file }, 'Ingestion error');
634
+ metrics.increment('ingestion.errors');
635
+ },
636
+ });
637
+
638
+ console.log(`Success: ${result.documentsProcessed}`);
639
+ console.log(`Failures: ${result.errors.length}`);
640
+ ```
641
+
642
+ ### 4. Use Appropriate Context Size
643
+
644
+ ```typescript
645
+ // ✅ GOOD - Balance context quality and cost
646
+ const result = await pipeline.query(question, {
647
+ limit: 5, // Enough for good context, not too much
648
+ threshold: 0.7, // Filter low-quality matches
649
+ });
650
+
651
+ const context = result.context.slice(0, 4000); // Limit tokens
652
+
653
+ // ❌ BAD - Too much context
654
+ const result = await pipeline.query(question, {
655
+ limit: 100, // Excessive, expensive, dilutes signal
656
+ });
657
+ ```
658
+
659
+ ### 5. Monitor Performance
660
+
661
+ ```typescript
662
+ class MonitoredPipeline {
663
+ private pipeline: RetrievalPipeline;
664
+
665
+ async query(query: string, options?: QueryOptions) {
666
+ const start = Date.now();
667
+
668
+ try {
669
+ const result = await this.pipeline.query(query, options);
670
+ const duration = Date.now() - start;
671
+
672
+ // Log metrics
673
+ metrics.histogram('query.duration', duration);
674
+ metrics.gauge('query.results', result.results.length);
675
+
676
+ logger.info({
677
+ query,
678
+ duration,
679
+ results: result.results.length,
680
+ }, 'Query completed');
681
+
682
+ return result;
683
+ } catch (error) {
684
+ metrics.increment('query.errors');
685
+ throw error;
686
+ }
687
+ }
688
+ }
689
+ ```
690
+
691
+ ---
692
+
693
+ ## Troubleshooting
694
+
695
+ ### Issue: Slow Ingestion
696
+
697
+ **Problem:** Document ingestion takes too long
698
+
699
+ **Solutions:**
700
+ ```typescript
701
+ // 1. Increase batch size
702
+ await pipeline.ingest(files, {
703
+ batchSize: 100, // Process more at once
704
+ });
705
+
706
+ // 2. Parallel processing
707
+ const batches = chunk(files, 100);
708
+ await Promise.all(
709
+ batches.map(batch => pipeline.ingest(batch))
710
+ );
711
+
712
+ // 3. Use faster embedding model
713
+ const embedder = new OpenAIEmbeddingGenerator({
714
+ model: 'text-embedding-3-small', // Faster than -large
715
+ dimensions: 512, // Smaller dimensions
716
+ });
717
+ ```
718
+
719
+ ### Issue: Poor Retrieval Quality
720
+
721
+ **Problem:** Retrieved context not relevant
722
+
723
+ **Solutions:**
724
+ ```typescript
725
+ // 1. Increase threshold
726
+ const result = await pipeline.query(question, {
727
+ threshold: 0.8, // Higher bar for relevance
728
+ });
729
+
730
+ // 2. Get more results, filter manually
731
+ const result = await pipeline.query(question, {
732
+ limit: 20,
733
+ });
734
+
735
+ const filtered = result.results
736
+ .filter(r => r.score >= 0.75)
737
+ .slice(0, 5);
738
+
739
+ // 3. Add metadata filtering
740
+ const result = await pipeline.query(question, {
741
+ filter: {
742
+ field: 'category',
743
+ operator: 'eq',
744
+ value: 'relevant-category',
745
+ },
746
+ });
747
+ ```
748
+
749
+ ### Issue: Out of Memory
750
+
751
+ **Problem:** Application crashes during ingestion
752
+
753
+ **Solution:** Stream processingor smaller batches
754
+
755
+ ```typescript
756
+ // Option 1: Smaller batches
757
+ await pipeline.ingest(files, {
758
+ batchSize: 10, // Reduce from 50
759
+ });
760
+
761
+ // Option 2: Process sequentially
762
+ for (const batch of chunks(files, 10)) {
763
+ await pipeline.ingest(batch);
764
+ await new Promise(resolve => setTimeout(resolve, 100)); // Brief pause
765
+ }
766
+ ```
767
+
768
+ ---
769
+
770
+ **Last Updated:** February 7, 2026
771
+ **Version:** 1.0.0