@devilsdev/rag-pipeline-utils 1.0.0 → 1.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 (44) hide show
  1. package/.dockerignore +14 -14
  2. package/.eslintignore +5 -0
  3. package/.eslintrc.json +21 -0
  4. package/.github/workflows/ci.yml +46 -42
  5. package/.github/workflows/release.yml +20 -21
  6. package/CHANGELOG.md +13 -0
  7. package/Dockerfile +22 -22
  8. package/LICENSE +674 -674
  9. package/README.md +117 -116
  10. package/__tests__/fixtures/sample-eval-dataset.json +14 -14
  11. package/__tests__/fixtures/sample.pdf +1 -0
  12. package/__tests__/integration/cli/config-flow.test.js +29 -44
  13. package/__tests__/integration/config/load-config.test.js +52 -52
  14. package/__tests__/unit/core/plugin-registry.test.js +55 -55
  15. package/__tests__/unit/reranker/llm-reranker.test.js +42 -42
  16. package/__tests__/unit/reranker/reranker.snapshot.test.js +17 -17
  17. package/bin/cli.js +168 -168
  18. package/docs-site/docs/Architecture.md +76 -76
  19. package/docs-site/docs/CLI.md +69 -69
  20. package/docs-site/docs/Evaluation.md +61 -61
  21. package/docs-site/docs/FAQ.md +50 -50
  22. package/docs-site/docs/Plugins.md +61 -61
  23. package/docs-site/docs/Usage.md +72 -72
  24. package/jest.config.js +20 -13
  25. package/package.json +4 -2
  26. package/public/src/App.jsx +57 -57
  27. package/release.config.js +36 -23
  28. package/scripts/setup.js +7 -0
  29. package/scripts/test-global-setup.js +18 -0
  30. package/server.js +31 -31
  31. package/src/config/load-config.js +35 -35
  32. package/src/config/validate-schema.js +49 -49
  33. package/src/core/create-pipeline.js +82 -82
  34. package/src/core/plugin-registry.js +61 -61
  35. package/src/dag/dag-engine.js +69 -69
  36. package/src/evaluate/evaluator.js +44 -44
  37. package/src/evaluate/scoring.js +73 -73
  38. package/src/loader/csv-loader.js +30 -30
  39. package/src/loader/directory-loader.js +37 -37
  40. package/src/loader/html-loader.js +57 -57
  41. package/src/loader/markdown-loader.js +58 -58
  42. package/src/reranker/llm-reranker.js +48 -48
  43. package/src/utils/logger.js +29 -29
  44. package/src/utils/retry.js +42 -42
package/release.config.js CHANGED
@@ -1,23 +1,36 @@
1
- /**
2
- * Version: 0.1.0
3
- * Path: /release.config.js
4
- * Description: Semantic release configuration for automated changelog, versioning, GitHub and npm publishing
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- export default {
9
- branches: ['main'],
10
- plugins: [
11
- '@semantic-release/commit-analyzer',
12
- '@semantic-release/release-notes-generator',
13
- ['@semantic-release/changelog', {
14
- changelogFile: 'CHANGELOG.md'
15
- }],
16
- '@semantic-release/npm',
17
- ['@semantic-release/git', {
18
- assets: ['CHANGELOG.md', 'package.json'],
19
- message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}'
20
- }],
21
- '@semantic-release/github'
22
- ]
23
- };
1
+ /**
2
+ * Version: 0.1.0
3
+ * Description: Semantic Release configuration for CI/CD automation with changelog, versioning, GitHub releases, and npm publishing.
4
+ * Author: Ali Kahwaji
5
+ */
6
+
7
+ export default {
8
+ branches: ['main'],
9
+ repositoryUrl: 'https://github.com/DevilsDev/rag-pipeline-utils',
10
+
11
+ plugins: [
12
+ '@semantic-release/commit-analyzer',
13
+ '@semantic-release/release-notes-generator',
14
+ [
15
+ '@semantic-release/changelog',
16
+ {
17
+ changelogFile: 'CHANGELOG.md',
18
+ changelogTitle: '# Changelog',
19
+ },
20
+ ],
21
+ [
22
+ '@semantic-release/npm',
23
+ {
24
+ npmPublish: true,
25
+ },
26
+ ],
27
+ [
28
+ '@semantic-release/git',
29
+ {
30
+ assets: ['package.json', 'package-lock.json', 'CHANGELOG.md'],
31
+ message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
32
+ },
33
+ ],
34
+ '@semantic-release/github',
35
+ ],
36
+ };
@@ -0,0 +1,7 @@
1
+ import fs from 'fs';
2
+ const fixturePath = './__tests__/fixtures/sample.pdf';
3
+
4
+ if (!fs.existsSync(fixturePath)) {
5
+ fs.mkdirSync('./__tests__/fixtures', { recursive: true });
6
+ fs.writeFileSync(fixturePath, 'Dummy PDF content for test');
7
+ }
@@ -0,0 +1,18 @@
1
+ // __tests__/setup/global.js
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ // __dirname polyfill for ESM
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const pdfPath = path.resolve(__dirname, '../fixtures/sample.pdf');
11
+
12
+ // Ensure the fixtures directory exists
13
+ fs.mkdirSync(path.dirname(pdfPath), { recursive: true });
14
+
15
+ // Create a minimal valid PDF file if it doesn't exist
16
+ if (!fs.existsSync(pdfPath)) {
17
+ fs.writeFileSync(pdfPath, '%PDF-1.4\n%EOF');
18
+ }
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,35 @@
1
- /**
2
- * Version: 0.1.0
3
- * Path: /src/config/load-config.js
4
- * Description: Loader and validator for `.ragrc.json` configuration files
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- import fs from 'fs';
9
- import path from 'path';
10
- import { validate } from './validate-schema.js';
11
-
12
- const DEFAULT_CONFIG_FILE = '.ragrc.json';
13
-
14
- /**
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
20
- */
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}`);
26
- }
27
-
28
- const raw = fs.readFileSync(fullPath, 'utf-8');
29
- const config = JSON.parse(raw);
30
-
31
- validate(config); // Throws if invalid
32
-
33
- return config;
34
- }
35
-
1
+ /**
2
+ * Version: 0.1.0
3
+ * Path: /src/config/load-config.js
4
+ * Description: Loader and validator for `.ragrc.json` configuration files
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { validate } from './validate-schema.js';
11
+
12
+ const DEFAULT_CONFIG_FILE = '.ragrc.json';
13
+
14
+ /**
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
20
+ */
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}`);
26
+ }
27
+
28
+ const raw = fs.readFileSync(fullPath, 'utf-8');
29
+ const config = JSON.parse(raw);
30
+
31
+ validate(config); // Throws if invalid
32
+
33
+ return config;
34
+ }
35
+
@@ -1,49 +1,49 @@
1
- /**
2
- * Version: 0.1.0
3
- * Path: /src/config/validate-schema.js
4
- * Description: JSON schema validation for `.ragrc.json` config using AJV
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- import Ajv from 'ajv';
9
-
10
- // Define allowed config structure
11
- const schema = {
12
- type: 'object',
13
- 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' }
23
- },
24
- required: ['strategy', 'size'],
25
- additionalProperties: false
26
- }
27
- },
28
- required: ['loader', 'embedder', 'retriever', 'llm'],
29
- additionalProperties: false
30
- };
31
-
32
- const ajv = new Ajv();
33
- const validateSchema = ajv.compile(schema);
34
-
35
- /**
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
41
- */
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
- }
48
- }
49
-
1
+ /**
2
+ * Version: 0.1.0
3
+ * Path: /src/config/validate-schema.js
4
+ * Description: JSON schema validation for `.ragrc.json` config using AJV
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ import Ajv from 'ajv';
9
+
10
+ // Define allowed config structure
11
+ const schema = {
12
+ type: 'object',
13
+ 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' }
23
+ },
24
+ required: ['strategy', 'size'],
25
+ additionalProperties: false
26
+ }
27
+ },
28
+ required: ['loader', 'embedder', 'retriever', 'llm'],
29
+ additionalProperties: false
30
+ };
31
+
32
+ const ajv = new Ajv();
33
+ const validateSchema = ajv.compile(schema);
34
+
35
+ /**
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
41
+ */
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
+ }
48
+ }
49
+
@@ -1,82 +1,82 @@
1
- /**
2
- * Version: 0.1.3
3
- * Path: /src/core/create-pipeline.js
4
- * Description: RAG pipeline factory with optional LLM reranker integration
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- import { PluginRegistry } 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
- const registry = new PluginRegistry();
14
-
15
- /**
16
- * Create a RAG pipeline instance using registered plugin components
17
- * @param {object} config
18
- * @param {string} config.loader
19
- * @param {string} config.embedder
20
- * @param {string} config.retriever
21
- * @param {string} config.llm
22
- * @param {boolean} [config.useReranker=false]
23
- * @returns {object} RAG pipeline instance
24
- */
25
- export function createRagPipeline({ loader, embedder, retriever, llm, useReranker = false }) {
26
- const rerankerInstance = useReranker ? new LLMReranker({ llm: registry.get('llm', llm) }) : null;
27
-
28
- 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);
34
-
35
- const documents = await loaderInstance.load(path);
36
- const chunks = documents.flatMap(doc => doc.chunk());
37
- const vectors = await embedderInstance.embed(chunks);
38
-
39
- await withRetry(() => retrieverInstance.store(vectors), {
40
- label: 'vector-store',
41
- retries: 3,
42
- initialDelay: 500
43
- });
44
-
45
- logger.info('Ingestion pipeline completed');
46
- },
47
-
48
- 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);
53
-
54
- const queryVector = await embedderInstance.embedQuery(prompt);
55
- let retrieved = await withRetry(() => retrieverInstance.retrieve(queryVector), {
56
- label: 'vector-retrieve',
57
- retries: 3,
58
- initialDelay: 500
59
- });
60
-
61
- if (rerankerInstance) {
62
- logger.info('Applying LLM reranker to retrieved chunks');
63
- retrieved = await withRetry(() => rerankerInstance.rerank(prompt, retrieved), {
64
- label: 'rerank',
65
- retries: 2,
66
- initialDelay: 400
67
- });
68
- }
69
-
70
- const result = await withRetry(() => llmInstance.generate(prompt, retrieved), {
71
- label: 'llm-generate',
72
- retries: 3,
73
- initialDelay: 500
74
- });
75
-
76
- return result;
77
- }
78
- };
79
- }
80
-
81
- export { registry };
82
-
1
+ /**
2
+ * Version: 0.1.3
3
+ * Path: /src/core/create-pipeline.js
4
+ * Description: RAG pipeline factory with optional LLM reranker integration
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ import { PluginRegistry } 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
+ const registry = new PluginRegistry();
14
+
15
+ /**
16
+ * Create a RAG pipeline instance using registered plugin components
17
+ * @param {object} config
18
+ * @param {string} config.loader
19
+ * @param {string} config.embedder
20
+ * @param {string} config.retriever
21
+ * @param {string} config.llm
22
+ * @param {boolean} [config.useReranker=false]
23
+ * @returns {object} RAG pipeline instance
24
+ */
25
+ export function createRagPipeline({ loader, embedder, retriever, llm, useReranker = false }) {
26
+ const rerankerInstance = useReranker ? new LLMReranker({ llm: registry.get('llm', llm) }) : null;
27
+
28
+ 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);
34
+
35
+ const documents = await loaderInstance.load(path);
36
+ const chunks = documents.flatMap(doc => doc.chunk());
37
+ const vectors = await embedderInstance.embed(chunks);
38
+
39
+ await withRetry(() => retrieverInstance.store(vectors), {
40
+ label: 'vector-store',
41
+ retries: 3,
42
+ initialDelay: 500
43
+ });
44
+
45
+ logger.info('Ingestion pipeline completed');
46
+ },
47
+
48
+ 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);
53
+
54
+ const queryVector = await embedderInstance.embedQuery(prompt);
55
+ let retrieved = await withRetry(() => retrieverInstance.retrieve(queryVector), {
56
+ label: 'vector-retrieve',
57
+ retries: 3,
58
+ initialDelay: 500
59
+ });
60
+
61
+ if (rerankerInstance) {
62
+ logger.info('Applying LLM reranker to retrieved chunks');
63
+ retrieved = await withRetry(() => rerankerInstance.rerank(prompt, retrieved), {
64
+ label: 'rerank',
65
+ retries: 2,
66
+ initialDelay: 400
67
+ });
68
+ }
69
+
70
+ const result = await withRetry(() => llmInstance.generate(prompt, retrieved), {
71
+ label: 'llm-generate',
72
+ retries: 3,
73
+ initialDelay: 500
74
+ });
75
+
76
+ return result;
77
+ }
78
+ };
79
+ }
80
+
81
+ export { registry };
82
+
@@ -1,61 +1,61 @@
1
- /**
2
- * Version: 1.0.0
3
- * Path: /src/core/plugin-registry.js
4
- * Description: Registry for managing pluggable components (loader, embedder, retriever, llm)
5
- * Author: Ali Kahwaji
6
- */
7
-
8
- export class PluginRegistry {
9
- #registry = {
10
- loader: new Map(),
11
- embedder: new Map(),
12
- retriever: new Map(),
13
- llm: new Map(),
14
- reranker: new Map(),
15
- };
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
- register(type, name, plugin) {
24
- const group = this.#registry[type];
25
- if (!group) {
26
- throw new Error(`Unknown plugin type: '${type}'`);
27
- }
28
- group.set(name, plugin);
29
- }
30
-
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
- get(type, name) {
38
- const group = this.#registry[type];
39
- if (!group) {
40
- throw new Error(`Unknown plugin type: '${type}'`);
41
- }
42
- const plugin = group.get(name);
43
- if (!plugin) {
44
- throw new Error(`Plugin not found: [${type}:${name}]`);
45
- }
46
- return plugin;
47
- }
48
-
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
- list(type) {
55
- const group = this.#registry[type];
56
- if (!group) {
57
- throw new Error(`Unknown plugin type: '${type}'`);
58
- }
59
- return [...group.keys()];
60
- }
61
- }
1
+ /**
2
+ * Version: 1.0.0
3
+ * Path: /src/core/plugin-registry.js
4
+ * Description: Registry for managing pluggable components (loader, embedder, retriever, llm)
5
+ * Author: Ali Kahwaji
6
+ */
7
+
8
+ export class PluginRegistry {
9
+ #registry = {
10
+ loader: new Map(),
11
+ embedder: new Map(),
12
+ retriever: new Map(),
13
+ llm: new Map(),
14
+ reranker: new Map(),
15
+ };
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
+ register(type, name, plugin) {
24
+ const group = this.#registry[type];
25
+ if (!group) {
26
+ throw new Error(`Unknown plugin type: '${type}'`);
27
+ }
28
+ group.set(name, plugin);
29
+ }
30
+
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
+ get(type, name) {
38
+ const group = this.#registry[type];
39
+ if (!group) {
40
+ throw new Error(`Unknown plugin type: '${type}'`);
41
+ }
42
+ const plugin = group.get(name);
43
+ if (!plugin) {
44
+ throw new Error(`Plugin not found: [${type}:${name}]`);
45
+ }
46
+ return plugin;
47
+ }
48
+
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
+ list(type) {
55
+ const group = this.#registry[type];
56
+ if (!group) {
57
+ throw new Error(`Unknown plugin type: '${type}'`);
58
+ }
59
+ return [...group.keys()];
60
+ }
61
+ }