@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,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version: 1.0.0
|
|
3
|
+
* Description: Verifies that plugin mock implementations expose required methods
|
|
4
|
+
* Author: Ali Kahwaji
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readdirSync, statSync } from 'fs';
|
|
8
|
+
import { resolve, join, extname } from 'path';
|
|
9
|
+
import { pathToFileURL } from 'url';
|
|
10
|
+
|
|
11
|
+
const MOCKS_DIR = resolve('__tests__/fixtures/src/mocks');
|
|
12
|
+
|
|
13
|
+
const pluginContracts = {
|
|
14
|
+
'openai-embedder.js': ['embed', 'embedQuery'],
|
|
15
|
+
'openai-llm.js': ['ask'],
|
|
16
|
+
'pdf-loader.js': ['load'],
|
|
17
|
+
'pinecone-retriever.js': ['store', 'search'],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const failures = [];
|
|
21
|
+
|
|
22
|
+
for (const file of readdirSync(MOCKS_DIR)) {
|
|
23
|
+
const fullPath = join(MOCKS_DIR, file);
|
|
24
|
+
const isFile = statSync(fullPath).isFile();
|
|
25
|
+
if (!isFile || extname(file) !== '.js') continue;
|
|
26
|
+
|
|
27
|
+
const expectedMethods = pluginContracts[file];
|
|
28
|
+
if (!expectedMethods) continue;
|
|
29
|
+
|
|
30
|
+
const mod = await import(pathToFileURL(fullPath).href);
|
|
31
|
+
const plugin = new mod.default();
|
|
32
|
+
|
|
33
|
+
for (const method of expectedMethods) {
|
|
34
|
+
if (typeof plugin[method] !== 'function') {
|
|
35
|
+
failures.push(`❌ ${file} failed: [${file}] missing method: ${method}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (failures.length > 0) {
|
|
41
|
+
failures.forEach(msg => console.error(msg));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
} else {
|
|
44
|
+
console.log('✅ All plugin mocks verified.');
|
|
45
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version: 1.0.0
|
|
3
|
+
* Description: Script to verify presence of all expected fixture plugins before running integration tests.
|
|
4
|
+
* Author: Ali Kahwaji
|
|
5
|
+
* File: scripts/verify-fixtures.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
import { resolve } from 'path';
|
|
10
|
+
|
|
11
|
+
const REQUIRED_FIXTURES = [
|
|
12
|
+
'__tests__/fixtures/sample.pdf',
|
|
13
|
+
'__tests__/fixtures/.ragrc.json',
|
|
14
|
+
'__tests__/fixtures/src/mocks/pdf-loader.js',
|
|
15
|
+
'__tests__/fixtures/src/mocks/openai-embedder.js',
|
|
16
|
+
'__tests__/fixtures/src/mocks/pinecone-retriever.js',
|
|
17
|
+
'__tests__/fixtures/src/mocks/openai-llm.js'
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
let missing = [];
|
|
21
|
+
for (const file of REQUIRED_FIXTURES) {
|
|
22
|
+
const fullPath = resolve(file);
|
|
23
|
+
if (!existsSync(fullPath)) {
|
|
24
|
+
missing.push(fullPath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (missing.length) {
|
|
29
|
+
console.error('[ERROR] Missing test fixture files:');
|
|
30
|
+
for (const f of missing) {
|
|
31
|
+
console.error(' -', f);
|
|
32
|
+
}
|
|
33
|
+
process.exit(1);
|
|
34
|
+
} else {
|
|
35
|
+
console.log('[OK] All fixture files are present.');
|
|
36
|
+
}
|
package/server.js
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.1.0
|
|
3
|
-
* Path: /server.js
|
|
4
|
-
* Description: Local static server for serving evaluation dashboard
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import express from 'express';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
|
|
12
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
-
const __dirname = path.dirname(__filename);
|
|
14
|
-
|
|
15
|
-
const app = express();
|
|
16
|
-
const port = process.env.PORT || 3000;
|
|
17
|
-
|
|
18
|
-
// Serve evaluation results statically
|
|
19
|
-
app.use('/results', express.static(path.join(__dirname, '__tests__/fixtures')));
|
|
20
|
-
|
|
21
|
-
// Serve public frontend assets
|
|
22
|
-
app.use(express.static(path.join(__dirname, 'public')));
|
|
23
|
-
|
|
24
|
-
// Fallback to index.html
|
|
25
|
-
app.get('*', (req, res) => {
|
|
26
|
-
res.sendFile(path.join(__dirname, 'public/index.html'));
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
app.listen(port, () => {
|
|
30
|
-
console.log(` Dashboard running at http://localhost:${port}`);
|
|
31
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Version: 0.1.0
|
|
3
|
+
* Path: /server.js
|
|
4
|
+
* Description: Local static server for serving evaluation dashboard
|
|
5
|
+
* Author: Ali Kahwaji
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import express from 'express';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
|
|
15
|
+
const app = express();
|
|
16
|
+
const port = process.env.PORT || 3000;
|
|
17
|
+
|
|
18
|
+
// Serve evaluation results statically
|
|
19
|
+
app.use('/results', express.static(path.join(__dirname, '__tests__/fixtures')));
|
|
20
|
+
|
|
21
|
+
// Serve public frontend assets
|
|
22
|
+
app.use(express.static(path.join(__dirname, 'public')));
|
|
23
|
+
|
|
24
|
+
// Fallback to index.html
|
|
25
|
+
app.get('*', (req, res) => {
|
|
26
|
+
res.sendFile(path.join(__dirname, 'public/index.html'));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
app.listen(port, () => {
|
|
30
|
+
console.log(` Dashboard running at http://localhost:${port}`);
|
|
31
|
+
});
|
|
@@ -1,35 +1,33 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
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
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Version: 1.1.0
|
|
3
|
+
* Description: Loads validated .ragrc.json config from disk
|
|
4
|
+
* Author: Ali Kahwaji
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { validateRagrcSchema } from './validate-schema.js';
|
|
10
|
+
import { logger } from '../utils/logger.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Loads and validates a RAG config file.
|
|
14
|
+
* @param {string} configPath - Absolute path to .ragrc.json
|
|
15
|
+
* @returns {object} Parsed config object
|
|
16
|
+
* @throws {Error} If file is missing or invalid
|
|
17
|
+
*/
|
|
18
|
+
export function loadRagConfig(configPath) {
|
|
19
|
+
if (!fs.existsSync(configPath)) {
|
|
20
|
+
throw new Error(`Config file not found: ${configPath}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
|
|
26
|
+
const result = validateRagrcSchema(parsed);
|
|
27
|
+
if (!result.valid) {
|
|
28
|
+
logger.error({ errors: result.errors }, 'Invalid .ragrc.json schema');
|
|
29
|
+
throw new Error('Config validation failed');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version: 1.1.1
|
|
3
|
+
* Description: Dynamic plugin loader with direct type:name registry mapping
|
|
4
|
+
* Author: Ali Kahwaji
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { pathToFileURL } from 'url';
|
|
10
|
+
import registry from '../core/plugin-registry.js';
|
|
11
|
+
import { validatePluginSchema } from './validate-schema.js';
|
|
12
|
+
import { logger } from '../utils/logger.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Loads plugins from validated config path and registers them.
|
|
16
|
+
* @param {string} configPath
|
|
17
|
+
*/
|
|
18
|
+
export async function loadPluginsFromJson(configPath) {
|
|
19
|
+
if (!fs.existsSync(configPath)) {
|
|
20
|
+
throw new Error(`Plugin config missing: ${configPath}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
24
|
+
const config = JSON.parse(raw);
|
|
25
|
+
|
|
26
|
+
const validation = validatePluginSchema(config);
|
|
27
|
+
if (!validation.valid) {
|
|
28
|
+
logger.error({ errors: validation.errors }, 'Invalid plugin config');
|
|
29
|
+
throw new Error('Plugin config validation failed');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const [type, namedPlugins] of Object.entries(config)) {
|
|
33
|
+
if (!['loader', 'embedder', 'retriever', 'llm'].includes(type)) continue;
|
|
34
|
+
|
|
35
|
+
for (const [name, relPath] of Object.entries(namedPlugins)) {
|
|
36
|
+
const absPath = path.resolve(path.dirname(configPath), relPath);
|
|
37
|
+
const fileUrl = pathToFileURL(absPath).href;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const mod = await import(fileUrl);
|
|
41
|
+
const PluginClass = mod?.default;
|
|
42
|
+
if (typeof PluginClass !== 'function') {
|
|
43
|
+
throw new Error(`Invalid export in plugin [${type}:${name}] at ${absPath}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const instance = new PluginClass();
|
|
47
|
+
registry.register(type, name, instance);
|
|
48
|
+
|
|
49
|
+
logger.info({ type, name }, `Registered plugin: ${type}:${name}`);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
logger.error({ type, name, err }, `Failed to register plugin [${type}:${name}]`);
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version: 0.1.0
|
|
3
|
+
* Description: Schema validator for plugin.config.json
|
|
4
|
+
* Author: Ali Kahwaji
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function validatePluginSchema(config) {
|
|
8
|
+
const validTypes = ['loader', 'embedder', 'retriever', 'llm'];
|
|
9
|
+
const errors = [];
|
|
10
|
+
|
|
11
|
+
for (const type of Object.keys(config)) {
|
|
12
|
+
if (!validTypes.includes(type)) {
|
|
13
|
+
errors.push(`Unknown plugin type: ${type}`);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
for (const [name, modulePath] of Object.entries(config[type])) {
|
|
17
|
+
if (typeof name !== 'string' || typeof modulePath !== 'string') {
|
|
18
|
+
errors.push(`Invalid plugin definition for type "${type}" and name "${name}"`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
valid: errors.length === 0,
|
|
25
|
+
errors
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
@@ -1,49 +1,107 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import Ajv from 'ajv';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
additionalProperties:
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Version: 1.3.0
|
|
3
|
+
* Description: AJV-powered validators for .ragrc.json full config and plugin-only structure
|
|
4
|
+
* Author: Ali Kahwaji
|
|
5
|
+
* File: /src/config/validate-schema.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import Ajv from 'ajv';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Full .ragrc.json schema (used in load-config.js)
|
|
12
|
+
*/
|
|
13
|
+
const ragrcSchema = {
|
|
14
|
+
type: 'object',
|
|
15
|
+
required: ['loader', 'embedder', 'retriever', 'llm', 'namespace', 'pipeline'],
|
|
16
|
+
properties: {
|
|
17
|
+
loader: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
minProperties: 1,
|
|
20
|
+
additionalProperties: { type: 'string' }
|
|
21
|
+
},
|
|
22
|
+
embedder: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
minProperties: 1,
|
|
25
|
+
additionalProperties: { type: 'string' }
|
|
26
|
+
},
|
|
27
|
+
retriever: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
minProperties: 1,
|
|
30
|
+
additionalProperties: { type: 'string' }
|
|
31
|
+
},
|
|
32
|
+
llm: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
minProperties: 1,
|
|
35
|
+
additionalProperties: { type: 'string' }
|
|
36
|
+
},
|
|
37
|
+
namespace: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
minLength: 1
|
|
40
|
+
},
|
|
41
|
+
pipeline: {
|
|
42
|
+
type: 'array',
|
|
43
|
+
items: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
enum: ['loader', 'embedder', 'retriever']
|
|
46
|
+
},
|
|
47
|
+
minItems: 1,
|
|
48
|
+
uniqueItems: true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
additionalProperties: false
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Minimal plugin-only schema (used in load-plugin-config.js)
|
|
56
|
+
*/
|
|
57
|
+
const pluginSchema = {
|
|
58
|
+
type: 'object',
|
|
59
|
+
required: ['loader', 'embedder', 'retriever', 'llm'],
|
|
60
|
+
properties: {
|
|
61
|
+
loader: {
|
|
62
|
+
type: 'object',
|
|
63
|
+
minProperties: 1,
|
|
64
|
+
additionalProperties: { type: 'string' }
|
|
65
|
+
},
|
|
66
|
+
embedder: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
minProperties: 1,
|
|
69
|
+
additionalProperties: { type: 'string' }
|
|
70
|
+
},
|
|
71
|
+
retriever: {
|
|
72
|
+
type: 'object',
|
|
73
|
+
minProperties: 1,
|
|
74
|
+
additionalProperties: { type: 'string' }
|
|
75
|
+
},
|
|
76
|
+
llm: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
minProperties: 1,
|
|
79
|
+
additionalProperties: { type: 'string' }
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
additionalProperties: true // allow namespace, pipeline, etc.
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Validates the full .ragrc.json config structure
|
|
87
|
+
* @param {object} config
|
|
88
|
+
* @returns {{ valid: boolean, errors?: any[] }}
|
|
89
|
+
*/
|
|
90
|
+
export function validateRagrcSchema(config) {
|
|
91
|
+
const ajv = new Ajv({ allErrors: true });
|
|
92
|
+
const validate = ajv.compile(ragrcSchema);
|
|
93
|
+
const valid = validate(config);
|
|
94
|
+
return valid ? { valid: true } : { valid: false, errors: validate.errors };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validates a plugin-only structure (for runtime plugin loading)
|
|
99
|
+
* @param {object} config
|
|
100
|
+
* @returns {{ valid: boolean, errors?: any[] }}
|
|
101
|
+
*/
|
|
102
|
+
export function validatePluginSchema(config) {
|
|
103
|
+
const ajv = new Ajv({ allErrors: true });
|
|
104
|
+
const validate = ajv.compile(pluginSchema);
|
|
105
|
+
const valid = validate(config);
|
|
106
|
+
return valid ? { valid: true } : { valid: false, errors: validate.errors };
|
|
107
|
+
}
|
|
@@ -1,82 +1,96 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.
|
|
3
|
-
* Path: /src/core/create-pipeline.js
|
|
4
|
-
* Description: RAG pipeline factory with
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import { logger } from '../utils/logger.js';
|
|
10
|
-
import { withRetry } from '../utils/retry.js';
|
|
11
|
-
import { LLMReranker } from '../reranker/llm-reranker.js';
|
|
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
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
label: '
|
|
72
|
-
retries: 3,
|
|
73
|
-
initialDelay: 500
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Version: 0.2.0
|
|
3
|
+
* Path: /src/core/create-pipeline.js
|
|
4
|
+
* Description: RAG pipeline factory with modular retry, logging, and reranker middleware
|
|
5
|
+
* Author: Ali Kahwaji
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import registry from './plugin-registry.js';
|
|
9
|
+
import { logger } from '../utils/logger.js';
|
|
10
|
+
import { withRetry } from '../utils/retry.js';
|
|
11
|
+
import { LLMReranker } from '../reranker/llm-reranker.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pipeline options to toggle middleware.
|
|
15
|
+
* @typedef {Object} PipelineOptions
|
|
16
|
+
* @property {boolean} [useRetry=true] - Enable retry logic on network/storage.
|
|
17
|
+
* @property {boolean} [useLogging=true] - Enable structured logging.
|
|
18
|
+
* @property {boolean} [useReranker=false] - Use reranker for query refinement.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a RAG pipeline instance.
|
|
23
|
+
* @param {object} config - Pipeline configuration.
|
|
24
|
+
* @param {string} config.loader
|
|
25
|
+
* @param {string} config.embedder
|
|
26
|
+
* @param {string} config.retriever
|
|
27
|
+
* @param {string} config.llm
|
|
28
|
+
* @param {PipelineOptions} [options]
|
|
29
|
+
* @returns {object} RAG pipeline instance
|
|
30
|
+
*/
|
|
31
|
+
export function createRagPipeline(
|
|
32
|
+
{ loader, embedder, retriever, llm },
|
|
33
|
+
{ useRetry = true, useLogging = true, useReranker = false } = {}
|
|
34
|
+
) {
|
|
35
|
+
const loaderInstance = registry.get('loader', loader);
|
|
36
|
+
const embedderInstance = registry.get('embedder', embedder);
|
|
37
|
+
const retrieverInstance = registry.get('retriever', retriever);
|
|
38
|
+
const llmInstance = registry.get('llm', llm);
|
|
39
|
+
const rerankerInstance = useReranker ? new LLMReranker({ llm: llmInstance }) : null;
|
|
40
|
+
|
|
41
|
+
const log = (level, msg, data = {}) => {
|
|
42
|
+
if (useLogging) logger[level](data, msg);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const wrap = async (fn, context) => {
|
|
46
|
+
return useRetry ? withRetry(fn, context) : fn();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
async ingest(docPath) {
|
|
51
|
+
log('info', 'Pipeline ingest start', { loader, embedder, retriever });
|
|
52
|
+
|
|
53
|
+
const documents = await loaderInstance.load(docPath);
|
|
54
|
+
const chunks = documents.flatMap(doc => doc.chunk());
|
|
55
|
+
const vectors = await embedderInstance.embed(chunks);
|
|
56
|
+
|
|
57
|
+
await wrap(() => retrieverInstance.store(vectors), {
|
|
58
|
+
label: 'vector-store',
|
|
59
|
+
retries: 3,
|
|
60
|
+
initialDelay: 500
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
log('info', 'Ingestion pipeline completed');
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async query(prompt) {
|
|
67
|
+
log('info', 'Pipeline query start', { prompt, embedder, retriever, llm, useReranker });
|
|
68
|
+
|
|
69
|
+
const queryVector = await embedderInstance.embedQuery(prompt);
|
|
70
|
+
let retrieved = await wrap(() => retrieverInstance.retrieve(queryVector), {
|
|
71
|
+
label: 'vector-retrieve',
|
|
72
|
+
retries: 3,
|
|
73
|
+
initialDelay: 500
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (rerankerInstance) {
|
|
77
|
+
log('info', 'Applying LLM reranker to retrieved chunks');
|
|
78
|
+
retrieved = await wrap(() => rerankerInstance.rerank(prompt, retrieved), {
|
|
79
|
+
label: 'rerank',
|
|
80
|
+
retries: 2,
|
|
81
|
+
initialDelay: 400
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const result = await wrap(() => llmInstance.generate(prompt, retrieved), {
|
|
86
|
+
label: 'llm-generate',
|
|
87
|
+
retries: 3,
|
|
88
|
+
initialDelay: 500
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { registry };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version: 1.0.0
|
|
3
|
+
* Description: Shared plugin interface contracts for runtime + CI mock validation
|
|
4
|
+
* Author: Ali Kahwaji
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const pluginContracts = {
|
|
8
|
+
loader: {
|
|
9
|
+
requiredMethods: ['load'],
|
|
10
|
+
},
|
|
11
|
+
embedder: {
|
|
12
|
+
requiredMethods: ['embed', 'embedQuery'],
|
|
13
|
+
},
|
|
14
|
+
retriever: {
|
|
15
|
+
requiredMethods: ['store', 'search'],
|
|
16
|
+
},
|
|
17
|
+
llm: {
|
|
18
|
+
requiredMethods: ['ask'],
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|