@memberjunction/search-engine 5.33.0 → 5.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/dist/generic/BaseReRanker.d.ts +164 -0
- package/dist/generic/BaseReRanker.d.ts.map +1 -0
- package/dist/generic/BaseReRanker.js +209 -0
- package/dist/generic/BaseReRanker.js.map +1 -0
- package/dist/generic/EntitySearchProvider.d.ts +36 -4
- package/dist/generic/EntitySearchProvider.d.ts.map +1 -1
- package/dist/generic/EntitySearchProvider.js +95 -19
- package/dist/generic/EntitySearchProvider.js.map +1 -1
- package/dist/generic/FullTextSearchProvider.d.ts +2 -2
- package/dist/generic/FullTextSearchProvider.d.ts.map +1 -1
- package/dist/generic/FullTextSearchProvider.js +14 -3
- package/dist/generic/FullTextSearchProvider.js.map +1 -1
- package/dist/generic/ISearchProvider.d.ts +44 -2
- package/dist/generic/ISearchProvider.d.ts.map +1 -1
- package/dist/generic/ISearchProvider.js +35 -1
- package/dist/generic/ISearchProvider.js.map +1 -1
- package/dist/generic/NoopReRanker.d.ts +28 -0
- package/dist/generic/NoopReRanker.d.ts.map +1 -0
- package/dist/generic/NoopReRanker.js +49 -0
- package/dist/generic/NoopReRanker.js.map +1 -0
- package/dist/generic/ScopeTemplateRenderer.d.ts +36 -0
- package/dist/generic/ScopeTemplateRenderer.d.ts.map +1 -0
- package/dist/generic/ScopeTemplateRenderer.js +110 -0
- package/dist/generic/ScopeTemplateRenderer.js.map +1 -0
- package/dist/generic/SearchEngine.d.ts +154 -12
- package/dist/generic/SearchEngine.d.ts.map +1 -1
- package/dist/generic/SearchEngine.js +662 -39
- package/dist/generic/SearchEngine.js.map +1 -1
- package/dist/generic/SearchFusion.d.ts +40 -6
- package/dist/generic/SearchFusion.d.ts.map +1 -1
- package/dist/generic/SearchFusion.js +139 -18
- package/dist/generic/SearchFusion.js.map +1 -1
- package/dist/generic/StorageSearchProvider.d.ts +9 -2
- package/dist/generic/StorageSearchProvider.d.ts.map +1 -1
- package/dist/generic/StorageSearchProvider.js +44 -12
- package/dist/generic/StorageSearchProvider.js.map +1 -1
- package/dist/generic/VectorSearchProvider.d.ts +9 -2
- package/dist/generic/VectorSearchProvider.d.ts.map +1 -1
- package/dist/generic/VectorSearchProvider.js +83 -13
- package/dist/generic/VectorSearchProvider.js.map +1 -1
- package/dist/generic/search.types.d.ts +206 -0
- package/dist/generic/search.types.d.ts.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/permissions/SearchScopePermissionResolver.d.ts +109 -0
- package/dist/permissions/SearchScopePermissionResolver.d.ts.map +1 -0
- package/dist/permissions/SearchScopePermissionResolver.js +159 -0
- package/dist/permissions/SearchScopePermissionResolver.js.map +1 -0
- package/dist/providers/AzureAISearchProvider.d.ts +37 -0
- package/dist/providers/AzureAISearchProvider.d.ts.map +1 -0
- package/dist/providers/AzureAISearchProvider.js +180 -0
- package/dist/providers/AzureAISearchProvider.js.map +1 -0
- package/dist/providers/ElasticsearchSearchProvider.d.ts +43 -0
- package/dist/providers/ElasticsearchSearchProvider.d.ts.map +1 -0
- package/dist/providers/ElasticsearchSearchProvider.js +200 -0
- package/dist/providers/ElasticsearchSearchProvider.js.map +1 -0
- package/dist/providers/OpenSearchSearchProvider.d.ts +36 -0
- package/dist/providers/OpenSearchSearchProvider.d.ts.map +1 -0
- package/dist/providers/OpenSearchSearchProvider.js +167 -0
- package/dist/providers/OpenSearchSearchProvider.js.map +1 -0
- package/dist/providers/TypesenseSearchProvider.d.ts +36 -0
- package/dist/providers/TypesenseSearchProvider.d.ts.map +1 -0
- package/dist/providers/TypesenseSearchProvider.js +161 -0
- package/dist/providers/TypesenseSearchProvider.js.map +1 -0
- package/dist/rerankers/BGEReRanker.d.ts +57 -0
- package/dist/rerankers/BGEReRanker.d.ts.map +1 -0
- package/dist/rerankers/BGEReRanker.js +193 -0
- package/dist/rerankers/BGEReRanker.js.map +1 -0
- package/dist/rerankers/CohereReRanker.d.ts +65 -0
- package/dist/rerankers/CohereReRanker.d.ts.map +1 -0
- package/dist/rerankers/CohereReRanker.js +155 -0
- package/dist/rerankers/CohereReRanker.js.map +1 -0
- package/dist/rerankers/OpenAIReRanker.d.ts +62 -0
- package/dist/rerankers/OpenAIReRanker.d.ts.map +1 -0
- package/dist/rerankers/OpenAIReRanker.js +197 -0
- package/dist/rerankers/OpenAIReRanker.js.map +1 -0
- package/dist/rerankers/RerankerBudgetGuard.d.ts +54 -0
- package/dist/rerankers/RerankerBudgetGuard.d.ts.map +1 -0
- package/dist/rerankers/RerankerBudgetGuard.js +67 -0
- package/dist/rerankers/RerankerBudgetGuard.js.map +1 -0
- package/dist/rerankers/VoyageReRanker.d.ts +59 -0
- package/dist/rerankers/VoyageReRanker.d.ts.map +1 -0
- package/dist/rerankers/VoyageReRanker.js +184 -0
- package/dist/rerankers/VoyageReRanker.js.map +1 -0
- package/package.json +13 -8
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview OpenAI chat-judge re-ranker (P2D.4).
|
|
3
|
+
*
|
|
4
|
+
* **Decision (2026-04-28):** OpenAI does not publish a first-party reranker endpoint.
|
|
5
|
+
* Their offerings are completions, chat, embeddings, and moderation — there is no
|
|
6
|
+
* dedicated `/rerank`. This implementation uses the documented fallback from the plan:
|
|
7
|
+
* a small chat-completion-based "judge" that scores each candidate's relevance to the
|
|
8
|
+
* query and returns the new ordering. Re-evaluate this decision when OpenAI ships a
|
|
9
|
+
* first-party reranker — the canonical plan calls out swapping in the first-party
|
|
10
|
+
* endpoint as soon as it exists.
|
|
11
|
+
*
|
|
12
|
+
* **Approach.** Single chat completion call, JSON-mode response. The model receives
|
|
13
|
+
* the query and a numbered list of documents and returns
|
|
14
|
+
* `{ "scores": [<number 0..1>, ...] }` with one score per document, in input order.
|
|
15
|
+
* We then sort by score, slice to topN, and return.
|
|
16
|
+
*
|
|
17
|
+
* **Cost.** `gpt-4o-mini` defaults: $0.15 / 1M input tokens, $0.60 / 1M output tokens
|
|
18
|
+
* (2026-04 pricing). Pre-call estimate uses a conservative ~80 tokens/doc input plus
|
|
19
|
+
* 8 tokens/doc output. Post-call cost reports the exact `usage.{prompt,completion}_tokens`
|
|
20
|
+
* from OpenAI so the budget guard sees real spend.
|
|
21
|
+
*
|
|
22
|
+
* **API key.** `GetAIAPIKey('OpenAILLM')` —
|
|
23
|
+
* AI_VENDOR_API_KEY__OPENAILLM or any registered AIAPIKeys subclass.
|
|
24
|
+
*
|
|
25
|
+
* @module @memberjunction/search-engine
|
|
26
|
+
*/
|
|
27
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
28
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
29
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
30
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
31
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
32
|
+
};
|
|
33
|
+
var OpenAIReRanker_1;
|
|
34
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
35
|
+
import { LogError } from '@memberjunction/core';
|
|
36
|
+
import { BaseReranker as AIBaseReranker, GetAIAPIKey, } from '@memberjunction/ai';
|
|
37
|
+
import { BaseReRanker } from '../generic/BaseReRanker.js';
|
|
38
|
+
const OPENAI_CHAT_URL = 'https://api.openai.com/v1/chat/completions';
|
|
39
|
+
const SYSTEM_PROMPT = [
|
|
40
|
+
'You are a precise relevance scorer for a search system.',
|
|
41
|
+
'Given a user query and a numbered list of candidate documents, return a JSON object',
|
|
42
|
+
'with a single key "scores" whose value is an array of numbers. The array must have',
|
|
43
|
+
'exactly one entry per input document, in the same order, where each entry is a',
|
|
44
|
+
'relevance score in [0, 1] representing how well that document answers the query.',
|
|
45
|
+
'Use the full range — do not cluster all scores in one band. Output JSON only.',
|
|
46
|
+
].join('\n');
|
|
47
|
+
class OpenAIScopeAIReranker extends AIBaseReranker {
|
|
48
|
+
constructor(apiKey, modelName) {
|
|
49
|
+
super(apiKey, modelName);
|
|
50
|
+
/** Last call's prompt+completion tokens, populated by `doRerank`. */
|
|
51
|
+
this.lastPromptTokens = 0;
|
|
52
|
+
this.lastCompletionTokens = 0;
|
|
53
|
+
}
|
|
54
|
+
async doRerank(params) {
|
|
55
|
+
const docList = params.documents
|
|
56
|
+
.map((d, i) => `[${i}] ${d.text}`)
|
|
57
|
+
.join('\n');
|
|
58
|
+
const userPrompt = `Query:\n${params.query}\n\nDocuments:\n${docList}`;
|
|
59
|
+
const response = await fetch(OPENAI_CHAT_URL, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: {
|
|
62
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
model: this._modelName,
|
|
67
|
+
messages: [
|
|
68
|
+
{ role: 'system', content: SYSTEM_PROMPT },
|
|
69
|
+
{ role: 'user', content: userPrompt },
|
|
70
|
+
],
|
|
71
|
+
response_format: { type: 'json_object' },
|
|
72
|
+
temperature: 0,
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const text = await response.text().catch(() => '');
|
|
77
|
+
throw new Error(`OpenAI rerank-judge call failed (${response.status}): ${text || response.statusText}`);
|
|
78
|
+
}
|
|
79
|
+
const json = await response.json();
|
|
80
|
+
this.lastPromptTokens = json.usage?.prompt_tokens ?? 0;
|
|
81
|
+
this.lastCompletionTokens = json.usage?.completion_tokens ?? 0;
|
|
82
|
+
const content = json.choices[0]?.message.content ?? '';
|
|
83
|
+
const parsed = JSON.parse(content);
|
|
84
|
+
if (!Array.isArray(parsed.scores)) {
|
|
85
|
+
throw new Error(`OpenAI rerank-judge returned malformed scores: ${content}`);
|
|
86
|
+
}
|
|
87
|
+
const scores = parsed.scores;
|
|
88
|
+
if (scores.length !== params.documents.length) {
|
|
89
|
+
throw new Error(`OpenAI rerank-judge returned ${scores.length} scores for ${params.documents.length} documents`);
|
|
90
|
+
}
|
|
91
|
+
// Build (doc, score) pairs, sort descending by score, optionally slice topK.
|
|
92
|
+
const pairs = params.documents.map((d, i) => ({
|
|
93
|
+
doc: d,
|
|
94
|
+
score: typeof scores[i] === 'number' ? scores[i] : 0,
|
|
95
|
+
}));
|
|
96
|
+
pairs.sort((a, b) => b.score - a.score);
|
|
97
|
+
const topK = params.topK ?? pairs.length;
|
|
98
|
+
return pairs.slice(0, topK).map((p, idx) => ({
|
|
99
|
+
id: p.doc.id,
|
|
100
|
+
relevanceScore: p.score,
|
|
101
|
+
document: p.doc,
|
|
102
|
+
rank: idx,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Chat-completion-based "judge" reranker. Configure per scope via
|
|
108
|
+
* `SearchScope.ScopeConfig.reRanker.driverClass = 'OpenAIReRanker'`.
|
|
109
|
+
*
|
|
110
|
+
* Override the model via `ScopeConfig.reRanker.config.model`
|
|
111
|
+
* (default: `gpt-4o-mini` — change when a first-party OpenAI rerank endpoint launches).
|
|
112
|
+
*/
|
|
113
|
+
let OpenAIReRanker = class OpenAIReRanker extends BaseReRanker {
|
|
114
|
+
constructor() {
|
|
115
|
+
super(...arguments);
|
|
116
|
+
this.currentAIReranker = null;
|
|
117
|
+
}
|
|
118
|
+
static { OpenAIReRanker_1 = this; }
|
|
119
|
+
/** Pricing in cents-per-token. As of 2026-04. */
|
|
120
|
+
static { this.PRICING = {
|
|
121
|
+
'gpt-4o-mini': { input: 0.15 / 1_000_000 * 100, output: 0.60 / 1_000_000 * 100 },
|
|
122
|
+
'gpt-4o': { input: 2.50 / 1_000_000 * 100, output: 10.00 / 1_000_000 * 100 },
|
|
123
|
+
'gpt-4.1-mini': { input: 0.40 / 1_000_000 * 100, output: 1.60 / 1_000_000 * 100 },
|
|
124
|
+
'gpt-4.1': { input: 2.00 / 1_000_000 * 100, output: 8.00 / 1_000_000 * 100 },
|
|
125
|
+
}; }
|
|
126
|
+
get DriverClass() {
|
|
127
|
+
return 'OpenAIReRanker';
|
|
128
|
+
}
|
|
129
|
+
get Name() {
|
|
130
|
+
return 'OpenAI';
|
|
131
|
+
}
|
|
132
|
+
get Version() {
|
|
133
|
+
return '1';
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Effective limit when the candidate list plus prompt fits in the model's input
|
|
137
|
+
* window. With gpt-4o-mini's 128k input context and ~80 tokens/doc, the practical
|
|
138
|
+
* cap is well above 1000. Returning 1000 to match the other rerankers; the
|
|
139
|
+
* SearchEngine will chunk above this when chunking is implemented.
|
|
140
|
+
*/
|
|
141
|
+
GetMaxResultCount() {
|
|
142
|
+
return 1000;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Pre-call cost estimate. ~80 input tokens/doc + 50 system/query tokens, plus
|
|
146
|
+
* 8 output tokens/doc. Conservative — actual usage tends to be lower.
|
|
147
|
+
*/
|
|
148
|
+
EstimateCostCents(resultCount) {
|
|
149
|
+
const pricing = OpenAIReRanker_1.PRICING['gpt-4o-mini'];
|
|
150
|
+
const inputTokens = resultCount * 80 + 50;
|
|
151
|
+
const outputTokens = resultCount * 8;
|
|
152
|
+
return inputTokens * pricing.input + outputTokens * pricing.output;
|
|
153
|
+
}
|
|
154
|
+
getAIReranker(config, _contextUser) {
|
|
155
|
+
if (this.currentAIReranker)
|
|
156
|
+
return this.currentAIReranker;
|
|
157
|
+
const apiKey = GetAIAPIKey('OpenAILLM');
|
|
158
|
+
if (!apiKey) {
|
|
159
|
+
LogError('OpenAIReRanker: no API key found for driver "OpenAILLM" — set AI_VENDOR_API_KEY__OPENAILLM or register an AIAPIKeys subclass.');
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const modelName = config?.['model'] ?? 'gpt-4o-mini';
|
|
163
|
+
this.currentAIReranker = new OpenAIScopeAIReranker(apiKey, modelName);
|
|
164
|
+
return this.currentAIReranker;
|
|
165
|
+
}
|
|
166
|
+
async ReRank(query, candidates, topN, contextUser, config) {
|
|
167
|
+
if (topN <= 0 || candidates.length === 0)
|
|
168
|
+
return [];
|
|
169
|
+
this.currentAIReranker = null;
|
|
170
|
+
const apiReranker = this.getAIReranker(config, contextUser);
|
|
171
|
+
try {
|
|
172
|
+
if (!apiReranker)
|
|
173
|
+
return candidates.slice(0, topN);
|
|
174
|
+
const result = await super.ReRank(query, candidates, topN, contextUser, config);
|
|
175
|
+
const modelName = config?.['model'] ?? 'gpt-4o-mini';
|
|
176
|
+
const pricing = OpenAIReRanker_1.PRICING[modelName] ?? OpenAIReRanker_1.PRICING['gpt-4o-mini'];
|
|
177
|
+
const cents = apiReranker.lastPromptTokens * pricing.input
|
|
178
|
+
+ apiReranker.lastCompletionTokens * pricing.output;
|
|
179
|
+
if (cents > 0)
|
|
180
|
+
this.reportCost(cents);
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
this.currentAIReranker = null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
OpenAIReRanker = OpenAIReRanker_1 = __decorate([
|
|
189
|
+
RegisterClass(BaseReRanker, 'OpenAIReRanker')
|
|
190
|
+
], OpenAIReRanker);
|
|
191
|
+
export { OpenAIReRanker };
|
|
192
|
+
/** Tree-shake prevention helper. */
|
|
193
|
+
export function LoadOpenAIReRanker() {
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
195
|
+
const _ref = OpenAIReRanker;
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=OpenAIReRanker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OpenAIReRanker.js","sourceRoot":"","sources":["../../src/rerankers/OpenAIReRanker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Reranker budget guard (P2D.6).
|
|
3
|
+
*
|
|
4
|
+
* Per-search-invocation circuit breaker that caps real-provider reranker spend
|
|
5
|
+
* (Cohere, Voyage, OpenAI) against `SearchScope.RerankerBudgetCents`. Wired into
|
|
6
|
+
* the SearchEngine's rerank stage so:
|
|
7
|
+
*
|
|
8
|
+
* 1. Before each rerank call, the engine asks the guard whether `EstimateCostCents`
|
|
9
|
+
* fits in the remaining budget. If not, the engine SKIPS the rerank and uses
|
|
10
|
+
* the unranked candidate list.
|
|
11
|
+
* 2. After each rerank call, the reranker's `CostReporter` callback (P2D.1
|
|
12
|
+
* contract) lands in `record()`, accumulating actual spend.
|
|
13
|
+
*
|
|
14
|
+
* NULL budget = uncapped; the guard always returns `canSpend = true` and accumulates
|
|
15
|
+
* spend purely for observability. This preserves Phase 1 behavior for any scope the
|
|
16
|
+
* admin hasn't explicitly capped.
|
|
17
|
+
*
|
|
18
|
+
* @module @memberjunction/search-engine
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Per-run budget tracker. One instance per search invocation; lives only as long
|
|
22
|
+
* as the SearchEngine.search call. Not thread-safe, but Node is single-threaded
|
|
23
|
+
* within a request so that's fine.
|
|
24
|
+
*/
|
|
25
|
+
export declare class RerankerBudgetGuard {
|
|
26
|
+
/** Total budget in cents, or null when uncapped. */
|
|
27
|
+
readonly Budget: number | null;
|
|
28
|
+
/** Accumulated cost in cents, populated via record(). */
|
|
29
|
+
Spent: number;
|
|
30
|
+
constructor(budgetCents: number | null | undefined);
|
|
31
|
+
/**
|
|
32
|
+
* Cents remaining in the budget. Returns null when uncapped, else
|
|
33
|
+
* `max(0, Budget - Spent)`.
|
|
34
|
+
*/
|
|
35
|
+
Remaining(): number | null;
|
|
36
|
+
/**
|
|
37
|
+
* Pre-call check: does `estimatedCents` fit in the remaining budget? Returns
|
|
38
|
+
* true when uncapped or when remaining ≥ estimate.
|
|
39
|
+
*
|
|
40
|
+
* Negative estimates are treated as zero (defensive — a buggy
|
|
41
|
+
* `EstimateCostCents` shouldn't trigger short-circuits prematurely).
|
|
42
|
+
*/
|
|
43
|
+
CanSpend(estimatedCents: number): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Post-call accumulator. Negative or non-finite values are clamped to 0.
|
|
46
|
+
*/
|
|
47
|
+
Record(actualCents: number): void;
|
|
48
|
+
/**
|
|
49
|
+
* Sugar for wiring into a reranker. Returns a closure that records cost on
|
|
50
|
+
* this guard — assignable directly to `BaseReRanker.CostReporter`.
|
|
51
|
+
*/
|
|
52
|
+
AsCostReporter(): (cents: number) => void;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=RerankerBudgetGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RerankerBudgetGuard.d.ts","sourceRoot":"","sources":["../../src/rerankers/RerankerBudgetGuard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,qBAAa,mBAAmB;IAC5B,oDAAoD;IACpD,SAAgB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,yDAAyD;IAClD,KAAK,SAAK;gBAEL,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAIlD;;;OAGG;IACI,SAAS,IAAI,MAAM,GAAG,IAAI;IAIjC;;;;;;OAMG;IACI,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO;IAMhD;;OAEG;IACI,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAKxC;;;OAGG;IACI,cAAc,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI;CAGnD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Reranker budget guard (P2D.6).
|
|
3
|
+
*
|
|
4
|
+
* Per-search-invocation circuit breaker that caps real-provider reranker spend
|
|
5
|
+
* (Cohere, Voyage, OpenAI) against `SearchScope.RerankerBudgetCents`. Wired into
|
|
6
|
+
* the SearchEngine's rerank stage so:
|
|
7
|
+
*
|
|
8
|
+
* 1. Before each rerank call, the engine asks the guard whether `EstimateCostCents`
|
|
9
|
+
* fits in the remaining budget. If not, the engine SKIPS the rerank and uses
|
|
10
|
+
* the unranked candidate list.
|
|
11
|
+
* 2. After each rerank call, the reranker's `CostReporter` callback (P2D.1
|
|
12
|
+
* contract) lands in `record()`, accumulating actual spend.
|
|
13
|
+
*
|
|
14
|
+
* NULL budget = uncapped; the guard always returns `canSpend = true` and accumulates
|
|
15
|
+
* spend purely for observability. This preserves Phase 1 behavior for any scope the
|
|
16
|
+
* admin hasn't explicitly capped.
|
|
17
|
+
*
|
|
18
|
+
* @module @memberjunction/search-engine
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Per-run budget tracker. One instance per search invocation; lives only as long
|
|
22
|
+
* as the SearchEngine.search call. Not thread-safe, but Node is single-threaded
|
|
23
|
+
* within a request so that's fine.
|
|
24
|
+
*/
|
|
25
|
+
export class RerankerBudgetGuard {
|
|
26
|
+
constructor(budgetCents) {
|
|
27
|
+
/** Accumulated cost in cents, populated via record(). */
|
|
28
|
+
this.Spent = 0;
|
|
29
|
+
this.Budget = budgetCents == null ? null : Math.max(0, budgetCents);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Cents remaining in the budget. Returns null when uncapped, else
|
|
33
|
+
* `max(0, Budget - Spent)`.
|
|
34
|
+
*/
|
|
35
|
+
Remaining() {
|
|
36
|
+
return this.Budget == null ? null : Math.max(0, this.Budget - this.Spent);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Pre-call check: does `estimatedCents` fit in the remaining budget? Returns
|
|
40
|
+
* true when uncapped or when remaining ≥ estimate.
|
|
41
|
+
*
|
|
42
|
+
* Negative estimates are treated as zero (defensive — a buggy
|
|
43
|
+
* `EstimateCostCents` shouldn't trigger short-circuits prematurely).
|
|
44
|
+
*/
|
|
45
|
+
CanSpend(estimatedCents) {
|
|
46
|
+
if (this.Budget == null)
|
|
47
|
+
return true;
|
|
48
|
+
const need = Math.max(0, estimatedCents);
|
|
49
|
+
return this.Remaining() >= need;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Post-call accumulator. Negative or non-finite values are clamped to 0.
|
|
53
|
+
*/
|
|
54
|
+
Record(actualCents) {
|
|
55
|
+
if (!Number.isFinite(actualCents) || actualCents <= 0)
|
|
56
|
+
return;
|
|
57
|
+
this.Spent += actualCents;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Sugar for wiring into a reranker. Returns a closure that records cost on
|
|
61
|
+
* this guard — assignable directly to `BaseReRanker.CostReporter`.
|
|
62
|
+
*/
|
|
63
|
+
AsCostReporter() {
|
|
64
|
+
return (cents) => this.Record(cents);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=RerankerBudgetGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RerankerBudgetGuard.js","sourceRoot":"","sources":["../../src/rerankers/RerankerBudgetGuard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,MAAM,OAAO,mBAAmB;IAM5B,YAAY,WAAsC;QAHlD,yDAAyD;QAClD,UAAK,GAAG,CAAC,CAAC;QAGb,IAAI,CAAC,MAAM,GAAG,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IACxE,CAAC;IAED;;;OAGG;IACI,SAAS;QACZ,OAAO,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9E,CAAC;IAED;;;;;;OAMG;IACI,QAAQ,CAAC,cAAsB;QAClC,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC,SAAS,EAAG,IAAI,IAAI,CAAC;IACrC,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,WAAmB;QAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,WAAW,IAAI,CAAC;YAAE,OAAO;QAC9D,IAAI,CAAC,KAAK,IAAI,WAAW,CAAC;IAC9B,CAAC;IAED;;;OAGG;IACI,cAAc;QACjB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACzC,CAAC;CACJ"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Voyage AI Rerank API integration (P2D.3).
|
|
3
|
+
*
|
|
4
|
+
* Calls Voyage's `rerank-2` (and `rerank-2-lite`) endpoint via the public REST API.
|
|
5
|
+
* No SDK dependency — Voyage's surface is small and fetch-based, so we call the
|
|
6
|
+
* endpoint directly and avoid pulling in `voyageai` as another optional peer.
|
|
7
|
+
*
|
|
8
|
+
* **Cost model.** Voyage bills per token (query + document tokens combined). Pricing
|
|
9
|
+
* as of 2026-04: `rerank-2` is $0.05 per 1M tokens; `rerank-2-lite` is $0.02 per 1M
|
|
10
|
+
* tokens. We approximate token count as `ceil(charCount / 4)` for the estimate, then
|
|
11
|
+
* report the exact `usage.total_tokens` returned by the API as the actual cost.
|
|
12
|
+
*
|
|
13
|
+
* **API key resolution.** Uses `GetAIAPIKey('VoyageLLM')` — sources from
|
|
14
|
+
* `AI_VENDOR_API_KEY__VOYAGELLM` env var or any registered AIAPIKeys subclass.
|
|
15
|
+
*
|
|
16
|
+
* @module @memberjunction/search-engine
|
|
17
|
+
*/
|
|
18
|
+
import { UserInfo } from '@memberjunction/core';
|
|
19
|
+
import { BaseReranker as AIBaseReranker } from '@memberjunction/ai';
|
|
20
|
+
import { BaseReRanker } from '../generic/BaseReRanker.js';
|
|
21
|
+
import { SearchResultItem } from '../generic/search.types.js';
|
|
22
|
+
/**
|
|
23
|
+
* Voyage `rerank-2` re-ranker. Configure per scope via
|
|
24
|
+
* `SearchScope.ScopeConfig.reRanker.driverClass = 'VoyageReRanker'`.
|
|
25
|
+
*
|
|
26
|
+
* Override the model via `ScopeConfig.reRanker.config.model = 'rerank-2-lite'` for
|
|
27
|
+
* the cheaper lite variant.
|
|
28
|
+
*/
|
|
29
|
+
export declare class VoyageReRanker extends BaseReRanker {
|
|
30
|
+
/** Pricing in cents-per-token, indexed by model name. As of 2026-04. */
|
|
31
|
+
private static readonly CENTS_PER_TOKEN;
|
|
32
|
+
/**
|
|
33
|
+
* Per-instance reranker, captured so we can read `lastTokensUsed` after a call to
|
|
34
|
+
* report exact cost. Recreated per ReRank invocation when config or contextUser
|
|
35
|
+
* change shouldn't be a concern in practice.
|
|
36
|
+
*/
|
|
37
|
+
private currentAIReranker;
|
|
38
|
+
get DriverClass(): string;
|
|
39
|
+
get Name(): string;
|
|
40
|
+
get Version(): string;
|
|
41
|
+
/** Voyage's published cap is 1000 documents per call. */
|
|
42
|
+
GetMaxResultCount(): number;
|
|
43
|
+
/**
|
|
44
|
+
* Pre-call cost estimate using `ceil(charCount / 4)` per document plus query
|
|
45
|
+
* tokens. Conservative — tends to over-estimate, which is what a budget guard
|
|
46
|
+
* wants. Default model is `rerank-2`; pass `config.model` to switch.
|
|
47
|
+
*/
|
|
48
|
+
EstimateCostCents(resultCount: number): number;
|
|
49
|
+
protected getAIReranker(config: Record<string, unknown> | undefined, _contextUser: UserInfo): AIBaseReranker | null;
|
|
50
|
+
/**
|
|
51
|
+
* Override `ReRank` to report exact post-call cost via `usage.total_tokens` from
|
|
52
|
+
* the Voyage response, not the pre-call estimate. The estimate exists only to feed
|
|
53
|
+
* the budget guard's pre-call short-circuit.
|
|
54
|
+
*/
|
|
55
|
+
ReRank(query: string, candidates: SearchResultItem[], topN: number, contextUser: UserInfo, config?: Record<string, unknown>): Promise<SearchResultItem[]>;
|
|
56
|
+
}
|
|
57
|
+
/** Tree-shake prevention helper. */
|
|
58
|
+
export declare function LoadVoyageReRanker(): void;
|
|
59
|
+
//# sourceMappingURL=VoyageReRanker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VoyageReRanker.d.ts","sourceRoot":"","sources":["../../src/rerankers/VoyageReRanker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAY,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EACH,YAAY,IAAI,cAAc,EAIjC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAyE3D;;;;;;GAMG;AACH,qBACa,cAAe,SAAQ,YAAY;IAC5C,wEAAwE;IACxE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAGrC;IAEF;;;;OAIG;IACH,OAAO,CAAC,iBAAiB,CAAsC;IAE/D,IAAW,WAAW,IAAI,MAAM,CAE/B;IACD,IAAoB,IAAI,IAAI,MAAM,CAEjC;IACD,IAAoB,OAAO,IAAI,MAAM,CAEpC;IAED,yDAAyD;IACzC,iBAAiB,IAAI,MAAM;IAI3C;;;;OAIG;IACa,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;cAS3C,aAAa,CAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC3C,YAAY,EAAE,QAAQ,GACvB,cAAc,GAAG,IAAI;IAiBxB;;;;OAIG;IACmB,MAAM,CACxB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,gBAAgB,EAAE,EAC9B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,QAAQ,EACrB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAsBjC;AAED,oCAAoC;AACpC,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Voyage AI Rerank API integration (P2D.3).
|
|
3
|
+
*
|
|
4
|
+
* Calls Voyage's `rerank-2` (and `rerank-2-lite`) endpoint via the public REST API.
|
|
5
|
+
* No SDK dependency — Voyage's surface is small and fetch-based, so we call the
|
|
6
|
+
* endpoint directly and avoid pulling in `voyageai` as another optional peer.
|
|
7
|
+
*
|
|
8
|
+
* **Cost model.** Voyage bills per token (query + document tokens combined). Pricing
|
|
9
|
+
* as of 2026-04: `rerank-2` is $0.05 per 1M tokens; `rerank-2-lite` is $0.02 per 1M
|
|
10
|
+
* tokens. We approximate token count as `ceil(charCount / 4)` for the estimate, then
|
|
11
|
+
* report the exact `usage.total_tokens` returned by the API as the actual cost.
|
|
12
|
+
*
|
|
13
|
+
* **API key resolution.** Uses `GetAIAPIKey('VoyageLLM')` — sources from
|
|
14
|
+
* `AI_VENDOR_API_KEY__VOYAGELLM` env var or any registered AIAPIKeys subclass.
|
|
15
|
+
*
|
|
16
|
+
* @module @memberjunction/search-engine
|
|
17
|
+
*/
|
|
18
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var VoyageReRanker_1;
|
|
25
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
26
|
+
import { LogError } from '@memberjunction/core';
|
|
27
|
+
import { BaseReranker as AIBaseReranker, GetAIAPIKey, } from '@memberjunction/ai';
|
|
28
|
+
import { BaseReRanker } from '../generic/BaseReRanker.js';
|
|
29
|
+
const VOYAGE_RERANK_URL = 'https://api.voyageai.com/v1/rerank';
|
|
30
|
+
/**
|
|
31
|
+
* Approximate Voyage's tokenizer by `ceil(charCount / 4)`. Real Voyage tokenization is
|
|
32
|
+
* BPE-based; this approximation is conservative enough to keep budget guards from
|
|
33
|
+
* under-counting in practice.
|
|
34
|
+
*/
|
|
35
|
+
function estimateTokens(text) {
|
|
36
|
+
return Math.max(1, Math.ceil(text.length / 4));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Inline AI-layer reranker that POSTs to Voyage's rerank endpoint.
|
|
40
|
+
*/
|
|
41
|
+
class VoyageScopeAIReranker extends AIBaseReranker {
|
|
42
|
+
constructor(apiKey, modelName) {
|
|
43
|
+
super(apiKey, modelName);
|
|
44
|
+
/**
|
|
45
|
+
* Last-call usage. Populated on each `doRerank()` so the SearchEngine wrapper can
|
|
46
|
+
* report exact cost via `usage.total_tokens` instead of the pre-call estimate.
|
|
47
|
+
*/
|
|
48
|
+
this.lastTokensUsed = 0;
|
|
49
|
+
}
|
|
50
|
+
async doRerank(params) {
|
|
51
|
+
const body = {
|
|
52
|
+
query: params.query,
|
|
53
|
+
documents: params.documents.map(d => d.text),
|
|
54
|
+
model: this._modelName,
|
|
55
|
+
top_k: params.topK ?? params.documents.length,
|
|
56
|
+
return_documents: false,
|
|
57
|
+
};
|
|
58
|
+
const response = await fetch(VOYAGE_RERANK_URL, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify(body),
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const text = await response.text().catch(() => '');
|
|
68
|
+
throw new Error(`Voyage rerank failed (${response.status}): ${text || response.statusText}`);
|
|
69
|
+
}
|
|
70
|
+
const json = await response.json();
|
|
71
|
+
this.lastTokensUsed = json.usage?.total_tokens ?? 0;
|
|
72
|
+
return json.data.map((r, idx) => ({
|
|
73
|
+
id: params.documents[r.index].id,
|
|
74
|
+
relevanceScore: r.relevance_score,
|
|
75
|
+
document: params.documents[r.index],
|
|
76
|
+
rank: idx,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Voyage `rerank-2` re-ranker. Configure per scope via
|
|
82
|
+
* `SearchScope.ScopeConfig.reRanker.driverClass = 'VoyageReRanker'`.
|
|
83
|
+
*
|
|
84
|
+
* Override the model via `ScopeConfig.reRanker.config.model = 'rerank-2-lite'` for
|
|
85
|
+
* the cheaper lite variant.
|
|
86
|
+
*/
|
|
87
|
+
let VoyageReRanker = class VoyageReRanker extends BaseReRanker {
|
|
88
|
+
constructor() {
|
|
89
|
+
super(...arguments);
|
|
90
|
+
/**
|
|
91
|
+
* Per-instance reranker, captured so we can read `lastTokensUsed` after a call to
|
|
92
|
+
* report exact cost. Recreated per ReRank invocation when config or contextUser
|
|
93
|
+
* change shouldn't be a concern in practice.
|
|
94
|
+
*/
|
|
95
|
+
this.currentAIReranker = null;
|
|
96
|
+
}
|
|
97
|
+
static { VoyageReRanker_1 = this; }
|
|
98
|
+
/** Pricing in cents-per-token, indexed by model name. As of 2026-04. */
|
|
99
|
+
static { this.CENTS_PER_TOKEN = {
|
|
100
|
+
'rerank-2': 0.05 / 1_000_000 * 100, // $0.05 / 1M tokens → 5e-6 cents/token
|
|
101
|
+
'rerank-2-lite': 0.02 / 1_000_000 * 100, // $0.02 / 1M tokens → 2e-6 cents/token
|
|
102
|
+
}; }
|
|
103
|
+
get DriverClass() {
|
|
104
|
+
return 'VoyageReRanker';
|
|
105
|
+
}
|
|
106
|
+
get Name() {
|
|
107
|
+
return 'Voyage';
|
|
108
|
+
}
|
|
109
|
+
get Version() {
|
|
110
|
+
return '1';
|
|
111
|
+
}
|
|
112
|
+
/** Voyage's published cap is 1000 documents per call. */
|
|
113
|
+
GetMaxResultCount() {
|
|
114
|
+
return 1000;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Pre-call cost estimate using `ceil(charCount / 4)` per document plus query
|
|
118
|
+
* tokens. Conservative — tends to over-estimate, which is what a budget guard
|
|
119
|
+
* wants. Default model is `rerank-2`; pass `config.model` to switch.
|
|
120
|
+
*/
|
|
121
|
+
EstimateCostCents(resultCount) {
|
|
122
|
+
// Use the default model's pricing for the estimate; the actual call will use
|
|
123
|
+
// whatever's in config.model and the post-call cost report will be exact.
|
|
124
|
+
const pricePerToken = VoyageReRanker_1.CENTS_PER_TOKEN['rerank-2'] ?? 0;
|
|
125
|
+
// Assume average document size ~250 chars (~62 tokens) and a 30-token query.
|
|
126
|
+
const approxTokens = resultCount * 62 + 30;
|
|
127
|
+
return approxTokens * pricePerToken;
|
|
128
|
+
}
|
|
129
|
+
getAIReranker(config, _contextUser) {
|
|
130
|
+
// Return the cached instance if `ReRank()` already constructed one for this
|
|
131
|
+
// invocation — super.ReRank calls us a second time during dispatch. Without
|
|
132
|
+
// the cache we'd build two separate Voyage clients per call and lose track of
|
|
133
|
+
// `lastTokensUsed`.
|
|
134
|
+
if (this.currentAIReranker)
|
|
135
|
+
return this.currentAIReranker;
|
|
136
|
+
const apiKey = GetAIAPIKey('VoyageLLM');
|
|
137
|
+
if (!apiKey) {
|
|
138
|
+
LogError('VoyageReRanker: no API key found for driver "VoyageLLM" — set AI_VENDOR_API_KEY__VOYAGELLM or register an AIAPIKeys subclass.');
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const modelName = config?.['model'] ?? 'rerank-2';
|
|
142
|
+
this.currentAIReranker = new VoyageScopeAIReranker(apiKey, modelName);
|
|
143
|
+
return this.currentAIReranker;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Override `ReRank` to report exact post-call cost via `usage.total_tokens` from
|
|
147
|
+
* the Voyage response, not the pre-call estimate. The estimate exists only to feed
|
|
148
|
+
* the budget guard's pre-call short-circuit.
|
|
149
|
+
*/
|
|
150
|
+
async ReRank(query, candidates, topN, contextUser, config) {
|
|
151
|
+
if (topN <= 0 || candidates.length === 0)
|
|
152
|
+
return [];
|
|
153
|
+
// Reset the per-call cache and prime it via getAIReranker. Super will reuse it.
|
|
154
|
+
this.currentAIReranker = null;
|
|
155
|
+
const apiReranker = this.getAIReranker(config, contextUser);
|
|
156
|
+
try {
|
|
157
|
+
if (!apiReranker) {
|
|
158
|
+
// No API key — fall through to a top-N slice without charge.
|
|
159
|
+
return candidates.slice(0, topN);
|
|
160
|
+
}
|
|
161
|
+
const result = await super.ReRank(query, candidates, topN, contextUser, config);
|
|
162
|
+
const modelName = config?.['model'] ?? 'rerank-2';
|
|
163
|
+
const pricePerToken = VoyageReRanker_1.CENTS_PER_TOKEN[modelName] ?? VoyageReRanker_1.CENTS_PER_TOKEN['rerank-2'];
|
|
164
|
+
const cents = apiReranker.lastTokensUsed * pricePerToken;
|
|
165
|
+
if (cents > 0) {
|
|
166
|
+
this.reportCost(cents);
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
this.currentAIReranker = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
VoyageReRanker = VoyageReRanker_1 = __decorate([
|
|
176
|
+
RegisterClass(BaseReRanker, 'VoyageReRanker')
|
|
177
|
+
], VoyageReRanker);
|
|
178
|
+
export { VoyageReRanker };
|
|
179
|
+
/** Tree-shake prevention helper. */
|
|
180
|
+
export function LoadVoyageReRanker() {
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
182
|
+
const _ref = VoyageReRanker;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=VoyageReRanker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VoyageReRanker.js","sourceRoot":"","sources":["../../src/rerankers/VoyageReRanker.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/search-engine",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.34.0",
|
|
5
5
|
"description": "MemberJunction: Reusable server-side search engine - vector + full-text + entity search with RRF fusion",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -16,16 +16,21 @@
|
|
|
16
16
|
"author": "MemberJunction.com",
|
|
17
17
|
"license": "ISC",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@memberjunction/ai": "5.
|
|
20
|
-
"@memberjunction/ai-vectordb": "5.
|
|
21
|
-
"@memberjunction/aiengine": "5.
|
|
22
|
-
"@memberjunction/core": "5.
|
|
23
|
-
"@memberjunction/core-entities": "5.
|
|
24
|
-
"@memberjunction/global": "5.
|
|
25
|
-
"@memberjunction/storage": "5.
|
|
19
|
+
"@memberjunction/ai": "5.34.0",
|
|
20
|
+
"@memberjunction/ai-vectordb": "5.34.0",
|
|
21
|
+
"@memberjunction/aiengine": "5.34.0",
|
|
22
|
+
"@memberjunction/core": "5.34.0",
|
|
23
|
+
"@memberjunction/core-entities": "5.34.0",
|
|
24
|
+
"@memberjunction/global": "5.34.0",
|
|
25
|
+
"@memberjunction/storage": "5.34.0",
|
|
26
|
+
"nunjucks": "^3.2.4"
|
|
27
|
+
},
|
|
28
|
+
"optionalDependencies": {
|
|
29
|
+
"cohere-ai": "^7.20.0"
|
|
26
30
|
},
|
|
27
31
|
"devDependencies": {
|
|
28
32
|
"@types/node": "24.10.11",
|
|
33
|
+
"@types/nunjucks": "^3.2.6",
|
|
29
34
|
"typescript": "^5.9.3"
|
|
30
35
|
},
|
|
31
36
|
"repository": {
|