@devilsdev/rag-pipeline-utils 2.1.7 → 2.1.8
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/LICENSE +674 -674
- package/README.md +395 -186
- package/bin/cli.js +14 -62
- package/contracts/embedder-contract.json +52 -0
- package/contracts/llm-contract.json +36 -0
- package/contracts/loader-contract.json +43 -0
- package/contracts/retriever-contract.json +68 -0
- package/dist/index.cjs +67 -0
- package/dist/index.mjs +51 -0
- package/package.json +107 -21
- package/.codecov.yml +0 -21
- package/.dockerignore +0 -14
- package/.eslintignore +0 -5
- package/.eslintrc.json +0 -28
- package/.github/PROJECT_ROADMAP.md +0 -102
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -8
- package/.github/release-template.md +0 -20
- package/.github/workflows/auto-release-note.yml +0 -73
- package/.github/workflows/ci.yml +0 -55
- package/.github/workflows/docs-ci.yml +0 -46
- package/.github/workflows/docs-deploy.yml +0 -64
- package/.github/workflows/enforce-release-review.yml +0 -50
- package/.github/workflows/generate-blog-post.yml +0 -44
- package/.github/workflows/post-release-generate-blog.yml +0 -71
- package/.github/workflows/publish-release-blog.yml +0 -47
- package/.github/workflows/release-sync.yml +0 -50
- package/.github/workflows/roadmap-label-sync.yml +0 -35
- package/.github/workflows/roadmap-labels.yml +0 -67
- package/.github/workflows/roadmap-sync.yml +0 -44
- package/.github/workflows/sync-roadmap.yml +0 -62
- package/.github/workflows/verify-blog-badge.yml +0 -50
- package/.husky/pre-commit +0 -6
- package/CHANGELOG.md +0 -131
- package/Dockerfile +0 -22
- package/Use-Cases.md +0 -134
- package/__tests__/fixtures/.ragrc.json +0 -8
- package/__tests__/fixtures/invalid-ragrc.json +0 -4
- package/__tests__/fixtures/sample-eval-dataset.json +0 -15
- package/__tests__/fixtures/sample.pdf +0 -1
- package/__tests__/fixtures/src/mocks/openai-embedder.js +0 -16
- package/__tests__/fixtures/src/mocks/openai-llm.js +0 -9
- package/__tests__/fixtures/src/mocks/pdf-loader.js +0 -17
- package/__tests__/fixtures/src/mocks/pinecone-retriever.js +0 -26
- package/__tests__/fixtures/valid-ragrc.json +0 -16
- package/__tests__/integration/cli/config-flow.test.js +0 -98
- package/__tests__/integration/config/load-config.test.js +0 -67
- package/__tests__/scripts/ensure-roadmap-labels.test.js +0 -64
- package/__tests__/unit/config/validate-schema.test.js +0 -121
- package/__tests__/unit/core/plugin-registry.test.js +0 -55
- package/__tests__/unit/reranker/__snapshots__/reranker.snapshot.test.js.snap +0 -15
- package/__tests__/unit/reranker/llm-reranker.test.js +0 -18
- package/__tests__/unit/reranker/reranker.enriched.test.js +0 -19
- package/__tests__/unit/reranker/reranker.fallback.test.js +0 -15
- package/__tests__/unit/reranker/reranker.snapshot.test.js +0 -15
- package/__tests__/unit/reranker/reranker.structured-output.test.js +0 -17
- package/codecov +0 -0
- package/codecov.SHA256SUM +0 -1
- package/codecov.SHA256SUM.sig +0 -16
- package/docs-site/README.md +0 -41
- package/docs-site/blog/2025-04-18-architecture-overview.md +0 -16
- package/docs-site/blog/2025-04-18-release-v2.1.1.md +0 -13
- package/docs-site/blog/2025-04-19-prompt-engineering-best-practices.md +0 -16
- package/docs-site/blog/2025-04-20-image-showcase.md +0 -14
- package/docs-site/blog/2025-04-20-release-2.1.1.md +0 -21
- package/docs-site/blog/2025-04-21-release-v2.1.3.md +0 -13
- package/docs-site/blog/2025-04-21-release-v2.1.4.md +0 -13
- package/docs-site/blog/2025-04-22-release-v2.1.5.md +0 -13
- package/docs-site/blog/2025-04-24-v2.1.6.md +0 -21
- package/docs-site/blog/authors.yml +0 -5
- package/docs-site/blog/tags.yml +0 -60
- package/docs-site/docs/Architecture.md +0 -76
- package/docs-site/docs/CLI.md +0 -69
- package/docs-site/docs/Evaluation.md +0 -61
- package/docs-site/docs/FAQ.md +0 -50
- package/docs-site/docs/Introduction.md +0 -58
- package/docs-site/docs/Plugins.md +0 -61
- package/docs-site/docs/Tutorials.md +0 -106
- package/docs-site/docs/Usage.md +0 -72
- package/docs-site/docs/Use-Cases.md +0 -77
- package/docs-site/docs/intro.mdx +0 -6
- package/docs-site/docusaurus.config.js +0 -88
- package/docs-site/package-lock.json +0 -17914
- package/docs-site/package.json +0 -47
- package/docs-site/sidebars.js +0 -39
- package/docs-site/src/components/HomepageFeatures/index.js +0 -56
- package/docs-site/src/components/HomepageFeatures/index.module.css +0 -57
- package/docs-site/src/components/HomepageHeader/index.js +0 -46
- package/docs-site/src/components/HomepageHeader/index.module.css +0 -77
- package/docs-site/src/css/custom.css +0 -104
- package/docs-site/src/pages/index.js +0 -20
- package/docs-site/src/pages/index.module.css +0 -23
- package/docs-site/src/pages/markdown-page.md +0 -7
- package/docs-site/static/.nojekyll +0 -0
- package/docs-site/static/img/authors/ali.jpg +0 -0
- package/docs-site/static/img/hero.jpg +0 -0
- package/docs-site/static/img/logo.png +0 -0
- package/docs-site/static/img/logo.svg +0 -6
- package/docs-site/static/img/social-card.jpg +0 -0
- package/jest.config.js +0 -24
- package/plugin.config.json +0 -14
- package/public/index.html +0 -0
- package/public/results/sample-eval.json +0 -0
- package/public/src/App.jsx +0 -57
- package/release.config.js +0 -36
- package/scripts/banner-injector.js +0 -102
- package/scripts/ci-runner.js +0 -51
- package/scripts/close-done-roadmap-issues.js +0 -65
- package/scripts/create-roadmap-issues.js +0 -47
- package/scripts/ensure-roadmap-labels.js +0 -73
- package/scripts/fix-mdx-blog-imports.js +0 -50
- package/scripts/fix-mdx-image-imports.js +0 -27
- package/scripts/generate-release-note.js +0 -110
- package/scripts/get-previous-tag.js +0 -40
- package/scripts/label-roadmap-issues.js +0 -125
- package/scripts/manual-test-checklist.md +0 -90
- package/scripts/repair-fixtures.js +0 -30
- package/scripts/setup.js +0 -31
- package/scripts/sync-labels.js +0 -71
- package/scripts/sync-roadmap-labels.js +0 -21
- package/scripts/tag-release.js +0 -64
- package/scripts/test-global-setup.js +0 -18
- package/scripts/validate-changelog-version.js +0 -48
- package/scripts/verify-fixture-mocks.js +0 -45
- package/scripts/verify-fixtures.js +0 -36
- package/server.js +0 -31
- package/src/config/load-config.js +0 -33
- package/src/config/load-plugin-config.js +0 -56
- package/src/config/validate-plugin-schema.js +0 -28
- package/src/config/validate-schema.js +0 -107
- package/src/core/create-pipeline.js +0 -96
- package/src/core/plugin-contracts.js +0 -21
- package/src/core/plugin-registry.js +0 -48
- package/src/dag/dag-engine.js +0 -70
- package/src/evaluate/evaluator.js +0 -44
- package/src/evaluate/scoring.js +0 -74
- package/src/loader/csv-loader.js +0 -30
- package/src/loader/directory-loader.js +0 -38
- package/src/loader/html-loader.js +0 -57
- package/src/loader/markdown-loader.js +0 -58
- package/src/mocks/openai-embedder.js +0 -30
- package/src/mocks/openai-llm.js +0 -18
- package/src/mocks/pdf-loader.js +0 -17
- package/src/mocks/pinecone-retriever.js +0 -25
- package/src/reranker/llm-reranker.js +0 -34
- package/src/utils/ci/diagnostic-reporter.js +0 -36
- package/src/utils/logger.js +0 -29
- package/src/utils/retry.js +0 -42
- package/src/utils/validate-plugin-contract.js +0 -36
|
@@ -1,107 +0,0 @@
|
|
|
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,96 +0,0 @@
|
|
|
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 };
|
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
|
|
@@ -1,48 +0,0 @@
|
|
|
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
|
-
register(type, name, plugin) {
|
|
18
|
-
const group = this.#registry[type];
|
|
19
|
-
if (!group) {
|
|
20
|
-
throw new Error(`Unknown plugin type: '${type}'`);
|
|
21
|
-
}
|
|
22
|
-
group.set(name, plugin);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
get(type, name) {
|
|
26
|
-
const group = this.#registry[type];
|
|
27
|
-
if (!group) {
|
|
28
|
-
throw new Error(`Unknown plugin type: '${type}'`);
|
|
29
|
-
}
|
|
30
|
-
const plugin = group.get(name);
|
|
31
|
-
if (!plugin) {
|
|
32
|
-
throw new Error(`Plugin not found: [${type}:${name}]`);
|
|
33
|
-
}
|
|
34
|
-
return plugin;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
list(type) {
|
|
38
|
-
const group = this.#registry[type];
|
|
39
|
-
if (!group) {
|
|
40
|
-
throw new Error(`Unknown plugin type: '${type}'`);
|
|
41
|
-
}
|
|
42
|
-
return [...group.keys()];
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const registry = new PluginRegistry();
|
|
47
|
-
|
|
48
|
-
export default registry;
|
package/src/dag/dag-engine.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.1.0
|
|
3
|
-
* Path: /src/dag/dag-engine.js
|
|
4
|
-
* Description: DAG executor for chained RAG pipelines
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export class DAGNode {
|
|
9
|
-
constructor(id, run) {
|
|
10
|
-
this.id = id;
|
|
11
|
-
this.run = run; // async (input) => output
|
|
12
|
-
this.inputs = [];
|
|
13
|
-
this.outputs = [];
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class DAG {
|
|
18
|
-
constructor() {
|
|
19
|
-
this.nodes = new Map();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
addNode(id, fn) {
|
|
23
|
-
const node = new DAGNode(id, fn);
|
|
24
|
-
this.nodes.set(id, node);
|
|
25
|
-
return node;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
connect(fromId, toId) {
|
|
29
|
-
const from = this.nodes.get(fromId);
|
|
30
|
-
const to = this.nodes.get(toId);
|
|
31
|
-
if (!from || !to) throw new Error(`Invalid edge: ${fromId} → ${toId}`);
|
|
32
|
-
from.outputs.push(to);
|
|
33
|
-
to.inputs.push(from);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Executes DAG from source nodes to sinks using topological order
|
|
38
|
-
* @param {any} seed - Initial input to the graph
|
|
39
|
-
* @returns {Promise<any>} - Final output from sink(s)
|
|
40
|
-
*/
|
|
41
|
-
async execute(seed) {
|
|
42
|
-
// const visited = new Set();
|
|
43
|
-
const order = this.topoSort();
|
|
44
|
-
const results = new Map();
|
|
45
|
-
|
|
46
|
-
for (const node of order) {
|
|
47
|
-
const inputValues = node.inputs.map(n => results.get(n.id));
|
|
48
|
-
const input = inputValues.length === 1 ? inputValues[0] : inputValues;
|
|
49
|
-
const output = await node.run(input ?? seed);
|
|
50
|
-
results.set(node.id, output);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const last = order.at(-1);
|
|
54
|
-
return results.get(last.id);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
topoSort() {
|
|
58
|
-
const order = [];
|
|
59
|
-
const visited = new Set();
|
|
60
|
-
const visit = (node) => {
|
|
61
|
-
if (visited.has(node.id)) return;
|
|
62
|
-
visited.add(node.id);
|
|
63
|
-
node.inputs.forEach(visit);
|
|
64
|
-
order.push(node);
|
|
65
|
-
};
|
|
66
|
-
for (const node of this.nodes.values()) visit(node);
|
|
67
|
-
return order;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.1.1
|
|
3
|
-
* Path: /src/evaluate/evaluator.js
|
|
4
|
-
* Description: Evaluation runner with scoring metrics for batch RAG QA
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createRagPipeline } from '../core/create-pipeline.js';
|
|
9
|
-
import { logger } from '../utils/logger.js';
|
|
10
|
-
import fs from 'fs/promises';
|
|
11
|
-
import path from 'path';
|
|
12
|
-
import { scoreAnswer } from './scoring.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Evaluate a list of prompt/answer pairs using the RAG pipeline and compute scores
|
|
16
|
-
* @param {string} datasetPath - Path to JSON file with { prompt, expected }[]
|
|
17
|
-
* @param {object} config - RAG plugin config (loader optional)
|
|
18
|
-
* @returns {Promise<Array<{ prompt: string, expected: string, actual: string, success: boolean, scores: object }>>}
|
|
19
|
-
*/
|
|
20
|
-
export async function evaluateRagDataset(datasetPath, config) {
|
|
21
|
-
const file = await fs.readFile(path.resolve(datasetPath), 'utf-8');
|
|
22
|
-
const cases = JSON.parse(file);
|
|
23
|
-
const pipeline = createRagPipeline(config);
|
|
24
|
-
|
|
25
|
-
const results = [];
|
|
26
|
-
for (const { prompt, expected } of cases) {
|
|
27
|
-
try {
|
|
28
|
-
logger.info({ prompt }, 'Evaluating case');
|
|
29
|
-
const actual = await pipeline.query(prompt);
|
|
30
|
-
const success = normalizeText(actual) === normalizeText(expected);
|
|
31
|
-
const scores = scoreAnswer(actual, expected);
|
|
32
|
-
results.push({ prompt, expected, actual, success, scores });
|
|
33
|
-
} catch (err) {
|
|
34
|
-
logger.error({ prompt, error: err.message }, 'Evaluation failed');
|
|
35
|
-
results.push({ prompt, expected, actual: '', success: false, scores: { bleu: 0, rouge: 0 } });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return results;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function normalizeText(txt) {
|
|
42
|
-
return txt.trim().toLowerCase().replace(/[^a-z0-9 ]/g, '').replace(/\s+/g, ' ');
|
|
43
|
-
}
|
|
44
|
-
|
package/src/evaluate/scoring.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.1.0
|
|
3
|
-
* Path: /src/evaluate/scoring.js
|
|
4
|
-
* Description: Basic scoring metrics (BLEU, ROUGE-L) for RAG output evaluation
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Tokenize a string by words
|
|
10
|
-
* @param {string} text
|
|
11
|
-
* @returns {string[]}
|
|
12
|
-
*/
|
|
13
|
-
function tokenize(text) {
|
|
14
|
-
return text.toLowerCase().replace(/[^a-z0-9 ]/g, '').split(/\s+/).filter(Boolean);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Compute BLEU-1 score: unigram precision
|
|
19
|
-
* @param {string[]} candidate - Generated tokens
|
|
20
|
-
* @param {string[]} reference - Reference tokens
|
|
21
|
-
* @returns {number}
|
|
22
|
-
*/
|
|
23
|
-
export function computeBLEU(candidate, reference) {
|
|
24
|
-
const candCounts = candidate.reduce((acc, t) => (acc[t] = (acc[t] || 0) + 1, acc), {});
|
|
25
|
-
const refCounts = reference.reduce((acc, t) => (acc[t] = (acc[t] || 0) + 1, acc), {});
|
|
26
|
-
|
|
27
|
-
let match = 0;
|
|
28
|
-
for (const token of Object.keys(candCounts)) {
|
|
29
|
-
match += Math.min(candCounts[token], refCounts[token] || 0);
|
|
30
|
-
}
|
|
31
|
-
return candidate.length ? match / candidate.length : 0;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Compute ROUGE-L (Longest Common Subsequence) recall
|
|
36
|
-
* @param {string[]} candidate
|
|
37
|
-
* @param {string[]} reference
|
|
38
|
-
* @returns {number}
|
|
39
|
-
*/
|
|
40
|
-
export function computeROUGE(candidate, reference) {
|
|
41
|
-
const m = candidate.length;
|
|
42
|
-
const n = reference.length;
|
|
43
|
-
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
44
|
-
|
|
45
|
-
for (let i = 1; i <= m; i++) {
|
|
46
|
-
for (let j = 1; j <= n; j++) {
|
|
47
|
-
if (candidate[i - 1] === reference[j - 1]) {
|
|
48
|
-
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
49
|
-
} else {
|
|
50
|
-
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const lcs = dp[m][n];
|
|
56
|
-
return reference.length ? lcs / reference.length : 0;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Score a prompt-answer pair using multiple metrics
|
|
61
|
-
* @param {string} actual - LLM response
|
|
62
|
-
* @param {string} expected - Reference answer
|
|
63
|
-
* @returns {{ bleu: number, rouge: number }}
|
|
64
|
-
*/
|
|
65
|
-
export function scoreAnswer(actual, expected) {
|
|
66
|
-
const candTokens = tokenize(actual);
|
|
67
|
-
const refTokens = tokenize(expected);
|
|
68
|
-
return {
|
|
69
|
-
bleu: computeBLEU(candTokens, refTokens),
|
|
70
|
-
rouge: computeROUGE(candTokens, refTokens)
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
package/src/loader/csv-loader.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.1.0
|
|
3
|
-
* Path: /src/loader/csv-loader.js
|
|
4
|
-
* Description: CSV document loader with row-wise chunking
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import fs from 'fs/promises';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { parse } from 'csv-parse/sync';
|
|
11
|
-
|
|
12
|
-
export class CSVLoader {
|
|
13
|
-
/**
|
|
14
|
-
* Load and parse a CSV file into chunks
|
|
15
|
-
* @param {string} filePath
|
|
16
|
-
* @returns {Promise<Array<{ chunk(): string[] }>>}
|
|
17
|
-
*/
|
|
18
|
-
async load(filePath) {
|
|
19
|
-
const absPath = path.resolve(filePath);
|
|
20
|
-
const raw = await fs.readFile(absPath, 'utf-8');
|
|
21
|
-
const records = parse(raw, { columns: true });
|
|
22
|
-
|
|
23
|
-
return [
|
|
24
|
-
{
|
|
25
|
-
chunk: () => records.map(r => Object.values(r).join(' '))
|
|
26
|
-
}
|
|
27
|
-
];
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.1.0
|
|
3
|
-
* Path: /src/loader/directory-loader.js
|
|
4
|
-
* Description: Recursive directory loader with file type routing
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import fg from 'fast-glob';
|
|
9
|
-
import { MarkdownLoader } from './markdown-loader.js';
|
|
10
|
-
import { HTMLLoader } from './html-loader.js';
|
|
11
|
-
import { CSVLoader } from './csv-loader.js';
|
|
12
|
-
|
|
13
|
-
const loaders = {
|
|
14
|
-
'.md': new MarkdownLoader(),
|
|
15
|
-
'.html': new HTMLLoader(),
|
|
16
|
-
'.csv': new CSVLoader()
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export class DirectoryLoader {
|
|
20
|
-
/**
|
|
21
|
-
* Load and route supported files from a directory
|
|
22
|
-
* @param {string} dirPath
|
|
23
|
-
* @returns {Promise<Array<{ chunk(): string[] }>>}
|
|
24
|
-
*/
|
|
25
|
-
async load(dirPath) {
|
|
26
|
-
const matches = await fg([`${dirPath}/**/*.{md,html,csv}`]);
|
|
27
|
-
const all = [];
|
|
28
|
-
for (const file of matches) {
|
|
29
|
-
const ext = file.slice(file.lastIndexOf('.'));
|
|
30
|
-
const loader = loaders[ext];
|
|
31
|
-
if (loader) {
|
|
32
|
-
const docs = await loader.load(file);
|
|
33
|
-
all.push(...docs);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return all;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.1.0
|
|
3
|
-
* Path: /src/loader/html-loader.js
|
|
4
|
-
* Description: Loader for HTML (.html) documents
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import fs from 'fs/promises';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { JSDOM } from 'jsdom';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* HTMLLoader reads HTML files and extracts visible text.
|
|
14
|
-
* Implements the Loader interface.
|
|
15
|
-
*/
|
|
16
|
-
export class HTMLLoader {
|
|
17
|
-
/**
|
|
18
|
-
* Load and extract text from an HTML file
|
|
19
|
-
* @param {string} filePath - Path to .html file
|
|
20
|
-
* @returns {Promise<Array<{ chunk(): string[] }>>}
|
|
21
|
-
*/
|
|
22
|
-
async load(filePath) {
|
|
23
|
-
const absPath = path.resolve(filePath);
|
|
24
|
-
const raw = await fs.readFile(absPath, 'utf-8');
|
|
25
|
-
const dom = new JSDOM(raw);
|
|
26
|
-
const text = dom.window.document.body.textContent || '';
|
|
27
|
-
|
|
28
|
-
return [
|
|
29
|
-
{
|
|
30
|
-
chunk: () => this._chunkText(text)
|
|
31
|
-
}
|
|
32
|
-
];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Chunk HTML text content by sentence
|
|
37
|
-
* @param {string} input
|
|
38
|
-
* @param {number} maxLen
|
|
39
|
-
* @returns {string[]}
|
|
40
|
-
*/
|
|
41
|
-
_chunkText(input, maxLen = 500) {
|
|
42
|
-
const sentences = input.split(/(?<=[.!?])\s+/);
|
|
43
|
-
const chunks = [];
|
|
44
|
-
let buffer = '';
|
|
45
|
-
|
|
46
|
-
for (const sentence of sentences) {
|
|
47
|
-
if ((buffer + sentence).length <= maxLen) {
|
|
48
|
-
buffer += sentence + ' ';
|
|
49
|
-
} else {
|
|
50
|
-
chunks.push(buffer.trim());
|
|
51
|
-
buffer = sentence + ' ';
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (buffer.trim()) chunks.push(buffer.trim());
|
|
55
|
-
return chunks;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.1.0
|
|
3
|
-
* Path: /src/loader/markdown-loader.js
|
|
4
|
-
* Description: Loader for Markdown (.md) documents
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import fs from 'fs/promises';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { marked } from 'marked';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* MarkdownLoader reads and parses .md files into plain text chunks.
|
|
14
|
-
* Implements the Loader interface.
|
|
15
|
-
*/
|
|
16
|
-
export class MarkdownLoader {
|
|
17
|
-
/**
|
|
18
|
-
* Load and parse Markdown file
|
|
19
|
-
* @param {string} filePath - Path to .md file
|
|
20
|
-
* @returns {Promise<Array<{ chunk(): string[] }>>}
|
|
21
|
-
*/
|
|
22
|
-
async load(filePath) {
|
|
23
|
-
const absPath = path.resolve(filePath);
|
|
24
|
-
const raw = await fs.readFile(absPath, 'utf-8');
|
|
25
|
-
const html = marked(raw);
|
|
26
|
-
const text = html.replace(/<[^>]+>/g, ''); // strip tags
|
|
27
|
-
|
|
28
|
-
return [
|
|
29
|
-
{
|
|
30
|
-
chunk: () => this._chunkText(text)
|
|
31
|
-
}
|
|
32
|
-
];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Simple sentence-based chunking strategy
|
|
37
|
-
* @param {string} input
|
|
38
|
-
* @param {number} maxLen
|
|
39
|
-
* @returns {string[]}
|
|
40
|
-
*/
|
|
41
|
-
_chunkText(input, maxLen = 500) {
|
|
42
|
-
const sentences = input.split(/(?<=[.!?])\s+/);
|
|
43
|
-
const chunks = [];
|
|
44
|
-
let buffer = '';
|
|
45
|
-
|
|
46
|
-
for (const sentence of sentences) {
|
|
47
|
-
if ((buffer + sentence).length <= maxLen) {
|
|
48
|
-
buffer += sentence + ' ';
|
|
49
|
-
} else {
|
|
50
|
-
chunks.push(buffer.trim());
|
|
51
|
-
buffer = sentence + ' ';
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (buffer.trim()) chunks.push(buffer.trim());
|
|
55
|
-
return chunks;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.1.0
|
|
3
|
-
* File: /src/mocks/openai-embedder.js
|
|
4
|
-
* Description: Mock implementation of an OpenAI embedder
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export default class OpenAIEmbedder {
|
|
9
|
-
/**
|
|
10
|
-
* Returns static vector embeddings for input chunks
|
|
11
|
-
* @param {string[]} chunks
|
|
12
|
-
* @returns {Promise<Array<{ id: string, values: number[], metadata: { text: string } }>>}
|
|
13
|
-
*/
|
|
14
|
-
async embed(chunks) {
|
|
15
|
-
return chunks.map((text, i) => ({
|
|
16
|
-
id: `v${i}`,
|
|
17
|
-
values: [0.1, 0.2, 0.3],
|
|
18
|
-
metadata: { text },
|
|
19
|
-
}));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Returns a static vector for the given query prompt
|
|
24
|
-
* @param {string} prompt
|
|
25
|
-
* @returns {Promise<number[]>}
|
|
26
|
-
*/
|
|
27
|
-
async embedQuery(prompt) {
|
|
28
|
-
return [0.1, 0.2, 0.3];
|
|
29
|
-
}
|
|
30
|
-
}
|
package/src/mocks/openai-llm.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.1.0
|
|
3
|
-
* File: /src/mocks/openai-llm.js
|
|
4
|
-
* Description: Mock implementation of an OpenAI LLM
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export default class OpenAILLM {
|
|
9
|
-
/**
|
|
10
|
-
* Generates a static answer from input prompt and context
|
|
11
|
-
* @param {string} prompt
|
|
12
|
-
* @param {Array<{ text: string }>} context
|
|
13
|
-
* @returns {Promise<string>}
|
|
14
|
-
*/
|
|
15
|
-
async generate(prompt, context) {
|
|
16
|
-
return `Generated answer using: ${context.map(d => d.text).join(', ')}`;
|
|
17
|
-
}
|
|
18
|
-
}
|