@exulu/backend 1.46.0 → 1.47.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/.agents/skills/mintlify/SKILL.md +347 -0
- package/.editorconfig +15 -0
- package/.eslintrc.json +52 -0
- package/.jscpd.json +18 -0
- package/.prettierignore +5 -0
- package/.prettierrc.json +12 -0
- package/CHANGELOG.md +11 -4
- package/README.md +747 -0
- package/SECURITY.md +5 -0
- package/dist/index.cjs +12015 -10506
- package/dist/index.d.cts +725 -667
- package/dist/index.d.ts +725 -667
- package/dist/index.js +12034 -10518
- package/ee/LICENSE.md +62 -0
- package/ee/agentic-retrieval/index.ts +1109 -0
- package/ee/documents/THIRD_PARTY_LICENSES/docling.txt +31 -0
- package/ee/documents/processing/build_pdf_processor.sh +35 -0
- package/ee/documents/processing/chunk_markdown.py +263 -0
- package/ee/documents/processing/doc_processor.ts +635 -0
- package/ee/documents/processing/pdf_processor.spec +115 -0
- package/ee/documents/processing/pdf_to_markdown.py +420 -0
- package/ee/documents/processing/requirements.txt +4 -0
- package/ee/entitlements.ts +49 -0
- package/ee/markdown.ts +686 -0
- package/ee/queues/decorator.ts +140 -0
- package/ee/queues/queues.ts +156 -0
- package/ee/queues/server.ts +6 -0
- package/ee/rbac-resolver.ts +51 -0
- package/ee/rbac-update.ts +111 -0
- package/ee/schemas.ts +347 -0
- package/ee/tokenizer.ts +80 -0
- package/ee/workers.ts +1423 -0
- package/eslint.config.js +88 -0
- package/jest.config.ts +25 -0
- package/license.md +73 -49
- package/mintlify-docs/.mintignore +7 -0
- package/mintlify-docs/AGENTS.md +33 -0
- package/mintlify-docs/CLAUDE.MD +50 -0
- package/mintlify-docs/CONTRIBUTING.md +32 -0
- package/mintlify-docs/LICENSE +21 -0
- package/mintlify-docs/README.md +55 -0
- package/mintlify-docs/ai-tools/claude-code.mdx +43 -0
- package/mintlify-docs/ai-tools/cursor.mdx +39 -0
- package/mintlify-docs/ai-tools/windsurf.mdx +39 -0
- package/mintlify-docs/api-reference/core-types/agent-types.mdx +110 -0
- package/mintlify-docs/api-reference/core-types/analytics-types.mdx +95 -0
- package/mintlify-docs/api-reference/core-types/configuration-types.mdx +83 -0
- package/mintlify-docs/api-reference/core-types/evaluation-types.mdx +106 -0
- package/mintlify-docs/api-reference/core-types/job-types.mdx +135 -0
- package/mintlify-docs/api-reference/core-types/overview.mdx +73 -0
- package/mintlify-docs/api-reference/core-types/prompt-types.mdx +102 -0
- package/mintlify-docs/api-reference/core-types/rbac-types.mdx +163 -0
- package/mintlify-docs/api-reference/core-types/session-types.mdx +77 -0
- package/mintlify-docs/api-reference/core-types/user-management.mdx +112 -0
- package/mintlify-docs/api-reference/core-types/workflow-types.mdx +88 -0
- package/mintlify-docs/api-reference/core-types.mdx +585 -0
- package/mintlify-docs/api-reference/dynamic-types.mdx +851 -0
- package/mintlify-docs/api-reference/endpoint/create.mdx +4 -0
- package/mintlify-docs/api-reference/endpoint/delete.mdx +4 -0
- package/mintlify-docs/api-reference/endpoint/get.mdx +4 -0
- package/mintlify-docs/api-reference/endpoint/webhook.mdx +4 -0
- package/mintlify-docs/api-reference/introduction.mdx +661 -0
- package/mintlify-docs/api-reference/mutations.mdx +1012 -0
- package/mintlify-docs/api-reference/openapi.json +217 -0
- package/mintlify-docs/api-reference/queries.mdx +1154 -0
- package/mintlify-docs/backend/introduction.mdx +218 -0
- package/mintlify-docs/changelog.mdx +293 -0
- package/mintlify-docs/community-edition.mdx +304 -0
- package/mintlify-docs/core/exulu-agent/api-reference.mdx +894 -0
- package/mintlify-docs/core/exulu-agent/configuration.mdx +690 -0
- package/mintlify-docs/core/exulu-agent/introduction.mdx +552 -0
- package/mintlify-docs/core/exulu-app/api-reference.mdx +481 -0
- package/mintlify-docs/core/exulu-app/configuration.mdx +319 -0
- package/mintlify-docs/core/exulu-app/introduction.mdx +117 -0
- package/mintlify-docs/core/exulu-authentication.mdx +810 -0
- package/mintlify-docs/core/exulu-chunkers/api-reference.mdx +1011 -0
- package/mintlify-docs/core/exulu-chunkers/configuration.mdx +596 -0
- package/mintlify-docs/core/exulu-chunkers/introduction.mdx +403 -0
- package/mintlify-docs/core/exulu-context/api-reference.mdx +911 -0
- package/mintlify-docs/core/exulu-context/configuration.mdx +648 -0
- package/mintlify-docs/core/exulu-context/introduction.mdx +394 -0
- package/mintlify-docs/core/exulu-database.mdx +811 -0
- package/mintlify-docs/core/exulu-default-agents.mdx +545 -0
- package/mintlify-docs/core/exulu-eval/api-reference.mdx +772 -0
- package/mintlify-docs/core/exulu-eval/configuration.mdx +680 -0
- package/mintlify-docs/core/exulu-eval/introduction.mdx +459 -0
- package/mintlify-docs/core/exulu-logging.mdx +464 -0
- package/mintlify-docs/core/exulu-otel.mdx +670 -0
- package/mintlify-docs/core/exulu-queues/api-reference.mdx +648 -0
- package/mintlify-docs/core/exulu-queues/configuration.mdx +650 -0
- package/mintlify-docs/core/exulu-queues/introduction.mdx +474 -0
- package/mintlify-docs/core/exulu-reranker/api-reference.mdx +630 -0
- package/mintlify-docs/core/exulu-reranker/configuration.mdx +663 -0
- package/mintlify-docs/core/exulu-reranker/introduction.mdx +516 -0
- package/mintlify-docs/core/exulu-tool/api-reference.mdx +723 -0
- package/mintlify-docs/core/exulu-tool/configuration.mdx +805 -0
- package/mintlify-docs/core/exulu-tool/introduction.mdx +539 -0
- package/mintlify-docs/core/exulu-variables/api-reference.mdx +699 -0
- package/mintlify-docs/core/exulu-variables/configuration.mdx +736 -0
- package/mintlify-docs/core/exulu-variables/introduction.mdx +511 -0
- package/mintlify-docs/development.mdx +94 -0
- package/mintlify-docs/docs.json +248 -0
- package/mintlify-docs/enterprise-edition.mdx +538 -0
- package/mintlify-docs/essentials/code.mdx +35 -0
- package/mintlify-docs/essentials/images.mdx +59 -0
- package/mintlify-docs/essentials/markdown.mdx +88 -0
- package/mintlify-docs/essentials/navigation.mdx +87 -0
- package/mintlify-docs/essentials/reusable-snippets.mdx +110 -0
- package/mintlify-docs/essentials/settings.mdx +318 -0
- package/mintlify-docs/favicon.svg +3 -0
- package/mintlify-docs/frontend/introduction.mdx +39 -0
- package/mintlify-docs/getting-started.mdx +267 -0
- package/mintlify-docs/guides/custom-agent.mdx +608 -0
- package/mintlify-docs/guides/first-agent.mdx +315 -0
- package/mintlify-docs/images/admin_ui.png +0 -0
- package/mintlify-docs/images/contexts.png +0 -0
- package/mintlify-docs/images/create_agents.png +0 -0
- package/mintlify-docs/images/evals.png +0 -0
- package/mintlify-docs/images/graphql.png +0 -0
- package/mintlify-docs/images/graphql_api.png +0 -0
- package/mintlify-docs/images/hero-dark.png +0 -0
- package/mintlify-docs/images/hero-light.png +0 -0
- package/mintlify-docs/images/hero.png +0 -0
- package/mintlify-docs/images/knowledge_sources.png +0 -0
- package/mintlify-docs/images/mcp.png +0 -0
- package/mintlify-docs/images/scaling.png +0 -0
- package/mintlify-docs/index.mdx +411 -0
- package/mintlify-docs/logo/dark.svg +9 -0
- package/mintlify-docs/logo/light.svg +9 -0
- package/mintlify-docs/partners.mdx +558 -0
- package/mintlify-docs/products.mdx +77 -0
- package/mintlify-docs/snippets/snippet-intro.mdx +4 -0
- package/mintlify-docs/styles.css +207 -0
- package/{documentation → old-documentation}/logging.md +3 -3
- package/package.json +35 -4
- package/skills-lock.json +10 -0
- package/types/context-processor.ts +45 -0
- package/types/exulu-table-definition.ts +79 -0
- package/types/file-types.ts +18 -0
- package/types/models/agent.ts +10 -12
- package/types/models/exulu-agent-tool-config.ts +11 -0
- package/types/models/rate-limiter-rules.ts +7 -0
- package/types/provider-config.ts +21 -0
- package/types/queue-config.ts +16 -0
- package/types/rbac-rights-modes.ts +1 -0
- package/types/statistics.ts +20 -0
- package/types/workflow.ts +31 -0
- package/changelog-backend-10.11.2025_03.12.2025.md +0 -316
- package/types/models/agent-backend.ts +0 -15
- /package/{documentation → old-documentation}/otel.md +0 -0
- /package/{patch-older-releases-readme.md → old-documentation/patch-older-releases.md} +0 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Overview"
|
|
3
|
+
description: "Improve search result relevance by reordering chunks with specialized reranking models"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
`ExuluReranker` is a component that improves search result quality by reordering chunks based on relevance to the user's query. After initial vector or hybrid search retrieves candidate results, a reranker applies more sophisticated relevance scoring to surface the most useful chunks at the top.
|
|
9
|
+
|
|
10
|
+
## Key features
|
|
11
|
+
|
|
12
|
+
<CardGroup cols={2}>
|
|
13
|
+
<Card title="Post-search refinement" icon="arrow-up-arrow-down">
|
|
14
|
+
Reorder search results after initial retrieval for better relevance
|
|
15
|
+
</Card>
|
|
16
|
+
<Card title="Model-agnostic" icon="wand-magic-sparkles">
|
|
17
|
+
Use any reranking API (Cohere, Voyage AI, custom models)
|
|
18
|
+
</Card>
|
|
19
|
+
<Card title="Simple integration" icon="plug">
|
|
20
|
+
Drop-in enhancement for ExuluContext search
|
|
21
|
+
</Card>
|
|
22
|
+
<Card title="Flexible scoring" icon="sliders">
|
|
23
|
+
Custom reranking logic or third-party services
|
|
24
|
+
</Card>
|
|
25
|
+
</CardGroup>
|
|
26
|
+
|
|
27
|
+
## What is a reranker?
|
|
28
|
+
|
|
29
|
+
A reranker is a specialized model or algorithm that:
|
|
30
|
+
|
|
31
|
+
1. **Receives initial search results** from vector or hybrid search
|
|
32
|
+
2. **Analyzes query-document pairs** to compute refined relevance scores
|
|
33
|
+
3. **Reorders the results** to surface the most relevant chunks first
|
|
34
|
+
4. **Returns the reordered chunks** to the application
|
|
35
|
+
|
|
36
|
+
Rerankers typically use cross-attention mechanisms or more sophisticated language models than embedding models, providing better relevance at the cost of higher latency. This makes them ideal for post-processing search results.
|
|
37
|
+
|
|
38
|
+
## Why use reranking?
|
|
39
|
+
|
|
40
|
+
<AccordionGroup>
|
|
41
|
+
<Accordion title="Better relevance">
|
|
42
|
+
Reranking models analyze the relationship between query and document more deeply than embedding similarity, leading to more accurate results.
|
|
43
|
+
</Accordion>
|
|
44
|
+
|
|
45
|
+
<Accordion title="Two-stage retrieval">
|
|
46
|
+
Fast vector search retrieves candidates, then slower but more accurate reranking refines the top results. This balances speed and quality.
|
|
47
|
+
</Accordion>
|
|
48
|
+
|
|
49
|
+
<Accordion title="Domain-specific ranking">
|
|
50
|
+
Custom rerankers can implement business logic, user preferences, or domain-specific relevance criteria.
|
|
51
|
+
</Accordion>
|
|
52
|
+
|
|
53
|
+
<Accordion title="Handle semantic nuances">
|
|
54
|
+
Rerankers can better understand negations, comparisons, and complex queries that embedding models might miss.
|
|
55
|
+
</Accordion>
|
|
56
|
+
</AccordionGroup>
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { ExuluReranker } from "@exulu/backend";
|
|
62
|
+
|
|
63
|
+
// Create a reranker using Cohere
|
|
64
|
+
const cohereReranker = new ExuluReranker({
|
|
65
|
+
id: "cohere_reranker",
|
|
66
|
+
name: "Cohere Rerank",
|
|
67
|
+
description: "Reranks search results using Cohere's rerank-english-v3.0 model",
|
|
68
|
+
execute: async ({ query, chunks }) => {
|
|
69
|
+
const response = await fetch("https://api.cohere.com/v1/rerank", {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: {
|
|
72
|
+
"Authorization": `Bearer ${process.env.COHERE_API_KEY}`,
|
|
73
|
+
"Content-Type": "application/json"
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
model: "rerank-english-v3.0",
|
|
77
|
+
query: query,
|
|
78
|
+
documents: chunks.map(chunk => chunk.chunk_content),
|
|
79
|
+
top_n: 10,
|
|
80
|
+
return_documents: false
|
|
81
|
+
})
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const data = await response.json();
|
|
85
|
+
|
|
86
|
+
// Reorder chunks based on Cohere's ranking
|
|
87
|
+
return data.results
|
|
88
|
+
.sort((a, b) => b.relevance_score - a.relevance_score)
|
|
89
|
+
.map(result => chunks[result.index]);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Use with ExuluContext
|
|
94
|
+
const docsContext = new ExuluContext({
|
|
95
|
+
id: "documentation",
|
|
96
|
+
name: "Documentation",
|
|
97
|
+
description: "Product docs",
|
|
98
|
+
active: true,
|
|
99
|
+
fields: [/* ... */],
|
|
100
|
+
sources: [],
|
|
101
|
+
resultReranker: async (results) => {
|
|
102
|
+
return cohereReranker.run(
|
|
103
|
+
results[0]?.query || "",
|
|
104
|
+
results
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Architecture
|
|
111
|
+
|
|
112
|
+
### Integration with ExuluContext
|
|
113
|
+
|
|
114
|
+
Rerankers integrate with ExuluContext through the `resultReranker` configuration:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const context = new ExuluContext({
|
|
118
|
+
// ... other config
|
|
119
|
+
resultReranker: async (chunks) => {
|
|
120
|
+
// Access the query from the first chunk's context
|
|
121
|
+
const query = chunks[0]?.context?.query || "";
|
|
122
|
+
|
|
123
|
+
// Apply reranking
|
|
124
|
+
return reranker.run(query, chunks);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
When a search is performed:
|
|
130
|
+
1. Vector/hybrid search retrieves initial candidates
|
|
131
|
+
2. `resultReranker` is called with the chunks
|
|
132
|
+
3. Reranker reorders the chunks
|
|
133
|
+
4. Reordered results are returned to the agent/user
|
|
134
|
+
|
|
135
|
+
### Chunk structure
|
|
136
|
+
|
|
137
|
+
Rerankers work with `VectorSearchChunkResult` objects:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
type VectorSearchChunkResult = {
|
|
141
|
+
chunk_content: string; // The text content
|
|
142
|
+
chunk_index: number; // Position in original document
|
|
143
|
+
chunk_id: string; // Unique chunk ID
|
|
144
|
+
chunk_source: string; // Source item ID
|
|
145
|
+
chunk_metadata: Record<string, string>;
|
|
146
|
+
chunk_created_at: string;
|
|
147
|
+
chunk_updated_at: string;
|
|
148
|
+
item_id: string; // Parent item ID
|
|
149
|
+
item_external_id: string;
|
|
150
|
+
item_name: string; // Parent item name
|
|
151
|
+
item_updated_at: string;
|
|
152
|
+
item_created_at: string;
|
|
153
|
+
chunk_cosine_distance?: number; // Similarity score
|
|
154
|
+
chunk_fts_rank?: number; // Keyword score
|
|
155
|
+
chunk_hybrid_score?: number; // Combined score
|
|
156
|
+
context?: {
|
|
157
|
+
name: string;
|
|
158
|
+
id: string;
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Common reranking strategies
|
|
164
|
+
|
|
165
|
+
<Tabs>
|
|
166
|
+
<Tab title="API-based reranking">
|
|
167
|
+
Use third-party reranking APIs like Cohere, Voyage AI, or Jina AI:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const apiReranker = new ExuluReranker({
|
|
171
|
+
id: "cohere_rerank",
|
|
172
|
+
name: "Cohere Reranker",
|
|
173
|
+
description: "Uses Cohere rerank API",
|
|
174
|
+
execute: async ({ query, chunks }) => {
|
|
175
|
+
const response = await fetch("https://api.cohere.com/v1/rerank", {
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: {
|
|
178
|
+
"Authorization": `Bearer ${API_KEY}`,
|
|
179
|
+
"Content-Type": "application/json"
|
|
180
|
+
},
|
|
181
|
+
body: JSON.stringify({
|
|
182
|
+
model: "rerank-english-v3.0",
|
|
183
|
+
query,
|
|
184
|
+
documents: chunks.map(c => c.chunk_content),
|
|
185
|
+
top_n: chunks.length
|
|
186
|
+
})
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const data = await response.json();
|
|
190
|
+
return data.results.map(r => chunks[r.index]);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
</Tab>
|
|
195
|
+
|
|
196
|
+
<Tab title="Custom scoring">
|
|
197
|
+
Implement custom business logic:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const customReranker = new ExuluReranker({
|
|
201
|
+
id: "custom_scorer",
|
|
202
|
+
name: "Custom Scorer",
|
|
203
|
+
description: "Custom relevance scoring with business rules",
|
|
204
|
+
execute: async ({ query, chunks }) => {
|
|
205
|
+
// Score each chunk
|
|
206
|
+
const scored = chunks.map(chunk => ({
|
|
207
|
+
chunk,
|
|
208
|
+
score: computeCustomScore(query, chunk)
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
// Sort by score descending
|
|
212
|
+
scored.sort((a, b) => b.score - a.score);
|
|
213
|
+
|
|
214
|
+
return scored.map(s => s.chunk);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
function computeCustomScore(query: string, chunk: VectorSearchChunkResult): number {
|
|
219
|
+
let score = chunk.chunk_hybrid_score || 0;
|
|
220
|
+
|
|
221
|
+
// Boost recent content
|
|
222
|
+
const age = Date.now() - new Date(chunk.chunk_updated_at).getTime();
|
|
223
|
+
const daysSinceUpdate = age / (1000 * 60 * 60 * 24);
|
|
224
|
+
score += Math.max(0, 1 - daysSinceUpdate / 365);
|
|
225
|
+
|
|
226
|
+
// Boost specific categories
|
|
227
|
+
if (chunk.chunk_metadata.category === "tutorial") {
|
|
228
|
+
score *= 1.2;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Penalize very short chunks
|
|
232
|
+
if (chunk.chunk_content.length < 100) {
|
|
233
|
+
score *= 0.7;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return score;
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
</Tab>
|
|
240
|
+
|
|
241
|
+
<Tab title="LLM-based reranking">
|
|
242
|
+
Use an LLM to judge relevance:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
const llmReranker = new ExuluReranker({
|
|
246
|
+
id: "llm_rerank",
|
|
247
|
+
name: "LLM Reranker",
|
|
248
|
+
description: "Uses GPT-4 to score relevance",
|
|
249
|
+
execute: async ({ query, chunks }) => {
|
|
250
|
+
// Score chunks in parallel
|
|
251
|
+
const scoredPromises = chunks.map(async (chunk) => {
|
|
252
|
+
const prompt = `On a scale of 0-10, how relevant is this passage to the query?
|
|
253
|
+
|
|
254
|
+
Query: "${query}"
|
|
255
|
+
|
|
256
|
+
Passage: "${chunk.chunk_content}"
|
|
257
|
+
|
|
258
|
+
Respond with only a number.`;
|
|
259
|
+
|
|
260
|
+
const response = await openai.chat.completions.create({
|
|
261
|
+
model: "gpt-4o-mini",
|
|
262
|
+
messages: [{ role: "user", content: prompt }],
|
|
263
|
+
temperature: 0
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const score = parseFloat(response.choices[0].message.content);
|
|
267
|
+
|
|
268
|
+
return { chunk, score };
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const scored = await Promise.all(scoredPromises);
|
|
272
|
+
scored.sort((a, b) => b.score - a.score);
|
|
273
|
+
|
|
274
|
+
return scored.map(s => s.chunk);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
<Warning>
|
|
280
|
+
LLM-based reranking can be expensive and slow. Use for small result sets or where quality is critical.
|
|
281
|
+
</Warning>
|
|
282
|
+
</Tab>
|
|
283
|
+
|
|
284
|
+
<Tab title="Hybrid approach">
|
|
285
|
+
Combine multiple signals:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
const hybridReranker = new ExuluReranker({
|
|
289
|
+
id: "hybrid_rerank",
|
|
290
|
+
name: "Hybrid Reranker",
|
|
291
|
+
description: "Combines API reranking with custom scoring",
|
|
292
|
+
execute: async ({ query, chunks }) => {
|
|
293
|
+
// Step 1: Get API reranking scores
|
|
294
|
+
const apiResponse = await fetch("https://api.cohere.com/v1/rerank", {
|
|
295
|
+
method: "POST",
|
|
296
|
+
headers: {
|
|
297
|
+
"Authorization": `Bearer ${API_KEY}`,
|
|
298
|
+
"Content-Type": "application/json"
|
|
299
|
+
},
|
|
300
|
+
body: JSON.stringify({
|
|
301
|
+
model: "rerank-english-v3.0",
|
|
302
|
+
query,
|
|
303
|
+
documents: chunks.map(c => c.chunk_content)
|
|
304
|
+
})
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const apiData = await apiResponse.json();
|
|
308
|
+
|
|
309
|
+
// Step 2: Combine with custom business logic
|
|
310
|
+
const scored = chunks.map((chunk, idx) => {
|
|
311
|
+
const apiScore = apiData.results.find(r => r.index === idx)?.relevance_score || 0;
|
|
312
|
+
const customScore = computeCustomScore(chunk);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
chunk,
|
|
316
|
+
score: (apiScore * 0.7) + (customScore * 0.3) // Weighted combination
|
|
317
|
+
};
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
scored.sort((a, b) => b.score - a.score);
|
|
321
|
+
return scored.map(s => s.chunk);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
</Tab>
|
|
326
|
+
</Tabs>
|
|
327
|
+
|
|
328
|
+
## Usage patterns
|
|
329
|
+
|
|
330
|
+
### With ExuluContext
|
|
331
|
+
|
|
332
|
+
The most common pattern is integrating with ExuluContext:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import { ExuluContext, ExuluReranker } from "@exulu/backend";
|
|
336
|
+
|
|
337
|
+
// Create reranker
|
|
338
|
+
const reranker = new ExuluReranker({
|
|
339
|
+
id: "my_reranker",
|
|
340
|
+
name: "My Reranker",
|
|
341
|
+
description: "Custom reranking logic",
|
|
342
|
+
execute: async ({ query, chunks }) => {
|
|
343
|
+
// Your reranking logic
|
|
344
|
+
return reorderedChunks;
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Use with context
|
|
349
|
+
const context = new ExuluContext({
|
|
350
|
+
id: "docs",
|
|
351
|
+
name: "Documentation",
|
|
352
|
+
// ... other config
|
|
353
|
+
resultReranker: async (chunks) => {
|
|
354
|
+
const query = chunks[0]?.context?.query || "";
|
|
355
|
+
return reranker.run(query, chunks);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Standalone usage
|
|
361
|
+
|
|
362
|
+
You can also use rerankers independently:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const chunks = await context.search({
|
|
366
|
+
query: "How do I authenticate?",
|
|
367
|
+
method: "hybridSearch",
|
|
368
|
+
// ... other params
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const rerankedChunks = await reranker.run(
|
|
372
|
+
"How do I authenticate?",
|
|
373
|
+
chunks.chunks
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
console.log(rerankedChunks[0].chunk_content); // Most relevant result
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Conditional reranking
|
|
380
|
+
|
|
381
|
+
Apply reranking only when needed:
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
resultReranker: async (chunks) => {
|
|
385
|
+
// Only rerank if we have enough results
|
|
386
|
+
if (chunks.length < 3) {
|
|
387
|
+
return chunks;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Only rerank if scores are similar (ambiguous results)
|
|
391
|
+
const scores = chunks
|
|
392
|
+
.map(c => c.chunk_hybrid_score || 0)
|
|
393
|
+
.filter(s => s > 0);
|
|
394
|
+
|
|
395
|
+
if (scores.length === 0) return chunks;
|
|
396
|
+
|
|
397
|
+
const avgScore = scores.reduce((a, b) => a + b) / scores.length;
|
|
398
|
+
const variance = scores.reduce((sum, score) => sum + Math.pow(score - avgScore, 2), 0) / scores.length;
|
|
399
|
+
|
|
400
|
+
// Low variance means clear ranking, skip reranking
|
|
401
|
+
if (variance < 0.01) {
|
|
402
|
+
return chunks;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// High variance, apply reranking
|
|
406
|
+
const query = chunks[0]?.context?.query || "";
|
|
407
|
+
return reranker.run(query, chunks);
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Popular reranking services
|
|
412
|
+
|
|
413
|
+
<CardGroup cols={2}>
|
|
414
|
+
<Card title="Cohere Rerank" icon="bolt" href="https://cohere.com/rerank">
|
|
415
|
+
High-quality reranking with multilingual support
|
|
416
|
+
</Card>
|
|
417
|
+
<Card title="Voyage AI" icon="ship" href="https://www.voyageai.com/">
|
|
418
|
+
Domain-specific reranking models
|
|
419
|
+
</Card>
|
|
420
|
+
<Card title="Jina AI" icon="cube" href="https://jina.ai/reranker/">
|
|
421
|
+
Open-source and API-based reranking
|
|
422
|
+
</Card>
|
|
423
|
+
<Card title="Custom models" icon="brain">
|
|
424
|
+
Self-hosted models like cross-encoders or custom LLM scoring
|
|
425
|
+
</Card>
|
|
426
|
+
</CardGroup>
|
|
427
|
+
|
|
428
|
+
## Best practices
|
|
429
|
+
|
|
430
|
+
<Tip>
|
|
431
|
+
**Rerank the right amount**: Reranking is slower than initial retrieval. Retrieve more candidates (e.g., 50-100) with fast vector search, then rerank the top 10-20.
|
|
432
|
+
</Tip>
|
|
433
|
+
|
|
434
|
+
<Note>
|
|
435
|
+
**Preserve metadata**: Ensure your reranker returns chunks with all their original metadata intact. Only change the order, not the content.
|
|
436
|
+
</Note>
|
|
437
|
+
|
|
438
|
+
<Warning>
|
|
439
|
+
**Watch latency**: Reranking adds latency to search. Test with production query volumes and consider caching or async reranking for non-critical queries.
|
|
440
|
+
</Warning>
|
|
441
|
+
|
|
442
|
+
<Info>
|
|
443
|
+
**Measure impact**: A/B test your reranker to ensure it improves relevance. Track metrics like click-through rate or user satisfaction.
|
|
444
|
+
</Info>
|
|
445
|
+
|
|
446
|
+
## Performance considerations
|
|
447
|
+
|
|
448
|
+
<AccordionGroup>
|
|
449
|
+
<Accordion title="Batch reranking">
|
|
450
|
+
Some APIs support batch reranking for better throughput:
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
execute: async ({ query, chunks }) => {
|
|
454
|
+
// Send all documents at once
|
|
455
|
+
const response = await cohereClient.rerank({
|
|
456
|
+
model: "rerank-english-v3.0",
|
|
457
|
+
query,
|
|
458
|
+
documents: chunks.map(c => c.chunk_content),
|
|
459
|
+
top_n: 20 // Limit results
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
return response.results.map(r => chunks[r.index]);
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
</Accordion>
|
|
466
|
+
|
|
467
|
+
<Accordion title="Caching">
|
|
468
|
+
Cache reranking results for repeated queries:
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
const cache = new Map();
|
|
472
|
+
|
|
473
|
+
execute: async ({ query, chunks }) => {
|
|
474
|
+
const cacheKey = `${query}:${chunks.map(c => c.chunk_id).join(",")}`;
|
|
475
|
+
|
|
476
|
+
if (cache.has(cacheKey)) {
|
|
477
|
+
return cache.get(cacheKey);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const reranked = await performReranking(query, chunks);
|
|
481
|
+
cache.set(cacheKey, reranked);
|
|
482
|
+
|
|
483
|
+
return reranked;
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
</Accordion>
|
|
487
|
+
|
|
488
|
+
<Accordion title="Parallel processing">
|
|
489
|
+
For custom scoring, process chunks in parallel:
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
execute: async ({ query, chunks }) => {
|
|
493
|
+
const scored = await Promise.all(
|
|
494
|
+
chunks.map(async (chunk) => ({
|
|
495
|
+
chunk,
|
|
496
|
+
score: await computeScore(query, chunk)
|
|
497
|
+
}))
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
scored.sort((a, b) => b.score - a.score);
|
|
501
|
+
return scored.map(s => s.chunk);
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
</Accordion>
|
|
505
|
+
</AccordionGroup>
|
|
506
|
+
|
|
507
|
+
## Next steps
|
|
508
|
+
|
|
509
|
+
<CardGroup cols={2}>
|
|
510
|
+
<Card title="Configuration" icon="gear" href="/core/exulu-reranker/configuration">
|
|
511
|
+
Learn about configuration options
|
|
512
|
+
</Card>
|
|
513
|
+
<Card title="API reference" icon="code" href="/core/exulu-reranker/api-reference">
|
|
514
|
+
Explore methods and properties
|
|
515
|
+
</Card>
|
|
516
|
+
</CardGroup>
|