@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.
- package/.codecov.yml +21 -0
- package/.dockerignore +14 -14
- package/.eslintignore +5 -0
- package/.eslintrc.json +21 -0
- package/.github/workflows/ci.yml +64 -42
- package/.husky/pre-commit +5 -0
- package/CHANGELOG.md +46 -0
- package/Dockerfile +22 -22
- package/LICENSE +674 -674
- package/README.md +117 -116
- package/__tests__/fixtures/.ragrc.json +8 -0
- package/__tests__/fixtures/invalid-ragrc.json +4 -0
- package/__tests__/fixtures/sample-eval-dataset.json +14 -14
- package/__tests__/fixtures/sample.pdf +1 -0
- package/__tests__/fixtures/src/mocks/openai-embedder.js +16 -0
- package/__tests__/fixtures/src/mocks/openai-llm.js +9 -0
- package/__tests__/fixtures/src/mocks/pdf-loader.js +17 -0
- package/__tests__/fixtures/src/mocks/pinecone-retriever.js +26 -0
- package/__tests__/fixtures/valid-ragrc.json +16 -0
- package/__tests__/integration/cli/config-flow.test.js +98 -44
- package/__tests__/integration/config/load-config.test.js +67 -52
- package/__tests__/unit/config/validate-schema.test.js +121 -0
- package/__tests__/unit/core/plugin-registry.test.js +55 -55
- package/__tests__/unit/reranker/llm-reranker.test.js +18 -42
- package/__tests__/unit/reranker/reranker.enriched.test.js +19 -0
- package/__tests__/unit/reranker/reranker.fallback.test.js +15 -0
- package/__tests__/unit/reranker/reranker.snapshot.test.js +15 -17
- package/__tests__/unit/reranker/reranker.structured-output.test.js +17 -0
- package/bin/cli.js +62 -168
- package/docs-site/docs/Architecture.md +76 -76
- package/docs-site/docs/CLI.md +69 -69
- package/docs-site/docs/Evaluation.md +61 -61
- package/docs-site/docs/FAQ.md +50 -50
- package/docs-site/docs/Plugins.md +61 -61
- package/docs-site/docs/Usage.md +72 -72
- package/jest.config.js +20 -13
- package/package.json +20 -3
- package/plugin.config.json +14 -0
- package/public/src/App.jsx +57 -57
- package/release.config.js +36 -23
- package/scripts/banner-injector.js +89 -0
- package/scripts/ci-runner.js +51 -0
- package/scripts/repair-fixtures.js +30 -0
- package/scripts/setup.js +31 -0
- package/scripts/test-global-setup.js +18 -0
- package/scripts/verify-fixture-mocks.js +45 -0
- package/scripts/verify-fixtures.js +36 -0
- package/server.js +31 -31
- package/src/config/load-config.js +33 -35
- package/src/config/load-plugin-config.js +56 -0
- package/src/config/validate-plugin-schema.js +28 -0
- package/src/config/validate-schema.js +107 -49
- package/src/core/create-pipeline.js +96 -82
- package/src/core/plugin-contracts.js +21 -0
- package/src/core/plugin-registry.js +48 -61
- package/src/dag/dag-engine.js +69 -69
- package/src/evaluate/evaluator.js +44 -44
- package/src/evaluate/scoring.js +73 -73
- package/src/loader/csv-loader.js +30 -30
- package/src/loader/directory-loader.js +37 -37
- package/src/loader/html-loader.js +57 -57
- package/src/loader/markdown-loader.js +58 -58
- package/src/mocks/openai-embedder.js +30 -0
- package/src/mocks/openai-llm.js +18 -0
- package/src/mocks/pdf-loader.js +17 -0
- package/src/mocks/pinecone-retriever.js +25 -0
- package/src/reranker/llm-reranker.js +34 -49
- package/src/utils/ci/diagnostic-reporter.js +36 -0
- package/src/utils/logger.js +29 -29
- package/src/utils/retry.js +42 -42
- package/src/utils/validate-plugin-contract.js +36 -0
- 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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Description:
|
|
5
|
-
* Author:
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { LLMReranker } from '../../../src/reranker/llm-reranker.js';
|
|
9
|
-
|
|
10
|
-
describe('LLMReranker', () => {
|
|
11
|
-
|
|
12
|
-
{
|
|
13
|
-
{ text: '
|
|
14
|
-
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
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
|
-
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();
|