thumbgate 1.30.0 → 1.34.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/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +54 -16
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/mcp/server-stdio.js +105 -10
- package/adapters/opencode/opencode.json +1 -1
- package/bench/observability-eval-suite.json +2 -2
- package/bin/cli.js +168 -31
- package/config/evals/generation-quality-golden.json +95 -0
- package/config/evals/rag-answer-quality-golden.json +91 -0
- package/config/evals/retrieval-hybrid-ablation.json +66 -0
- package/config/evals/retrieval-ranking-golden.json +522 -0
- package/config/gates/claim-verifiers.example.json +42 -0
- package/config/gates/claim-verifiers.json +25 -0
- package/config/gates/default.json +217 -50
- package/config/mcp-allowlists.json +233 -206
- package/config/model-tiers.json +7 -2
- package/glama.json +6 -0
- package/hooks/hooks.json +1 -1
- package/package.json +69 -12
- package/public/assets/diagrams/before-after.svg +17 -16
- package/public/assets/diagrams/hero-thumbs.svg +68 -0
- package/public/assets/diagrams/loop.svg +19 -13
- package/public/assets/diagrams/self-improving-thumbs-loop.svg +105 -0
- package/public/compare.html +1 -0
- package/public/dashboard.html +126 -28
- package/public/evaluations.html +1 -1
- package/public/index.html +142 -13
- package/public/numbers.html +3 -2
- package/public/pricing.html +143 -30
- package/scripts/a-plus-evidence-scorecard.js +303 -0
- package/scripts/agent-readiness.js +110 -0
- package/scripts/async-eval-observability.js +36 -11
- package/scripts/audit-trail.js +37 -1
- package/scripts/auto-promote-gates.js +149 -34
- package/scripts/auto-wire-hooks.js +20 -8
- package/scripts/cli-schema.js +14 -0
- package/scripts/colbert-style-maxsim.js +236 -0
- package/scripts/cross-encoder-reranker.js +356 -126
- package/scripts/dashboard-chat.js +350 -17
- package/scripts/document-intake.js +283 -7
- package/scripts/eval-quality-suite.js +204 -0
- package/scripts/feedback-loop.js +115 -7
- package/scripts/feedback-paths.js +32 -13
- package/scripts/feedback-quality.js +53 -0
- package/scripts/feedback-schema.js +3 -0
- package/scripts/file-ledger-lock.js +130 -0
- package/scripts/filesystem-search.js +17 -7
- package/scripts/financial-control-plane.js +1514 -0
- package/scripts/gates-engine.js +202 -7
- package/scripts/gemini-embedding-policy.js +1 -0
- package/scripts/harness-tool-names.js +70 -0
- package/scripts/hook-runtime.js +15 -3
- package/scripts/hook-stop-anti-claim.js +63 -3
- package/scripts/human-escalation.js +353 -41
- package/scripts/lesson-db.js +16 -5
- package/scripts/lesson-embedding-index.js +67 -20
- package/scripts/lesson-embedding-maintenance.js +177 -0
- package/scripts/lesson-reranker.js +55 -9
- package/scripts/lesson-retrieval.js +305 -29
- package/scripts/lesson-search.js +22 -8
- package/scripts/llm-client.js +304 -15
- package/scripts/model-tier-router.js +593 -0
- package/scripts/pragmatic-hybrid-search.js +379 -0
- package/scripts/provider-action-normalizer.js +11 -4
- package/scripts/rag-document-pipeline.js +461 -0
- package/scripts/rag-structured-output.js +441 -0
- package/scripts/ragas-style-metrics.js +351 -0
- package/scripts/request-envelope.js +178 -0
- package/scripts/rerank-pipeline.js +370 -0
- package/scripts/rerank-quality-eval.js +155 -0
- package/scripts/retrieval-hybrid-ablation.js +120 -0
- package/scripts/retrieval-quality-tier.js +118 -0
- package/scripts/secret-scanner.js +395 -4
- package/scripts/self-distill-agent.js +7 -1
- package/scripts/self-healing-check.js +25 -0
- package/scripts/skill-packs.js +183 -0
- package/scripts/slow-loop.js +72 -0
- package/scripts/statusline-links.js +1 -1
- package/scripts/statusline.sh +8 -1
- package/scripts/telemetry-analytics.js +13 -1
- package/scripts/thumbgate-search.js +98 -6
- package/scripts/tier-budget-guard.js +186 -0
- package/scripts/tool-registry.js +141 -5
- package/scripts/universal-claim-evaluator.js +767 -0
- package/scripts/vector-store.js +154 -17
- package/scripts/verify-marketing-pages-deployed.js +85 -3
- package/scripts/workflow-sentinel.js +77 -11
- package/server.json +44 -0
- package/smithery.yaml +17 -0
- package/src/api/server.js +196 -13
|
@@ -17,6 +17,7 @@ const fs = require('fs');
|
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const {
|
|
19
19
|
cacheUpdateHookCommand,
|
|
20
|
+
claimStopHookCommand,
|
|
20
21
|
codexCacheUpdateHookCommand,
|
|
21
22
|
codexPreToolHookCommand,
|
|
22
23
|
codexSessionStartHookCommand,
|
|
@@ -36,7 +37,7 @@ function getHome() {
|
|
|
36
37
|
// --- Hook definitions ---
|
|
37
38
|
const CLAUDE_HOOKS = {
|
|
38
39
|
PreToolUse: {
|
|
39
|
-
matcher: '
|
|
40
|
+
matcher: '.*',
|
|
40
41
|
hooks: [{ type: 'command', command: preToolHookCommand() }],
|
|
41
42
|
},
|
|
42
43
|
UserPromptSubmit: {
|
|
@@ -49,11 +50,14 @@ const CLAUDE_HOOKS = {
|
|
|
49
50
|
SessionStart: {
|
|
50
51
|
hooks: [{ type: 'command', command: sessionStartHookCommand() }],
|
|
51
52
|
},
|
|
53
|
+
Stop: {
|
|
54
|
+
hooks: [{ type: 'command', command: claimStopHookCommand() }],
|
|
55
|
+
},
|
|
52
56
|
};
|
|
53
57
|
|
|
54
58
|
const CODEX_HOOKS = {
|
|
55
59
|
PreToolUse: {
|
|
56
|
-
matcher: '
|
|
60
|
+
matcher: '.*',
|
|
57
61
|
hooks: [{ type: 'command', command: codexPreToolHookCommand() }],
|
|
58
62
|
},
|
|
59
63
|
UserPromptSubmit: {
|
|
@@ -384,6 +388,7 @@ function wireClaudeHooks(options) {
|
|
|
384
388
|
UserPromptSubmit: /(hook-auto-capture\.sh|hook-auto-capture\b)/,
|
|
385
389
|
PostToolUse: /(hook-thumbgate-cache-updater|cache-update\b)/,
|
|
386
390
|
SessionStart: /(thumbgate_session_start\.sh|session-start\b)/,
|
|
391
|
+
Stop: /(hook-stop-anti-claim\.js|claim-stop-check\b)/,
|
|
387
392
|
};
|
|
388
393
|
|
|
389
394
|
for (const [lifecycle, hookDef] of Object.entries(CLAUDE_HOOKS)) {
|
|
@@ -456,8 +461,13 @@ function codexTomlConfigPath(configPath = codexConfigPath()) {
|
|
|
456
461
|
return path.join(path.dirname(configPath), 'config.toml');
|
|
457
462
|
}
|
|
458
463
|
|
|
464
|
+
function escapeRegexLiteral(value) {
|
|
465
|
+
return String(value).replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
466
|
+
}
|
|
467
|
+
|
|
459
468
|
function tomlSectionRegex(name) {
|
|
460
|
-
|
|
469
|
+
const sectionName = escapeRegexLiteral(name);
|
|
470
|
+
return new RegExp(String.raw`^\[${sectionName}\]\n(?:^(?!\[).*(?:\n|$))*`, 'm');
|
|
461
471
|
}
|
|
462
472
|
|
|
463
473
|
function codexUserPromptTomlBlock() {
|
|
@@ -470,10 +480,11 @@ function upsertCodexUserPromptToml(configPath, dryRun = false) {
|
|
|
470
480
|
const current = fs.existsSync(tomlPath) ? fs.readFileSync(tomlPath, 'utf8') : '';
|
|
471
481
|
const canonicalBlock = codexUserPromptTomlBlock();
|
|
472
482
|
const sectionRegex = tomlSectionRegex('hooks.user_prompt_submit');
|
|
473
|
-
|
|
483
|
+
const existingMatch = sectionRegex.exec(current);
|
|
484
|
+
let next;
|
|
474
485
|
|
|
475
|
-
if (
|
|
476
|
-
const existingBlock =
|
|
486
|
+
if (existingMatch) {
|
|
487
|
+
const existingBlock = existingMatch[0];
|
|
477
488
|
if (existingBlock === canonicalBlock) {
|
|
478
489
|
return { changed: false, settingsPath: tomlPath };
|
|
479
490
|
}
|
|
@@ -610,7 +621,7 @@ function wireGeminiHooks(options) {
|
|
|
610
621
|
if (!hookAlreadyPresent(settings.hooks.PreToolUse, preToolCmd)) {
|
|
611
622
|
settings.hooks.PreToolUse = settings.hooks.PreToolUse || [];
|
|
612
623
|
settings.hooks.PreToolUse.push({
|
|
613
|
-
matcher: '
|
|
624
|
+
matcher: '.*',
|
|
614
625
|
hooks: [{ type: 'command', command: preToolCmd }],
|
|
615
626
|
});
|
|
616
627
|
added.push({ lifecycle: 'PreToolUse', command: preToolCmd });
|
|
@@ -661,7 +672,7 @@ function wireForgeHooks(options) {
|
|
|
661
672
|
if (!hookAlreadyPresent(existing.hooks.PreToolUse, preToolCmd)) {
|
|
662
673
|
existing.hooks.PreToolUse = existing.hooks.PreToolUse || [];
|
|
663
674
|
existing.hooks.PreToolUse.push({
|
|
664
|
-
matcher: '
|
|
675
|
+
matcher: '.*',
|
|
665
676
|
hooks: [{ type: 'command', command: preToolCmd }],
|
|
666
677
|
});
|
|
667
678
|
added.push({ lifecycle: 'PreToolUse', command: preToolCmd });
|
|
@@ -765,6 +776,7 @@ module.exports = {
|
|
|
765
776
|
pruneStaleHooksInFile,
|
|
766
777
|
CLAUDE_HOOKS,
|
|
767
778
|
preToolHookCommand,
|
|
779
|
+
claimStopHookCommand,
|
|
768
780
|
userPromptHookCommand,
|
|
769
781
|
sessionStartHookCommand,
|
|
770
782
|
};
|
package/scripts/cli-schema.js
CHANGED
|
@@ -505,6 +505,20 @@ const CLI_COMMANDS = [
|
|
|
505
505
|
group: 'gates',
|
|
506
506
|
flags: [],
|
|
507
507
|
},
|
|
508
|
+
{
|
|
509
|
+
name: 'verify-claims',
|
|
510
|
+
aliases: ['verify-claim'],
|
|
511
|
+
description: 'Recheck factual claims against configured SQLite, filesystem, and JSON sources',
|
|
512
|
+
group: 'gates',
|
|
513
|
+
mcpTool: 'verify_claim',
|
|
514
|
+
flags: [
|
|
515
|
+
{ name: 'claim', type: 'string', required: true, description: 'Factual claim text to verify' },
|
|
516
|
+
{ name: 'config', type: 'string', description: 'Verifier config path (default .thumbgate/claim-verifiers.json)' },
|
|
517
|
+
{ name: 'cwd', type: 'string', description: 'Root directory that contains configured sources' },
|
|
518
|
+
{ name: 'advisory', type: 'boolean', description: 'Do not fail an otherwise parseable claim only because no verifier is configured' },
|
|
519
|
+
{ name: 'json', type: 'boolean', description: 'Output a machine-readable verdict' },
|
|
520
|
+
],
|
|
521
|
+
},
|
|
508
522
|
{
|
|
509
523
|
name: 'hermes-gate',
|
|
510
524
|
description: 'Hermes Agent pre_tool_call hook: gate runtime tool calls (incl. skill_manage) before they run',
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ColBERT-style late interaction (MaxSim) for ThumbGate lesson reranking.
|
|
5
|
+
*
|
|
6
|
+
* This is NOT a pretrained ColBERT neural model. It implements the *interaction
|
|
7
|
+
* pattern* ColBERT made famous:
|
|
8
|
+
* - encode query and document as multi-vector bags (one vector per token)
|
|
9
|
+
* - score with MaxSim: sum over query tokens of max cosine vs any doc token
|
|
10
|
+
*
|
|
11
|
+
* Token vectors are deterministic hashed character-n-gram projections (local-only,
|
|
12
|
+
* no GPU, no network). That gives late interaction without shipping a 100MB model
|
|
13
|
+
* in the npm package — while remaining honest about model provenance.
|
|
14
|
+
*
|
|
15
|
+
* For true neural ColBERT, operators can plug vectors via `tokenEmbedder`.
|
|
16
|
+
*
|
|
17
|
+
* @see https://arxiv.org/abs/2004.12832 (ColBERT MaxSim)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const DEFAULT_DIM = 32;
|
|
21
|
+
const DEFAULT_NGRAM = 3;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Deterministic 32-bit hash (FNV-1a style) for seedable projections.
|
|
25
|
+
* @param {string} str
|
|
26
|
+
* @returns {number}
|
|
27
|
+
*/
|
|
28
|
+
function hash32(str) {
|
|
29
|
+
let h = 0x811c9dc5;
|
|
30
|
+
for (let i = 0; i < str.length; i += 1) {
|
|
31
|
+
h ^= str.charCodeAt(i);
|
|
32
|
+
h = Math.imul(h, 0x01000193);
|
|
33
|
+
}
|
|
34
|
+
return h >>> 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tokenize into lowercase word tokens (length >= 2).
|
|
39
|
+
* @param {string} text
|
|
40
|
+
* @returns {string[]}
|
|
41
|
+
*/
|
|
42
|
+
function tokenize(text) {
|
|
43
|
+
if (!text) return [];
|
|
44
|
+
return String(text)
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
.replace(/[^\w\s]/g, ' ')
|
|
47
|
+
.split(/[\s_]+/)
|
|
48
|
+
.filter((t) => t.length >= 2);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Character n-grams for a token (with edge markers).
|
|
53
|
+
* @param {string} token
|
|
54
|
+
* @param {number} n
|
|
55
|
+
* @returns {string[]}
|
|
56
|
+
*/
|
|
57
|
+
function charNgrams(token, n = DEFAULT_NGRAM) {
|
|
58
|
+
const padded = `#${token}#`;
|
|
59
|
+
if (padded.length < n) return [padded];
|
|
60
|
+
const grams = [];
|
|
61
|
+
for (let i = 0; i <= padded.length - n; i += 1) {
|
|
62
|
+
grams.push(padded.slice(i, i + n));
|
|
63
|
+
}
|
|
64
|
+
return grams;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Build a unit-length multi-dim embedding for one token via hashed n-grams.
|
|
69
|
+
* @param {string} token
|
|
70
|
+
* @param {{ dim?: number, ngram?: number }} [opts]
|
|
71
|
+
* @returns {Float64Array}
|
|
72
|
+
*/
|
|
73
|
+
function embedToken(token, opts = {}) {
|
|
74
|
+
const dim = opts.dim ?? DEFAULT_DIM;
|
|
75
|
+
const ngram = opts.ngram ?? DEFAULT_NGRAM;
|
|
76
|
+
const vec = new Float64Array(dim);
|
|
77
|
+
for (const gram of charNgrams(token, ngram)) {
|
|
78
|
+
const h = hash32(gram);
|
|
79
|
+
const idx = h % dim;
|
|
80
|
+
const sign = (h & 1) === 0 ? 1 : -1;
|
|
81
|
+
vec[idx] += sign;
|
|
82
|
+
// Second hash for denser projection (locality-sensitive bag)
|
|
83
|
+
const h2 = hash32(`${gram}:2`);
|
|
84
|
+
vec[h2 % dim] += ((h2 >> 1) & 1) === 0 ? 0.5 : -0.5;
|
|
85
|
+
}
|
|
86
|
+
// L2 normalize
|
|
87
|
+
let norm = 0;
|
|
88
|
+
for (let i = 0; i < dim; i += 1) norm += vec[i] * vec[i];
|
|
89
|
+
norm = Math.sqrt(norm) || 1;
|
|
90
|
+
for (let i = 0; i < dim; i += 1) vec[i] /= norm;
|
|
91
|
+
return vec;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Cosine similarity for unit vectors (dot product).
|
|
96
|
+
* @param {Float64Array|number[]} a
|
|
97
|
+
* @param {Float64Array|number[]} b
|
|
98
|
+
* @returns {number}
|
|
99
|
+
*/
|
|
100
|
+
function cosine(a, b) {
|
|
101
|
+
const n = Math.min(a.length, b.length);
|
|
102
|
+
let s = 0;
|
|
103
|
+
for (let i = 0; i < n; i += 1) s += a[i] * b[i];
|
|
104
|
+
return s;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Encode text as a bag of token vectors (ColBERT multi-vector representation).
|
|
109
|
+
* @param {string} text
|
|
110
|
+
* @param {{ dim?: number, ngram?: number, maxTokens?: number, tokenEmbedder?: (t: string) => Float64Array|number[] }} [opts]
|
|
111
|
+
* @returns {{ tokens: string[], vectors: Array<Float64Array|number[]> }}
|
|
112
|
+
*/
|
|
113
|
+
function encodeMultiVector(text, opts = {}) {
|
|
114
|
+
const maxTokens = opts.maxTokens ?? 64;
|
|
115
|
+
const tokens = tokenize(text).slice(0, maxTokens);
|
|
116
|
+
const embedder = opts.tokenEmbedder || ((t) => embedToken(t, opts));
|
|
117
|
+
const vectors = tokens.map((t) => embedder(t));
|
|
118
|
+
return { tokens, vectors };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* ColBERT MaxSim: Σ_i max_j cos(q_i, d_j), normalized by |Q|.
|
|
123
|
+
* @param {Array<Float64Array|number[]>} queryVectors
|
|
124
|
+
* @param {Array<Float64Array|number[]>} docVectors
|
|
125
|
+
* @returns {number} score in [0, 1] approximately
|
|
126
|
+
*/
|
|
127
|
+
function maxSim(queryVectors, docVectors) {
|
|
128
|
+
if (!queryVectors.length || !docVectors.length) return 0;
|
|
129
|
+
let total = 0;
|
|
130
|
+
for (const q of queryVectors) {
|
|
131
|
+
let best = -1;
|
|
132
|
+
for (const d of docVectors) {
|
|
133
|
+
const c = cosine(q, d);
|
|
134
|
+
if (c > best) best = c;
|
|
135
|
+
}
|
|
136
|
+
total += Math.max(0, best);
|
|
137
|
+
}
|
|
138
|
+
// Normalize by query length so longer queries don't dominate
|
|
139
|
+
return Math.min(1, total / queryVectors.length);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Score a (query, document) pair with ColBERT-style late interaction.
|
|
144
|
+
* @param {string} query
|
|
145
|
+
* @param {string} document
|
|
146
|
+
* @param {object} [opts]
|
|
147
|
+
* @returns {{ score: number, queryTokens: string[], docTokens: string[], mode: string }}
|
|
148
|
+
*/
|
|
149
|
+
function scoreLateInteraction(query, document, opts = {}) {
|
|
150
|
+
const q = encodeMultiVector(query, opts);
|
|
151
|
+
const d = encodeMultiVector(document, opts);
|
|
152
|
+
const score = maxSim(q.vectors, d.vectors);
|
|
153
|
+
return {
|
|
154
|
+
score: Number(score.toFixed(6)),
|
|
155
|
+
queryTokens: q.tokens,
|
|
156
|
+
docTokens: d.tokens,
|
|
157
|
+
mode: opts.tokenEmbedder ? 'colbert-style-external' : 'colbert-style-hash',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Rerank candidates by MaxSim late interaction.
|
|
163
|
+
* @param {string} query
|
|
164
|
+
* @param {Array<object>} candidates
|
|
165
|
+
* @param {{ topK?: number, textOf?: (c: object) => string, blendWeight?: number }} [options]
|
|
166
|
+
* @returns {Array<object>} candidates with maxSimScore + optional blend into rerankedScore
|
|
167
|
+
*/
|
|
168
|
+
function rerankWithMaxSim(query, candidates, options = {}) {
|
|
169
|
+
const {
|
|
170
|
+
topK = 5,
|
|
171
|
+
textOf = defaultTextOf,
|
|
172
|
+
blendWeight = 0.55,
|
|
173
|
+
dim,
|
|
174
|
+
ngram,
|
|
175
|
+
maxTokens,
|
|
176
|
+
tokenEmbedder,
|
|
177
|
+
} = options;
|
|
178
|
+
|
|
179
|
+
if (!candidates || candidates.length === 0) return [];
|
|
180
|
+
if (candidates.length === 1) {
|
|
181
|
+
const only = candidates[0];
|
|
182
|
+
return [{
|
|
183
|
+
...only,
|
|
184
|
+
maxSimScore: 1,
|
|
185
|
+
rerankedScore: only.rerankedScore ?? only.relevanceScore ?? 1,
|
|
186
|
+
lateInteractionMode: 'trivial',
|
|
187
|
+
}].slice(0, topK);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const qEnc = encodeMultiVector(query, { dim, ngram, maxTokens, tokenEmbedder });
|
|
191
|
+
const scored = candidates.map((c) => {
|
|
192
|
+
const docText = textOf(c);
|
|
193
|
+
const dEnc = encodeMultiVector(docText, { dim, ngram, maxTokens, tokenEmbedder });
|
|
194
|
+
const ms = maxSim(qEnc.vectors, dEnc.vectors);
|
|
195
|
+
const orig = Number(c.rerankedScore ?? c.relevanceScore ?? c.score ?? 0);
|
|
196
|
+
const blended = blendWeight * ms + (1 - blendWeight) * Math.max(0, Math.min(1, orig));
|
|
197
|
+
return {
|
|
198
|
+
...c,
|
|
199
|
+
maxSimScore: Number(ms.toFixed(6)),
|
|
200
|
+
rerankedScore: Number(blended.toFixed(6)),
|
|
201
|
+
lateInteractionMode: tokenEmbedder ? 'colbert-style-external' : 'colbert-style-hash',
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
return scored
|
|
206
|
+
.sort((a, b) => (b.rerankedScore || 0) - (a.rerankedScore || 0))
|
|
207
|
+
.slice(0, topK);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function defaultTextOf(candidate) {
|
|
211
|
+
if (!candidate || typeof candidate !== 'object') return String(candidate || '');
|
|
212
|
+
return [
|
|
213
|
+
candidate.title,
|
|
214
|
+
candidate.whatWentWrong,
|
|
215
|
+
candidate.whatToChange,
|
|
216
|
+
candidate.howToAvoid,
|
|
217
|
+
candidate.summary,
|
|
218
|
+
candidate.content,
|
|
219
|
+
candidate.context,
|
|
220
|
+
Array.isArray(candidate.tags) ? candidate.tags.join(' ') : '',
|
|
221
|
+
].filter(Boolean).join(' ');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = {
|
|
225
|
+
hash32,
|
|
226
|
+
tokenize,
|
|
227
|
+
charNgrams,
|
|
228
|
+
embedToken,
|
|
229
|
+
cosine,
|
|
230
|
+
encodeMultiVector,
|
|
231
|
+
maxSim,
|
|
232
|
+
scoreLateInteraction,
|
|
233
|
+
rerankWithMaxSim,
|
|
234
|
+
DEFAULT_DIM,
|
|
235
|
+
DEFAULT_NGRAM,
|
|
236
|
+
};
|