@devilsdev/rag-pipeline-utils 1.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/.dockerignore +14 -0
- package/.github/workflows/ci.yml +42 -0
- package/.github/workflows/release.yml +21 -0
- package/Dockerfile +22 -0
- package/LICENSE +674 -0
- package/README.md +116 -0
- package/__tests__/fixtures/sample-eval-dataset.json +15 -0
- package/__tests__/integration/cli/config-flow.test.js +44 -0
- package/__tests__/integration/config/load-config.test.js +52 -0
- package/__tests__/unit/core/plugin-registry.test.js +55 -0
- package/__tests__/unit/reranker/__snapshots__/reranker.snapshot.test.js.snap +15 -0
- package/__tests__/unit/reranker/llm-reranker.test.js +42 -0
- package/__tests__/unit/reranker/reranker.snapshot.test.js +17 -0
- package/bin/cli.js +168 -0
- package/docs-site/README.md +41 -0
- package/docs-site/blog/2019-05-28-first-blog-post.md +12 -0
- package/docs-site/blog/2019-05-29-long-blog-post.md +44 -0
- package/docs-site/blog/2021-08-01-mdx-blog-post.mdx +24 -0
- package/docs-site/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs-site/blog/2021-08-26-welcome/index.md +29 -0
- package/docs-site/blog/authors.yml +23 -0
- package/docs-site/blog/tags.yml +19 -0
- package/docs-site/docs/Architecture.md +76 -0
- package/docs-site/docs/CLI.md +69 -0
- package/docs-site/docs/Evaluation.md +61 -0
- package/docs-site/docs/FAQ.md +50 -0
- package/docs-site/docs/Introduction.md +58 -0
- package/docs-site/docs/Plugins.md +61 -0
- package/docs-site/docs/Usage.md +72 -0
- package/docs-site/docusaurus.config.js +57 -0
- package/docs-site/package-lock.json +17914 -0
- package/docs-site/package.json +44 -0
- package/docs-site/sidebars.js +22 -0
- package/docs-site/src/components/HomepageFeatures/index.js +59 -0
- package/docs-site/src/components/HomepageFeatures/styles.module.css +58 -0
- package/docs-site/src/css/custom.css +81 -0
- package/docs-site/src/pages/index.js +37 -0
- package/docs-site/src/pages/index.module.css +23 -0
- package/docs-site/src/pages/markdown-page.md +7 -0
- package/docs-site/static/.nojekyll +0 -0
- package/docs-site/static/img/favicon.ico +0 -0
- package/docs-site/static/img/logo.png +0 -0
- package/docs-site/static/img/logo.svg +1539 -0
- package/docs-site/static/img/social-card.jpg +0 -0
- package/jest.config.js +14 -0
- package/package.json +65 -0
- package/public/index.html +0 -0
- package/public/results/sample-eval.json +0 -0
- package/public/src/App.jsx +57 -0
- package/release.config.js +23 -0
- package/server.js +31 -0
- package/src/config/load-config.js +35 -0
- package/src/config/validate-schema.js +49 -0
- package/src/core/create-pipeline.js +82 -0
- package/src/core/plugin-registry.js +61 -0
- package/src/dag/dag-engine.js +70 -0
- package/src/evaluate/evaluator.js +44 -0
- package/src/evaluate/scoring.js +74 -0
- package/src/loader/csv-loader.js +30 -0
- package/src/loader/directory-loader.js +38 -0
- package/src/loader/html-loader.js +57 -0
- package/src/loader/markdown-loader.js +58 -0
- package/src/reranker/llm-reranker.js +49 -0
- package/src/utils/logger.js +29 -0
- package/src/utils/retry.js +42 -0
|
@@ -0,0 +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
|
+
}
|
|
@@ -0,0 +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
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version: 0.1.0
|
|
3
|
+
* Path: /src/reranker/llm-reranker.js
|
|
4
|
+
* Description: LLM-based reranker that reorders retrieved context for relevance
|
|
5
|
+
* Author: Ali Kahwaji
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* LLMReranker ranks context chunks by relevance to a given prompt.
|
|
10
|
+
* @interface
|
|
11
|
+
*/
|
|
12
|
+
export class LLMReranker {
|
|
13
|
+
constructor({ llm }) {
|
|
14
|
+
if (!llm || typeof llm.generate !== 'function') {
|
|
15
|
+
throw new Error('LLM instance must implement generate(prompt, context)');
|
|
16
|
+
}
|
|
17
|
+
this.llm = llm;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Reranks retrieved context chunks using the LLM.
|
|
22
|
+
* @param {string} prompt - The user's question or query
|
|
23
|
+
* @param {Array<{ text: string, metadata?: object }>} documents - Retrieved context chunks
|
|
24
|
+
* @param {number} [topK=5] - Number of top-ranked documents to return
|
|
25
|
+
* @returns {Promise<Array>} - Reranked top K chunks
|
|
26
|
+
*/
|
|
27
|
+
async rerank(prompt, documents, topK = 5) {
|
|
28
|
+
const contextText = documents.map((d, i) => `Chunk ${i + 1}: ${d.text}`).join('\n');
|
|
29
|
+
|
|
30
|
+
const instruction = `Rank the following document chunks by how well they answer the prompt.\n
|
|
31
|
+
Prompt: ${prompt}\n
|
|
32
|
+
Chunks:\n${contextText}\n
|
|
33
|
+
Respond with a JSON array of chunk indexes in descending order of relevance.`;
|
|
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
|
+
|
|
@@ -0,0 +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
|
+
|
|
@@ -0,0 +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
|
+
|