@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
|
@@ -1,57 +1,57 @@
|
|
|
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
|
+
/**
|
|
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 +1,58 @@
|
|
|
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
|
+
/**
|
|
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
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version: 0.1.0
|
|
3
|
+
* File: /src/mocks/pdf-loader.js
|
|
4
|
+
* Description: Mock implementation of a PDF document loader
|
|
5
|
+
* Author: Ali Kahwaji
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export default class PDFLoader {
|
|
9
|
+
/**
|
|
10
|
+
* Loads a mock PDF file and returns an array of chunked content.
|
|
11
|
+
* @param {string} filePath - Path to the PDF file
|
|
12
|
+
* @returns {Promise<Array<{ chunk: () => string[] }>>}
|
|
13
|
+
*/
|
|
14
|
+
async load(filePath) {
|
|
15
|
+
return [{ chunk: () => [`Test loaded: ${filePath}`] }];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version: 0.1.0
|
|
3
|
+
* File: /src/mocks/pinecone-retriever.js
|
|
4
|
+
* Description: Mock implementation of a Pinecone retriever
|
|
5
|
+
* Author: Ali Kahwaji
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export default class PineconeRetriever {
|
|
9
|
+
/**
|
|
10
|
+
* Mocks vector storage (no-op).
|
|
11
|
+
*/
|
|
12
|
+
async store() {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Returns mock retrieved documents
|
|
16
|
+
* @returns {Promise<Array<{ id: string, text: string, metadata: object }>>}
|
|
17
|
+
*/
|
|
18
|
+
async retrieve() {
|
|
19
|
+
return [
|
|
20
|
+
{ id: 'a', text: 'Chunk about pine trees', metadata: {} },
|
|
21
|
+
{ id: 'b', text: 'Chunk about vectors', metadata: {} },
|
|
22
|
+
{ id: 'c', text: 'Chunk about databases', metadata: {} },
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,49 +1,34 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const result = await this.llm.generate(instruction, []);
|
|
36
|
-
|
|
37
|
-
let indexOrder = [];
|
|
38
|
-
try {
|
|
39
|
-
indexOrder = JSON.parse(result.match(/\[.*\]/s)?.[0] || '[]');
|
|
40
|
-
} catch (err) {
|
|
41
|
-
console.warn('Failed to parse reranker output:', result);
|
|
42
|
-
return documents.slice(0, topK); // fallback
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const ranked = indexOrder.map(i => documents[i]).filter(Boolean);
|
|
46
|
-
return ranked.slice(0, topK);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Version: 1.0.4
|
|
3
|
+
* Description: LLM reranker using LLM to reorder documents based on relevance
|
|
4
|
+
* Author: devilsdev
|
|
5
|
+
* File: src/reranker/llm-reranker.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class LLMReranker {
|
|
9
|
+
/**
|
|
10
|
+
* @param {Object} options
|
|
11
|
+
* @param {{ generate(prompt: string): Promise<string> }} options.llm - An LLM client with a `generate` method
|
|
12
|
+
*/
|
|
13
|
+
constructor({ llm }) {
|
|
14
|
+
this.llm = llm;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Rerank documents based on the LLM output
|
|
19
|
+
* @param {string} query
|
|
20
|
+
* @param {Array<{ text: string }>} docs
|
|
21
|
+
* @returns {Promise<Array<{ text: string }>>}
|
|
22
|
+
*/
|
|
23
|
+
async rerank(query, docs) {
|
|
24
|
+
const prompt = `Given the query: "${query}", rank the following documents by relevance: \n${docs.map((d, i) => `[${i}]: ${d.text}`).join('\n')}\nReturn an array of indices like [2, 0, 1]`;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const json = await this.llm.generate(prompt);
|
|
28
|
+
const indices = JSON.parse(json);
|
|
29
|
+
return indices.map(i => docs[i]).filter(Boolean);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
return docs; // Fallback if LLM response is not parsable
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version: 1.0.0
|
|
3
|
+
* Description: Enhanced CI diagnostics for plugin mock verification
|
|
4
|
+
* Author: Ali Kahwaji
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Pretty prints CI validation errors and guidance
|
|
12
|
+
* @param {Array<{ file: string, missing: string[] }>} failures
|
|
13
|
+
*/
|
|
14
|
+
export function reportPluginValidationFailures(failures) {
|
|
15
|
+
if (!failures.length) return;
|
|
16
|
+
|
|
17
|
+
console.log(`\n${chalk.redBright.bold('❌ Plugin Mock Validation Failed')}`);
|
|
18
|
+
|
|
19
|
+
failures.forEach(({ file, missing }) => {
|
|
20
|
+
const fileName = path.basename(file);
|
|
21
|
+
console.log(
|
|
22
|
+
`${chalk.red(` ✖ ${fileName}`)} ${chalk.gray(`missing: ${missing.join(', ')}`)}`
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Add method-level fix hints
|
|
26
|
+
missing.forEach(method => {
|
|
27
|
+
const pluginType = fileName.split('.')[0].split('-')[1] || 'unknown';
|
|
28
|
+
console.error(` → Fix: implement method ${chalk.cyan(`${method}()`)} for plugin type ${chalk.yellow(pluginType)}`);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(`\n${chalk.yellowBright('Recommended Fixes:')}`);
|
|
33
|
+
console.log(' • Implement all required methods defined in /src/core/plugin-contracts.js');
|
|
34
|
+
console.log(` • Re-run: ${chalk.cyan('npm run ci')} to confirm validation`);
|
|
35
|
+
console.log(` • Optionally use: ${chalk.cyan('scripts/repair-fixtures.js')} to auto-repair mocks\n`);
|
|
36
|
+
}
|
package/src/utils/logger.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.1.0
|
|
3
|
-
* Path: /src/utils/logger.js
|
|
4
|
-
* Description: Centralized structured logger using Pino
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import pino from 'pino';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Create a structured JSON logger instance with sensible defaults.
|
|
12
|
-
* Output can be redirected or formatted in dev/prod contexts.
|
|
13
|
-
*/
|
|
14
|
-
const logger = pino({
|
|
15
|
-
name: 'rag-pipeline-utils',
|
|
16
|
-
level: process.env.LOG_LEVEL || 'info',
|
|
17
|
-
transport: process.env.NODE_ENV === 'development' ? {
|
|
18
|
-
target: 'pino-pretty',
|
|
19
|
-
options: {
|
|
20
|
-
colorize: true,
|
|
21
|
-
translateTime: 'SYS:standard',
|
|
22
|
-
ignore: 'pid,hostname'
|
|
23
|
-
}
|
|
24
|
-
} : undefined
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
export { logger };
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Version: 0.1.0
|
|
3
|
+
* Path: /src/utils/logger.js
|
|
4
|
+
* Description: Centralized structured logger using Pino
|
|
5
|
+
* Author: Ali Kahwaji
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import pino from 'pino';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a structured JSON logger instance with sensible defaults.
|
|
12
|
+
* Output can be redirected or formatted in dev/prod contexts.
|
|
13
|
+
*/
|
|
14
|
+
const logger = pino({
|
|
15
|
+
name: 'rag-pipeline-utils',
|
|
16
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
17
|
+
transport: process.env.NODE_ENV === 'development' ? {
|
|
18
|
+
target: 'pino-pretty',
|
|
19
|
+
options: {
|
|
20
|
+
colorize: true,
|
|
21
|
+
translateTime: 'SYS:standard',
|
|
22
|
+
ignore: 'pid,hostname'
|
|
23
|
+
}
|
|
24
|
+
} : undefined
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export { logger };
|
|
28
|
+
|
|
29
|
+
|
package/src/utils/retry.js
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version: 0.1.0
|
|
3
|
-
* Path: /src/utils/retry.js
|
|
4
|
-
* Description: Generic retry wrapper with exponential backoff for async operations
|
|
5
|
-
* Author: Ali Kahwaji
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { logger } from './logger.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Retry wrapper for async functions with exponential backoff
|
|
12
|
-
*
|
|
13
|
-
* @param {Function} fn - The async function to retry
|
|
14
|
-
* @param {Object} options
|
|
15
|
-
* @param {number} [options.retries=3] - Number of retry attempts
|
|
16
|
-
* @param {number} [options.initialDelay=300] - Initial delay in ms
|
|
17
|
-
* @param {string} [options.label] - Optional label for logging context
|
|
18
|
-
* @returns {Promise<*>} - The resolved result of the function
|
|
19
|
-
* @throws {Error} - The final error if all retries fail
|
|
20
|
-
*/
|
|
21
|
-
export async function withRetry(fn, { retries = 3, initialDelay = 300, label = 'operation' } = {}) {
|
|
22
|
-
let attempt = 0;
|
|
23
|
-
let delay = initialDelay;
|
|
24
|
-
|
|
25
|
-
while (attempt <= retries) {
|
|
26
|
-
try {
|
|
27
|
-
if (attempt > 0) {
|
|
28
|
-
logger.warn({ attempt, label }, `Retrying ${label} (attempt ${attempt})`);
|
|
29
|
-
}
|
|
30
|
-
return await fn();
|
|
31
|
-
} catch (err) {
|
|
32
|
-
attempt++;
|
|
33
|
-
if (attempt > retries) {
|
|
34
|
-
logger.error({ label, error: err.message }, `Failed ${label} after ${retries} retries`);
|
|
35
|
-
throw err;
|
|
36
|
-
}
|
|
37
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
38
|
-
delay *= 2; // Exponential backoff
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Version: 0.1.0
|
|
3
|
+
* Path: /src/utils/retry.js
|
|
4
|
+
* Description: Generic retry wrapper with exponential backoff for async operations
|
|
5
|
+
* Author: Ali Kahwaji
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { logger } from './logger.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Retry wrapper for async functions with exponential backoff
|
|
12
|
+
*
|
|
13
|
+
* @param {Function} fn - The async function to retry
|
|
14
|
+
* @param {Object} options
|
|
15
|
+
* @param {number} [options.retries=3] - Number of retry attempts
|
|
16
|
+
* @param {number} [options.initialDelay=300] - Initial delay in ms
|
|
17
|
+
* @param {string} [options.label] - Optional label for logging context
|
|
18
|
+
* @returns {Promise<*>} - The resolved result of the function
|
|
19
|
+
* @throws {Error} - The final error if all retries fail
|
|
20
|
+
*/
|
|
21
|
+
export async function withRetry(fn, { retries = 3, initialDelay = 300, label = 'operation' } = {}) {
|
|
22
|
+
let attempt = 0;
|
|
23
|
+
let delay = initialDelay;
|
|
24
|
+
|
|
25
|
+
while (attempt <= retries) {
|
|
26
|
+
try {
|
|
27
|
+
if (attempt > 0) {
|
|
28
|
+
logger.warn({ attempt, label }, `Retrying ${label} (attempt ${attempt})`);
|
|
29
|
+
}
|
|
30
|
+
return await fn();
|
|
31
|
+
} catch (err) {
|
|
32
|
+
attempt++;
|
|
33
|
+
if (attempt > retries) {
|
|
34
|
+
logger.error({ label, error: err.message }, `Failed ${label} after ${retries} retries`);
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
38
|
+
delay *= 2; // Exponential backoff
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version: 1.0.0
|
|
3
|
+
* Description: Type-safe plugin contract validator for runtime safety
|
|
4
|
+
* Author: Ali Kahwaji
|
|
5
|
+
* File: /src/utils/validate-plugin-contract.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { pluginContracts } from '../core/plugin-contracts.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validates that a plugin instance implements all required methods.
|
|
12
|
+
* @param {string} type - Plugin type (e.g. 'loader', 'embedder')
|
|
13
|
+
* @param {object} instance - Instantiated plugin module
|
|
14
|
+
* @param {string} filePath - Path to plugin (used in error reporting)
|
|
15
|
+
* @throws {Error} if any required method is missing or not a function
|
|
16
|
+
*/
|
|
17
|
+
export function validatePluginContract(type, instance, filePath) {
|
|
18
|
+
const expectedMethods = pluginContracts[type];
|
|
19
|
+
if (!expectedMethods) {
|
|
20
|
+
throw new Error(`[validatePluginContract] Unknown plugin type: ${type}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const missing = expectedMethods.filter(method =>
|
|
24
|
+
typeof instance[method] !== 'function'
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (missing.length > 0) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`[validatePluginContract] Plugin '${filePath}' is missing required methods for '${type}': ${missing.join(', ')}`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Optionally wraps plugin instance with a Proxy for method usage auditing (see enforcePluginProxy)
|
|
36
|
+
*/
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
release:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
steps:
|
|
11
|
-
- uses: actions/checkout@v3
|
|
12
|
-
- uses: actions/setup-node@v3
|
|
13
|
-
with:
|
|
14
|
-
node-version: 18
|
|
15
|
-
registry-url: 'https://registry.npmjs.org'
|
|
16
|
-
|
|
17
|
-
- run: npm ci
|
|
18
|
-
- run: npx semantic-release
|
|
19
|
-
env:
|
|
20
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
21
|
-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|