@exulu/backend 1.48.2 → 1.49.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/index.cjs +351 -42
- package/dist/index.d.cts +96 -1
- package/dist/index.d.ts +96 -1
- package/dist/index.js +340 -38
- package/ee/{markdown.ts → chunking/markdown.ts} +2 -2
- package/ee/python/README.md +295 -0
- package/ee/python/documents/processing/README.md +155 -0
- package/ee/{documents → python/documents}/processing/doc_processor.ts +25 -17
- package/ee/{documents/processing/pdf_to_markdown.py → python/documents/processing/document_to_markdown.py} +3 -10
- package/ee/python/setup.sh +180 -0
- package/package.json +14 -3
- package/scripts/postinstall.cjs +149 -0
- package/.agents/skills/mintlify/SKILL.md +0 -347
- package/.editorconfig +0 -15
- package/.eslintrc.json +0 -52
- package/.github/workflows/release-backend.yml +0 -38
- package/.husky/commit-msg +0 -1
- package/.jscpd.json +0 -18
- package/.mcp.json +0 -25
- package/.nvmrc +0 -1
- package/.prettierignore +0 -5
- package/.prettierrc.json +0 -12
- package/CHANGELOG.md +0 -8
- package/SECURITY.md +0 -5
- package/commitlint.config.js +0 -4
- package/devops/documentation/patch-older-releases.md +0 -42
- package/ee/documents/processing/build_pdf_processor.sh +0 -35
- package/ee/documents/processing/chunk_markdown.py +0 -263
- package/ee/documents/processing/pdf_processor.spec +0 -115
- package/eslint.config.js +0 -88
- package/jest.config.ts +0 -25
- package/mintlify-docs/.mintignore +0 -7
- package/mintlify-docs/AGENTS.md +0 -33
- package/mintlify-docs/CLAUDE.MD +0 -50
- package/mintlify-docs/CONTRIBUTING.md +0 -32
- package/mintlify-docs/LICENSE +0 -21
- package/mintlify-docs/README.md +0 -55
- package/mintlify-docs/ai-tools/claude-code.mdx +0 -43
- package/mintlify-docs/ai-tools/cursor.mdx +0 -39
- package/mintlify-docs/ai-tools/windsurf.mdx +0 -39
- package/mintlify-docs/api-reference/core-types/agent-types.mdx +0 -110
- package/mintlify-docs/api-reference/core-types/analytics-types.mdx +0 -95
- package/mintlify-docs/api-reference/core-types/configuration-types.mdx +0 -83
- package/mintlify-docs/api-reference/core-types/evaluation-types.mdx +0 -106
- package/mintlify-docs/api-reference/core-types/job-types.mdx +0 -135
- package/mintlify-docs/api-reference/core-types/overview.mdx +0 -73
- package/mintlify-docs/api-reference/core-types/prompt-types.mdx +0 -102
- package/mintlify-docs/api-reference/core-types/rbac-types.mdx +0 -163
- package/mintlify-docs/api-reference/core-types/session-types.mdx +0 -77
- package/mintlify-docs/api-reference/core-types/user-management.mdx +0 -112
- package/mintlify-docs/api-reference/core-types/workflow-types.mdx +0 -88
- package/mintlify-docs/api-reference/core-types.mdx +0 -585
- package/mintlify-docs/api-reference/dynamic-types.mdx +0 -851
- package/mintlify-docs/api-reference/endpoint/create.mdx +0 -4
- package/mintlify-docs/api-reference/endpoint/delete.mdx +0 -4
- package/mintlify-docs/api-reference/endpoint/get.mdx +0 -4
- package/mintlify-docs/api-reference/endpoint/webhook.mdx +0 -4
- package/mintlify-docs/api-reference/introduction.mdx +0 -661
- package/mintlify-docs/api-reference/mutations.mdx +0 -1012
- package/mintlify-docs/api-reference/openapi.json +0 -217
- package/mintlify-docs/api-reference/queries.mdx +0 -1154
- package/mintlify-docs/backend/introduction.mdx +0 -218
- package/mintlify-docs/changelog.mdx +0 -387
- package/mintlify-docs/community-edition.mdx +0 -304
- package/mintlify-docs/core/exulu-agent/api-reference.mdx +0 -894
- package/mintlify-docs/core/exulu-agent/configuration.mdx +0 -690
- package/mintlify-docs/core/exulu-agent/introduction.mdx +0 -552
- package/mintlify-docs/core/exulu-app/api-reference.mdx +0 -481
- package/mintlify-docs/core/exulu-app/configuration.mdx +0 -319
- package/mintlify-docs/core/exulu-app/introduction.mdx +0 -117
- package/mintlify-docs/core/exulu-authentication.mdx +0 -810
- package/mintlify-docs/core/exulu-chunkers/api-reference.mdx +0 -1011
- package/mintlify-docs/core/exulu-chunkers/configuration.mdx +0 -596
- package/mintlify-docs/core/exulu-chunkers/introduction.mdx +0 -403
- package/mintlify-docs/core/exulu-context/api-reference.mdx +0 -911
- package/mintlify-docs/core/exulu-context/configuration.mdx +0 -648
- package/mintlify-docs/core/exulu-context/introduction.mdx +0 -394
- package/mintlify-docs/core/exulu-database.mdx +0 -811
- package/mintlify-docs/core/exulu-default-agents.mdx +0 -545
- package/mintlify-docs/core/exulu-eval/api-reference.mdx +0 -772
- package/mintlify-docs/core/exulu-eval/configuration.mdx +0 -680
- package/mintlify-docs/core/exulu-eval/introduction.mdx +0 -459
- package/mintlify-docs/core/exulu-logging.mdx +0 -464
- package/mintlify-docs/core/exulu-otel.mdx +0 -670
- package/mintlify-docs/core/exulu-queues/api-reference.mdx +0 -648
- package/mintlify-docs/core/exulu-queues/configuration.mdx +0 -650
- package/mintlify-docs/core/exulu-queues/introduction.mdx +0 -474
- package/mintlify-docs/core/exulu-reranker/api-reference.mdx +0 -630
- package/mintlify-docs/core/exulu-reranker/configuration.mdx +0 -663
- package/mintlify-docs/core/exulu-reranker/introduction.mdx +0 -516
- package/mintlify-docs/core/exulu-tool/api-reference.mdx +0 -723
- package/mintlify-docs/core/exulu-tool/configuration.mdx +0 -805
- package/mintlify-docs/core/exulu-tool/introduction.mdx +0 -539
- package/mintlify-docs/core/exulu-variables/api-reference.mdx +0 -699
- package/mintlify-docs/core/exulu-variables/configuration.mdx +0 -736
- package/mintlify-docs/core/exulu-variables/introduction.mdx +0 -511
- package/mintlify-docs/development.mdx +0 -94
- package/mintlify-docs/docs.json +0 -248
- package/mintlify-docs/enterprise-edition.mdx +0 -538
- package/mintlify-docs/essentials/code.mdx +0 -35
- package/mintlify-docs/essentials/images.mdx +0 -59
- package/mintlify-docs/essentials/markdown.mdx +0 -88
- package/mintlify-docs/essentials/navigation.mdx +0 -87
- package/mintlify-docs/essentials/reusable-snippets.mdx +0 -110
- package/mintlify-docs/essentials/settings.mdx +0 -318
- package/mintlify-docs/favicon.svg +0 -3
- package/mintlify-docs/frontend/introduction.mdx +0 -39
- package/mintlify-docs/getting-started.mdx +0 -267
- package/mintlify-docs/guides/custom-agent.mdx +0 -608
- package/mintlify-docs/guides/first-agent.mdx +0 -315
- 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 +0 -411
- package/mintlify-docs/logo/dark.svg +0 -9
- package/mintlify-docs/logo/light.svg +0 -9
- package/mintlify-docs/partners.mdx +0 -558
- package/mintlify-docs/products.mdx +0 -77
- package/mintlify-docs/snippets/snippet-intro.mdx +0 -4
- package/mintlify-docs/styles.css +0 -207
- package/ngrok.bash +0 -1
- package/ngrok.md +0 -6
- package/ngrok.yml +0 -10
- package/release.config.cjs +0 -15
- package/skills-lock.json +0 -10
- package/types/context-processor.ts +0 -45
- package/types/enums/eval-types.ts +0 -5
- package/types/enums/field-types.ts +0 -1
- package/types/enums/jobs.ts +0 -11
- package/types/enums/statistics.ts +0 -13
- package/types/exulu-table-definition.ts +0 -79
- package/types/file-types.ts +0 -18
- package/types/models/agent-session.ts +0 -27
- package/types/models/agent.ts +0 -68
- package/types/models/context.ts +0 -53
- package/types/models/embedding.ts +0 -17
- package/types/models/eval-run.ts +0 -40
- package/types/models/exulu-agent-tool-config.ts +0 -11
- package/types/models/item.ts +0 -21
- package/types/models/job.ts +0 -8
- package/types/models/project.ts +0 -16
- package/types/models/rate-limiter-rules.ts +0 -7
- package/types/models/test-case.ts +0 -25
- package/types/models/tool.ts +0 -9
- package/types/models/user-role.ts +0 -12
- package/types/models/user.ts +0 -20
- package/types/models/variable.ts +0 -8
- package/types/models/vector-methods.ts +0 -7
- package/types/provider-config.ts +0 -21
- package/types/queue-config.ts +0 -16
- package/types/rbac-rights-modes.ts +0 -1
- package/types/statistics.ts +0 -20
- package/types/workflow.ts +0 -31
- /package/ee/{documents → python/documents}/THIRD_PARTY_LICENSES/docling.txt +0 -0
- /package/ee/{documents/processing → python}/requirements.txt +0 -0
|
@@ -1,663 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Configuration"
|
|
3
|
-
description: "Complete guide to configuring ExuluReranker for search result optimization"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Constructor parameters
|
|
7
|
-
|
|
8
|
-
The ExuluReranker constructor accepts a configuration object with the following parameters:
|
|
9
|
-
|
|
10
|
-
```typescript
|
|
11
|
-
const reranker = new ExuluReranker({
|
|
12
|
-
id: string,
|
|
13
|
-
name: string,
|
|
14
|
-
description: string,
|
|
15
|
-
execute: ExecuteFunction
|
|
16
|
-
});
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Required parameters
|
|
20
|
-
|
|
21
|
-
### id
|
|
22
|
-
|
|
23
|
-
<ParamField path="id" type="string" required>
|
|
24
|
-
Unique identifier for the reranker. Used for tracking and logging.
|
|
25
|
-
</ParamField>
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
id: "cohere_reranker"
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
<Info>
|
|
32
|
-
Unlike tools and contexts, the reranker ID doesn't have strict formatting requirements, but using snake_case or kebab-case is recommended for consistency.
|
|
33
|
-
</Info>
|
|
34
|
-
|
|
35
|
-
### name
|
|
36
|
-
|
|
37
|
-
<ParamField path="name" type="string" required>
|
|
38
|
-
Human-readable name for the reranker
|
|
39
|
-
</ParamField>
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
name: "Cohere Rerank v3"
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### description
|
|
46
|
-
|
|
47
|
-
<ParamField path="description" type="string" required>
|
|
48
|
-
Description of what this reranker does and which model/algorithm it uses
|
|
49
|
-
</ParamField>
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
description: "Reranks search results using Cohere's rerank-english-v3.0 model for improved relevance"
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### execute
|
|
56
|
-
|
|
57
|
-
<ParamField path="execute" type="ExecuteFunction" required>
|
|
58
|
-
The async function that implements the reranking logic
|
|
59
|
-
</ParamField>
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
execute: async ({ query, chunks }: {
|
|
63
|
-
query: string;
|
|
64
|
-
chunks: VectorSearchChunkResult[];
|
|
65
|
-
}) => Promise<VectorSearchChunkResult[]>
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
The execute function receives:
|
|
69
|
-
|
|
70
|
-
<ParamField path="query" type="string">
|
|
71
|
-
The user's search query
|
|
72
|
-
</ParamField>
|
|
73
|
-
|
|
74
|
-
<ParamField path="chunks" type="VectorSearchChunkResult[]">
|
|
75
|
-
Array of search result chunks to rerank
|
|
76
|
-
</ParamField>
|
|
77
|
-
|
|
78
|
-
The function must return:
|
|
79
|
-
|
|
80
|
-
<ResponseField name="return" type="Promise<VectorSearchChunkResult[]>">
|
|
81
|
-
Reordered array of chunks, typically sorted by relevance (most relevant first)
|
|
82
|
-
</ResponseField>
|
|
83
|
-
|
|
84
|
-
## Execute function implementation
|
|
85
|
-
|
|
86
|
-
### Basic structure
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
execute: async ({ query, chunks }) => {
|
|
90
|
-
// 1. Extract content from chunks
|
|
91
|
-
const documents = chunks.map(chunk => chunk.chunk_content);
|
|
92
|
-
|
|
93
|
-
// 2. Call reranking service/model
|
|
94
|
-
const rankings = await rerankingService.rank(query, documents);
|
|
95
|
-
|
|
96
|
-
// 3. Reorder chunks based on rankings
|
|
97
|
-
const reordered = rankings.map(ranking => chunks[ranking.index]);
|
|
98
|
-
|
|
99
|
-
// 4. Return reordered chunks
|
|
100
|
-
return reordered;
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Working with chunk data
|
|
105
|
-
|
|
106
|
-
Each chunk in the input array has this structure:
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
type VectorSearchChunkResult = {
|
|
110
|
-
// Content
|
|
111
|
-
chunk_content: string; // The actual text
|
|
112
|
-
chunk_index: number; // Position in original document
|
|
113
|
-
chunk_metadata: Record<string, string>;
|
|
114
|
-
|
|
115
|
-
// IDs
|
|
116
|
-
chunk_id: string; // Unique chunk ID
|
|
117
|
-
chunk_source: string; // Source item ID
|
|
118
|
-
item_id: string; // Parent item ID
|
|
119
|
-
item_external_id: string; // External reference
|
|
120
|
-
|
|
121
|
-
// Names
|
|
122
|
-
item_name: string; // Parent item name
|
|
123
|
-
|
|
124
|
-
// Timestamps
|
|
125
|
-
chunk_created_at: string;
|
|
126
|
-
chunk_updated_at: string;
|
|
127
|
-
item_created_at: string;
|
|
128
|
-
item_updated_at: string;
|
|
129
|
-
|
|
130
|
-
// Scores (from initial search)
|
|
131
|
-
chunk_cosine_distance?: number; // Vector similarity
|
|
132
|
-
chunk_fts_rank?: number; // Keyword search score
|
|
133
|
-
chunk_hybrid_score?: number; // Combined score
|
|
134
|
-
|
|
135
|
-
// Context
|
|
136
|
-
context?: {
|
|
137
|
-
name: string; // Context name
|
|
138
|
-
id: string; // Context ID
|
|
139
|
-
};
|
|
140
|
-
};
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
<Note>
|
|
144
|
-
The reranker should only change the **order** of chunks. Do not modify the chunk content or metadata. Return the same chunk objects in a different order.
|
|
145
|
-
</Note>
|
|
146
|
-
|
|
147
|
-
## Configuration examples
|
|
148
|
-
|
|
149
|
-
### Cohere reranker
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
import { ExuluReranker } from "@exulu/backend";
|
|
153
|
-
|
|
154
|
-
const cohereReranker = new ExuluReranker({
|
|
155
|
-
id: "cohere_rerank_v3",
|
|
156
|
-
name: "Cohere Rerank English v3",
|
|
157
|
-
description: "Uses Cohere's rerank-english-v3.0 model for high-quality reranking",
|
|
158
|
-
execute: async ({ query, chunks }) => {
|
|
159
|
-
// Call Cohere rerank API
|
|
160
|
-
const response = await fetch("https://api.cohere.com/v1/rerank", {
|
|
161
|
-
method: "POST",
|
|
162
|
-
headers: {
|
|
163
|
-
"Authorization": `Bearer ${process.env.COHERE_API_KEY}`,
|
|
164
|
-
"Content-Type": "application/json"
|
|
165
|
-
},
|
|
166
|
-
body: JSON.stringify({
|
|
167
|
-
model: "rerank-english-v3.0",
|
|
168
|
-
query: query,
|
|
169
|
-
documents: chunks.map(chunk => chunk.chunk_content),
|
|
170
|
-
top_n: Math.min(chunks.length, 20), // Limit to top 20
|
|
171
|
-
return_documents: false
|
|
172
|
-
})
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
if (!response.ok) {
|
|
176
|
-
console.error("Cohere rerank failed:", await response.text());
|
|
177
|
-
return chunks; // Return original order on error
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const data = await response.json();
|
|
181
|
-
|
|
182
|
-
// Reorder chunks based on Cohere's ranking
|
|
183
|
-
return data.results
|
|
184
|
-
.sort((a, b) => b.relevance_score - a.relevance_score)
|
|
185
|
-
.map(result => chunks[result.index]);
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Voyage AI reranker
|
|
191
|
-
|
|
192
|
-
```typescript
|
|
193
|
-
const voyageReranker = new ExuluReranker({
|
|
194
|
-
id: "voyage_rerank_2",
|
|
195
|
-
name: "Voyage Rerank 2",
|
|
196
|
-
description: "Uses Voyage AI's rerank-2 model",
|
|
197
|
-
execute: async ({ query, chunks }) => {
|
|
198
|
-
const response = await fetch("https://api.voyageai.com/v1/rerank", {
|
|
199
|
-
method: "POST",
|
|
200
|
-
headers: {
|
|
201
|
-
"Authorization": `Bearer ${process.env.VOYAGE_API_KEY}`,
|
|
202
|
-
"Content-Type": "application/json"
|
|
203
|
-
},
|
|
204
|
-
body: JSON.stringify({
|
|
205
|
-
query: query,
|
|
206
|
-
documents: chunks.map(c => c.chunk_content),
|
|
207
|
-
model: "rerank-2",
|
|
208
|
-
top_k: chunks.length
|
|
209
|
-
})
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
const data = await response.json();
|
|
213
|
-
|
|
214
|
-
// Map indices back to chunks
|
|
215
|
-
return data.results.map(result => chunks[result.index]);
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### Custom scoring reranker
|
|
221
|
-
|
|
222
|
-
```typescript
|
|
223
|
-
const customReranker = new ExuluReranker({
|
|
224
|
-
id: "custom_business_rules",
|
|
225
|
-
name: "Custom Business Rules Reranker",
|
|
226
|
-
description: "Reranks based on recency, category, and length",
|
|
227
|
-
execute: async ({ query, chunks }) => {
|
|
228
|
-
// Score each chunk
|
|
229
|
-
const scored = chunks.map(chunk => {
|
|
230
|
-
let score = chunk.chunk_hybrid_score || 0;
|
|
231
|
-
|
|
232
|
-
// Boost recent content (up to +1.0)
|
|
233
|
-
const daysSinceUpdate = (Date.now() - new Date(chunk.chunk_updated_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
234
|
-
const recencyBoost = Math.max(0, 1 - daysSinceUpdate / 365);
|
|
235
|
-
score += recencyBoost;
|
|
236
|
-
|
|
237
|
-
// Boost specific categories
|
|
238
|
-
if (chunk.chunk_metadata.category === "tutorial") {
|
|
239
|
-
score *= 1.3;
|
|
240
|
-
} else if (chunk.chunk_metadata.category === "reference") {
|
|
241
|
-
score *= 1.1;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Penalize very short chunks (likely incomplete)
|
|
245
|
-
if (chunk.chunk_content.length < 100) {
|
|
246
|
-
score *= 0.6;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Boost chunks with exact keyword matches
|
|
250
|
-
const queryTerms = query.toLowerCase().split(/\s+/);
|
|
251
|
-
const content = chunk.chunk_content.toLowerCase();
|
|
252
|
-
const exactMatches = queryTerms.filter(term => content.includes(term)).length;
|
|
253
|
-
score += exactMatches * 0.1;
|
|
254
|
-
|
|
255
|
-
return { chunk, score };
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
// Sort by score descending
|
|
259
|
-
scored.sort((a, b) => b.score - a.score);
|
|
260
|
-
|
|
261
|
-
return scored.map(s => s.chunk);
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### LLM-based reranker
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
import OpenAI from "openai";
|
|
270
|
-
|
|
271
|
-
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
272
|
-
|
|
273
|
-
const llmReranker = new ExuluReranker({
|
|
274
|
-
id: "gpt4_reranker",
|
|
275
|
-
name: "GPT-4 Reranker",
|
|
276
|
-
description: "Uses GPT-4 to judge relevance of each result",
|
|
277
|
-
execute: async ({ query, chunks }) => {
|
|
278
|
-
// Limit to top 10 to control cost
|
|
279
|
-
const topChunks = chunks.slice(0, 10);
|
|
280
|
-
|
|
281
|
-
// Score chunks in parallel
|
|
282
|
-
const scoredPromises = topChunks.map(async (chunk, idx) => {
|
|
283
|
-
const prompt = `On a scale of 0-10, how relevant is this passage to answering the user's question?
|
|
284
|
-
|
|
285
|
-
Question: "${query}"
|
|
286
|
-
|
|
287
|
-
Passage:
|
|
288
|
-
"""
|
|
289
|
-
${chunk.chunk_content}
|
|
290
|
-
"""
|
|
291
|
-
|
|
292
|
-
Consider:
|
|
293
|
-
- Direct relevance to the question
|
|
294
|
-
- Quality and completeness of information
|
|
295
|
-
- Clarity and usefulness
|
|
296
|
-
|
|
297
|
-
Respond with ONLY a number between 0 and 10.`;
|
|
298
|
-
|
|
299
|
-
try {
|
|
300
|
-
const response = await openai.chat.completions.create({
|
|
301
|
-
model: "gpt-4o-mini",
|
|
302
|
-
messages: [{ role: "user", content: prompt }],
|
|
303
|
-
temperature: 0,
|
|
304
|
-
max_tokens: 5
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
const scoreText = response.choices[0].message.content?.trim() || "0";
|
|
308
|
-
const score = parseFloat(scoreText);
|
|
309
|
-
|
|
310
|
-
return { chunk, score: isNaN(score) ? 0 : score, idx };
|
|
311
|
-
} catch (error) {
|
|
312
|
-
console.error(`Error scoring chunk ${idx}:`, error);
|
|
313
|
-
return { chunk, score: 0, idx };
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
const scored = await Promise.all(scoredPromises);
|
|
318
|
-
|
|
319
|
-
// Sort by score descending
|
|
320
|
-
scored.sort((a, b) => b.score - a.score);
|
|
321
|
-
|
|
322
|
-
// Return reordered chunks plus remaining chunks
|
|
323
|
-
return [
|
|
324
|
-
...scored.map(s => s.chunk),
|
|
325
|
-
...chunks.slice(10)
|
|
326
|
-
];
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
<Warning>
|
|
332
|
-
LLM-based reranking can be expensive. A query with 10 chunks costs 10 LLM calls. Use for critical queries or with cheaper models like GPT-4o-mini.
|
|
333
|
-
</Warning>
|
|
334
|
-
|
|
335
|
-
### Hybrid reranker
|
|
336
|
-
|
|
337
|
-
Combine API reranking with custom business logic:
|
|
338
|
-
|
|
339
|
-
```typescript
|
|
340
|
-
const hybridReranker = new ExuluReranker({
|
|
341
|
-
id: "hybrid_cohere_custom",
|
|
342
|
-
name: "Hybrid Cohere + Custom",
|
|
343
|
-
description: "Combines Cohere reranking with custom business rules",
|
|
344
|
-
execute: async ({ query, chunks }) => {
|
|
345
|
-
// Step 1: Get Cohere scores
|
|
346
|
-
const cohereResponse = await fetch("https://api.cohere.com/v1/rerank", {
|
|
347
|
-
method: "POST",
|
|
348
|
-
headers: {
|
|
349
|
-
"Authorization": `Bearer ${process.env.COHERE_API_KEY}`,
|
|
350
|
-
"Content-Type": "application/json"
|
|
351
|
-
},
|
|
352
|
-
body: JSON.stringify({
|
|
353
|
-
model: "rerank-english-v3.0",
|
|
354
|
-
query: query,
|
|
355
|
-
documents: chunks.map(c => c.chunk_content)
|
|
356
|
-
})
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
const cohereData = await cohereResponse.json();
|
|
360
|
-
|
|
361
|
-
// Step 2: Compute custom scores
|
|
362
|
-
const scored = chunks.map((chunk, idx) => {
|
|
363
|
-
// Get Cohere relevance score (0-1)
|
|
364
|
-
const cohereScore = cohereData.results.find(r => r.index === idx)?.relevance_score || 0;
|
|
365
|
-
|
|
366
|
-
// Compute custom score
|
|
367
|
-
let customScore = 0;
|
|
368
|
-
|
|
369
|
-
// Recency
|
|
370
|
-
const daysSinceUpdate = (Date.now() - new Date(chunk.chunk_updated_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
371
|
-
customScore += Math.max(0, 1 - daysSinceUpdate / 180); // Recent = higher score
|
|
372
|
-
|
|
373
|
-
// Category preference
|
|
374
|
-
if (chunk.chunk_metadata.priority === "high") {
|
|
375
|
-
customScore += 0.5;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Normalize custom score to 0-1
|
|
379
|
-
customScore = Math.min(1, customScore);
|
|
380
|
-
|
|
381
|
-
// Step 3: Combine scores (70% Cohere, 30% custom)
|
|
382
|
-
const finalScore = (cohereScore * 0.7) + (customScore * 0.3);
|
|
383
|
-
|
|
384
|
-
return { chunk, score: finalScore };
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
// Sort by combined score
|
|
388
|
-
scored.sort((a, b) => b.score - a.score);
|
|
389
|
-
|
|
390
|
-
return scored.map(s => s.chunk);
|
|
391
|
-
}
|
|
392
|
-
});
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
### Cached reranker
|
|
396
|
-
|
|
397
|
-
Add caching to reduce API calls:
|
|
398
|
-
|
|
399
|
-
```typescript
|
|
400
|
-
import crypto from "crypto";
|
|
401
|
-
|
|
402
|
-
const cache = new Map<string, VectorSearchChunkResult[]>();
|
|
403
|
-
const CACHE_TTL = 3600000; // 1 hour
|
|
404
|
-
const cacheTimestamps = new Map<string, number>();
|
|
405
|
-
|
|
406
|
-
const cachedReranker = new ExuluReranker({
|
|
407
|
-
id: "cached_cohere",
|
|
408
|
-
name: "Cached Cohere Reranker",
|
|
409
|
-
description: "Cohere reranking with caching",
|
|
410
|
-
execute: async ({ query, chunks }) => {
|
|
411
|
-
// Create cache key from query + chunk IDs
|
|
412
|
-
const chunkIds = chunks.map(c => c.chunk_id).sort().join(",");
|
|
413
|
-
const cacheKey = crypto
|
|
414
|
-
.createHash("md5")
|
|
415
|
-
.update(`${query}:${chunkIds}`)
|
|
416
|
-
.digest("hex");
|
|
417
|
-
|
|
418
|
-
// Check cache
|
|
419
|
-
const cached = cache.get(cacheKey);
|
|
420
|
-
const timestamp = cacheTimestamps.get(cacheKey);
|
|
421
|
-
|
|
422
|
-
if (cached && timestamp && Date.now() - timestamp < CACHE_TTL) {
|
|
423
|
-
console.log("Reranker cache hit");
|
|
424
|
-
return cached;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Call Cohere
|
|
428
|
-
const response = await fetch("https://api.cohere.com/v1/rerank", {
|
|
429
|
-
method: "POST",
|
|
430
|
-
headers: {
|
|
431
|
-
"Authorization": `Bearer ${process.env.COHERE_API_KEY}`,
|
|
432
|
-
"Content-Type": "application/json"
|
|
433
|
-
},
|
|
434
|
-
body: JSON.stringify({
|
|
435
|
-
model: "rerank-english-v3.0",
|
|
436
|
-
query,
|
|
437
|
-
documents: chunks.map(c => c.chunk_content)
|
|
438
|
-
})
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
const data = await response.json();
|
|
442
|
-
const reranked = data.results
|
|
443
|
-
.sort((a, b) => b.relevance_score - a.relevance_score)
|
|
444
|
-
.map(r => chunks[r.index]);
|
|
445
|
-
|
|
446
|
-
// Store in cache
|
|
447
|
-
cache.set(cacheKey, reranked);
|
|
448
|
-
cacheTimestamps.set(cacheKey, Date.now());
|
|
449
|
-
|
|
450
|
-
// Clean old cache entries
|
|
451
|
-
if (cache.size > 1000) {
|
|
452
|
-
const oldestKey = Array.from(cacheTimestamps.entries())
|
|
453
|
-
.sort((a, b) => a[1] - b[1])[0][0];
|
|
454
|
-
cache.delete(oldestKey);
|
|
455
|
-
cacheTimestamps.delete(oldestKey);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
return reranked;
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
### Error-resilient reranker
|
|
464
|
-
|
|
465
|
-
Always return results even if reranking fails:
|
|
466
|
-
|
|
467
|
-
```typescript
|
|
468
|
-
const resilientReranker = new ExuluReranker({
|
|
469
|
-
id: "resilient_reranker",
|
|
470
|
-
name: "Resilient Reranker",
|
|
471
|
-
description: "Falls back to original order on errors",
|
|
472
|
-
execute: async ({ query, chunks }) => {
|
|
473
|
-
try {
|
|
474
|
-
const response = await fetch("https://api.cohere.com/v1/rerank", {
|
|
475
|
-
method: "POST",
|
|
476
|
-
headers: {
|
|
477
|
-
"Authorization": `Bearer ${process.env.COHERE_API_KEY}`,
|
|
478
|
-
"Content-Type": "application/json"
|
|
479
|
-
},
|
|
480
|
-
body: JSON.stringify({
|
|
481
|
-
model: "rerank-english-v3.0",
|
|
482
|
-
query,
|
|
483
|
-
documents: chunks.map(c => c.chunk_content)
|
|
484
|
-
})
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
if (!response.ok) {
|
|
488
|
-
throw new Error(`Rerank API failed: ${response.status}`);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const data = await response.json();
|
|
492
|
-
|
|
493
|
-
return data.results
|
|
494
|
-
.sort((a, b) => b.relevance_score - a.relevance_score)
|
|
495
|
-
.map(r => chunks[r.index]);
|
|
496
|
-
|
|
497
|
-
} catch (error) {
|
|
498
|
-
console.error("Reranking failed, returning original order:", error);
|
|
499
|
-
|
|
500
|
-
// Return original order as fallback
|
|
501
|
-
return chunks;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
});
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
## Integration with ExuluContext
|
|
508
|
-
|
|
509
|
-
To use a reranker with ExuluContext, pass it to the `resultReranker` configuration:
|
|
510
|
-
|
|
511
|
-
```typescript
|
|
512
|
-
import { ExuluContext, ExuluReranker } from "@exulu/backend";
|
|
513
|
-
|
|
514
|
-
const reranker = new ExuluReranker({
|
|
515
|
-
id: "my_reranker",
|
|
516
|
-
name: "My Reranker",
|
|
517
|
-
description: "Custom reranking",
|
|
518
|
-
execute: async ({ query, chunks }) => {
|
|
519
|
-
// Your logic
|
|
520
|
-
return reorderedChunks;
|
|
521
|
-
}
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
const context = new ExuluContext({
|
|
525
|
-
id: "documentation",
|
|
526
|
-
name: "Documentation",
|
|
527
|
-
description: "Product docs",
|
|
528
|
-
active: true,
|
|
529
|
-
fields: [/* ... */],
|
|
530
|
-
sources: [],
|
|
531
|
-
embedder: myEmbedder,
|
|
532
|
-
resultReranker: async (chunks) => {
|
|
533
|
-
// Extract query from chunk context or use default
|
|
534
|
-
const query = chunks[0]?.context?.query || "";
|
|
535
|
-
|
|
536
|
-
// Run reranker
|
|
537
|
-
return reranker.run(query, chunks);
|
|
538
|
-
},
|
|
539
|
-
configuration: {
|
|
540
|
-
calculateVectors: "onInsert",
|
|
541
|
-
maxRetrievalResults: 50 // Retrieve more candidates for reranking
|
|
542
|
-
}
|
|
543
|
-
});
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
<Tip>
|
|
547
|
-
Retrieve more initial candidates than you need (e.g., 50) so the reranker has more options to choose from. Then limit the final results in your application or by using `top_n` in the reranking API.
|
|
548
|
-
</Tip>
|
|
549
|
-
|
|
550
|
-
## Best practices
|
|
551
|
-
|
|
552
|
-
<AccordionGroup>
|
|
553
|
-
<Accordion title="Error handling">
|
|
554
|
-
Always handle errors gracefully and return the original chunk order as a fallback:
|
|
555
|
-
|
|
556
|
-
```typescript
|
|
557
|
-
execute: async ({ query, chunks }) => {
|
|
558
|
-
try {
|
|
559
|
-
return await performReranking(query, chunks);
|
|
560
|
-
} catch (error) {
|
|
561
|
-
console.error("Reranking failed:", error);
|
|
562
|
-
return chunks; // Fallback to original order
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
```
|
|
566
|
-
</Accordion>
|
|
567
|
-
|
|
568
|
-
<Accordion title="Preserve chunk objects">
|
|
569
|
-
Don't modify the chunk objects. Only change their order:
|
|
570
|
-
|
|
571
|
-
```typescript
|
|
572
|
-
// Good: Return the same objects in new order
|
|
573
|
-
return data.results.map(r => chunks[r.index]);
|
|
574
|
-
|
|
575
|
-
// Bad: Creating new objects loses metadata
|
|
576
|
-
return data.results.map(r => ({
|
|
577
|
-
chunk_content: chunks[r.index].chunk_content
|
|
578
|
-
}));
|
|
579
|
-
```
|
|
580
|
-
</Accordion>
|
|
581
|
-
|
|
582
|
-
<Accordion title="Limit reranking scope">
|
|
583
|
-
Reranking is slower than initial retrieval. Limit the number of chunks:
|
|
584
|
-
|
|
585
|
-
```typescript
|
|
586
|
-
execute: async ({ query, chunks }) => {
|
|
587
|
-
// Only rerank top 20
|
|
588
|
-
const toRerank = chunks.slice(0, 20);
|
|
589
|
-
const rest = chunks.slice(20);
|
|
590
|
-
|
|
591
|
-
const reranked = await performReranking(query, toRerank);
|
|
592
|
-
|
|
593
|
-
return [...reranked, ...rest];
|
|
594
|
-
}
|
|
595
|
-
```
|
|
596
|
-
</Accordion>
|
|
597
|
-
|
|
598
|
-
<Accordion title="Monitor performance">
|
|
599
|
-
Track reranking latency and success rate:
|
|
600
|
-
|
|
601
|
-
```typescript
|
|
602
|
-
execute: async ({ query, chunks }) => {
|
|
603
|
-
const startTime = Date.now();
|
|
604
|
-
|
|
605
|
-
try {
|
|
606
|
-
const result = await performReranking(query, chunks);
|
|
607
|
-
|
|
608
|
-
const latency = Date.now() - startTime;
|
|
609
|
-
console.log(`Reranking completed in ${latency}ms`);
|
|
610
|
-
|
|
611
|
-
return result;
|
|
612
|
-
} catch (error) {
|
|
613
|
-
console.error("Reranking failed:", error);
|
|
614
|
-
return chunks;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
```
|
|
618
|
-
</Accordion>
|
|
619
|
-
|
|
620
|
-
<Accordion title="Consider cost">
|
|
621
|
-
Reranking APIs charge per request. Estimate costs:
|
|
622
|
-
|
|
623
|
-
```typescript
|
|
624
|
-
// Cohere: ~$1 per 1000 searches (rerank-english-v3.0)
|
|
625
|
-
// With 10 results per search, 20 chunks reranked
|
|
626
|
-
// = $1 per 1000 searches
|
|
627
|
-
|
|
628
|
-
// For high-volume applications, consider:
|
|
629
|
-
// - Caching results
|
|
630
|
-
// - Only reranking when needed
|
|
631
|
-
// - Using cheaper or self-hosted models
|
|
632
|
-
```
|
|
633
|
-
</Accordion>
|
|
634
|
-
</AccordionGroup>
|
|
635
|
-
|
|
636
|
-
## Environment variables
|
|
637
|
-
|
|
638
|
-
Most rerankers require API keys:
|
|
639
|
-
|
|
640
|
-
```bash
|
|
641
|
-
# Cohere
|
|
642
|
-
COHERE_API_KEY=your_cohere_api_key
|
|
643
|
-
|
|
644
|
-
# Voyage AI
|
|
645
|
-
VOYAGE_API_KEY=your_voyage_api_key
|
|
646
|
-
|
|
647
|
-
# Jina AI
|
|
648
|
-
JINA_API_KEY=your_jina_api_key
|
|
649
|
-
|
|
650
|
-
# OpenAI (for LLM-based reranking)
|
|
651
|
-
OPENAI_API_KEY=your_openai_api_key
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
## Next steps
|
|
655
|
-
|
|
656
|
-
<CardGroup cols={2}>
|
|
657
|
-
<Card title="API reference" icon="code" href="/core/exulu-reranker/api-reference">
|
|
658
|
-
Explore methods and properties
|
|
659
|
-
</Card>
|
|
660
|
-
<Card title="ExuluContext" icon="database" href="/core/exulu-context/introduction">
|
|
661
|
-
Learn about search contexts
|
|
662
|
-
</Card>
|
|
663
|
-
</CardGroup>
|