@devilsdev/rag-pipeline-utils 1.0.1 → 2.0.1

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 (59) hide show
  1. package/.codecov.yml +21 -0
  2. package/.github/workflows/ci.yml +18 -0
  3. package/.github/workflows/docs-deploy.yml +36 -0
  4. package/.github/workflows/publish-release-blog.yml +51 -0
  5. package/.husky/pre-commit +6 -0
  6. package/CHANGELOG.md +40 -0
  7. package/README.md +101 -72
  8. package/USE_CASES.md +134 -0
  9. package/__tests__/fixtures/.ragrc.json +8 -0
  10. package/__tests__/fixtures/invalid-ragrc.json +4 -0
  11. package/__tests__/fixtures/src/mocks/openai-embedder.js +16 -0
  12. package/__tests__/fixtures/src/mocks/openai-llm.js +9 -0
  13. package/__tests__/fixtures/src/mocks/pdf-loader.js +17 -0
  14. package/__tests__/fixtures/src/mocks/pinecone-retriever.js +26 -0
  15. package/__tests__/fixtures/valid-ragrc.json +16 -0
  16. package/__tests__/integration/cli/config-flow.test.js +84 -15
  17. package/__tests__/integration/config/load-config.test.js +45 -30
  18. package/__tests__/unit/config/validate-schema.test.js +121 -0
  19. package/__tests__/unit/reranker/llm-reranker.test.js +9 -33
  20. package/__tests__/unit/reranker/reranker.enriched.test.js +19 -0
  21. package/__tests__/unit/reranker/reranker.fallback.test.js +15 -0
  22. package/__tests__/unit/reranker/reranker.snapshot.test.js +9 -11
  23. package/__tests__/unit/reranker/reranker.structured-output.test.js +17 -0
  24. package/bin/cli.js +49 -155
  25. package/docs-site/blog/2021-08-26-welcome/index.md +0 -1
  26. package/docs-site/blog/2025-04-17-release-2.0.md +67 -0
  27. package/docs-site/blog/2025-04-18-release-2.0.0.md +21 -0
  28. package/docs-site/blog/authors.yml +5 -23
  29. package/docs-site/blog/tags.yml +9 -0
  30. package/docs-site/docs/USE_CASES.md +0 -0
  31. package/docs-site/docusaurus.config.js +13 -0
  32. package/docs-site/sidebars.js +21 -9
  33. package/package.json +25 -2
  34. package/plugin.config.json +14 -0
  35. package/scripts/banner-injector.js +89 -0
  36. package/scripts/ci-runner.js +51 -0
  37. package/scripts/repair-fixtures.js +30 -0
  38. package/scripts/setup.js +26 -2
  39. package/scripts/validate-changelog-version.js +48 -0
  40. package/scripts/verify-fixture-mocks.js +45 -0
  41. package/scripts/verify-fixtures.js +36 -0
  42. package/src/config/load-config.js +19 -21
  43. package/src/config/load-plugin-config.js +56 -0
  44. package/src/config/validate-plugin-schema.js +28 -0
  45. package/src/config/validate-schema.js +88 -30
  46. package/src/core/create-pipeline.js +40 -26
  47. package/src/core/plugin-contracts.js +21 -0
  48. package/src/core/plugin-registry.js +4 -17
  49. package/src/mocks/openai-embedder.js +30 -0
  50. package/src/mocks/openai-llm.js +18 -0
  51. package/src/mocks/pdf-loader.js +17 -0
  52. package/src/mocks/pinecone-retriever.js +25 -0
  53. package/src/reranker/llm-reranker.js +28 -43
  54. package/src/utils/ci/diagnostic-reporter.js +36 -0
  55. package/src/utils/validate-plugin-contract.js +36 -0
  56. package/.github/workflows/release.yml +0 -20
  57. package/docs-site/blog/2019-05-28-first-blog-post.md +0 -12
  58. package/docs-site/blog/2019-05-29-long-blog-post.md +0 -44
  59. package/docs-site/blog/2021-08-01-mdx-blog-post.mdx +0 -24
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Version: 1.0.0
3
+ * Description: Auto-repairs mock plugin fixtures for integration tests
4
+ * Author: Ali Kahwaji
5
+ */
6
+
7
+ import { mkdirSync, copyFileSync, existsSync } from 'fs';
8
+ import { resolve, join } from 'path';
9
+
10
+ const MOCKS = [
11
+ 'pdf-loader.js',
12
+ 'openai-embedder.js',
13
+ 'pinecone-retriever.js',
14
+ 'openai-llm.js'
15
+ ];
16
+
17
+ const SRC_FIXTURES = resolve('__tests__/fixtures/src/mocks');
18
+ const DEST_FIXTURES = resolve('__tests__/__temp__/cli-config-fallback/src/mocks');
19
+
20
+ mkdirSync(DEST_FIXTURES, { recursive: true });
21
+
22
+ MOCKS.forEach((file) => {
23
+ const src = join(SRC_FIXTURES, file);
24
+ const dest = join(DEST_FIXTURES, file);
25
+ if (!existsSync(src)) throw new Error(`[FIXTURE MISSING] ${src}`);
26
+ copyFileSync(src, dest);
27
+ console.log(`[COPIED] ${file}`);
28
+ });
29
+
30
+ console.log('✅ Fixture mocks successfully repaired.');
package/scripts/setup.js CHANGED
@@ -1,7 +1,31 @@
1
+ /**
2
+ * Version: 1.1.0
3
+ * Description: Test setup script to ensure fixtures exist and validate mocks before running tests.
4
+ * Author: Ali Kahwaji
5
+ */
6
+
1
7
  import fs from 'fs';
2
- const fixturePath = './__tests__/fixtures/sample.pdf';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { execSync } from 'child_process';
11
+
12
+ // Resolve __dirname in ES module scope
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ const fixturePath = path.resolve(__dirname, '../__tests__/fixtures/sample.pdf');
3
17
 
18
+ // Ensure PDF test fixture
4
19
  if (!fs.existsSync(fixturePath)) {
5
- fs.mkdirSync('./__tests__/fixtures', { recursive: true });
20
+ fs.mkdirSync(path.dirname(fixturePath), { recursive: true });
6
21
  fs.writeFileSync(fixturePath, 'Dummy PDF content for test');
22
+ console.log('[setup] Created sample.pdf fixture');
23
+ }
24
+
25
+ // Validate plugin fixture presence
26
+ try {
27
+ execSync('node ./scripts/verify-fixtures.js', { stdio: 'inherit' });
28
+ } catch (err) {
29
+ console.error('[setup] Fixture verification failed. Please check missing mocks.');
30
+ process.exit(1);
7
31
  }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Version: 1.1.0
3
+ * Description: Ensures CHANGELOG.md contains the current version and auto-corrects format if needed.
4
+ * Author: Ali Kahwaji
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { execSync } from 'child_process';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json')));
16
+ const version = pkg.version;
17
+ const changelogPath = path.resolve(__dirname, '../CHANGELOG.md');
18
+
19
+ let changelog = fs.readFileSync(changelogPath, 'utf-8');
20
+
21
+ // Step 1: Normalize invalid headings like "# [2.0.0](...)"
22
+ const normalized = changelog.replace(/^# \[([0-9]+\.[0-9]+\.[0-9]+)\]\([^)]+\)/gm, (match, v) => {
23
+ const today = new Date().toISOString().split('T')[0];
24
+ return `## [${v}] - ${today}`;
25
+ });
26
+
27
+ // Step 2: Update file if normalization occurred
28
+ if (normalized !== changelog) {
29
+ fs.writeFileSync(changelogPath, normalized);
30
+ console.log(`🛠️ CHANGELOG.md heading normalized for version ${version}`);
31
+ changelog = normalized;
32
+
33
+ // Auto-stage if in git project (Husky compatibility)
34
+ try {
35
+ execSync(`git add ${changelogPath}`);
36
+ console.log('CHANGELOG.md staged after correction');
37
+ } catch {
38
+ // Non-fatal
39
+ }
40
+ }
41
+
42
+ // Step 3: Validate presence of correct heading
43
+ const headingPattern = new RegExp(`^## \\[${version}\\]`, 'm');
44
+ if (!headingPattern.test(changelog)) {
45
+ throw new Error(`❌ CHANGELOG.md does not contain a properly formatted entry for version ${version}`);
46
+ }
47
+
48
+ console.log(`✅ CHANGELOG.md contains a valid entry for version ${version}`);
@@ -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
+ }
@@ -1,35 +1,33 @@
1
1
  /**
2
- * Version: 0.1.0
3
- * Path: /src/config/load-config.js
4
- * Description: Loader and validator for `.ragrc.json` configuration files
2
+ * Version: 1.1.0
3
+ * Description: Loads validated .ragrc.json config from disk
5
4
  * Author: Ali Kahwaji
6
5
  */
7
6
 
8
7
  import fs from 'fs';
9
8
  import path from 'path';
10
- import { validate } from './validate-schema.js';
11
-
12
- const DEFAULT_CONFIG_FILE = '.ragrc.json';
9
+ import { validateRagrcSchema } from './validate-schema.js';
10
+ import { logger } from '../utils/logger.js';
13
11
 
14
12
  /**
15
- * Load and validate the RAG configuration from a file.
16
- *
17
- * @param {string} [configPath] - Optional custom config path
18
- * @returns {object} - Validated config object
19
- * @throws {Error} - If file not found or validation fails
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
20
17
  */
21
- export function loadRagConfig(configPath = DEFAULT_CONFIG_FILE) {
22
- const fullPath = path.resolve(process.cwd(), configPath);
23
-
24
- if (!fs.existsSync(fullPath)) {
25
- throw new Error(`Config file not found: ${fullPath}`);
18
+ export function loadRagConfig(configPath) {
19
+ if (!fs.existsSync(configPath)) {
20
+ throw new Error(`Config file not found: ${configPath}`);
26
21
  }
27
22
 
28
- const raw = fs.readFileSync(fullPath, 'utf-8');
29
- const config = JSON.parse(raw);
23
+ const raw = fs.readFileSync(configPath, 'utf-8');
24
+ const parsed = JSON.parse(raw);
30
25
 
31
- validate(config); // Throws if invalid
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
+ }
32
31
 
33
- return config;
32
+ return parsed;
34
33
  }
35
-
@@ -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
1
  /**
2
- * Version: 0.1.0
3
- * Path: /src/config/validate-schema.js
4
- * Description: JSON schema validation for `.ragrc.json` config using AJV
2
+ * Version: 1.3.0
3
+ * Description: AJV-powered validators for .ragrc.json full config and plugin-only structure
5
4
  * Author: Ali Kahwaji
5
+ * File: /src/config/validate-schema.js
6
6
  */
7
7
 
8
8
  import Ajv from 'ajv';
9
9
 
10
- // Define allowed config structure
11
- const schema = {
10
+ /**
11
+ * Full .ragrc.json schema (used in load-config.js)
12
+ */
13
+ const ragrcSchema = {
12
14
  type: 'object',
15
+ required: ['loader', 'embedder', 'retriever', 'llm', 'namespace', 'pipeline'],
13
16
  properties: {
14
- loader: { type: 'string' },
15
- embedder: { type: 'string' },
16
- retriever: { type: 'string' },
17
- llm: { type: 'string' },
18
- chunk: {
19
- type: 'object',
20
- properties: {
21
- strategy: { type: 'string' },
22
- size: { type: 'number' }
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']
23
46
  },
24
- required: ['strategy', 'size'],
25
- additionalProperties: false
47
+ minItems: 1,
48
+ uniqueItems: true
26
49
  }
27
50
  },
28
- required: ['loader', 'embedder', 'retriever', 'llm'],
29
51
  additionalProperties: false
30
52
  };
31
53
 
32
- const ajv = new Ajv();
33
- const validateSchema = ajv.compile(schema);
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
+ };
34
84
 
35
85
  /**
36
- * Validates a given config object against the `.ragrc.json` schema.
37
- * Throws if the schema is invalid.
38
- *
39
- * @param {object} config - Configuration object
40
- * @throws {Error} - Validation errors, if any
86
+ * Validates the full .ragrc.json config structure
87
+ * @param {object} config
88
+ * @returns {{ valid: boolean, errors?: any[] }}
41
89
  */
42
- export function validate(config) {
43
- const valid = validateSchema(config);
44
- if (!valid) {
45
- const messages = validateSchema.errors.map(e => `${e.instancePath} ${e.message}`).join(', ');
46
- throw new Error(`Invalid config: ${messages}`);
47
- }
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 };
48
95
  }
49
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,73 +1,88 @@
1
1
  /**
2
- * Version: 0.1.3
2
+ * Version: 0.2.0
3
3
  * Path: /src/core/create-pipeline.js
4
- * Description: RAG pipeline factory with optional LLM reranker integration
4
+ * Description: RAG pipeline factory with modular retry, logging, and reranker middleware
5
5
  * Author: Ali Kahwaji
6
6
  */
7
7
 
8
- import { PluginRegistry } from './plugin-registry.js';
8
+ import registry from './plugin-registry.js';
9
9
  import { logger } from '../utils/logger.js';
10
10
  import { withRetry } from '../utils/retry.js';
11
11
  import { LLMReranker } from '../reranker/llm-reranker.js';
12
12
 
13
- const registry = new PluginRegistry();
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
+ */
14
20
 
15
21
  /**
16
- * Create a RAG pipeline instance using registered plugin components
17
- * @param {object} config
22
+ * Creates a RAG pipeline instance.
23
+ * @param {object} config - Pipeline configuration.
18
24
  * @param {string} config.loader
19
25
  * @param {string} config.embedder
20
26
  * @param {string} config.retriever
21
27
  * @param {string} config.llm
22
- * @param {boolean} [config.useReranker=false]
28
+ * @param {PipelineOptions} [options]
23
29
  * @returns {object} RAG pipeline instance
24
30
  */
25
- export function createRagPipeline({ loader, embedder, retriever, llm, useReranker = false }) {
26
- const rerankerInstance = useReranker ? new LLMReranker({ llm: registry.get('llm', llm) }) : null;
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
+ };
27
48
 
28
49
  return {
29
- async ingest(path) {
30
- logger.info({ loader, embedder, retriever }, 'Pipeline ingest start');
31
- const loaderInstance = registry.get('loader', loader);
32
- const embedderInstance = registry.get('embedder', embedder);
33
- const retrieverInstance = registry.get('retriever', retriever);
50
+ async ingest(docPath) {
51
+ log('info', 'Pipeline ingest start', { loader, embedder, retriever });
34
52
 
35
- const documents = await loaderInstance.load(path);
53
+ const documents = await loaderInstance.load(docPath);
36
54
  const chunks = documents.flatMap(doc => doc.chunk());
37
55
  const vectors = await embedderInstance.embed(chunks);
38
56
 
39
- await withRetry(() => retrieverInstance.store(vectors), {
57
+ await wrap(() => retrieverInstance.store(vectors), {
40
58
  label: 'vector-store',
41
59
  retries: 3,
42
60
  initialDelay: 500
43
61
  });
44
62
 
45
- logger.info('Ingestion pipeline completed');
63
+ log('info', 'Ingestion pipeline completed');
46
64
  },
47
65
 
48
66
  async query(prompt) {
49
- logger.info({ prompt, embedder, retriever, llm, useReranker }, 'Pipeline query start');
50
- const embedderInstance = registry.get('embedder', embedder);
51
- const retrieverInstance = registry.get('retriever', retriever);
52
- const llmInstance = registry.get('llm', llm);
67
+ log('info', 'Pipeline query start', { prompt, embedder, retriever, llm, useReranker });
53
68
 
54
69
  const queryVector = await embedderInstance.embedQuery(prompt);
55
- let retrieved = await withRetry(() => retrieverInstance.retrieve(queryVector), {
70
+ let retrieved = await wrap(() => retrieverInstance.retrieve(queryVector), {
56
71
  label: 'vector-retrieve',
57
72
  retries: 3,
58
73
  initialDelay: 500
59
74
  });
60
75
 
61
76
  if (rerankerInstance) {
62
- logger.info('Applying LLM reranker to retrieved chunks');
63
- retrieved = await withRetry(() => rerankerInstance.rerank(prompt, retrieved), {
77
+ log('info', 'Applying LLM reranker to retrieved chunks');
78
+ retrieved = await wrap(() => rerankerInstance.rerank(prompt, retrieved), {
64
79
  label: 'rerank',
65
80
  retries: 2,
66
81
  initialDelay: 400
67
82
  });
68
83
  }
69
84
 
70
- const result = await withRetry(() => llmInstance.generate(prompt, retrieved), {
85
+ const result = await wrap(() => llmInstance.generate(prompt, retrieved), {
71
86
  label: 'llm-generate',
72
87
  retries: 3,
73
88
  initialDelay: 500
@@ -79,4 +94,3 @@ export function createRagPipeline({ loader, embedder, retriever, llm, useReranke
79
94
  }
80
95
 
81
96
  export { registry };
82
-
@@ -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
+
@@ -14,12 +14,6 @@ export class PluginRegistry {
14
14
  reranker: new Map(),
15
15
  };
16
16
 
17
- /**
18
- * Registers a plugin instance under a specific type and name.
19
- * @param {'loader' | 'embedder' | 'retriever' | 'llm' | 'reranker'} type - Plugin type category.
20
- * @param {string} name - Unique name/key for this plugin.
21
- * @param {object} plugin - Plugin implementation (e.g., instance or factory).
22
- */
23
17
  register(type, name, plugin) {
24
18
  const group = this.#registry[type];
25
19
  if (!group) {
@@ -28,12 +22,6 @@ export class PluginRegistry {
28
22
  group.set(name, plugin);
29
23
  }
30
24
 
31
- /**
32
- * Retrieves a plugin by type and name.
33
- * @param {'loader' | 'embedder' | 'retriever' | 'llm' | 'reranker'} type - Plugin type.
34
- * @param {string} name - Plugin name/key.
35
- * @returns {object} - The plugin instance.
36
- */
37
25
  get(type, name) {
38
26
  const group = this.#registry[type];
39
27
  if (!group) {
@@ -46,11 +34,6 @@ export class PluginRegistry {
46
34
  return plugin;
47
35
  }
48
36
 
49
- /**
50
- * Lists all registered plugin names for a type.
51
- * @param {'loader' | 'embedder' | 'retriever' | 'llm' | 'reranker'} type - Plugin category.
52
- * @returns {string[]} - Array of registered plugin names.
53
- */
54
37
  list(type) {
55
38
  const group = this.#registry[type];
56
39
  if (!group) {
@@ -59,3 +42,7 @@ export class PluginRegistry {
59
42
  return [...group.keys()];
60
43
  }
61
44
  }
45
+
46
+ const registry = new PluginRegistry();
47
+
48
+ export default registry;