@devilsdev/rag-pipeline-utils 1.0.0 → 2.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.
Files changed (72) hide show
  1. package/.codecov.yml +21 -0
  2. package/.dockerignore +14 -14
  3. package/.eslintignore +5 -0
  4. package/.eslintrc.json +21 -0
  5. package/.github/workflows/ci.yml +64 -42
  6. package/.husky/pre-commit +5 -0
  7. package/CHANGELOG.md +46 -0
  8. package/Dockerfile +22 -22
  9. package/LICENSE +674 -674
  10. package/README.md +117 -116
  11. package/__tests__/fixtures/.ragrc.json +8 -0
  12. package/__tests__/fixtures/invalid-ragrc.json +4 -0
  13. package/__tests__/fixtures/sample-eval-dataset.json +14 -14
  14. package/__tests__/fixtures/sample.pdf +1 -0
  15. package/__tests__/fixtures/src/mocks/openai-embedder.js +16 -0
  16. package/__tests__/fixtures/src/mocks/openai-llm.js +9 -0
  17. package/__tests__/fixtures/src/mocks/pdf-loader.js +17 -0
  18. package/__tests__/fixtures/src/mocks/pinecone-retriever.js +26 -0
  19. package/__tests__/fixtures/valid-ragrc.json +16 -0
  20. package/__tests__/integration/cli/config-flow.test.js +98 -44
  21. package/__tests__/integration/config/load-config.test.js +67 -52
  22. package/__tests__/unit/config/validate-schema.test.js +121 -0
  23. package/__tests__/unit/core/plugin-registry.test.js +55 -55
  24. package/__tests__/unit/reranker/llm-reranker.test.js +18 -42
  25. package/__tests__/unit/reranker/reranker.enriched.test.js +19 -0
  26. package/__tests__/unit/reranker/reranker.fallback.test.js +15 -0
  27. package/__tests__/unit/reranker/reranker.snapshot.test.js +15 -17
  28. package/__tests__/unit/reranker/reranker.structured-output.test.js +17 -0
  29. package/bin/cli.js +62 -168
  30. package/docs-site/docs/Architecture.md +76 -76
  31. package/docs-site/docs/CLI.md +69 -69
  32. package/docs-site/docs/Evaluation.md +61 -61
  33. package/docs-site/docs/FAQ.md +50 -50
  34. package/docs-site/docs/Plugins.md +61 -61
  35. package/docs-site/docs/Usage.md +72 -72
  36. package/jest.config.js +20 -13
  37. package/package.json +20 -3
  38. package/plugin.config.json +14 -0
  39. package/public/src/App.jsx +57 -57
  40. package/release.config.js +36 -23
  41. package/scripts/banner-injector.js +89 -0
  42. package/scripts/ci-runner.js +51 -0
  43. package/scripts/repair-fixtures.js +30 -0
  44. package/scripts/setup.js +31 -0
  45. package/scripts/test-global-setup.js +18 -0
  46. package/scripts/verify-fixture-mocks.js +45 -0
  47. package/scripts/verify-fixtures.js +36 -0
  48. package/server.js +31 -31
  49. package/src/config/load-config.js +33 -35
  50. package/src/config/load-plugin-config.js +56 -0
  51. package/src/config/validate-plugin-schema.js +28 -0
  52. package/src/config/validate-schema.js +107 -49
  53. package/src/core/create-pipeline.js +96 -82
  54. package/src/core/plugin-contracts.js +21 -0
  55. package/src/core/plugin-registry.js +48 -61
  56. package/src/dag/dag-engine.js +69 -69
  57. package/src/evaluate/evaluator.js +44 -44
  58. package/src/evaluate/scoring.js +73 -73
  59. package/src/loader/csv-loader.js +30 -30
  60. package/src/loader/directory-loader.js +37 -37
  61. package/src/loader/html-loader.js +57 -57
  62. package/src/loader/markdown-loader.js +58 -58
  63. package/src/mocks/openai-embedder.js +30 -0
  64. package/src/mocks/openai-llm.js +18 -0
  65. package/src/mocks/pdf-loader.js +17 -0
  66. package/src/mocks/pinecone-retriever.js +25 -0
  67. package/src/reranker/llm-reranker.js +34 -49
  68. package/src/utils/ci/diagnostic-reporter.js +36 -0
  69. package/src/utils/logger.js +29 -29
  70. package/src/utils/retry.js +42 -42
  71. package/src/utils/validate-plugin-contract.js +36 -0
  72. package/.github/workflows/release.yml +0 -21
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Version: 1.1.0
3
+ * Description: Schema validation tests for .ragrc.json using AJV and external fixtures
4
+ * Author: Ali Kahwaji
5
+ * File: __tests__/unit/config/validate-schema.test.js
6
+ */
7
+
8
+ import { readFileSync } from 'fs';
9
+ import { resolve } from 'path';
10
+ import { validateRagrcSchema, validatePluginSchema } from '../../../src/config/validate-schema.js';
11
+
12
+ const readFixture = (name) => {
13
+ const filePath = resolve('__tests__/fixtures',name.replace('.ragrc', '-ragrc'));
14
+ return JSON.parse(readFileSync(filePath, 'utf-8'));
15
+ };
16
+
17
+ describe('validateRagrcSchema', () => {
18
+ test('passes with valid structured plugin schema', () => {
19
+ const config = readFixture('valid-ragrc.json');
20
+ const result = validateRagrcSchema(config);
21
+ expect(result.valid).toBe(true);
22
+ expect(result.errors).toBeUndefined(); // updated: null => undefined
23
+ });
24
+
25
+ test('fails with missing pipeline', () => {
26
+ const config = {
27
+ loader: { pdf: './src/mocks/pdf-loader.js' },
28
+ namespace: 'no-pipeline'
29
+ };
30
+ const result = validateRagrcSchema(config);
31
+ expect(result.valid).toBe(false);
32
+ expect(result.errors).toEqual(
33
+ expect.arrayContaining([
34
+ expect.objectContaining({
35
+ keyword: 'required',
36
+ params: expect.objectContaining({ missingProperty: 'pipeline' })
37
+ })
38
+ ])
39
+ );
40
+ });
41
+
42
+ test('fails with non-object plugin sections', () => {
43
+ const config = {
44
+ loader: './src/invalid.js', // should be object
45
+ embedder: { openai: './src/mocks/openai-embedder.js' },
46
+ retriever: { pinecone: './src/mocks/pinecone-retriever.js' },
47
+ llm: { openai: './src/mocks/openai-llm.js' },
48
+ pipeline: ['loader'],
49
+ namespace: 'invalid-section'
50
+ };
51
+ const result = validateRagrcSchema(config);
52
+ expect(result.valid).toBe(false);
53
+ expect(result.errors).toEqual(
54
+ expect.arrayContaining([
55
+ expect.objectContaining({ instancePath: '/loader' })
56
+ ])
57
+ );
58
+ });
59
+
60
+ test('fails with non-string namespace', () => {
61
+ const config = {
62
+ loader: { pdf: './src/mocks/pdf-loader.js' },
63
+ embedder: { openai: './src/mocks/openai-embedder.js' },
64
+ retriever: { pinecone: './src/mocks/pinecone-retriever.js' },
65
+ llm: { openai: './src/mocks/openai-llm.js' },
66
+ pipeline: ['loader'],
67
+ namespace: 12345
68
+ };
69
+ const result = validateRagrcSchema(config);
70
+ expect(result.valid).toBe(false);
71
+ expect(result.errors).toEqual(
72
+ expect.arrayContaining([
73
+ expect.objectContaining({ instancePath: '/namespace' })
74
+ ])
75
+ );
76
+ });
77
+ });
78
+
79
+ describe('validatePluginSchema (subset)', () => {
80
+ test('passes with plugin-only valid structure', () => {
81
+ const config = {
82
+ loader: { pdf: './src/mocks/pdf-loader.js' },
83
+ embedder: { openai: './src/mocks/openai-embedder.js' },
84
+ retriever: { pinecone: './src/mocks/pinecone-retriever.js' },
85
+ llm: { openai: './src/mocks/openai-llm.js' }
86
+ };
87
+ const result = validatePluginSchema(config);
88
+ expect(result.valid).toBe(true);
89
+ });
90
+
91
+ test('fails when plugin section is missing', () => {
92
+ const config = {
93
+ embedder: { openai: './mock.js' },
94
+ retriever: { pinecone: './mock.js' },
95
+ llm: { openai: './mock.js' }
96
+ };
97
+ const result = validatePluginSchema(config);
98
+ expect(result.valid).toBe(false);
99
+ expect(result.errors).toEqual(
100
+ expect.arrayContaining([
101
+ expect.objectContaining({ instancePath: '', message: expect.stringContaining('must have required property') })
102
+ ])
103
+ );
104
+ });
105
+
106
+ test('fails when plugin paths are not strings', () => {
107
+ const config = {
108
+ loader: { pdf: 12345 },
109
+ embedder: { openai: './mock.js' },
110
+ retriever: { pinecone: './mock.js' },
111
+ llm: { openai: './mock.js' }
112
+ };
113
+ const result = validatePluginSchema(config);
114
+ expect(result.valid).toBe(false);
115
+ expect(result.errors).toEqual(
116
+ expect.arrayContaining([
117
+ expect.objectContaining({ instancePath: '/loader/pdf' })
118
+ ])
119
+ );
120
+ });
121
+ });
@@ -1,55 +1,55 @@
1
- /**
2
- * Version: 0.1.0
3
- * Path: /__tests__/unit/core/plugin-registry.test.js
4
- * Description: Unit tests for the PluginRegistry class
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- import { PluginRegistry } from '../../../src/core/plugin-registry.js';
9
-
10
- describe('PluginRegistry', () => {
11
- let registry;
12
-
13
- beforeEach(() => {
14
- registry = new PluginRegistry();
15
- });
16
-
17
- test('registers and retrieves a plugin successfully', () => {
18
- const mockPlugin = { name: 'test-loader' };
19
- registry.register('loader', 'test', mockPlugin);
20
- const result = registry.get('loader', 'test');
21
- expect(result).toBe(mockPlugin);
22
- });
23
-
24
- test('overwrites existing plugin under the same name', () => {
25
- const firstPlugin = { name: 'first' };
26
- const secondPlugin = { name: 'second' };
27
- registry.register('embedder', 'dup', firstPlugin);
28
- registry.register('embedder', 'dup', secondPlugin);
29
- const result = registry.get('embedder', 'dup');
30
- expect(result).toBe(secondPlugin);
31
- });
32
-
33
- test('throws error for unknown plugin type on registration', () => {
34
- expect(() => registry.register('unknown-type', 'x', {})).toThrow(/Unknown plugin type/);
35
- });
36
-
37
- test('throws error when retrieving a nonexistent plugin', () => {
38
- expect(() => registry.get('retriever', 'missing')).toThrow(/Plugin not found/);
39
- });
40
-
41
- test('lists all registered plugin names for a type', () => {
42
- registry.register('llm', 'gpt-4', {});
43
- registry.register('llm', 'gpt-3.5', {});
44
- const list = registry.list('llm');
45
- expect(list).toContain('gpt-4');
46
- expect(list).toContain('gpt-3.5');
47
- expect(list.length).toBe(2);
48
- });
49
-
50
- test('throws error when listing plugins for invalid type', () => {
51
- expect(() => registry.list('invalid')).toThrow(/Unknown plugin type/);
52
- });
53
- });
54
-
55
-
1
+ /**
2
+ * Version: 0.1.0
3
+ * Path: /__tests__/unit/core/plugin-registry.test.js
4
+ * Description: Unit tests for the PluginRegistry class
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ import { PluginRegistry } from '../../../src/core/plugin-registry.js';
9
+
10
+ describe('PluginRegistry', () => {
11
+ let registry;
12
+
13
+ beforeEach(() => {
14
+ registry = new PluginRegistry();
15
+ });
16
+
17
+ test('registers and retrieves a plugin successfully', () => {
18
+ const mockPlugin = { name: 'test-loader' };
19
+ registry.register('loader', 'test', mockPlugin);
20
+ const result = registry.get('loader', 'test');
21
+ expect(result).toBe(mockPlugin);
22
+ });
23
+
24
+ test('overwrites existing plugin under the same name', () => {
25
+ const firstPlugin = { name: 'first' };
26
+ const secondPlugin = { name: 'second' };
27
+ registry.register('embedder', 'dup', firstPlugin);
28
+ registry.register('embedder', 'dup', secondPlugin);
29
+ const result = registry.get('embedder', 'dup');
30
+ expect(result).toBe(secondPlugin);
31
+ });
32
+
33
+ test('throws error for unknown plugin type on registration', () => {
34
+ expect(() => registry.register('unknown-type', 'x', {})).toThrow(/Unknown plugin type/);
35
+ });
36
+
37
+ test('throws error when retrieving a nonexistent plugin', () => {
38
+ expect(() => registry.get('retriever', 'missing')).toThrow(/Plugin not found/);
39
+ });
40
+
41
+ test('lists all registered plugin names for a type', () => {
42
+ registry.register('llm', 'gpt-4', {});
43
+ registry.register('llm', 'gpt-3.5', {});
44
+ const list = registry.list('llm');
45
+ expect(list).toContain('gpt-4');
46
+ expect(list).toContain('gpt-3.5');
47
+ expect(list.length).toBe(2);
48
+ });
49
+
50
+ test('throws error when listing plugins for invalid type', () => {
51
+ expect(() => registry.list('invalid')).toThrow(/Unknown plugin type/);
52
+ });
53
+ });
54
+
55
+
@@ -1,42 +1,18 @@
1
- /**
2
- * Version: 0.1.0
3
- * Path: /__tests__/unit/reranker/llm-reranker.test.js
4
- * Description: Unit tests for LLM-based context reranker
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
9
-
10
- describe('LLMReranker', () => {
11
- const mockDocs = [
12
- { text: 'context about math' },
13
- { text: 'context about vectors' },
14
- { text: 'context about nothing' }
15
- ];
16
-
17
- test('returns reranked documents based on mock LLM JSON output', async () => {
18
- const mockLLM = {
19
- async generate() {
20
- return '[1, 0, 2]';
21
- }
22
- };
23
- const reranker = new LLMReranker({ llm: mockLLM });
24
- const result = await reranker.rerank('What are vectors?', mockDocs);
25
- expect(result.map(d => d.text)).toEqual([
26
- 'context about vectors',
27
- 'context about math',
28
- 'context about nothing'
29
- ]);
30
- });
31
-
32
- test('gracefully falls back if LLM returns invalid JSON', async () => {
33
- const mockLLM = {
34
- async generate() {
35
- return 'this is not JSON';
36
- }
37
- };
38
- const reranker = new LLMReranker({ llm: mockLLM });
39
- const result = await reranker.rerank('Explain.', mockDocs);
40
- expect(result.length).toBe(0);
41
- });
42
- });
1
+ /**
2
+ * File: __tests__/unit/reranker/llm-reranker.test.js
3
+ * Version: 1.0.0
4
+ * Description: Validates core behavior of the LLMReranker module
5
+ * Author: @devilsdev
6
+ */
7
+
8
+ import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
9
+
10
+ describe('LLMReranker', () => {
11
+ test('returns reranked documents based on mock LLM JSON output', async () => {
12
+ const mockLLM = { async generate() { return '[1, 0, 2]'; } };
13
+ const docs = [{ text: 'A' }, { text: 'B' }, { text: 'C' }];
14
+ const reranker = new LLMReranker({ llm: mockLLM });
15
+ const result = await reranker.rerank('What is A?', docs);
16
+ expect(result.map(d => d.text)).toEqual(['B', 'A', 'C']);
17
+ });
18
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * File: __tests__/unit/reranker/reranker.enriched.test.js
3
+ * Version: 1.0.0
4
+ * Description: Tests support for extended document structures
5
+ * Author: @devilsdev
6
+ */
7
+ import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
8
+
9
+ test('supports documents with metadata', async () => {
10
+ const mockLLM = { async generate() { return '[2, 0, 1]'; } };
11
+ const docs = [
12
+ { text: 'One', source: 'alpha' },
13
+ { text: 'Two', source: 'beta' },
14
+ { text: 'Three', source: 'gamma' }
15
+ ];
16
+ const reranker = new LLMReranker({ llm: mockLLM });
17
+ const result = await reranker.rerank('Choose the most complete', docs);
18
+ expect(result[0]).toHaveProperty('source', 'gamma');
19
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * File: __tests__/unit/reranker/reranker.fallback.test.js
3
+ * Version: 1.0.0
4
+ * Description: Validates fallback handling for invalid LLM output
5
+ * Author: @devilsdev
6
+ */
7
+ import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
8
+
9
+ test('gracefully handles invalid LLM output', async () => {
10
+ const mockLLM = { async generate() { return 'INVALID_JSON'; } };
11
+ const docs = [{ text: 'X' }, { text: 'Y' }, { text: 'Z' }];
12
+ const reranker = new LLMReranker({ llm: mockLLM });
13
+ const result = await reranker.rerank('Prompt?', docs);
14
+ expect(result.map(d => d.text)).toEqual(['X', 'Y', 'Z']);
15
+ });
@@ -1,17 +1,15 @@
1
- import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
2
-
3
- test('LLMReranker output snapshot', async () => {
4
- const mockLLM = {
5
- async generate() {
6
- return '[1, 0, 2]';
7
- }
8
- };
9
- const mockDocs = [
10
- { text: 'A' },
11
- { text: 'B' },
12
- { text: 'C' }
13
- ];
14
- const reranker = new LLMReranker({ llm: mockLLM });
15
- const result = await reranker.rerank('What is A?', mockDocs);
16
- expect(result).toMatchSnapshot();
17
- });
1
+ /**
2
+ * File: __tests__/unit/reranker/reranker.snapshot.test.js
3
+ * Version: 1.0.0
4
+ * Description: Captures a snapshot baseline of reranked results
5
+ * Author: @devilsdev
6
+ */
7
+ import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
8
+
9
+ test('LLMReranker output snapshot', async () => {
10
+ const mockLLM = { async generate() { return '[1, 0, 2]'; } };
11
+ const mockDocs = [{ text: 'A' }, { text: 'B' }, { text: 'C' }];
12
+ const reranker = new LLMReranker({ llm: mockLLM });
13
+ const result = await reranker.rerank('What is A?', mockDocs);
14
+ expect(result).toMatchSnapshot();
15
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * File: __tests__/unit/reranker/reranker.structured-output.test.js
3
+ * Version: 1.0.0
4
+ * Description: Validates structured LLM output parsing
5
+ * Author: @devilsdev
6
+ */
7
+ import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
8
+
9
+ test('parses structured LLM JSON result', async () => {
10
+ const mockLLM = { async generate() {
11
+ return JSON.stringify({ ranking: [0, 2, 1] });
12
+ }};
13
+ const docs = [{ text: 'Alpha' }, { text: 'Beta' }, { text: 'Gamma' }];
14
+ const reranker = new LLMReranker({ llm: mockLLM });
15
+ const result = await reranker.rerank('Which is alpha?', docs);
16
+ expect(result[0].text).toBe('Alpha');
17
+ });
package/bin/cli.js CHANGED
@@ -1,168 +1,62 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Version: 0.1.7
5
- * Path: /bin/cli.js
6
- * Description: CLI interface for rag-pipeline utilities with loaders, reranker, and evaluation
7
- * Author: Ali Kahwaji
8
- */
9
-
10
- import { Command } from 'commander';
11
- import { createRagPipeline, registry } from '../src/core/create-pipeline.js';
12
- import { loadRagConfig } from '../src/config/load-config.js';
13
- import { logger } from '../src/utils/logger.js';
14
- import { evaluateRagDataset } from '../src/evaluate/evaluator.js';
15
- import { LLMReranker } from '../src/reranker/llm-reranker.js';
16
-
17
- import { MarkdownLoader } from '../src/loader/markdown-loader.js';
18
- import { HTMLLoader } from '../src/loader/html-loader.js';
19
- import { CSVLoader } from '../src/loader/csv-loader.js';
20
- import { DirectoryLoader } from '../src/loader/directory-loader.js';
21
-
22
- // Sample/mock plugin implementations
23
- class PDFLoader {
24
- async load(path) {
25
- return [{ chunk: () => ['Sample PDF chunk.'] }];
26
- }
27
- }
28
- class OpenAIEmbedder {
29
- async embed(chunks) {
30
- return chunks.map((text, i) => ({ id: `v${i}`, values: [0.1, 0.2, 0.3], metadata: { text } }));
31
- }
32
- async embedQuery(prompt) {
33
- return [0.1, 0.2, 0.3];
34
- }
35
- }
36
- class PineconeRetriever {
37
- async store(vectors) {}
38
- async retrieve(vector) {
39
- return [
40
- { id: 'a', text: 'Chunk about pine trees', metadata: {} },
41
- { id: 'b', text: 'Chunk about vectors', metadata: {} },
42
- { id: 'c', text: 'Chunk about databases', metadata: {} }
43
- ];
44
- }
45
- }
46
- class OpenAILLM {
47
- async generate(prompt, context) {
48
- return `Generated answer using: ${context.map(d => d.text).join(', ')}`;
49
- }
50
- }
51
-
52
- // Register plugins
53
- registry.register('loader', 'pdf', new PDFLoader());
54
- registry.register('loader', 'markdown', new MarkdownLoader());
55
- registry.register('loader', 'html', new HTMLLoader());
56
- registry.register('loader', 'csv', new CSVLoader());
57
- registry.register('loader', 'directory', new DirectoryLoader());
58
- registry.register('embedder', 'openai', new OpenAIEmbedder());
59
- registry.register('retriever', 'pinecone', new PineconeRetriever());
60
- registry.register('llm', 'openai-gpt-4', new OpenAILLM());
61
-
62
- const program = new Command();
63
- program
64
- .name('rag-pipeline')
65
- .description('CLI for RAG pipeline utilities')
66
- .version('0.1.7');
67
-
68
- function resolveConfig(cliOpts) {
69
- try {
70
- const fileConfig = loadRagConfig();
71
- return { ...fileConfig, ...cliOpts };
72
- } catch (err) {
73
- logger.warn({ error: err.message }, 'Config file fallback failed');
74
- return cliOpts;
75
- }
76
- }
77
-
78
- program
79
- .command('ingest')
80
- .argument('<path>', 'Path to document(s)')
81
- .option('--loader <type>', 'Document loader type')
82
- .option('--embedder <type>', 'Embedder type')
83
- .option('--retriever <type>', 'Vector store retriever type')
84
- .action(async (path, opts) => {
85
- try {
86
- const config = resolveConfig(opts);
87
- const pipeline = createRagPipeline(config);
88
- await pipeline.ingest(path);
89
- logger.info('Ingestion complete');
90
- } catch (err) {
91
- logger.error({ error: err.message }, 'Ingestion failed');
92
- process.exit(1);
93
- }
94
- });
95
-
96
- program
97
- .command('query')
98
- .argument('<prompt>', 'Prompt to submit')
99
- .option('--embedder <type>', 'Embedder type')
100
- .option('--retriever <type>', 'Retriever type')
101
- .option('--llm <type>', 'LLM type')
102
- .action(async (prompt, opts) => {
103
- try {
104
- const config = resolveConfig(opts);
105
- const pipeline = createRagPipeline(config);
106
- const answer = await pipeline.query(prompt);
107
- logger.info({ answer }, 'Query complete');
108
- console.log('\n Answer:\n', answer);
109
- } catch (err) {
110
- logger.error({ error: err.message }, 'Query failed');
111
- process.exit(1);
112
- }
113
- });
114
-
115
- program
116
- .command('evaluate')
117
- .argument('<dataset>', 'Path to JSON file of evaluation cases')
118
- .option('--embedder <type>', 'Embedder type')
119
- .option('--retriever <type>', 'Retriever type')
120
- .option('--llm <type>', 'LLM type')
121
- .action(async (dataset, opts) => {
122
- try {
123
- const config = resolveConfig(opts);
124
- const results = await evaluateRagDataset(dataset, config);
125
- const passRate = results.filter(r => r.success).length / results.length;
126
- const avgBLEU = results.reduce((sum, r) => sum + r.scores.bleu, 0) / results.length;
127
- const avgROUGE = results.reduce((sum, r) => sum + r.scores.rouge, 0) / results.length;
128
-
129
- results.forEach((r, i) => {
130
- console.log(`\nCase ${i + 1}:\nPrompt: ${r.prompt}\nPass: ${r.success}\nBLEU: ${r.scores.bleu.toFixed(2)}\nROUGE: ${r.scores.rouge.toFixed(2)}`);
131
- });
132
-
133
- console.log(`\n Summary:\nPassed: ${Math.round(passRate * 100)}%\nAvg BLEU: ${avgBLEU.toFixed(2)}\nAvg ROUGE: ${avgROUGE.toFixed(2)}`);
134
- } catch (err) {
135
- logger.error({ error: err.message }, 'Evaluation failed');
136
- process.exit(1);
137
- }
138
- });
139
-
140
- program
141
- .command('rerank')
142
- .argument('<prompt>', 'Prompt to rerank context for')
143
- .option('--retriever <type>', 'Retriever type')
144
- .option('--llm <type>', 'LLM type')
145
- .action(async (prompt, opts) => {
146
- try {
147
- const config = resolveConfig(opts);
148
- const retriever = registry.get('retriever', config.retriever);
149
- const llm = registry.get('llm', config.llm);
150
- const embedder = registry.get('embedder', config.embedder);
151
-
152
- const queryVector = await embedder.embedQuery(prompt);
153
- const docs = await retriever.retrieve(queryVector);
154
-
155
- const reranker = new LLMReranker({ llm });
156
- const reranked = await reranker.rerank(prompt, docs);
157
-
158
- console.log('\n Reranked Results:');
159
- reranked.forEach((doc, i) => {
160
- console.log(`\n#${i + 1}: ${doc.text}`);
161
- });
162
- } catch (err) {
163
- logger.error({ error: err.message }, 'Rerank failed');
164
- process.exit(1);
165
- }
166
- });
167
-
168
- program.parse();
1
+ /**
2
+ * Version: 1.1.0
3
+ * Description: Final CLI entrypoint with dynamic plugin loading and full config validation
4
+ * Author: Ali Kahwaji
5
+ */
6
+
7
+ import { resolve } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { loadRagConfig } from '../src/config/load-config.js';
10
+ import { loadPluginsFromJson } from '../src/config/load-plugin-config.js';
11
+ import registry from '../src/core/plugin-registry.js';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = resolve(__filename, '..');
15
+ const CONFIG_PATH = resolve('.ragrc.json');
16
+
17
+ const args = process.argv.slice(2);
18
+ const command = args[0];
19
+
20
+ async function runCLI() {
21
+ try {
22
+ const config = loadRagConfig(CONFIG_PATH);
23
+ await loadPluginsFromJson(CONFIG_PATH);
24
+
25
+ switch (command) {
26
+ case 'ingest': {
27
+ const input = args[1];
28
+ const loader = registry.get('loader', Object.keys(config.loader)[0]);
29
+ const embedder = registry.get('embedder', Object.keys(config.embedder)[0]);
30
+ const retriever = registry.get('retriever', Object.keys(config.retriever)[0]);
31
+
32
+ const docs = await loader.load(input);
33
+ const vectors = await embedder.embed(docs);
34
+ await retriever.store(vectors, config.namespace);
35
+
36
+ console.log('Ingestion complete');
37
+ break;
38
+ }
39
+ case 'query': {
40
+ const question = args.slice(1).join(' ');
41
+ const embedder = registry.get('embedder', Object.keys(config.embedder)[0]);
42
+ const retriever = registry.get('retriever', Object.keys(config.retriever)[0]);
43
+ const llm = registry.get('llm', Object.keys(config.llm)[0]);
44
+
45
+ const queryVec = await embedder.embedQuery(question);
46
+ const docs = await retriever.search(queryVec, config.namespace);
47
+ const answer = await llm.ask(question, docs);
48
+
49
+ console.log('Answer:', answer);
50
+ break;
51
+ }
52
+ default:
53
+ console.error('Unknown command:', command);
54
+ process.exit(1);
55
+ }
56
+ } catch (err) {
57
+ console.error('Error during execution:', err.message);
58
+ process.exit(1);
59
+ }
60
+ }
61
+
62
+ await runCLI();