@devilsdev/rag-pipeline-utils 2.4.5 → 2.6.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.
Files changed (258) hide show
  1. package/.ragrc.schema.json +83 -192
  2. package/CHANGELOG.md +431 -0
  3. package/README.md +418 -117
  4. package/SECURITY.md +5 -5
  5. package/dist/experimental.cjs +2 -0
  6. package/dist/experimental.d.ts +1 -0
  7. package/dist/experimental.mjs +5 -0
  8. package/dist/index.cjs +6442 -1355
  9. package/dist/index.d.ts +129 -2
  10. package/dist/index.mjs +1 -1
  11. package/dist/testing.cjs +2 -0
  12. package/dist/testing.d.ts +1 -0
  13. package/dist/testing.mjs +5 -0
  14. package/dist/types/adapters/ai-sdk.d.ts +53 -0
  15. package/dist/types/agentic/agentic-pipeline.d.ts +103 -0
  16. package/dist/types/agentic/iterative-retriever.d.ts +85 -0
  17. package/dist/types/agentic/query-planner.d.ts +145 -0
  18. package/dist/types/agentic/self-critique.d.ts +45 -0
  19. package/dist/types/ai/adaptive-retrieval.d.ts +44 -0
  20. package/dist/types/ai/federation/federated-learning-coordinator.d.ts +62 -0
  21. package/dist/types/ai/multimodal-processing.d.ts +32 -0
  22. package/dist/types/ai/multimodal.d.ts +39 -0
  23. package/dist/types/ai/retrieval-engine.d.ts +37 -0
  24. package/dist/types/ai/training/model-training-orchestrator.d.ts +31 -0
  25. package/dist/types/cache/component-keys.d.ts +7 -0
  26. package/dist/types/cache/memory-cache.d.ts +54 -0
  27. package/dist/types/cache/pipeline-cache.d.ts +66 -0
  28. package/dist/types/cache/redis-cache.d.ts +46 -0
  29. package/dist/types/chunking/chunking-engine.d.ts +70 -0
  30. package/dist/types/chunking/strategies/fixed-size.d.ts +17 -0
  31. package/dist/types/chunking/strategies/recursive.d.ts +20 -0
  32. package/dist/types/chunking/strategies/semantic.d.ts +19 -0
  33. package/dist/types/chunking/strategies/sentence.d.ts +13 -0
  34. package/dist/types/chunking/strategies/structure-aware.d.ts +19 -0
  35. package/dist/types/citation/citation-tracker.d.ts +64 -0
  36. package/dist/types/citation/hallucination-detector.d.ts +40 -0
  37. package/dist/types/citation/source-mapper.d.ts +37 -0
  38. package/dist/types/connectors/anthropic-connector.d.ts +97 -0
  39. package/dist/types/connectors/base-connector.d.ts +87 -0
  40. package/dist/types/connectors/cohere-connector.d.ts +94 -0
  41. package/dist/types/connectors/connector-utils.d.ts +110 -0
  42. package/dist/types/connectors/local-embedder.d.ts +95 -0
  43. package/dist/types/connectors/memory-retriever.d.ts +99 -0
  44. package/dist/types/connectors/ollama-connector.d.ts +115 -0
  45. package/dist/types/connectors/openai-connector.d.ts +129 -0
  46. package/dist/types/connectors/rag-prompt.d.ts +40 -0
  47. package/dist/types/core/create-pipeline.d.ts +8 -0
  48. package/dist/types/core/plugin-registry.d.ts +136 -0
  49. package/dist/types/core/stage-contracts.d.ts +36 -0
  50. package/dist/types/core/validate-plugin.d.ts +45 -0
  51. package/dist/types/cost/cost-calculator.d.ts +152 -0
  52. package/dist/types/cost/cost-tracker.d.ts +126 -0
  53. package/dist/types/cost/token-budget.d.ts +110 -0
  54. package/dist/types/dag/core/error-context.d.ts +239 -0
  55. package/dist/types/dag/core/execution-scheduler.d.ts +191 -0
  56. package/dist/types/dag/core/topology-validation.d.ts +226 -0
  57. package/dist/types/dag/dag-engine.d.ts +156 -0
  58. package/dist/types/dag/dag-node.d.ts +37 -0
  59. package/dist/types/dashboard/dashboard-generator.d.ts +127 -0
  60. package/dist/types/dashboard/metrics-aggregator.d.ts +218 -0
  61. package/dist/types/debug/execution-tracer.d.ts +57 -0
  62. package/dist/types/debug/trace-inspector.d.ts +31 -0
  63. package/dist/types/ecosystem/plugin-certification.d.ts +169 -0
  64. package/dist/types/enterprise/data-governance.d.ts +203 -0
  65. package/dist/types/enterprise/multi-tenancy.d.ts +137 -0
  66. package/dist/types/enterprise/sso-integration.d.ts +420 -0
  67. package/dist/types/evaluate/context-metrics.d.ts +55 -0
  68. package/dist/types/evaluate/faithfulness.d.ts +41 -0
  69. package/dist/types/evaluate/groundedness.d.ts +60 -0
  70. package/dist/types/evaluate/pipeline-evaluator.d.ts +88 -0
  71. package/dist/types/evaluate/relevance.d.ts +21 -0
  72. package/dist/types/evaluate/scoring.d.ts +123 -0
  73. package/dist/types/evaluate/semantic-similarity.d.ts +14 -0
  74. package/dist/types/experimental.d.ts +15 -0
  75. package/dist/types/graph/entity-extractor.d.ts +84 -0
  76. package/dist/types/graph/graph-index.d.ts +49 -0
  77. package/dist/types/graph/graph-retriever.d.ts +81 -0
  78. package/dist/types/graph/knowledge-graph.d.ts +129 -0
  79. package/dist/types/grounding/check-answer.d.ts +101 -0
  80. package/dist/types/guardrails/guardrails-pipeline.d.ts +63 -0
  81. package/dist/types/guardrails/injection-detector.d.ts +47 -0
  82. package/dist/types/guardrails/post-generation-guard.d.ts +61 -0
  83. package/dist/types/guardrails/pre-retrieval-guard.d.ts +64 -0
  84. package/dist/types/guardrails/retrieval-guard.d.ts +57 -0
  85. package/dist/types/mcp/mcp-server.d.ts +85 -0
  86. package/dist/types/mcp/mcp-tool-builder.d.ts +55 -0
  87. package/dist/types/reranker/cascade-reranker.d.ts +40 -0
  88. package/dist/types/reranker/embedding-reranker.d.ts +77 -0
  89. package/dist/types/reranker/scoring-reranker.d.ts +41 -0
  90. package/dist/types/retrieval/bm25-search.d.ts +43 -0
  91. package/dist/types/retrieval/file-vector-store.d.ts +99 -0
  92. package/dist/types/retrieval/hybrid-retriever.d.ts +35 -0
  93. package/dist/types/retrieval/rank-fusion.d.ts +10 -0
  94. package/dist/types/security/jwt-config.d.ts +80 -0
  95. package/dist/types/security/jwt-validator.d.ts +195 -0
  96. package/dist/types/security/plugin-sandbox.d.ts +158 -0
  97. package/dist/types/security/plugin-signature-verifier.d.ts +139 -0
  98. package/dist/types/security/validator.d.ts +5 -0
  99. package/dist/types/streaming/embedding-stream.d.ts +65 -0
  100. package/dist/types/streaming/sse-adapter.d.ts +59 -0
  101. package/dist/types/streaming/stream-router.d.ts +88 -0
  102. package/dist/types/streaming/streaming-embedder.d.ts +99 -0
  103. package/dist/types/streaming/websocket-adapter.d.ts +71 -0
  104. package/dist/types/templates/scaffold.d.ts +55 -0
  105. package/dist/types/templates/template-registry.d.ts +63 -0
  106. package/dist/types/testing.d.ts +168 -0
  107. package/dist/types/utils/embed-query.d.ts +20 -0
  108. package/dist/types/utils/logger.d.ts +50 -0
  109. package/dist/types/utils/rate-limiter.d.ts +66 -0
  110. package/dist/types/utils/retry.d.ts +27 -0
  111. package/dist/types/utils/run-abort.d.ts +34 -0
  112. package/dist/types/utils/secure-logging.d.ts +51 -0
  113. package/dist/types/utils/structured-logger.d.ts +126 -0
  114. package/package.json +72 -33
  115. package/src/adapters/ai-sdk.js +215 -0
  116. package/src/agentic/agentic-pipeline.js +75 -7
  117. package/src/agentic/iterative-retriever.js +47 -10
  118. package/src/agentic/query-planner.js +329 -78
  119. package/src/cache/component-keys.js +38 -0
  120. package/src/cache/memory-cache.js +87 -0
  121. package/src/cache/pipeline-cache.js +260 -0
  122. package/src/cache/redis-cache.js +83 -0
  123. package/src/citation/citation-tracker.js +6 -4
  124. package/src/citation/hallucination-detector.js +2 -2
  125. package/src/citation/source-mapper.js +15 -28
  126. package/src/cli/commands/ai.js +106 -0
  127. package/src/cli/commands/plugin-hub/event-handlers.js +6 -0
  128. package/src/cli/commands/plugin-hub/hub-commands.js +0 -74
  129. package/src/cli/commands/plugin-hub/local-commands.js +143 -0
  130. package/src/cli/commands/plugin-hub.js +114 -49
  131. package/src/cli/diagnostics/config-checker.js +76 -63
  132. package/src/cli/diagnostics/environment-checker.js +62 -31
  133. package/src/cli/diagnostics/plugin-checker.js +36 -89
  134. package/src/cli/doctor-command.js +39 -5
  135. package/src/cli/enhanced-cli-commands.js +104 -233
  136. package/src/cli/handlers/ai-handler.js +364 -0
  137. package/src/cli/handlers/diagnostics-handler.js +78 -31
  138. package/src/cli/handlers/ingest-handler.js +28 -22
  139. package/src/cli/handlers/init-handler.js +148 -25
  140. package/src/cli/handlers/query-handler.js +203 -25
  141. package/src/cli/handlers/run-handler.js +16 -167
  142. package/src/cli/handlers/serve-handler.js +60 -0
  143. package/src/cli/services/bench-stats.js +54 -0
  144. package/src/cli/services/benchmark.js +161 -0
  145. package/src/cli/services/feedback.js +155 -0
  146. package/src/cli/services/instrument.js +83 -0
  147. package/src/cli/services/rag-service.js +361 -0
  148. package/src/cli/shared/cli-utils.js +24 -7
  149. package/src/cli/shared/config-context.js +21 -0
  150. package/src/cli/wizard/config-builder.js +81 -0
  151. package/src/cli/wizard/init-wizard.js +602 -0
  152. package/src/config/component-factory.js +125 -0
  153. package/src/config/enhanced-ragrc-schema.js +0 -2
  154. package/src/config/load-config.js +29 -10
  155. package/src/config/load-plugin-config.js +46 -2
  156. package/src/config/validate-schema.js +4 -1
  157. package/src/connectors/anthropic-connector.js +115 -57
  158. package/src/connectors/base-connector.js +74 -19
  159. package/src/connectors/cohere-connector.js +108 -34
  160. package/src/connectors/connector-utils.js +302 -0
  161. package/src/connectors/local-embedder.js +63 -5
  162. package/src/connectors/memory-retriever.js +125 -14
  163. package/src/connectors/ollama-connector.js +112 -51
  164. package/src/connectors/openai-connector.js +153 -44
  165. package/src/connectors/rag-prompt.js +88 -0
  166. package/src/core/create-pipeline.js +307 -61
  167. package/src/core/performance/parallel-processor.js +62 -1
  168. package/src/core/plugin-registry.js +125 -26
  169. package/src/core/stage-contracts.js +36 -0
  170. package/src/core/validate-plugin.js +85 -0
  171. package/src/cost/cost-calculator.js +205 -32
  172. package/src/debug/execution-tracer.js +14 -4
  173. package/src/dev/dev-server.js +173 -11
  174. package/src/dev/hot-reload.js +44 -4
  175. package/src/ecosystem/plugin-checks.js +181 -0
  176. package/src/ecosystem/plugin-hub.js +50 -125
  177. package/src/ecosystem/plugin-installer.js +298 -0
  178. package/src/ecosystem/plugin-probe.js +127 -0
  179. package/src/ecosystem/plugin-scanner.js +185 -0
  180. package/src/ecosystem/tar-extract.js +114 -0
  181. package/src/enterprise/audit-logging.js +158 -29
  182. package/src/enterprise/sso-integration.js +716 -228
  183. package/src/evaluate/context-metrics.js +28 -16
  184. package/src/evaluate/evaluator.js +3 -3
  185. package/src/evaluate/faithfulness.js +18 -14
  186. package/src/evaluate/groundedness.js +43 -12
  187. package/src/evaluate/pipeline-evaluator.js +35 -8
  188. package/src/evaluate/scoring.js +251 -3
  189. package/src/evaluate/semantic-similarity.js +79 -0
  190. package/src/experimental.js +71 -0
  191. package/src/grounding/check-answer.js +173 -0
  192. package/src/guardrails/guardrails-pipeline.js +69 -7
  193. package/src/guardrails/injection-detector.js +283 -0
  194. package/src/guardrails/post-generation-guard.js +19 -0
  195. package/src/guardrails/pre-retrieval-guard.js +36 -36
  196. package/src/guardrails/retrieval-guard.js +58 -8
  197. package/src/index.js +58 -1
  198. package/src/loader/csv-loader.js +3 -2
  199. package/src/loader/document-reader.js +91 -0
  200. package/src/loader/html-loader.js +3 -1
  201. package/src/loader/markdown-loader.js +3 -2
  202. package/src/reranker/cascade-reranker.js +3 -0
  203. package/src/reranker/embedding-reranker.js +136 -21
  204. package/src/reranker/llm-reranker.js +79 -12
  205. package/src/reranker/scoring-reranker.js +3 -0
  206. package/src/retrieval/bm25-search.js +12 -6
  207. package/src/retrieval/file-vector-store.js +241 -0
  208. package/src/retrieval/hybrid-retriever.js +22 -19
  209. package/src/security/jwt-config.js +13 -2
  210. package/src/security/jwt-validator.js +139 -34
  211. package/src/security/plugin-sandbox.js +18 -1
  212. package/src/security/plugin-signature-verifier.js +245 -70
  213. package/src/testing.js +254 -0
  214. package/src/utils/deprecate.js +69 -0
  215. package/src/utils/embed-query.js +33 -0
  216. package/src/utils/input-sanitizer.js +19 -4
  217. package/src/utils/logger.js +34 -4
  218. package/src/utils/run-abort.js +102 -0
  219. package/src/ai/federated-learning.js +0 -211
  220. package/src/ai/federation.js +0 -71
  221. package/src/ai/index.js +0 -22
  222. package/src/ai/model-trainer.js +0 -217
  223. package/src/ai/model-training.js +0 -202
  224. package/src/ai/multimodal/multi-modal-processor.js +0 -345
  225. package/src/ai/orchestrator.js +0 -65
  226. package/src/ai/retrieval/adaptive-retrieval-engine.js +0 -398
  227. package/src/cli/commands/ai-ml/adaptive-commands.js +0 -178
  228. package/src/cli/commands/ai-ml/federated-commands.js +0 -207
  229. package/src/cli/commands/ai-ml/index.js +0 -17
  230. package/src/cli/commands/ai-ml/multimodal-commands.js +0 -187
  231. package/src/cli/commands/ai-ml/shared.js +0 -51
  232. package/src/cli/commands/ai-ml/training-commands.js +0 -228
  233. package/src/cli/commands/ai-ml.js +0 -386
  234. package/src/cli/commands/docs.js +0 -228
  235. package/src/cli/commands/dx.js +0 -538
  236. package/src/cli/commands/wizard.js +0 -627
  237. package/src/cli/input-sanitization-middleware.js +0 -356
  238. package/src/cli/interactive-wizard.js +0 -373
  239. package/src/cli/templates/development.template.json +0 -64
  240. package/src/cli/templates/minimal.template.json +0 -33
  241. package/src/cli/templates/production.template.json +0 -104
  242. package/src/cli/templates/testing.template.json +0 -45
  243. package/src/cli/wizard/configuration.js +0 -148
  244. package/src/cli/wizard/observability.js +0 -79
  245. package/src/cli/wizard/performance.js +0 -76
  246. package/src/cli/wizard/plugin-selection.js +0 -297
  247. package/src/cli/wizard/preview-save.js +0 -172
  248. package/src/cli/wizard/project-setup.js +0 -66
  249. package/src/core/pipeline-factory.js +0 -59
  250. package/src/dag/enhanced-dag-engine.js +0 -894
  251. package/src/dx/index.js +0 -40
  252. package/src/dx/integration-templates.js +0 -662
  253. package/src/dx/performance-profiler.js +0 -968
  254. package/src/dx/realtime-debugger.js +0 -755
  255. package/src/dx/visual-pipeline-builder.js +0 -578
  256. package/src/ingest.js +0 -59
  257. package/src/pipeline/pipeline.js +0 -18
  258. package/src/query.js +0 -117
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Semantic similarity between an answer and a reference answer, from the
5
+ * cosine similarity of their embeddings. Unlike BLEU and ROUGE it does not
6
+ * penalise a correct answer that is worded differently.
7
+ *
8
+ * Embeddings capture topic and meaning, not facts: "twenty days" and
9
+ * "thirty days" score as very similar. Use it next to the grounding checks
10
+ * (computeFaithfulness, checkAnswer), not instead of them.
11
+ */
12
+
13
+ function cosine(a, b) {
14
+ if (
15
+ !Array.isArray(a) ||
16
+ !Array.isArray(b) ||
17
+ a.length !== b.length ||
18
+ a.length === 0
19
+ ) {
20
+ throw new Error(
21
+ `Embeddings must be non-empty arrays of the same length (got ${a && a.length} and ${b && b.length})`,
22
+ );
23
+ }
24
+ let dot = 0;
25
+ let na = 0;
26
+ let nb = 0;
27
+ for (let i = 0; i < a.length; i += 1) {
28
+ dot += a[i] * b[i];
29
+ na += a[i] * a[i];
30
+ nb += b[i] * b[i];
31
+ }
32
+ if (na === 0 || nb === 0) return 0;
33
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
34
+ }
35
+
36
+ async function embedText(embedder, text, signal) {
37
+ const options = signal ? { signal } : {};
38
+ if (typeof embedder.embedQuery === 'function') {
39
+ return embedder.embedQuery(text, options);
40
+ }
41
+ return embedder.embed(text, options);
42
+ }
43
+
44
+ /**
45
+ * @param {string} answer - The generated answer
46
+ * @param {string} reference - The reference (ground-truth) answer
47
+ * @param {Object} options
48
+ * @param {Object} options.embedder - Any embedder with embed() or embedQuery()
49
+ * (a built-in connector, LocalEmbedder fitted to your corpus, an AI SDK
50
+ * adapter, HashEmbedder from /testing)
51
+ * @param {AbortSignal} [options.signal]
52
+ * @returns {Promise<number>} Cosine similarity clamped to 0..1
53
+ */
54
+ async function computeSemanticSimilarity(
55
+ answer,
56
+ reference,
57
+ { embedder, signal } = {},
58
+ ) {
59
+ if (typeof answer !== 'string' || typeof reference !== 'string') {
60
+ throw new TypeError('answer and reference must be strings');
61
+ }
62
+ if (
63
+ !embedder ||
64
+ (typeof embedder.embed !== 'function' &&
65
+ typeof embedder.embedQuery !== 'function')
66
+ ) {
67
+ throw new TypeError(
68
+ 'computeSemanticSimilarity needs an embedder with embed() or embedQuery()',
69
+ );
70
+ }
71
+ if (answer.trim() === '' || reference.trim() === '') return 0;
72
+ const [a, b] = await Promise.all([
73
+ embedText(embedder, answer, signal),
74
+ embedText(embedder, reference, signal),
75
+ ]);
76
+ return Math.min(1, Math.max(0, cosine(a, b)));
77
+ }
78
+
79
+ module.exports = { computeSemanticSimilarity };
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @devilsdev/rag-pipeline-utils/experimental
5
+ *
6
+ * Modules that are simulated, incomplete or not safe for production. They
7
+ * are kept for experimentation and are NOT covered by the semver guarantees
8
+ * in SEMVER.md. See docs-site/docs/Experimental.md for what each one really
9
+ * does. Do not use them for authentication, tenant isolation, compliance or
10
+ * plugin isolation.
11
+ *
12
+ * - Model training / federated learning: metrics and weights are random.
13
+ * - MultiModalProcessor: returns random embeddings; only stores content.
14
+ * - AdaptiveRetrievalEngine: in-memory placeholder strategy.
15
+ * - SSO providers: OAuth2/OIDC use PKCE and OIDC validates id_tokens, but
16
+ * the flows are lightly tested; SAML refuses to authenticate (no XML
17
+ * signature verification); Active Directory is a development stub. A
18
+ * development bypass (any code accepted, fixed identity) exists only
19
+ * with `devBypass: true` outside production.
20
+ * - TenantManager / ResourceMonitor: usage figures are random.
21
+ * - DataGovernanceManager: random classification; erasure deletes nothing.
22
+ * - PluginSandbox: execute() is not functional; plugins are not isolated.
23
+ * - PluginCertification: automated checks return fixed scores (use
24
+ * `rag-pipeline hub certify check` for computed scores).
25
+ */
26
+
27
+ const { MultiModalProcessor } = require('./ai/multimodal');
28
+ const { AdaptiveRetrievalEngine } = require('./ai/retrieval-engine');
29
+ const {
30
+ FederatedLearningCoordinator,
31
+ } = require('./ai/federation/federated-learning-coordinator');
32
+ const {
33
+ ModelTrainingOrchestrator,
34
+ } = require('./ai/training/model-training-orchestrator');
35
+ const { DataGovernanceManager } = require('./enterprise/data-governance');
36
+ const {
37
+ TenantManager,
38
+ ResourceMonitor,
39
+ } = require('./enterprise/multi-tenancy');
40
+ const {
41
+ SSOManager,
42
+ SAMLProvider,
43
+ OAuth2Provider,
44
+ ActiveDirectoryProvider,
45
+ OIDCProvider,
46
+ } = require('./enterprise/sso-integration');
47
+ const { PluginSandbox } = require('./security/plugin-sandbox');
48
+ const { PluginCertification } = require('./ecosystem/plugin-certification');
49
+
50
+ module.exports = {
51
+ // AI/ML
52
+ MultiModalProcessor,
53
+ AdaptiveRetrievalEngine,
54
+ FederatedLearningCoordinator,
55
+ ModelTrainingOrchestrator,
56
+
57
+ // Enterprise
58
+ DataGovernanceManager,
59
+ DataGovernance: DataGovernanceManager,
60
+ TenantManager,
61
+ ResourceMonitor,
62
+ SSOManager,
63
+ SAMLProvider,
64
+ OAuth2Provider,
65
+ ActiveDirectoryProvider,
66
+ OIDCProvider,
67
+
68
+ // Security / ecosystem
69
+ PluginSandbox,
70
+ PluginCertification,
71
+ };
@@ -0,0 +1,173 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Check an answer that any framework produced (LangChain, LlamaIndex, Mastra,
5
+ * the AI SDK, your own code) against the sources it was given: citations
6
+ * per sentence, groundedness, unsupported sentences and evaluation scores,
7
+ * without using createRagPipeline().
8
+ */
9
+
10
+ const { CitationTracker } = require('../citation/citation-tracker');
11
+ const { PipelineEvaluator } = require('../evaluate/pipeline-evaluator');
12
+ const {
13
+ computeSemanticSimilarity,
14
+ } = require('../evaluate/semantic-similarity');
15
+
16
+ /**
17
+ * Text of one source in the shapes common frameworks use.
18
+ * @private
19
+ */
20
+ function sourceText(source) {
21
+ if (source == null) return '';
22
+ if (typeof source === 'string') return source;
23
+ if (typeof source.content === 'string') return source.content; // this library
24
+ if (typeof source.pageContent === 'string') return source.pageContent; // LangChain Document
25
+ if (typeof source.text === 'string') return source.text; // LlamaIndex node, plain
26
+ if (source.node) return sourceText(source.node); // LlamaIndex NodeWithScore
27
+ if (typeof source.getContent === 'function')
28
+ return String(source.getContent());
29
+ return '';
30
+ }
31
+
32
+ /**
33
+ * An id for a source: its own id when it has one, otherwise its position.
34
+ * @private
35
+ */
36
+ function sourceId(source, index) {
37
+ const candidates = [
38
+ source && source.id,
39
+ source && source.id_,
40
+ source && source.node && (source.node.id_ || source.node.id),
41
+ source && source.metadata && (source.metadata.id || source.metadata.source),
42
+ ];
43
+ const id = candidates.find(
44
+ (c) => typeof c === 'string' || typeof c === 'number',
45
+ );
46
+ return id !== undefined ? String(id) : String(index + 1);
47
+ }
48
+
49
+ /**
50
+ * Normalize sources to `{ id, content, metadata }`.
51
+ *
52
+ * Accepts strings, `{ id, content }` (this library), LangChain Documents
53
+ * (`{ pageContent, metadata }`), LlamaIndex nodes (`{ text }`) and
54
+ * `NodeWithScore` (`{ node, score }`).
55
+ *
56
+ * @param {Array<string|Object>} sources
57
+ * @returns {Array<{id: string, content: string, metadata: Object}>}
58
+ */
59
+ function normalizeSources(sources) {
60
+ if (!Array.isArray(sources)) {
61
+ throw new TypeError('sources must be an array');
62
+ }
63
+ return sources
64
+ .map((source, index) => ({
65
+ id: sourceId(source, index),
66
+ content: sourceText(source),
67
+ metadata:
68
+ (source &&
69
+ (source.metadata || (source.node && source.node.metadata))) ||
70
+ {},
71
+ }))
72
+ .filter((s) => s.content.trim() !== '');
73
+ }
74
+
75
+ /**
76
+ * @typedef {Object} AnswerCheck
77
+ * @property {boolean} grounded - Every sentence is supported by a source
78
+ * @property {number} groundedness - Share of supported sentences, 0..1
79
+ * @property {string[]} unsupported - Sentences no source supports (this
80
+ * includes a sentence whose numbers do not match its source)
81
+ * @property {Array<{sentence: string, sources: Array}>} citations - Sources
82
+ * supporting each sentence
83
+ * @property {{scores: Object, details: Object, metadata: Object}|undefined} evaluation
84
+ * - Faithfulness, relevance, context precision (and context recall when a
85
+ * referenceAnswer is given); only when `evaluate` is not false
86
+ * @property {Array<{id: string, content: string, metadata: Object}>} sources
87
+ * - The sources as they were checked
88
+ */
89
+
90
+ /**
91
+ * Check an answer against the sources it was generated from.
92
+ *
93
+ * @example
94
+ * // A LangChain chain's output
95
+ * const check = await checkAnswer({
96
+ * question: input,
97
+ * answer: result.answer,
98
+ * sources: result.context, // LangChain Documents
99
+ * });
100
+ * if (!check.grounded) console.warn(check.unsupported);
101
+ *
102
+ * @param {Object} params
103
+ * @param {string} params.answer - The generated answer
104
+ * @param {Array<string|Object>} params.sources - Documents the answer was
105
+ * generated from (see normalizeSources for accepted shapes)
106
+ * @param {string} [params.question] - The question; needed for relevance
107
+ * @param {string} [params.referenceAnswer] - Ground truth, for context recall
108
+ * (and semantic similarity when an embedder is given)
109
+ * @param {Object} [params.embedder] - With a referenceAnswer, adds
110
+ * `evaluation.scores.semanticSimilarity` (computeSemanticSimilarity)
111
+ * @param {boolean} [params.evaluate=true] - Also compute evaluation scores
112
+ * @returns {Promise<AnswerCheck>}
113
+ */
114
+ async function checkAnswer({
115
+ answer,
116
+ sources,
117
+ question,
118
+ referenceAnswer,
119
+ embedder,
120
+ evaluate = true,
121
+ } = {}) {
122
+ if (typeof answer !== 'string') {
123
+ throw new TypeError('answer must be a string');
124
+ }
125
+ const docs = normalizeSources(sources);
126
+
127
+ // Models sometimes return nothing; that is an ungrounded answer, not an
128
+ // error. (CitationTracker rejects empty text.)
129
+ if (answer.trim() === '') {
130
+ return {
131
+ grounded: false,
132
+ groundedness: 0,
133
+ unsupported: [],
134
+ citations: [],
135
+ evaluation: undefined,
136
+ sources: docs,
137
+ };
138
+ }
139
+
140
+ const tracked = new CitationTracker().track(answer, docs);
141
+ const sentences =
142
+ (tracked.hallucinationReport && tracked.hallucinationReport.sentences) ||
143
+ [];
144
+ const unsupported = sentences
145
+ .filter((s) => s.classification !== 'grounded')
146
+ .map((s) => s.text);
147
+
148
+ let evaluation;
149
+ if (evaluate) {
150
+ evaluation = await new PipelineEvaluator().evaluate(
151
+ { query: question || '', answer, results: docs },
152
+ referenceAnswer ? { referenceAnswer } : {},
153
+ );
154
+ if (referenceAnswer && embedder) {
155
+ evaluation.scores.semanticSimilarity = await computeSemanticSimilarity(
156
+ answer,
157
+ referenceAnswer,
158
+ { embedder },
159
+ );
160
+ }
161
+ }
162
+
163
+ return {
164
+ grounded: sentences.length > 0 && unsupported.length === 0,
165
+ groundedness: tracked.groundednessScore,
166
+ unsupported,
167
+ citations: tracked.citations,
168
+ evaluation,
169
+ sources: docs,
170
+ };
171
+ }
172
+
173
+ module.exports = { checkAnswer, normalizeSources };
@@ -16,6 +16,20 @@ const DEFAULT_CONFIG = {
16
16
  strict: false, // If true, block on any guard failure; if false, warn and continue
17
17
  };
18
18
 
19
+ /**
20
+ * Reduce removed documents to their ids so filtered-out (possibly
21
+ * access-controlled) content is never returned to the caller.
22
+ *
23
+ * @param {{results: Array, removed: Array, metadata: object}} filterOutput
24
+ * @returns {{results: Array, removed: Array<{id: *}>, metadata: object}}
25
+ */
26
+ function summarizeFilter(filterOutput) {
27
+ return {
28
+ ...filterOutput,
29
+ removed: filterOutput.removed.map((doc) => ({ id: doc.id })),
30
+ };
31
+ }
32
+
19
33
  /**
20
34
  * Pipeline wrapper that applies all three guardrail layers (pre-retrieval,
21
35
  * retrieval, and post-generation) around an existing RAG pipeline. In strict
@@ -92,21 +106,49 @@ class GuardrailsPipeline extends EventEmitter {
92
106
  query = guardrails.preRetrieval.sanitizedQuery;
93
107
  }
94
108
 
95
- // 2. Run the underlying pipeline with (possibly sanitized) query
109
+ // 2. Run the underlying pipeline with (possibly sanitized) query. The
110
+ // retrieval guard is passed in as options.filterResults so it runs
111
+ // between retrieval and generation (supported by createRagPipeline).
112
+ let preGenerationFilter = null;
113
+ const filterOptions = this.retrievalGuard
114
+ ? {
115
+ filterResults: (docs) => {
116
+ preGenerationFilter = this.retrievalGuard.filter(
117
+ docs,
118
+ input.context,
119
+ );
120
+ return preGenerationFilter.results;
121
+ },
122
+ }
123
+ : {};
96
124
  const result = await this.pipeline.run({
97
125
  ...input,
98
126
  query,
99
- options: { ...input.options, citations: true },
127
+ options: { ...input.options, citations: true, ...filterOptions },
100
128
  });
101
129
 
102
130
  // 3. Retrieval guard on results
103
- if (this.retrievalGuard && result.results) {
104
- guardrails.retrieval = this.retrievalGuard.filter(
105
- result.results,
106
- input.context,
131
+ if (this.retrievalGuard && preGenerationFilter) {
132
+ guardrails.retrieval = summarizeFilter(preGenerationFilter);
133
+ this.emit('retrieval', guardrails.retrieval);
134
+ } else if (this.retrievalGuard && result.results) {
135
+ // The wrapped pipeline ignored options.filterResults, so it generated
136
+ // from unfiltered documents. Filtering now only fixes result.results;
137
+ // if ACL removed anything the answer may contain restricted content.
138
+ guardrails.retrieval = summarizeFilter(
139
+ this.retrievalGuard.filter(result.results, input.context),
107
140
  );
108
141
  this.emit('retrieval', guardrails.retrieval);
109
142
  result.results = guardrails.retrieval.results;
143
+
144
+ if (guardrails.retrieval.metadata.reasons.acl) {
145
+ return {
146
+ success: false,
147
+ error:
148
+ 'Retrieval guard removed access-controlled documents after generation; answer withheld',
149
+ guardrails,
150
+ };
151
+ }
110
152
  }
111
153
 
112
154
  // 4. Post-generation guard on answer
@@ -118,19 +160,39 @@ class GuardrailsPipeline extends EventEmitter {
118
160
  this.emit('postGeneration', guardrails.postGeneration);
119
161
 
120
162
  if (!guardrails.postGeneration.safe && this.config.strict) {
163
+ // Do not spread result: it carries the raw answer and citations.
121
164
  return {
122
- ...result,
123
165
  success: false,
124
166
  error: 'Post-generation guard flagged output',
125
167
  guardrails,
126
168
  };
127
169
  }
128
170
  result.answer = guardrails.postGeneration.sanitizedOutput;
171
+ this._redactDeep(result.citations);
172
+ this._redactDeep(result.evaluation);
129
173
  }
130
174
 
131
175
  result.guardrails = guardrails;
132
176
  return result;
133
177
  }
178
+
179
+ /**
180
+ * Citations and evaluation details copy sentences from the raw answer, so
181
+ * scrub every string in them with the same PII patterns as the answer.
182
+ *
183
+ * @param {*} value - Object, array or string to redact in place
184
+ * @returns {*} The redacted value
185
+ * @private
186
+ */
187
+ _redactDeep(value) {
188
+ if (typeof value === 'string') return this.postGuard.redact(value);
189
+ if (value && typeof value === 'object') {
190
+ for (const key of Object.keys(value)) {
191
+ value[key] = this._redactDeep(value[key]);
192
+ }
193
+ }
194
+ return value;
195
+ }
134
196
  }
135
197
 
136
198
  module.exports = { GuardrailsPipeline, DEFAULT_CONFIG };