@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.
- package/.ragrc.schema.json +83 -192
- package/CHANGELOG.md +431 -0
- package/README.md +418 -117
- package/SECURITY.md +5 -5
- package/dist/experimental.cjs +2 -0
- package/dist/experimental.d.ts +1 -0
- package/dist/experimental.mjs +5 -0
- package/dist/index.cjs +6442 -1355
- package/dist/index.d.ts +129 -2
- package/dist/index.mjs +1 -1
- package/dist/testing.cjs +2 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.mjs +5 -0
- package/dist/types/adapters/ai-sdk.d.ts +53 -0
- package/dist/types/agentic/agentic-pipeline.d.ts +103 -0
- package/dist/types/agentic/iterative-retriever.d.ts +85 -0
- package/dist/types/agentic/query-planner.d.ts +145 -0
- package/dist/types/agentic/self-critique.d.ts +45 -0
- package/dist/types/ai/adaptive-retrieval.d.ts +44 -0
- package/dist/types/ai/federation/federated-learning-coordinator.d.ts +62 -0
- package/dist/types/ai/multimodal-processing.d.ts +32 -0
- package/dist/types/ai/multimodal.d.ts +39 -0
- package/dist/types/ai/retrieval-engine.d.ts +37 -0
- package/dist/types/ai/training/model-training-orchestrator.d.ts +31 -0
- package/dist/types/cache/component-keys.d.ts +7 -0
- package/dist/types/cache/memory-cache.d.ts +54 -0
- package/dist/types/cache/pipeline-cache.d.ts +66 -0
- package/dist/types/cache/redis-cache.d.ts +46 -0
- package/dist/types/chunking/chunking-engine.d.ts +70 -0
- package/dist/types/chunking/strategies/fixed-size.d.ts +17 -0
- package/dist/types/chunking/strategies/recursive.d.ts +20 -0
- package/dist/types/chunking/strategies/semantic.d.ts +19 -0
- package/dist/types/chunking/strategies/sentence.d.ts +13 -0
- package/dist/types/chunking/strategies/structure-aware.d.ts +19 -0
- package/dist/types/citation/citation-tracker.d.ts +64 -0
- package/dist/types/citation/hallucination-detector.d.ts +40 -0
- package/dist/types/citation/source-mapper.d.ts +37 -0
- package/dist/types/connectors/anthropic-connector.d.ts +97 -0
- package/dist/types/connectors/base-connector.d.ts +87 -0
- package/dist/types/connectors/cohere-connector.d.ts +94 -0
- package/dist/types/connectors/connector-utils.d.ts +110 -0
- package/dist/types/connectors/local-embedder.d.ts +95 -0
- package/dist/types/connectors/memory-retriever.d.ts +99 -0
- package/dist/types/connectors/ollama-connector.d.ts +115 -0
- package/dist/types/connectors/openai-connector.d.ts +129 -0
- package/dist/types/connectors/rag-prompt.d.ts +40 -0
- package/dist/types/core/create-pipeline.d.ts +8 -0
- package/dist/types/core/plugin-registry.d.ts +136 -0
- package/dist/types/core/stage-contracts.d.ts +36 -0
- package/dist/types/core/validate-plugin.d.ts +45 -0
- package/dist/types/cost/cost-calculator.d.ts +152 -0
- package/dist/types/cost/cost-tracker.d.ts +126 -0
- package/dist/types/cost/token-budget.d.ts +110 -0
- package/dist/types/dag/core/error-context.d.ts +239 -0
- package/dist/types/dag/core/execution-scheduler.d.ts +191 -0
- package/dist/types/dag/core/topology-validation.d.ts +226 -0
- package/dist/types/dag/dag-engine.d.ts +156 -0
- package/dist/types/dag/dag-node.d.ts +37 -0
- package/dist/types/dashboard/dashboard-generator.d.ts +127 -0
- package/dist/types/dashboard/metrics-aggregator.d.ts +218 -0
- package/dist/types/debug/execution-tracer.d.ts +57 -0
- package/dist/types/debug/trace-inspector.d.ts +31 -0
- package/dist/types/ecosystem/plugin-certification.d.ts +169 -0
- package/dist/types/enterprise/data-governance.d.ts +203 -0
- package/dist/types/enterprise/multi-tenancy.d.ts +137 -0
- package/dist/types/enterprise/sso-integration.d.ts +420 -0
- package/dist/types/evaluate/context-metrics.d.ts +55 -0
- package/dist/types/evaluate/faithfulness.d.ts +41 -0
- package/dist/types/evaluate/groundedness.d.ts +60 -0
- package/dist/types/evaluate/pipeline-evaluator.d.ts +88 -0
- package/dist/types/evaluate/relevance.d.ts +21 -0
- package/dist/types/evaluate/scoring.d.ts +123 -0
- package/dist/types/evaluate/semantic-similarity.d.ts +14 -0
- package/dist/types/experimental.d.ts +15 -0
- package/dist/types/graph/entity-extractor.d.ts +84 -0
- package/dist/types/graph/graph-index.d.ts +49 -0
- package/dist/types/graph/graph-retriever.d.ts +81 -0
- package/dist/types/graph/knowledge-graph.d.ts +129 -0
- package/dist/types/grounding/check-answer.d.ts +101 -0
- package/dist/types/guardrails/guardrails-pipeline.d.ts +63 -0
- package/dist/types/guardrails/injection-detector.d.ts +47 -0
- package/dist/types/guardrails/post-generation-guard.d.ts +61 -0
- package/dist/types/guardrails/pre-retrieval-guard.d.ts +64 -0
- package/dist/types/guardrails/retrieval-guard.d.ts +57 -0
- package/dist/types/mcp/mcp-server.d.ts +85 -0
- package/dist/types/mcp/mcp-tool-builder.d.ts +55 -0
- package/dist/types/reranker/cascade-reranker.d.ts +40 -0
- package/dist/types/reranker/embedding-reranker.d.ts +77 -0
- package/dist/types/reranker/scoring-reranker.d.ts +41 -0
- package/dist/types/retrieval/bm25-search.d.ts +43 -0
- package/dist/types/retrieval/file-vector-store.d.ts +99 -0
- package/dist/types/retrieval/hybrid-retriever.d.ts +35 -0
- package/dist/types/retrieval/rank-fusion.d.ts +10 -0
- package/dist/types/security/jwt-config.d.ts +80 -0
- package/dist/types/security/jwt-validator.d.ts +195 -0
- package/dist/types/security/plugin-sandbox.d.ts +158 -0
- package/dist/types/security/plugin-signature-verifier.d.ts +139 -0
- package/dist/types/security/validator.d.ts +5 -0
- package/dist/types/streaming/embedding-stream.d.ts +65 -0
- package/dist/types/streaming/sse-adapter.d.ts +59 -0
- package/dist/types/streaming/stream-router.d.ts +88 -0
- package/dist/types/streaming/streaming-embedder.d.ts +99 -0
- package/dist/types/streaming/websocket-adapter.d.ts +71 -0
- package/dist/types/templates/scaffold.d.ts +55 -0
- package/dist/types/templates/template-registry.d.ts +63 -0
- package/dist/types/testing.d.ts +168 -0
- package/dist/types/utils/embed-query.d.ts +20 -0
- package/dist/types/utils/logger.d.ts +50 -0
- package/dist/types/utils/rate-limiter.d.ts +66 -0
- package/dist/types/utils/retry.d.ts +27 -0
- package/dist/types/utils/run-abort.d.ts +34 -0
- package/dist/types/utils/secure-logging.d.ts +51 -0
- package/dist/types/utils/structured-logger.d.ts +126 -0
- package/package.json +72 -33
- package/src/adapters/ai-sdk.js +215 -0
- package/src/agentic/agentic-pipeline.js +75 -7
- package/src/agentic/iterative-retriever.js +47 -10
- package/src/agentic/query-planner.js +329 -78
- package/src/cache/component-keys.js +38 -0
- package/src/cache/memory-cache.js +87 -0
- package/src/cache/pipeline-cache.js +260 -0
- package/src/cache/redis-cache.js +83 -0
- package/src/citation/citation-tracker.js +6 -4
- package/src/citation/hallucination-detector.js +2 -2
- package/src/citation/source-mapper.js +15 -28
- package/src/cli/commands/ai.js +106 -0
- package/src/cli/commands/plugin-hub/event-handlers.js +6 -0
- package/src/cli/commands/plugin-hub/hub-commands.js +0 -74
- package/src/cli/commands/plugin-hub/local-commands.js +143 -0
- package/src/cli/commands/plugin-hub.js +114 -49
- package/src/cli/diagnostics/config-checker.js +76 -63
- package/src/cli/diagnostics/environment-checker.js +62 -31
- package/src/cli/diagnostics/plugin-checker.js +36 -89
- package/src/cli/doctor-command.js +39 -5
- package/src/cli/enhanced-cli-commands.js +104 -233
- package/src/cli/handlers/ai-handler.js +364 -0
- package/src/cli/handlers/diagnostics-handler.js +78 -31
- package/src/cli/handlers/ingest-handler.js +28 -22
- package/src/cli/handlers/init-handler.js +148 -25
- package/src/cli/handlers/query-handler.js +203 -25
- package/src/cli/handlers/run-handler.js +16 -167
- package/src/cli/handlers/serve-handler.js +60 -0
- package/src/cli/services/bench-stats.js +54 -0
- package/src/cli/services/benchmark.js +161 -0
- package/src/cli/services/feedback.js +155 -0
- package/src/cli/services/instrument.js +83 -0
- package/src/cli/services/rag-service.js +361 -0
- package/src/cli/shared/cli-utils.js +24 -7
- package/src/cli/shared/config-context.js +21 -0
- package/src/cli/wizard/config-builder.js +81 -0
- package/src/cli/wizard/init-wizard.js +602 -0
- package/src/config/component-factory.js +125 -0
- package/src/config/enhanced-ragrc-schema.js +0 -2
- package/src/config/load-config.js +29 -10
- package/src/config/load-plugin-config.js +46 -2
- package/src/config/validate-schema.js +4 -1
- package/src/connectors/anthropic-connector.js +115 -57
- package/src/connectors/base-connector.js +74 -19
- package/src/connectors/cohere-connector.js +108 -34
- package/src/connectors/connector-utils.js +302 -0
- package/src/connectors/local-embedder.js +63 -5
- package/src/connectors/memory-retriever.js +125 -14
- package/src/connectors/ollama-connector.js +112 -51
- package/src/connectors/openai-connector.js +153 -44
- package/src/connectors/rag-prompt.js +88 -0
- package/src/core/create-pipeline.js +307 -61
- package/src/core/performance/parallel-processor.js +62 -1
- package/src/core/plugin-registry.js +125 -26
- package/src/core/stage-contracts.js +36 -0
- package/src/core/validate-plugin.js +85 -0
- package/src/cost/cost-calculator.js +205 -32
- package/src/debug/execution-tracer.js +14 -4
- package/src/dev/dev-server.js +173 -11
- package/src/dev/hot-reload.js +44 -4
- package/src/ecosystem/plugin-checks.js +181 -0
- package/src/ecosystem/plugin-hub.js +50 -125
- package/src/ecosystem/plugin-installer.js +298 -0
- package/src/ecosystem/plugin-probe.js +127 -0
- package/src/ecosystem/plugin-scanner.js +185 -0
- package/src/ecosystem/tar-extract.js +114 -0
- package/src/enterprise/audit-logging.js +158 -29
- package/src/enterprise/sso-integration.js +716 -228
- package/src/evaluate/context-metrics.js +28 -16
- package/src/evaluate/evaluator.js +3 -3
- package/src/evaluate/faithfulness.js +18 -14
- package/src/evaluate/groundedness.js +43 -12
- package/src/evaluate/pipeline-evaluator.js +35 -8
- package/src/evaluate/scoring.js +251 -3
- package/src/evaluate/semantic-similarity.js +79 -0
- package/src/experimental.js +71 -0
- package/src/grounding/check-answer.js +173 -0
- package/src/guardrails/guardrails-pipeline.js +69 -7
- package/src/guardrails/injection-detector.js +283 -0
- package/src/guardrails/post-generation-guard.js +19 -0
- package/src/guardrails/pre-retrieval-guard.js +36 -36
- package/src/guardrails/retrieval-guard.js +58 -8
- package/src/index.js +58 -1
- package/src/loader/csv-loader.js +3 -2
- package/src/loader/document-reader.js +91 -0
- package/src/loader/html-loader.js +3 -1
- package/src/loader/markdown-loader.js +3 -2
- package/src/reranker/cascade-reranker.js +3 -0
- package/src/reranker/embedding-reranker.js +136 -21
- package/src/reranker/llm-reranker.js +79 -12
- package/src/reranker/scoring-reranker.js +3 -0
- package/src/retrieval/bm25-search.js +12 -6
- package/src/retrieval/file-vector-store.js +241 -0
- package/src/retrieval/hybrid-retriever.js +22 -19
- package/src/security/jwt-config.js +13 -2
- package/src/security/jwt-validator.js +139 -34
- package/src/security/plugin-sandbox.js +18 -1
- package/src/security/plugin-signature-verifier.js +245 -70
- package/src/testing.js +254 -0
- package/src/utils/deprecate.js +69 -0
- package/src/utils/embed-query.js +33 -0
- package/src/utils/input-sanitizer.js +19 -4
- package/src/utils/logger.js +34 -4
- package/src/utils/run-abort.js +102 -0
- package/src/ai/federated-learning.js +0 -211
- package/src/ai/federation.js +0 -71
- package/src/ai/index.js +0 -22
- package/src/ai/model-trainer.js +0 -217
- package/src/ai/model-training.js +0 -202
- package/src/ai/multimodal/multi-modal-processor.js +0 -345
- package/src/ai/orchestrator.js +0 -65
- package/src/ai/retrieval/adaptive-retrieval-engine.js +0 -398
- package/src/cli/commands/ai-ml/adaptive-commands.js +0 -178
- package/src/cli/commands/ai-ml/federated-commands.js +0 -207
- package/src/cli/commands/ai-ml/index.js +0 -17
- package/src/cli/commands/ai-ml/multimodal-commands.js +0 -187
- package/src/cli/commands/ai-ml/shared.js +0 -51
- package/src/cli/commands/ai-ml/training-commands.js +0 -228
- package/src/cli/commands/ai-ml.js +0 -386
- package/src/cli/commands/docs.js +0 -228
- package/src/cli/commands/dx.js +0 -538
- package/src/cli/commands/wizard.js +0 -627
- package/src/cli/input-sanitization-middleware.js +0 -356
- package/src/cli/interactive-wizard.js +0 -373
- package/src/cli/templates/development.template.json +0 -64
- package/src/cli/templates/minimal.template.json +0 -33
- package/src/cli/templates/production.template.json +0 -104
- package/src/cli/templates/testing.template.json +0 -45
- package/src/cli/wizard/configuration.js +0 -148
- package/src/cli/wizard/observability.js +0 -79
- package/src/cli/wizard/performance.js +0 -76
- package/src/cli/wizard/plugin-selection.js +0 -297
- package/src/cli/wizard/preview-save.js +0 -172
- package/src/cli/wizard/project-setup.js +0 -66
- package/src/core/pipeline-factory.js +0 -59
- package/src/dag/enhanced-dag-engine.js +0 -894
- package/src/dx/index.js +0 -40
- package/src/dx/integration-templates.js +0 -662
- package/src/dx/performance-profiler.js +0 -968
- package/src/dx/realtime-debugger.js +0 -755
- package/src/dx/visual-pipeline-builder.js +0 -578
- package/src/ingest.js +0 -59
- package/src/pipeline/pipeline.js +0 -18
- package/src/query.js +0 -117
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
tokenize,
|
|
5
|
-
splitSentences,
|
|
6
|
-
computeJaccardSimilarity,
|
|
7
|
-
} = require('./scoring');
|
|
3
|
+
const { tokenize, splitSentences, computeSupportScore } = require('./scoring');
|
|
8
4
|
|
|
9
5
|
/**
|
|
10
6
|
* Compute context precision: the fraction of retrieved documents that are relevant to the query.
|
|
@@ -12,7 +8,7 @@ const {
|
|
|
12
8
|
* @param {string} query - The user query
|
|
13
9
|
* @param {Array<{content: string}>} retrievedDocs - Array of retrieved documents
|
|
14
10
|
* @param {object} [options={}] - Configuration options
|
|
15
|
-
* @param {number} [options.threshold=0.2] - Minimum
|
|
11
|
+
* @param {number} [options.threshold=0.2] - Minimum share of query content terms a doc must contain to be considered relevant
|
|
16
12
|
* @returns {{ score: number, relevantDocs: number, totalDocs: number, perDocScores: Array<{ index: number, score: number, isRelevant: boolean }> }}
|
|
17
13
|
*/
|
|
18
14
|
function computeContextPrecision(query, retrievedDocs, options = {}) {
|
|
@@ -39,7 +35,8 @@ function computeContextPrecision(query, retrievedDocs, options = {}) {
|
|
|
39
35
|
}
|
|
40
36
|
|
|
41
37
|
const docTokens = tokenize(docContent);
|
|
42
|
-
|
|
38
|
+
// Share of the query's content terms that the document contains.
|
|
39
|
+
const score = computeSupportScore(queryTokens, docTokens);
|
|
43
40
|
perDocScores.push({ index: i, score, isRelevant: score >= threshold });
|
|
44
41
|
}
|
|
45
42
|
|
|
@@ -51,22 +48,34 @@ function computeContextPrecision(query, retrievedDocs, options = {}) {
|
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
/**
|
|
54
|
-
* Compute context recall: the fraction of
|
|
51
|
+
* Compute context recall: the fraction of the claims in a REFERENCE
|
|
52
|
+
* (ground-truth) answer that the retrieved documents support. It measures
|
|
53
|
+
* whether retrieval found everything needed to answer correctly; pass the
|
|
54
|
+
* expected answer, not the generated one (scoring the generated answer
|
|
55
|
+
* against the documents is faithfulness).
|
|
55
56
|
*
|
|
56
|
-
*
|
|
57
|
+
* A claim is one sentence of the reference answer. It is supported when some
|
|
58
|
+
* document scores at least `threshold` with computeSupportScore, including
|
|
59
|
+
* the numeric consistency check.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} referenceAnswer - The reference (ground-truth) answer text
|
|
57
62
|
* @param {Array<{content: string}>} retrievedDocs - Array of retrieved documents
|
|
58
63
|
* @param {object} [options={}] - Configuration options
|
|
59
|
-
* @param {number} [options.threshold=0.
|
|
64
|
+
* @param {number} [options.threshold=0.5] - Minimum share of a claim's content terms a doc must contain to support it
|
|
60
65
|
* @returns {{ score: number, supportedClaims: number, totalClaims: number, details: Array<{ claim: string, score: number, isSupported: boolean, bestDocIndex: number }> }}
|
|
61
66
|
*/
|
|
62
|
-
function computeContextRecall(
|
|
63
|
-
const { threshold = 0.
|
|
64
|
-
|
|
65
|
-
if (
|
|
67
|
+
function computeContextRecall(referenceAnswer, retrievedDocs, options = {}) {
|
|
68
|
+
const { threshold = 0.5 } = options;
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
!referenceAnswer ||
|
|
72
|
+
typeof referenceAnswer !== 'string' ||
|
|
73
|
+
referenceAnswer.trim().length === 0
|
|
74
|
+
) {
|
|
66
75
|
return { score: 0, supportedClaims: 0, totalClaims: 0, details: [] };
|
|
67
76
|
}
|
|
68
77
|
|
|
69
|
-
const claims = splitSentences(
|
|
78
|
+
const claims = splitSentences(referenceAnswer);
|
|
70
79
|
|
|
71
80
|
if (claims.length === 0) {
|
|
72
81
|
return { score: 0, supportedClaims: 0, totalClaims: 0, details: [] };
|
|
@@ -101,7 +110,10 @@ function computeContextRecall(answer, retrievedDocs, options = {}) {
|
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
const docTokens = tokenize(docContent);
|
|
104
|
-
const similarity =
|
|
113
|
+
const similarity = computeSupportScore(claimTokens, docTokens, null, {
|
|
114
|
+
claimText: claim,
|
|
115
|
+
docText: docContent,
|
|
116
|
+
});
|
|
105
117
|
|
|
106
118
|
if (similarity > bestScore) {
|
|
107
119
|
bestScore = similarity;
|
|
@@ -22,15 +22,15 @@ async function evaluateRagDataset(_datasetPath, _config) {
|
|
|
22
22
|
const results = [];
|
|
23
23
|
for (const { prompt, expected } of cases) {
|
|
24
24
|
try {
|
|
25
|
-
logger.info({ prompt }
|
|
25
|
+
logger.info('Evaluating case', { prompt });
|
|
26
26
|
const result = await pipeline.run({ query: prompt });
|
|
27
|
-
const actual = result.
|
|
27
|
+
const actual = typeof result.answer === 'string' ? result.answer : '';
|
|
28
28
|
const success =
|
|
29
29
|
result.success && normalizeText(actual) === normalizeText(expected);
|
|
30
30
|
const scores = scoreAnswer(actual, expected);
|
|
31
31
|
results.push({ prompt, expected, actual, success, scores });
|
|
32
32
|
} catch (err) {
|
|
33
|
-
logger.error({ prompt, error: err.message }
|
|
33
|
+
logger.error('Evaluation failed', { prompt, error: err.message });
|
|
34
34
|
results.push({
|
|
35
35
|
prompt,
|
|
36
36
|
expected,
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
splitSentences,
|
|
5
|
-
tokenize,
|
|
6
|
-
computeJaccardSimilarity,
|
|
7
|
-
} = require('./scoring');
|
|
3
|
+
const { splitSentences, tokenize, computeSupportScore } = require('./scoring');
|
|
8
4
|
const { mapSentenceToSources } = require('../citation/source-mapper');
|
|
9
5
|
|
|
10
6
|
/**
|
|
@@ -14,11 +10,11 @@ const { mapSentenceToSources } = require('../citation/source-mapper');
|
|
|
14
10
|
* @param {string} answer - The generated answer text
|
|
15
11
|
* @param {Array<{content: string}>} retrievedDocs - Array of retrieved documents with content
|
|
16
12
|
* @param {object} [options={}] - Configuration options
|
|
17
|
-
* @param {number} [options.threshold=0.
|
|
13
|
+
* @param {number} [options.threshold=0.5] - Minimum similarity score to consider a sentence faithful
|
|
18
14
|
* @returns {{ score: number, faithfulSentences: number, totalSentences: number, details: Array<{ sentence: string, score: number, isFaithful: boolean, bestSource: number }> }}
|
|
19
15
|
*/
|
|
20
16
|
function computeFaithfulness(answer, retrievedDocs, options = {}) {
|
|
21
|
-
const { threshold = 0.
|
|
17
|
+
const { threshold = 0.5 } = options;
|
|
22
18
|
|
|
23
19
|
if (!answer || typeof answer !== 'string' || answer.trim().length === 0) {
|
|
24
20
|
return { score: 0, faithfulSentences: 0, totalSentences: 0, details: [] };
|
|
@@ -60,7 +56,10 @@ function computeFaithfulness(answer, retrievedDocs, options = {}) {
|
|
|
60
56
|
}
|
|
61
57
|
|
|
62
58
|
const docTokens = tokenize(docContent);
|
|
63
|
-
const similarity =
|
|
59
|
+
const similarity = computeSupportScore(sentenceTokens, docTokens, null, {
|
|
60
|
+
claimText: sentence,
|
|
61
|
+
docText: docContent,
|
|
62
|
+
});
|
|
64
63
|
|
|
65
64
|
if (similarity > bestScore) {
|
|
66
65
|
bestScore = similarity;
|
|
@@ -102,13 +101,18 @@ function computeFaithfulnessFromCitations(citationResult) {
|
|
|
102
101
|
? citationResult.groundednessScore
|
|
103
102
|
: 0;
|
|
104
103
|
|
|
104
|
+
// CitationTracker only lists sources at or above its similarity threshold,
|
|
105
|
+
// so a sentence is faithful exactly when it has at least one source.
|
|
105
106
|
const details = Array.isArray(citationResult.citations)
|
|
106
|
-
? citationResult.citations.map((c) =>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
? citationResult.citations.map((c) => {
|
|
108
|
+
const best = Array.isArray(c.sources) ? c.sources[0] : undefined;
|
|
109
|
+
return {
|
|
110
|
+
sentence: c.sentence || c.text || '',
|
|
111
|
+
score: best ? best.score : 0,
|
|
112
|
+
isFaithful: Boolean(best),
|
|
113
|
+
bestSource: best ? best.docIndex : -1,
|
|
114
|
+
};
|
|
115
|
+
})
|
|
112
116
|
: [];
|
|
113
117
|
|
|
114
118
|
return { score, details };
|
|
@@ -16,20 +16,40 @@ const DEFAULT_WEIGHTS = {
|
|
|
16
16
|
contextRecall: 0.3,
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
/** Reason reported when context recall cannot be computed. */
|
|
20
|
+
const NO_REFERENCE_REASON =
|
|
21
|
+
'No reference answer provided: context recall measures how much of a ' +
|
|
22
|
+
'reference (ground-truth) answer the retrieved documents support.';
|
|
23
|
+
|
|
19
24
|
/**
|
|
20
|
-
* Compute a composite groundedness score
|
|
21
|
-
*
|
|
25
|
+
* Compute a composite groundedness score from faithfulness, context precision
|
|
26
|
+
* and (when a reference answer is given) context recall.
|
|
27
|
+
*
|
|
28
|
+
* Formula: a weighted mean over the components that are available,
|
|
29
|
+
*
|
|
30
|
+
* score = sum(w_i * m_i) / sum(w_i)
|
|
31
|
+
*
|
|
32
|
+
* With a reference answer and the default weights this is
|
|
33
|
+
* 0.4 * faithfulness + 0.3 * contextPrecision + 0.3 * contextRecall.
|
|
34
|
+
* Without one, context recall is left out and the remaining weights are
|
|
35
|
+
* renormalised:
|
|
36
|
+
* (0.4 * faithfulness + 0.3 * contextPrecision) / 0.7.
|
|
37
|
+
* If every available weight is 0 the score is 0.
|
|
22
38
|
*
|
|
23
39
|
* @param {string} query - The user query
|
|
24
40
|
* @param {string} answer - The generated answer text
|
|
25
41
|
* @param {Array<{content: string}>} retrievedDocs - Array of retrieved documents
|
|
26
42
|
* @param {object} [options={}] - Configuration options
|
|
27
43
|
* @param {{ faithfulness: number, contextPrecision: number, contextRecall: number }} [options.weights] - Custom weights for each component
|
|
28
|
-
* @param {number} [options.threshold=0.
|
|
44
|
+
* @param {number} [options.threshold=0.5] - Similarity threshold passed to component metrics
|
|
45
|
+
* @param {string} [options.referenceAnswer] - Reference (ground-truth) answer used for context recall
|
|
29
46
|
* @returns {{ score: number, components: { faithfulness: object, contextPrecision: object, contextRecall: object }, weights: { faithfulness: number, contextPrecision: number, contextRecall: number } }}
|
|
47
|
+
* `components.contextRecall` is `{ score: null, skipped: true, reason }`
|
|
48
|
+
* when no reference answer was given.
|
|
30
49
|
*/
|
|
31
50
|
function computeGroundedness(query, answer, retrievedDocs, options = {}) {
|
|
32
|
-
const {
|
|
51
|
+
const { threshold = 0.5, referenceAnswer } = options;
|
|
52
|
+
const weights = { ...DEFAULT_WEIGHTS, ...(options.weights || {}) };
|
|
33
53
|
|
|
34
54
|
const faithfulness = computeFaithfulness(answer, retrievedDocs, {
|
|
35
55
|
threshold,
|
|
@@ -37,14 +57,25 @@ function computeGroundedness(query, answer, retrievedDocs, options = {}) {
|
|
|
37
57
|
const contextPrecision = computeContextPrecision(query, retrievedDocs, {
|
|
38
58
|
threshold: threshold * 0.67,
|
|
39
59
|
});
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
const hasReference =
|
|
61
|
+
typeof referenceAnswer === 'string' && referenceAnswer.trim().length > 0;
|
|
62
|
+
const contextRecall = hasReference
|
|
63
|
+
? computeContextRecall(referenceAnswer, retrievedDocs, { threshold })
|
|
64
|
+
: { score: null, skipped: true, reason: NO_REFERENCE_REASON };
|
|
65
|
+
|
|
66
|
+
const parts = [
|
|
67
|
+
[weights.faithfulness, faithfulness.score],
|
|
68
|
+
[weights.contextPrecision, contextPrecision.score],
|
|
69
|
+
];
|
|
70
|
+
if (hasReference) parts.push([weights.contextRecall, contextRecall.score]);
|
|
43
71
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
72
|
+
let weighted = 0;
|
|
73
|
+
let totalWeight = 0;
|
|
74
|
+
for (const [w, m] of parts) {
|
|
75
|
+
weighted += w * m;
|
|
76
|
+
totalWeight += w;
|
|
77
|
+
}
|
|
78
|
+
const score = totalWeight > 0 ? weighted / totalWeight : 0;
|
|
48
79
|
|
|
49
80
|
return {
|
|
50
81
|
score,
|
|
@@ -57,4 +88,4 @@ function computeGroundedness(query, answer, retrievedDocs, options = {}) {
|
|
|
57
88
|
};
|
|
58
89
|
}
|
|
59
90
|
|
|
60
|
-
module.exports = { computeGroundedness, DEFAULT_WEIGHTS };
|
|
91
|
+
module.exports = { computeGroundedness, DEFAULT_WEIGHTS, NO_REFERENCE_REASON };
|
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
computeContextPrecision,
|
|
11
11
|
computeContextRecall,
|
|
12
12
|
} = require('./context-metrics');
|
|
13
|
-
const { computeGroundedness } = require('./groundedness');
|
|
13
|
+
const { computeGroundedness, NO_REFERENCE_REASON } = require('./groundedness');
|
|
14
14
|
const { scoreAnswer } = require('./scoring');
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -25,7 +25,7 @@ const DEFAULT_CONFIG = {
|
|
|
25
25
|
'contextRecall',
|
|
26
26
|
'groundedness',
|
|
27
27
|
],
|
|
28
|
-
threshold: 0.
|
|
28
|
+
threshold: 0.5,
|
|
29
29
|
includeBLEU: false,
|
|
30
30
|
includeROUGE: false,
|
|
31
31
|
};
|
|
@@ -64,8 +64,14 @@ class PipelineEvaluator extends EventEmitter {
|
|
|
64
64
|
* @param {Array<{content: string}>} pipelineResult.results - Retrieved documents
|
|
65
65
|
* @param {object} [options={}] - Additional evaluation options
|
|
66
66
|
* @param {object} [options.citationResult] - Pre-computed citation result for faithfulness shortcut
|
|
67
|
-
* @param {string} [options.referenceAnswer] - Reference
|
|
68
|
-
*
|
|
67
|
+
* @param {string} [options.referenceAnswer] - Reference (ground-truth)
|
|
68
|
+
* answer. Required for context recall (how much of it the retrieved
|
|
69
|
+
* documents support) and used for BLEU/ROUGE. Without it, contextRecall is
|
|
70
|
+
* left out of `scores`, `details.contextRecall` is
|
|
71
|
+
* `{ score: null, skipped: true, reason }`, it is listed in
|
|
72
|
+
* `metadata.metricsSkipped`, and groundedness renormalises over
|
|
73
|
+
* faithfulness and context precision.
|
|
74
|
+
* @returns {{ scores: Object<string, number>, details: Object<string, object>, metadata: { metricsComputed: string[], metricsSkipped: Array<{metric: string, reason: string}>, threshold: number, timestamp: string } }}
|
|
69
75
|
*/
|
|
70
76
|
evaluate(pipelineResult, options = {}) {
|
|
71
77
|
const { citationResult, referenceAnswer } = options;
|
|
@@ -76,6 +82,7 @@ class PipelineEvaluator extends EventEmitter {
|
|
|
76
82
|
const scores = {};
|
|
77
83
|
const details = {};
|
|
78
84
|
const metricsComputed = [];
|
|
85
|
+
const metricsSkipped = [];
|
|
79
86
|
const { metrics, threshold } = this.config;
|
|
80
87
|
|
|
81
88
|
if (metrics.includes('faithfulness')) {
|
|
@@ -106,16 +113,35 @@ class PipelineEvaluator extends EventEmitter {
|
|
|
106
113
|
metricsComputed.push('contextPrecision');
|
|
107
114
|
}
|
|
108
115
|
|
|
116
|
+
const hasReference =
|
|
117
|
+
typeof referenceAnswer === 'string' && referenceAnswer.trim().length > 0;
|
|
118
|
+
|
|
109
119
|
if (metrics.includes('contextRecall')) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
120
|
+
if (hasReference) {
|
|
121
|
+
const recallResult = computeContextRecall(referenceAnswer, results, {
|
|
122
|
+
threshold,
|
|
123
|
+
});
|
|
124
|
+
scores.contextRecall = recallResult.score;
|
|
125
|
+
details.contextRecall = recallResult;
|
|
126
|
+
metricsComputed.push('contextRecall');
|
|
127
|
+
} else {
|
|
128
|
+
// No ground truth: report why instead of a made-up score.
|
|
129
|
+
details.contextRecall = {
|
|
130
|
+
score: null,
|
|
131
|
+
skipped: true,
|
|
132
|
+
reason: NO_REFERENCE_REASON,
|
|
133
|
+
};
|
|
134
|
+
metricsSkipped.push({
|
|
135
|
+
metric: 'contextRecall',
|
|
136
|
+
reason: NO_REFERENCE_REASON,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
114
139
|
}
|
|
115
140
|
|
|
116
141
|
if (metrics.includes('groundedness')) {
|
|
117
142
|
const groundednessResult = computeGroundedness(query, answer, results, {
|
|
118
143
|
threshold,
|
|
144
|
+
referenceAnswer: hasReference ? referenceAnswer : undefined,
|
|
119
145
|
});
|
|
120
146
|
scores.groundedness = groundednessResult.score;
|
|
121
147
|
details.groundedness = groundednessResult;
|
|
@@ -142,6 +168,7 @@ class PipelineEvaluator extends EventEmitter {
|
|
|
142
168
|
details,
|
|
143
169
|
metadata: {
|
|
144
170
|
metricsComputed,
|
|
171
|
+
metricsSkipped,
|
|
145
172
|
threshold,
|
|
146
173
|
timestamp: new Date().toISOString(),
|
|
147
174
|
},
|
package/src/evaluate/scoring.js
CHANGED
|
@@ -11,10 +11,14 @@
|
|
|
11
11
|
* @returns {string[]}
|
|
12
12
|
*/
|
|
13
13
|
function tokenize(text) {
|
|
14
|
-
|
|
14
|
+
// Unicode-aware: keep letters/digits in any script, drop apostrophes so
|
|
15
|
+
// "don't" stays one token, and treat every other character (including
|
|
16
|
+
// newlines and hyphens) as a separator.
|
|
17
|
+
return String(text)
|
|
15
18
|
.toLowerCase()
|
|
16
|
-
.replace(/[
|
|
17
|
-
.
|
|
19
|
+
.replace(/['’]/g, '')
|
|
20
|
+
.replace(/[^\p{L}\p{N}]+/gu, ' ')
|
|
21
|
+
.split(' ')
|
|
18
22
|
.filter(Boolean);
|
|
19
23
|
}
|
|
20
24
|
|
|
@@ -108,7 +112,251 @@ function computeJaccardSimilarity(tokensA, tokensB) {
|
|
|
108
112
|
return intersection.size / union.size;
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Common English function words. Ignored when measuring how much of a
|
|
117
|
+
* sentence a document supports, so "the" and "is" cannot ground a claim.
|
|
118
|
+
*/
|
|
119
|
+
const STOPWORDS = new Set(
|
|
120
|
+
(
|
|
121
|
+
'a an and are as at be been but by can could did do does for from had has ' +
|
|
122
|
+
'have he her his how i if in into is it its may me might must my no not ' +
|
|
123
|
+
'of on or our she should so than that the their them then there these ' +
|
|
124
|
+
'they this those to was we were what when where which while who why will ' +
|
|
125
|
+
'with would you your'
|
|
126
|
+
).split(' '),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Content tokens of a sentence: tokens minus stopwords. Falls back to all
|
|
131
|
+
* tokens when nothing is left (e.g. "Yes."), so short sentences are still
|
|
132
|
+
* scored rather than skipped.
|
|
133
|
+
*
|
|
134
|
+
* @param {string[]} tokens
|
|
135
|
+
* @returns {string[]}
|
|
136
|
+
*/
|
|
137
|
+
function contentTokens(tokens) {
|
|
138
|
+
const content = tokens.filter((t) => !STOPWORDS.has(t));
|
|
139
|
+
return content.length > 0 ? content : tokens;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Values of English number words. "one" is left out on purpose: it is far
|
|
143
|
+
* more often a pronoun ("no one", "one of") than a quantity. */
|
|
144
|
+
const NUMBER_WORDS = {
|
|
145
|
+
zero: 0,
|
|
146
|
+
two: 2,
|
|
147
|
+
three: 3,
|
|
148
|
+
four: 4,
|
|
149
|
+
five: 5,
|
|
150
|
+
six: 6,
|
|
151
|
+
seven: 7,
|
|
152
|
+
eight: 8,
|
|
153
|
+
nine: 9,
|
|
154
|
+
ten: 10,
|
|
155
|
+
eleven: 11,
|
|
156
|
+
twelve: 12,
|
|
157
|
+
thirteen: 13,
|
|
158
|
+
fourteen: 14,
|
|
159
|
+
fifteen: 15,
|
|
160
|
+
sixteen: 16,
|
|
161
|
+
seventeen: 17,
|
|
162
|
+
eighteen: 18,
|
|
163
|
+
nineteen: 19,
|
|
164
|
+
twenty: 20,
|
|
165
|
+
thirty: 30,
|
|
166
|
+
forty: 40,
|
|
167
|
+
fifty: 50,
|
|
168
|
+
sixty: 60,
|
|
169
|
+
seventy: 70,
|
|
170
|
+
eighty: 80,
|
|
171
|
+
ninety: 90,
|
|
172
|
+
};
|
|
173
|
+
/** Multipliers; "one" counts as 1 only inside a run ("one hundred"). */
|
|
174
|
+
const NUMBER_SCALES = {
|
|
175
|
+
hundred: 100,
|
|
176
|
+
thousand: 1000,
|
|
177
|
+
million: 1e6,
|
|
178
|
+
billion: 1e9,
|
|
179
|
+
trillion: 1e12,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Digit numbers, optionally with thousands separators or a decimal part (a
|
|
184
|
+
* trailing "%" is ignored). The lookbehind skips digits glued to letters (BM25).
|
|
185
|
+
*/
|
|
186
|
+
const DIGIT_NUMBER_RE = /(?<![\p{L}\p{N}])\d+(?:[.,]\d+)*/gu;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Turn one run of number words into its value: "twenty five" -> 25,
|
|
190
|
+
* "two hundred" -> 200, "three thousand four hundred" -> 3400.
|
|
191
|
+
*
|
|
192
|
+
* @param {string[]} words
|
|
193
|
+
* @returns {number}
|
|
194
|
+
*/
|
|
195
|
+
function parseNumberWords(words) {
|
|
196
|
+
let total = 0;
|
|
197
|
+
let current = 0;
|
|
198
|
+
for (const w of words) {
|
|
199
|
+
if (w === 'one') current += 1;
|
|
200
|
+
else if (w in NUMBER_WORDS) current += NUMBER_WORDS[w];
|
|
201
|
+
else if (w === 'hundred') current = (current || 1) * 100;
|
|
202
|
+
else {
|
|
203
|
+
total += (current || 1) * NUMBER_SCALES[w];
|
|
204
|
+
current = 0;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return total + current;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Extract the numbers stated in a text, as normalised value strings, so
|
|
212
|
+
* "20", "20%", "twenty" and "Twenty" all yield "20", "1,000" yields "1000"
|
|
213
|
+
* and "2.5" yields "2.5".
|
|
214
|
+
*
|
|
215
|
+
* @param {string} text
|
|
216
|
+
* @returns {{ values: string[], parts: string[] }} values: one entry per
|
|
217
|
+
* number as written; parts: values plus each number word on its own
|
|
218
|
+
* (used on the document side so "twenty-five" also supports "20" and "5"
|
|
219
|
+
* is not required)
|
|
220
|
+
*/
|
|
221
|
+
function extractNumbers(text) {
|
|
222
|
+
const values = [];
|
|
223
|
+
const parts = [];
|
|
224
|
+
if (!text || typeof text !== 'string') return { values, parts };
|
|
225
|
+
|
|
226
|
+
for (const match of text.matchAll(DIGIT_NUMBER_RE)) {
|
|
227
|
+
const raw = match[0];
|
|
228
|
+
const pieces = /^\d{1,3}(,\d{3})+(\.\d+)?$/.test(raw)
|
|
229
|
+
? [raw.replace(/,/g, '')]
|
|
230
|
+
: raw.split(',');
|
|
231
|
+
for (const piece of pieces) {
|
|
232
|
+
const n = Number(piece);
|
|
233
|
+
if (Number.isFinite(n)) values.push(String(n));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const words = text.toLowerCase().split(/[^\p{L}]+/u);
|
|
238
|
+
let run = [];
|
|
239
|
+
const flush = () => {
|
|
240
|
+
// "and" only joins a run ("two hundred and five"); never ends one.
|
|
241
|
+
while (run.length && run[run.length - 1] === 'and') run.pop();
|
|
242
|
+
// A lone "one" is not treated as a number (see NUMBER_WORDS).
|
|
243
|
+
if (run.length && !(run.length === 1 && run[0] === 'one')) {
|
|
244
|
+
const numberWords = run.filter((w) => w !== 'and');
|
|
245
|
+
values.push(String(parseNumberWords(numberWords)));
|
|
246
|
+
for (const w of numberWords) {
|
|
247
|
+
if (w in NUMBER_WORDS) parts.push(String(NUMBER_WORDS[w]));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
run = [];
|
|
251
|
+
};
|
|
252
|
+
for (const w of words) {
|
|
253
|
+
const last = run[run.length - 1];
|
|
254
|
+
const isNumberWord =
|
|
255
|
+
w in NUMBER_WORDS ||
|
|
256
|
+
w in NUMBER_SCALES ||
|
|
257
|
+
w === 'one' ||
|
|
258
|
+
(w === 'and' && last in NUMBER_SCALES);
|
|
259
|
+
if (isNumberWord) {
|
|
260
|
+
run.push(w);
|
|
261
|
+
} else {
|
|
262
|
+
flush();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
flush();
|
|
266
|
+
|
|
267
|
+
return { values, parts: [...values, ...parts] };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Numeric consistency between a claim and a document: every number the claim
|
|
272
|
+
* states (digits, decimals, percentages or number words) must also appear in
|
|
273
|
+
* the document. Lexical overlap alone would ground "thirty days" against a
|
|
274
|
+
* source that says "twenty days".
|
|
275
|
+
*
|
|
276
|
+
* @param {string} claimText
|
|
277
|
+
* @param {string} docText
|
|
278
|
+
* @returns {{ consistent: boolean, claimNumbers: string[], missing: string[] }}
|
|
279
|
+
*/
|
|
280
|
+
function checkNumericConsistency(claimText, docText) {
|
|
281
|
+
const claimNumbers = [...new Set(extractNumbers(claimText).values)];
|
|
282
|
+
if (claimNumbers.length === 0) {
|
|
283
|
+
return { consistent: true, claimNumbers, missing: [] };
|
|
284
|
+
}
|
|
285
|
+
const docNumbers = new Set(extractNumbers(docText).parts);
|
|
286
|
+
const missing = claimNumbers.filter((n) => !docNumbers.has(n));
|
|
287
|
+
return { consistent: missing.length === 0, claimNumbers, missing };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Multiplier applied to the support score when a claim states numbers the
|
|
292
|
+
* document does not contain. Together with the share of matching numbers it
|
|
293
|
+
* keeps the score below 0.4, i.e. under the default 0.5 support threshold.
|
|
294
|
+
*/
|
|
295
|
+
const NUMERIC_MISMATCH_FACTOR = 0.4;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* How much of a claim is supported by a document: the (optionally
|
|
299
|
+
* IDF-weighted) fraction of the claim's unique content tokens that appear in
|
|
300
|
+
* the document. Unlike Jaccard against the whole document, this does not
|
|
301
|
+
* shrink as the document gets longer, so a sentence copied verbatim from a
|
|
302
|
+
* long chunk scores 1.
|
|
303
|
+
*
|
|
304
|
+
* Numeric consistency: when `options.claimText` and `options.docText` are
|
|
305
|
+
* given and the claim states a number the document does not contain (see
|
|
306
|
+
* checkNumericConsistency), the score becomes
|
|
307
|
+
* lexicalScore * NUMERIC_MISMATCH_FACTOR (0.4) * matchedNumbers / claimNumbers
|
|
308
|
+
* which is always below 0.4, so the claim fails the default 0.5 threshold.
|
|
309
|
+
*
|
|
310
|
+
* @param {string[]} claimTokens - Tokens of the sentence/claim/query
|
|
311
|
+
* @param {string[]|Set<string>} docTokens - Tokens of the document
|
|
312
|
+
* @param {Map<string, number>|null} [idfWeights] - Optional token weights
|
|
313
|
+
* @param {{ claimText?: string, docText?: string }} [options] - Raw texts
|
|
314
|
+
* enabling the numeric consistency check
|
|
315
|
+
* @returns {number} Support score between 0 and 1
|
|
316
|
+
*/
|
|
317
|
+
function computeSupportScore(
|
|
318
|
+
claimTokens,
|
|
319
|
+
docTokens,
|
|
320
|
+
idfWeights = null,
|
|
321
|
+
options = {},
|
|
322
|
+
) {
|
|
323
|
+
const claim = [...new Set(contentTokens(claimTokens))];
|
|
324
|
+
if (claim.length === 0) return 0;
|
|
325
|
+
const docSet = docTokens instanceof Set ? docTokens : new Set(docTokens);
|
|
326
|
+
|
|
327
|
+
let matched = 0;
|
|
328
|
+
let total = 0;
|
|
329
|
+
for (const token of claim) {
|
|
330
|
+
const weight = idfWeights ? (idfWeights.get(token) ?? 1) : 1;
|
|
331
|
+
total += weight;
|
|
332
|
+
if (docSet.has(token)) matched += weight;
|
|
333
|
+
}
|
|
334
|
+
let score = total > 0 ? matched / total : 0;
|
|
335
|
+
|
|
336
|
+
const { claimText, docText } = options || {};
|
|
337
|
+
if (
|
|
338
|
+
score > 0 &&
|
|
339
|
+
typeof claimText === 'string' &&
|
|
340
|
+
typeof docText === 'string'
|
|
341
|
+
) {
|
|
342
|
+
const numeric = checkNumericConsistency(claimText, docText);
|
|
343
|
+
if (!numeric.consistent) {
|
|
344
|
+
const share =
|
|
345
|
+
(numeric.claimNumbers.length - numeric.missing.length) /
|
|
346
|
+
numeric.claimNumbers.length;
|
|
347
|
+
score *= NUMERIC_MISMATCH_FACTOR * share;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return score;
|
|
351
|
+
}
|
|
352
|
+
|
|
111
353
|
module.exports = {
|
|
354
|
+
STOPWORDS,
|
|
355
|
+
contentTokens,
|
|
356
|
+
computeSupportScore,
|
|
357
|
+
checkNumericConsistency,
|
|
358
|
+
extractNumbers,
|
|
359
|
+
NUMERIC_MISMATCH_FACTOR,
|
|
112
360
|
scoreAnswer,
|
|
113
361
|
tokenize,
|
|
114
362
|
computeBLEU,
|