@devilsdev/rag-pipeline-utils 2.3.1 → 2.4.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 (160) hide show
  1. package/README.md +94 -480
  2. package/contracts/loader-contract.json +11 -6
  3. package/contracts/reranker-contract.json +18 -26
  4. package/dist/index.cjs +21190 -47
  5. package/dist/index.d.ts +298 -68
  6. package/dist/index.mjs +9 -76
  7. package/package.json +37 -11
  8. package/src/agentic/agentic-pipeline.js +235 -0
  9. package/src/agentic/iterative-retriever.js +213 -0
  10. package/src/agentic/query-planner.js +208 -0
  11. package/src/agentic/self-critique.js +102 -0
  12. package/src/ai/adaptive-retrieval.js +0 -3
  13. package/src/ai/federated-learning.js +0 -3
  14. package/src/ai/federation.js +1 -6
  15. package/src/ai/index.js +9 -20
  16. package/src/ai/model-training.js +0 -3
  17. package/src/ai/multimodal-processing.js +0 -3
  18. package/src/ai/multimodal.js +1 -6
  19. package/src/ai/orchestrator.js +1 -6
  20. package/src/ai/retrieval-engine.js +1 -6
  21. package/src/chunking/chunking-engine.js +142 -0
  22. package/src/chunking/strategies/fixed-size.js +54 -0
  23. package/src/chunking/strategies/recursive.js +127 -0
  24. package/src/chunking/strategies/semantic.js +128 -0
  25. package/src/chunking/strategies/sentence.js +32 -0
  26. package/src/chunking/strategies/structure-aware.js +138 -0
  27. package/src/citation/citation-tracker.js +161 -0
  28. package/src/citation/hallucination-detector.js +74 -0
  29. package/src/citation/source-mapper.js +95 -0
  30. package/src/cli/commands/ai-ml/adaptive-commands.js +178 -0
  31. package/src/cli/commands/ai-ml/federated-commands.js +207 -0
  32. package/src/cli/commands/ai-ml/index.js +17 -0
  33. package/src/cli/commands/ai-ml/multimodal-commands.js +187 -0
  34. package/src/cli/commands/ai-ml/shared.js +51 -0
  35. package/src/cli/commands/ai-ml/training-commands.js +228 -0
  36. package/src/cli/commands/ai-ml.js +176 -757
  37. package/src/cli/commands/plugin-hub/certification-commands.js +221 -0
  38. package/src/cli/commands/plugin-hub/event-handlers.js +64 -0
  39. package/src/cli/commands/plugin-hub/hub-commands.js +290 -0
  40. package/src/cli/commands/plugin-hub/review-commands.js +78 -0
  41. package/src/cli/commands/plugin-hub.js +106 -782
  42. package/src/cli/diagnostics/config-checker.js +81 -0
  43. package/src/cli/diagnostics/dependency-checker.js +69 -0
  44. package/src/cli/diagnostics/environment-checker.js +45 -0
  45. package/src/cli/diagnostics/performance-checker.js +51 -0
  46. package/src/cli/diagnostics/plugin-checker.js +106 -0
  47. package/src/cli/diagnostics/security-checker.js +61 -0
  48. package/src/cli/doctor-command.js +70 -369
  49. package/src/cli/enhanced-cli-commands.js +162 -706
  50. package/src/cli/handlers/config-handler.js +126 -0
  51. package/src/cli/handlers/diagnostics-handler.js +113 -0
  52. package/src/cli/handlers/ingest-handler.js +42 -0
  53. package/src/cli/handlers/init-handler.js +51 -0
  54. package/src/cli/handlers/query-handler.js +45 -0
  55. package/src/cli/handlers/run-handler.js +181 -0
  56. package/src/cli/interactive-wizard.js +99 -723
  57. package/src/cli/shared/cli-utils.js +89 -0
  58. package/src/cli/wizard/configuration.js +148 -0
  59. package/src/cli/wizard/observability.js +79 -0
  60. package/src/cli/wizard/performance.js +76 -0
  61. package/src/cli/wizard/plugin-selection.js +297 -0
  62. package/src/cli/wizard/preview-save.js +172 -0
  63. package/src/cli/wizard/project-setup.js +66 -0
  64. package/src/config/memory-optimization.js +3 -3
  65. package/src/connectors/anthropic-connector.js +179 -0
  66. package/src/connectors/base-connector.js +94 -0
  67. package/src/connectors/cohere-connector.js +143 -0
  68. package/src/connectors/local-embedder.js +171 -0
  69. package/src/connectors/memory-retriever.js +150 -0
  70. package/src/connectors/ollama-connector.js +205 -0
  71. package/src/connectors/openai-connector.js +173 -0
  72. package/src/core/create-pipeline.js +21 -1
  73. package/src/core/observability/tracing.js +2 -1
  74. package/src/core/pipeline-factory.js +0 -1
  75. package/src/core/plugin-registry.js +14 -14
  76. package/src/cost/cost-calculator.js +146 -0
  77. package/src/cost/cost-tracker.js +260 -0
  78. package/src/cost/token-budget.js +245 -0
  79. package/src/dag/core/execution-scheduler.js +2 -1
  80. package/src/dag/core/topology-validation.js +0 -3
  81. package/src/dag/dag-engine.js +3 -5
  82. package/src/dag/dag-node.js +0 -1
  83. package/src/dag/enhanced-dag-engine.js +171 -99
  84. package/src/dashboard/dashboard-generator.js +642 -0
  85. package/src/dashboard/metrics-aggregator.js +409 -0
  86. package/src/debug/execution-tracer.js +210 -0
  87. package/src/debug/trace-inspector.js +281 -0
  88. package/src/dev/dev-server.js +5 -5
  89. package/src/dev/hot-reload.js +3 -2
  90. package/src/dx/integration-templates.js +0 -3
  91. package/src/ecosystem/plugin-analytics-dashboard.js +2 -2
  92. package/src/ecosystem/plugin-hub.js +2 -2
  93. package/src/enterprise/audit-logging.js +2 -2
  94. package/src/enterprise/sso-integration.js +52 -32
  95. package/src/evaluate/context-metrics.js +127 -0
  96. package/src/evaluate/evaluator.js +4 -2
  97. package/src/evaluate/faithfulness.js +117 -0
  98. package/src/evaluate/groundedness.js +60 -0
  99. package/src/evaluate/pipeline-evaluator.js +156 -0
  100. package/src/evaluate/relevance.js +129 -0
  101. package/src/evaluate/scoring.js +27 -0
  102. package/src/graph/entity-extractor.js +183 -0
  103. package/src/graph/graph-index.js +113 -0
  104. package/src/graph/graph-retriever.js +244 -0
  105. package/src/graph/knowledge-graph.js +256 -0
  106. package/src/guardrails/guardrails-pipeline.js +136 -0
  107. package/src/guardrails/post-generation-guard.js +170 -0
  108. package/src/guardrails/pre-retrieval-guard.js +161 -0
  109. package/src/guardrails/retrieval-guard.js +126 -0
  110. package/src/index.js +255 -9
  111. package/src/loader/html-loader.js +4 -15
  112. package/src/loader/markdown-loader.js +4 -15
  113. package/src/mcp/mcp-server.js +199 -0
  114. package/src/mcp/mcp-tool-builder.js +121 -0
  115. package/src/observability/audit-logger.js +0 -2
  116. package/src/observability/event-logger.js +2 -1
  117. package/src/observability/security-monitor.js +0 -2
  118. package/src/observability/telemetry.js +0 -2
  119. package/src/reranker/cascade-reranker.js +79 -0
  120. package/src/reranker/embedding-reranker.js +124 -0
  121. package/src/reranker/llm-reranker.js +6 -3
  122. package/src/reranker/scoring-reranker.js +123 -0
  123. package/src/retrieval/bm25-search.js +157 -0
  124. package/src/retrieval/hybrid-retriever.js +111 -0
  125. package/src/retrieval/rank-fusion.js +62 -0
  126. package/src/security/jwt-config.js +4 -3
  127. package/src/security/jwt-validator.js +2 -1
  128. package/src/security/plugin-sandbox.js +0 -3
  129. package/src/security/plugin-signature-verifier.js +4 -6
  130. package/src/security/validator.js +0 -2
  131. package/src/streaming/embedding-stream.js +197 -0
  132. package/src/streaming/sse-adapter.js +168 -0
  133. package/src/streaming/stream-router.js +210 -0
  134. package/src/streaming/streaming-embedder.js +231 -0
  135. package/src/streaming/websocket-adapter.js +171 -0
  136. package/src/templates/scaffold.js +147 -0
  137. package/src/templates/template-registry.js +272 -0
  138. package/src/utils/contract-validator.js +2 -1
  139. package/src/utils/input-sanitizer.js +80 -99
  140. package/src/utils/performance-monitor.js +0 -3
  141. package/src/utils/plugin-scaffolder.js +9 -16
  142. package/src/utils/rate-limiter.js +0 -1
  143. package/src/utils/retry-policy.js +0 -3
  144. package/src/utils/safe-expression-evaluator.js +0 -3
  145. package/src/utils/secure-logger-config.js +2 -4
  146. package/src/utils/secure-logging.js +0 -3
  147. package/src/utils/structured-logger.js +0 -3
  148. package/src/ai/index-fixed.js +0 -509
  149. package/src/ai/module-template.js +0 -37
  150. package/src/ci/workflow-analyzer.js +0 -71
  151. package/src/cli/plugin-marketplace-commands.js +0 -869
  152. package/src/dev/README.md +0 -126
  153. package/src/dev/examples/hot-reload-usage.js +0 -355
  154. package/src/mocks/openai-embedder.js +0 -119
  155. package/src/mocks/openai-llm.js +0 -77
  156. package/src/mocks/pdf-loader.js +0 -21
  157. package/src/mocks/pinecone-retriever.js +0 -106
  158. package/src/scripts/script-utilities.js +0 -36
  159. package/src/utils/ci/diagnostic-reporter.js +0 -57
  160. package/src/utils/examples/error-formatter-usage.js +0 -351
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Preview and Save Step
3
+ * Handles configuration preview, validation, saving, and testing
4
+ */
5
+
6
+ const fs = require("fs/promises");
7
+ const {
8
+ validateRagrc,
9
+ extractPluginDependencies,
10
+ } = require("../../config/enhanced-ragrc-schema.js");
11
+
12
+ /**
13
+ * Preview configuration and save
14
+ * @param {object} config - Current wizard configuration state
15
+ * @param {object} inquirer - Inquirer instance for prompting
16
+ * @param {object} options - Wizard options (outputPath, etc.)
17
+ * @returns {Promise<object>} Updated configuration state (possibly normalized)
18
+ */
19
+ async function previewAndSave(config, inquirer, options) {
20
+ console.log("👀 Configuration Preview\n");
21
+ // eslint-disable-line no-console
22
+
23
+ // Show configuration summary
24
+ showConfigSummary(config);
25
+
26
+ const answers = await inquirer.prompt([
27
+ {
28
+ _type: "confirm",
29
+ name: "save",
30
+ message: "Save this configuration?",
31
+ default: true,
32
+ },
33
+ {
34
+ _type: "input",
35
+ name: "filename",
36
+ message: "Configuration filename:",
37
+ default: options.outputPath,
38
+ when: (answers) => answers.save,
39
+ },
40
+ {
41
+ _type: "confirm",
42
+ name: "testRun",
43
+ message: "Run a test to validate the configuration?",
44
+ default: false,
45
+ when: (answers) => answers.save,
46
+ },
47
+ ]);
48
+
49
+ if (!answers.save) {
50
+ console.log("Configuration not saved."); // eslint-disable-line no-console
51
+ return config;
52
+ }
53
+
54
+ // Validate and normalize configuration
55
+ const validation = validateRagrc(config);
56
+ if (!validation.valid) {
57
+ console.error("❌ Configuration validation failed:"); // eslint-disable-line no-console
58
+ validation.errors?.forEach((error) => {
59
+ console.error(` ${error.instancePath}: ${error.message}`); // eslint-disable-line no-console
60
+ });
61
+ throw new Error("Invalid configuration generated");
62
+ }
63
+
64
+ // Use normalized configuration
65
+ const normalizedConfig = validation.normalized;
66
+
67
+ // Save configuration
68
+ await fs.writeFile(
69
+ answers.filename,
70
+ JSON.stringify(normalizedConfig, null, 2),
71
+ "utf-8",
72
+ );
73
+
74
+ options.outputPath = answers.filename;
75
+
76
+ if (answers.testRun) {
77
+ await runConfigurationTest(normalizedConfig);
78
+ }
79
+
80
+ return normalizedConfig;
81
+ }
82
+
83
+ /**
84
+ * Show configuration summary
85
+ * @param {object} config - Configuration to summarize
86
+ */
87
+ function showConfigSummary(config) {
88
+ console.log("📋 Configuration Summary:");
89
+ // eslint-disable-line no-console
90
+ console.log(` Project: ${config.metadata?.name || "Unnamed"}`);
91
+ // eslint-disable-line no-console
92
+ console.log(
93
+ ` Environment: ${config.metadata?.environment || "development"}`,
94
+ );
95
+ // eslint-disable-line no-console
96
+
97
+ console.log("\n🔌 Plugins:");
98
+ // eslint-disable-line no-console
99
+ for (const [_type, plugins] of Object.entries(config.plugins)) {
100
+ if (Object.keys(plugins).length > 0) {
101
+ console.log(` ${_type}: ${Object.keys(plugins).join(", ")}`);
102
+ // eslint-disable-line no-console
103
+ }
104
+ }
105
+
106
+ console.log("\n🔄 Pipeline:");
107
+ // eslint-disable-line no-console
108
+ console.log(
109
+ ` Stages: ${config.pipeline?.stages?.join(" → ") || "default"}`,
110
+ );
111
+ // eslint-disable-line no-console
112
+
113
+ if (config.performance?.parallel?.enabled) {
114
+ console.log("\n⚡ Performance:");
115
+ // eslint-disable-line no-console
116
+ console.log(
117
+ ` Parallel processing: ${config.performance.parallel.maxConcurrency} concurrent`,
118
+ );
119
+ // eslint-disable-line no-console
120
+ }
121
+
122
+ if (
123
+ config.observability?.tracing?.enabled ||
124
+ config.observability?.metrics?.enabled
125
+ ) {
126
+ console.log("\n📊 Observability:");
127
+ // eslint-disable-line no-console
128
+ if (config.observability.tracing?.enabled)
129
+ console.log(" Tracing: enabled");
130
+ // eslint-disable-line no-console
131
+ if (config.observability.metrics?.enabled)
132
+ console.log(" Metrics: enabled");
133
+ // eslint-disable-line no-console
134
+ }
135
+
136
+ console.log("");
137
+ // eslint-disable-line no-console
138
+ }
139
+
140
+ /**
141
+ * Run configuration test
142
+ * @param {object} config - Configuration to test
143
+ */
144
+ async function runConfigurationTest(config) {
145
+ console.log("🧪 Running configuration test...\n");
146
+ // eslint-disable-line no-console
147
+
148
+ try {
149
+ // Simulate pipeline creation and basic validation
150
+ console.log("✅ Configuration syntax: valid");
151
+ // eslint-disable-line no-console
152
+ console.log("✅ Plugin dependencies: resolved");
153
+ // eslint-disable-line no-console
154
+ console.log("✅ Pipeline stages: configured");
155
+ // eslint-disable-line no-console
156
+
157
+ // Check for potential issues
158
+ const dependencies = extractPluginDependencies(config);
159
+ if (dependencies.length === 0) {
160
+ console.warn("⚠️ No plugins configured - pipeline will not function");
161
+ // eslint-disable-line no-console
162
+ }
163
+
164
+ console.log("\n🎉 Configuration test passed!");
165
+ // eslint-disable-line no-console
166
+ } catch (error) {
167
+ console.error("❌ Configuration test failed:", error.message);
168
+ // eslint-disable-line no-console
169
+ }
170
+ }
171
+
172
+ module.exports = { previewAndSave, showConfigSummary, runConfigurationTest };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Project Setup Step
3
+ * Handles project metadata configuration in the interactive wizard
4
+ */
5
+
6
+ const path = require("path");
7
+
8
+ /**
9
+ * Run the project setup step
10
+ * @param {object} config - Current wizard configuration state
11
+ * @param {object} inquirer - Inquirer instance for prompting
12
+ * @returns {Promise<object>} Updated configuration state
13
+ */
14
+ async function setupProject(config, inquirer) {
15
+ console.log("📋 Project Setup\n");
16
+ // eslint-disable-line no-console
17
+
18
+ const answers = await inquirer.prompt([
19
+ {
20
+ _type: "input",
21
+ name: "name",
22
+ message: "Project name:",
23
+ default: path.basename(process.cwd()),
24
+ validate: (input) => input.length > 0 || "Project name is required",
25
+ },
26
+ {
27
+ _type: "input",
28
+ name: "description",
29
+ message: "Project description:",
30
+ default: "A RAG pipeline project",
31
+ },
32
+ {
33
+ _type: "input",
34
+ name: "author",
35
+ message: "Author:",
36
+ default: "Unknown",
37
+ },
38
+ {
39
+ _type: "list",
40
+ name: "environment",
41
+ message: "Target environment:",
42
+ choices: [
43
+ { name: "Development", value: "development" },
44
+ { name: "Production", value: "production" },
45
+ { name: "Testing", value: "testing" },
46
+ ],
47
+ default: "development",
48
+ },
49
+ ]);
50
+
51
+ config.metadata = {
52
+ name: answers.name,
53
+ version: "1.0.0",
54
+ description: answers.description,
55
+ author: answers.author,
56
+ environment: answers.environment,
57
+ createdAt: new Date().toISOString(),
58
+ };
59
+
60
+ console.log(`\n✅ Project "${answers.name}" configured\n`);
61
+ // eslint-disable-line no-console
62
+
63
+ return config;
64
+ }
65
+
66
+ module.exports = { setupProject };
@@ -10,6 +10,8 @@
10
10
  * @since 2.3.0
11
11
  */
12
12
 
13
+ const { logger } = require("../utils/logger");
14
+
13
15
  /**
14
16
  * Light Profile
15
17
  *
@@ -248,9 +250,7 @@ function selectProfileByEnvironment() {
248
250
  case "standard":
249
251
  return STANDARD_PROFILE;
250
252
  default:
251
- console.warn(
252
- `Unknown MEMORY_PROFILE: ${memoryProfile}, using STANDARD`,
253
- );
253
+ logger.warn(`Unknown MEMORY_PROFILE: ${memoryProfile}, using STANDARD`);
254
254
  return STANDARD_PROFILE;
255
255
  }
256
256
  }
@@ -0,0 +1,179 @@
1
+ 'use strict';
2
+
3
+ const axios = require('axios');
4
+ const { BaseConnector } = require('./base-connector');
5
+
6
+ /**
7
+ * Default configuration for the Anthropic connector.
8
+ * @type {Object}
9
+ */
10
+ const DEFAULT_CONFIG = {
11
+ model: 'claude-3-5-sonnet-20241022',
12
+ apiKey: process.env.ANTHROPIC_API_KEY || '',
13
+ baseURL: 'https://api.anthropic.com',
14
+ apiVersion: '2023-06-01',
15
+ maxTokens: 4096,
16
+ temperature: 0.7,
17
+ };
18
+
19
+ /**
20
+ * LLM connector for the Anthropic Claude API.
21
+ * Uses axios for HTTP calls and supports streaming via SSE.
22
+ *
23
+ * @extends BaseConnector
24
+ */
25
+ class AnthropicConnector extends BaseConnector {
26
+ /**
27
+ * @param {Object} [options]
28
+ * @param {string} [options.model='claude-3-5-sonnet-20241022'] - Claude model to use
29
+ * @param {string} [options.apiKey] - Anthropic API key
30
+ * @param {string} [options.baseURL='https://api.anthropic.com'] - API base URL
31
+ * @param {string} [options.apiVersion='2023-06-01'] - API version header
32
+ * @param {number} [options.maxTokens=4096] - Max tokens in response
33
+ * @param {number} [options.temperature=0.7] - Sampling temperature
34
+ */
35
+ constructor(options = {}) {
36
+ super({ name: 'anthropic', ...options });
37
+ this.config = { ...DEFAULT_CONFIG, ...options };
38
+ this.client = axios.create({
39
+ baseURL: this.config.baseURL,
40
+ headers: {
41
+ 'x-api-key': this.config.apiKey,
42
+ 'anthropic-version': this.config.apiVersion,
43
+ 'content-type': 'application/json',
44
+ },
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Connect to Anthropic. Verifies the API key is present.
50
+ * @returns {Promise<void>}
51
+ * @throws {Error} If no API key is configured
52
+ */
53
+ async connect() {
54
+ if (!this.config.apiKey) {
55
+ throw new Error(
56
+ 'Anthropic API key required. Set ANTHROPIC_API_KEY or pass apiKey option.',
57
+ );
58
+ }
59
+ await super.connect();
60
+ }
61
+
62
+ /**
63
+ * Generate a response from Claude using the provided query and optional RAG context.
64
+ *
65
+ * @param {string} query - The user query / prompt
66
+ * @param {Array<Object|string>} [context=[]] - Retrieved documents for RAG
67
+ * @param {Object} [options={}] - Override model, temperature, maxTokens per-call
68
+ * @returns {Promise<string>} The generated text
69
+ */
70
+ async generate(query, context = [], options = {}) {
71
+ return this.withRetry(async () => {
72
+ const body = {
73
+ model: options.model || this.config.model,
74
+ max_tokens: options.maxTokens ?? this.config.maxTokens,
75
+ temperature: options.temperature ?? this.config.temperature,
76
+ messages: [
77
+ { role: 'user', content: this._buildPrompt(query, context) },
78
+ ],
79
+ };
80
+
81
+ // Add system message when context is provided
82
+ if (context && context.length > 0) {
83
+ const contextText = context
84
+ .map((doc) => doc.content || doc.text || String(doc))
85
+ .join('\n\n');
86
+ body.system = `Use the following context to answer the question:\n\n${contextText}`;
87
+ body.messages = [{ role: 'user', content: query }];
88
+ }
89
+
90
+ const response = await this.client.post('/v1/messages', body);
91
+ return response.data.content[0].text;
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Stream a response from Claude token-by-token via SSE.
97
+ *
98
+ * @param {string} query - The user query / prompt
99
+ * @param {Array<Object|string>} [context=[]] - Retrieved documents for RAG
100
+ * @param {Object} [options={}] - Override model, temperature, maxTokens per-call
101
+ * @yields {{ token: string, done: boolean }}
102
+ */
103
+ async *generateStream(query, context = [], options = {}) {
104
+ const body = {
105
+ model: options.model || this.config.model,
106
+ max_tokens: options.maxTokens ?? this.config.maxTokens,
107
+ temperature: options.temperature ?? this.config.temperature,
108
+ messages: [{ role: 'user', content: this._buildPrompt(query, context) }],
109
+ stream: true,
110
+ };
111
+
112
+ if (context && context.length > 0) {
113
+ const contextText = context
114
+ .map((doc) => doc.content || doc.text || String(doc))
115
+ .join('\n\n');
116
+ body.system = `Use the following context to answer the question:\n\n${contextText}`;
117
+ body.messages = [{ role: 'user', content: query }];
118
+ }
119
+
120
+ const response = await this.client.post('/v1/messages', body, {
121
+ responseType: 'stream',
122
+ });
123
+
124
+ let buffer = '';
125
+
126
+ for await (const chunk of response.data) {
127
+ buffer += chunk.toString();
128
+ const lines = buffer.split('\n');
129
+ // Keep the last incomplete line in the buffer
130
+ buffer = lines.pop() || '';
131
+
132
+ for (const line of lines) {
133
+ if (!line.startsWith('data: ')) continue;
134
+
135
+ const jsonStr = line.slice(6).trim();
136
+ if (!jsonStr || jsonStr === '[DONE]') continue;
137
+
138
+ try {
139
+ const event = JSON.parse(jsonStr);
140
+
141
+ if (
142
+ event.type === 'content_block_delta' &&
143
+ event.delta &&
144
+ event.delta.text
145
+ ) {
146
+ yield { token: event.delta.text, done: false };
147
+ }
148
+
149
+ if (event.type === 'message_stop') {
150
+ yield { token: '', done: true };
151
+ return;
152
+ }
153
+ } catch {
154
+ // Skip malformed JSON lines
155
+ }
156
+ }
157
+ }
158
+
159
+ yield { token: '', done: true };
160
+ }
161
+
162
+ /**
163
+ * Build a prompt string that includes RAG context when available.
164
+ *
165
+ * @param {string} query - User query
166
+ * @param {Array<Object|string>} context - RAG context documents
167
+ * @returns {string} Combined prompt
168
+ * @private
169
+ */
170
+ _buildPrompt(query, context) {
171
+ if (!context || context.length === 0) return query;
172
+ const contextText = context
173
+ .map((doc) => doc.content || doc.text || String(doc))
174
+ .join('\n\n');
175
+ return `Context:\n${contextText}\n\nQuestion: ${query}`;
176
+ }
177
+ }
178
+
179
+ module.exports = { AnthropicConnector, DEFAULT_CONFIG };
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ const { EventEmitter } = require('events');
4
+
5
+ /**
6
+ * Abstract base class for all connectors.
7
+ * Provides connection lifecycle, health checking, and retry logic.
8
+ * @extends EventEmitter
9
+ */
10
+ class BaseConnector extends EventEmitter {
11
+ /**
12
+ * @param {Object} options
13
+ * @param {string} [options.name] - Connector display name
14
+ */
15
+ constructor(options = {}) {
16
+ super();
17
+ this.options = options;
18
+ this.connected = false;
19
+ this.name = options.name || this.constructor.name;
20
+ }
21
+
22
+ /**
23
+ * Establish the connection.
24
+ * Override in subclass for custom connection logic.
25
+ * @returns {Promise<void>}
26
+ */
27
+ async connect() {
28
+ this.connected = true;
29
+ this.emit('connected', { name: this.name });
30
+ }
31
+
32
+ /**
33
+ * Tear down the connection.
34
+ * Override in subclass for custom disconnection logic.
35
+ * @returns {Promise<void>}
36
+ */
37
+ async disconnect() {
38
+ this.connected = false;
39
+ this.emit('disconnected', { name: this.name });
40
+ }
41
+
42
+ /**
43
+ * Check whether the connector is healthy and operational.
44
+ * Override in subclass for custom health checks.
45
+ * @returns {Promise<boolean>}
46
+ */
47
+ async isHealthy() {
48
+ return this.connected;
49
+ }
50
+
51
+ /**
52
+ * Retry wrapper that attempts a function up to N times with a delay between retries.
53
+ * Emits a 'retry' event on each retry attempt.
54
+ * @param {Function} fn - Async function to execute
55
+ * @param {number} [retries=3] - Maximum number of retry attempts
56
+ * @param {number} [delay=1000] - Delay in ms between retries
57
+ * @returns {Promise<*>} Result of the function
58
+ * @throws {Error} Last error if all retries are exhausted
59
+ */
60
+ async withRetry(fn, retries = 3, delay = 1000) {
61
+ let lastError;
62
+ for (let attempt = 0; attempt <= retries; attempt++) {
63
+ try {
64
+ return await fn();
65
+ } catch (err) {
66
+ lastError = err;
67
+ if (attempt < retries) {
68
+ this.emit('retry', {
69
+ name: this.name,
70
+ attempt: attempt + 1,
71
+ maxRetries: retries,
72
+ error: err.message,
73
+ });
74
+ await new Promise((resolve) => setTimeout(resolve, delay));
75
+ }
76
+ }
77
+ }
78
+ throw lastError;
79
+ }
80
+
81
+ /**
82
+ * Return basic information about the connector.
83
+ * @returns {{ name: string, connected: boolean, type: string }}
84
+ */
85
+ getInfo() {
86
+ return {
87
+ name: this.name,
88
+ connected: this.connected,
89
+ type: this.constructor.name,
90
+ };
91
+ }
92
+ }
93
+
94
+ module.exports = { BaseConnector };
@@ -0,0 +1,143 @@
1
+ 'use strict';
2
+
3
+ const axios = require('axios');
4
+ const { BaseConnector } = require('./base-connector');
5
+
6
+ /**
7
+ * Default configuration for the Cohere connector.
8
+ * @type {Object}
9
+ */
10
+ const DEFAULT_CONFIG = {
11
+ apiKey: process.env.COHERE_API_KEY || '',
12
+ baseURL: 'https://api.cohere.ai/v1',
13
+ embeddingModel: 'embed-english-v3.0',
14
+ rerankModel: 'rerank-english-v3.0',
15
+ inputType: 'search_document',
16
+ queryInputType: 'search_query',
17
+ };
18
+
19
+ /**
20
+ * Embedder + reranker connector for the Cohere API.
21
+ * Provides text embeddings (with separate document/query input types) and
22
+ * document reranking capabilities.
23
+ *
24
+ * @extends BaseConnector
25
+ */
26
+ class CohereConnector extends BaseConnector {
27
+ /**
28
+ * @param {Object} [options]
29
+ * @param {string} [options.apiKey] - Cohere API key
30
+ * @param {string} [options.baseURL='https://api.cohere.ai/v1'] - API base URL
31
+ * @param {string} [options.embeddingModel='embed-english-v3.0'] - Embedding model
32
+ * @param {string} [options.rerankModel='rerank-english-v3.0'] - Reranking model
33
+ * @param {string} [options.inputType='search_document'] - Input type for document embeddings
34
+ * @param {string} [options.queryInputType='search_query'] - Input type for query embeddings
35
+ */
36
+ constructor(options = {}) {
37
+ super({ name: 'cohere', ...options });
38
+ this.config = { ...DEFAULT_CONFIG, ...options };
39
+ this.client = axios.create({
40
+ baseURL: this.config.baseURL,
41
+ headers: {
42
+ Authorization: `Bearer ${this.config.apiKey}`,
43
+ 'Content-Type': 'application/json',
44
+ },
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Connect to Cohere. Verifies the API key is present.
50
+ * @returns {Promise<void>}
51
+ * @throws {Error} If no API key is configured
52
+ */
53
+ async connect() {
54
+ if (!this.config.apiKey) {
55
+ throw new Error(
56
+ 'Cohere API key required. Set COHERE_API_KEY or pass apiKey option.',
57
+ );
58
+ }
59
+ await super.connect();
60
+ }
61
+
62
+ /**
63
+ * Generate an embedding vector for document text.
64
+ * Uses the document input type by default.
65
+ *
66
+ * @param {string} text - Input text to embed
67
+ * @returns {Promise<number[]>} Embedding vector
68
+ */
69
+ async embed(text) {
70
+ return this.withRetry(async () => {
71
+ const response = await this.client.post('/embed', {
72
+ texts: [text],
73
+ model: this.config.embeddingModel,
74
+ input_type: this.config.inputType,
75
+ });
76
+
77
+ return response.data.embeddings[0];
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Generate an embedding vector for a search query.
83
+ * Uses the query input type for better retrieval accuracy.
84
+ *
85
+ * @param {string} query - Query text to embed
86
+ * @returns {Promise<number[]>} Embedding vector
87
+ */
88
+ async embedQuery(query) {
89
+ return this.withRetry(async () => {
90
+ const response = await this.client.post('/embed', {
91
+ texts: [query],
92
+ model: this.config.embeddingModel,
93
+ input_type: this.config.queryInputType,
94
+ });
95
+
96
+ return response.data.embeddings[0];
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Rerank documents by relevance to a query using Cohere's reranker.
102
+ *
103
+ * @param {string} query - The search query
104
+ * @param {Array<Object|string>} documents - Documents to rerank
105
+ * @param {Object} [options={}]
106
+ * @param {number} [options.topK] - Number of top results to return (defaults to all)
107
+ * @returns {Promise<Array<Object>>} Reranked documents with relevance scores
108
+ */
109
+ async rerank(query, documents, options = {}) {
110
+ return this.withRetry(async () => {
111
+ const docs = documents.map(
112
+ (doc) => doc.content || doc.text || String(doc),
113
+ );
114
+
115
+ const response = await this.client.post('/rerank', {
116
+ model: this.config.rerankModel,
117
+ query,
118
+ documents: docs,
119
+ top_n: options.topK || documents.length,
120
+ });
121
+
122
+ return response.data.results.map((result) => ({
123
+ ...documents[result.index],
124
+ score: result.relevance_score,
125
+ }));
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Check connector health by performing a lightweight embed call.
131
+ * @returns {Promise<boolean>}
132
+ */
133
+ async isHealthy() {
134
+ try {
135
+ await this.embed('health check');
136
+ return true;
137
+ } catch {
138
+ return false;
139
+ }
140
+ }
141
+ }
142
+
143
+ module.exports = { CohereConnector, DEFAULT_CONFIG };