@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.
Files changed (164) hide show
  1. package/dist/index.cjs +351 -42
  2. package/dist/index.d.cts +96 -1
  3. package/dist/index.d.ts +96 -1
  4. package/dist/index.js +340 -38
  5. package/ee/{markdown.ts → chunking/markdown.ts} +2 -2
  6. package/ee/python/README.md +295 -0
  7. package/ee/python/documents/processing/README.md +155 -0
  8. package/ee/{documents → python/documents}/processing/doc_processor.ts +25 -17
  9. package/ee/{documents/processing/pdf_to_markdown.py → python/documents/processing/document_to_markdown.py} +3 -10
  10. package/ee/python/setup.sh +180 -0
  11. package/package.json +14 -3
  12. package/scripts/postinstall.cjs +149 -0
  13. package/.agents/skills/mintlify/SKILL.md +0 -347
  14. package/.editorconfig +0 -15
  15. package/.eslintrc.json +0 -52
  16. package/.github/workflows/release-backend.yml +0 -38
  17. package/.husky/commit-msg +0 -1
  18. package/.jscpd.json +0 -18
  19. package/.mcp.json +0 -25
  20. package/.nvmrc +0 -1
  21. package/.prettierignore +0 -5
  22. package/.prettierrc.json +0 -12
  23. package/CHANGELOG.md +0 -8
  24. package/SECURITY.md +0 -5
  25. package/commitlint.config.js +0 -4
  26. package/devops/documentation/patch-older-releases.md +0 -42
  27. package/ee/documents/processing/build_pdf_processor.sh +0 -35
  28. package/ee/documents/processing/chunk_markdown.py +0 -263
  29. package/ee/documents/processing/pdf_processor.spec +0 -115
  30. package/eslint.config.js +0 -88
  31. package/jest.config.ts +0 -25
  32. package/mintlify-docs/.mintignore +0 -7
  33. package/mintlify-docs/AGENTS.md +0 -33
  34. package/mintlify-docs/CLAUDE.MD +0 -50
  35. package/mintlify-docs/CONTRIBUTING.md +0 -32
  36. package/mintlify-docs/LICENSE +0 -21
  37. package/mintlify-docs/README.md +0 -55
  38. package/mintlify-docs/ai-tools/claude-code.mdx +0 -43
  39. package/mintlify-docs/ai-tools/cursor.mdx +0 -39
  40. package/mintlify-docs/ai-tools/windsurf.mdx +0 -39
  41. package/mintlify-docs/api-reference/core-types/agent-types.mdx +0 -110
  42. package/mintlify-docs/api-reference/core-types/analytics-types.mdx +0 -95
  43. package/mintlify-docs/api-reference/core-types/configuration-types.mdx +0 -83
  44. package/mintlify-docs/api-reference/core-types/evaluation-types.mdx +0 -106
  45. package/mintlify-docs/api-reference/core-types/job-types.mdx +0 -135
  46. package/mintlify-docs/api-reference/core-types/overview.mdx +0 -73
  47. package/mintlify-docs/api-reference/core-types/prompt-types.mdx +0 -102
  48. package/mintlify-docs/api-reference/core-types/rbac-types.mdx +0 -163
  49. package/mintlify-docs/api-reference/core-types/session-types.mdx +0 -77
  50. package/mintlify-docs/api-reference/core-types/user-management.mdx +0 -112
  51. package/mintlify-docs/api-reference/core-types/workflow-types.mdx +0 -88
  52. package/mintlify-docs/api-reference/core-types.mdx +0 -585
  53. package/mintlify-docs/api-reference/dynamic-types.mdx +0 -851
  54. package/mintlify-docs/api-reference/endpoint/create.mdx +0 -4
  55. package/mintlify-docs/api-reference/endpoint/delete.mdx +0 -4
  56. package/mintlify-docs/api-reference/endpoint/get.mdx +0 -4
  57. package/mintlify-docs/api-reference/endpoint/webhook.mdx +0 -4
  58. package/mintlify-docs/api-reference/introduction.mdx +0 -661
  59. package/mintlify-docs/api-reference/mutations.mdx +0 -1012
  60. package/mintlify-docs/api-reference/openapi.json +0 -217
  61. package/mintlify-docs/api-reference/queries.mdx +0 -1154
  62. package/mintlify-docs/backend/introduction.mdx +0 -218
  63. package/mintlify-docs/changelog.mdx +0 -387
  64. package/mintlify-docs/community-edition.mdx +0 -304
  65. package/mintlify-docs/core/exulu-agent/api-reference.mdx +0 -894
  66. package/mintlify-docs/core/exulu-agent/configuration.mdx +0 -690
  67. package/mintlify-docs/core/exulu-agent/introduction.mdx +0 -552
  68. package/mintlify-docs/core/exulu-app/api-reference.mdx +0 -481
  69. package/mintlify-docs/core/exulu-app/configuration.mdx +0 -319
  70. package/mintlify-docs/core/exulu-app/introduction.mdx +0 -117
  71. package/mintlify-docs/core/exulu-authentication.mdx +0 -810
  72. package/mintlify-docs/core/exulu-chunkers/api-reference.mdx +0 -1011
  73. package/mintlify-docs/core/exulu-chunkers/configuration.mdx +0 -596
  74. package/mintlify-docs/core/exulu-chunkers/introduction.mdx +0 -403
  75. package/mintlify-docs/core/exulu-context/api-reference.mdx +0 -911
  76. package/mintlify-docs/core/exulu-context/configuration.mdx +0 -648
  77. package/mintlify-docs/core/exulu-context/introduction.mdx +0 -394
  78. package/mintlify-docs/core/exulu-database.mdx +0 -811
  79. package/mintlify-docs/core/exulu-default-agents.mdx +0 -545
  80. package/mintlify-docs/core/exulu-eval/api-reference.mdx +0 -772
  81. package/mintlify-docs/core/exulu-eval/configuration.mdx +0 -680
  82. package/mintlify-docs/core/exulu-eval/introduction.mdx +0 -459
  83. package/mintlify-docs/core/exulu-logging.mdx +0 -464
  84. package/mintlify-docs/core/exulu-otel.mdx +0 -670
  85. package/mintlify-docs/core/exulu-queues/api-reference.mdx +0 -648
  86. package/mintlify-docs/core/exulu-queues/configuration.mdx +0 -650
  87. package/mintlify-docs/core/exulu-queues/introduction.mdx +0 -474
  88. package/mintlify-docs/core/exulu-reranker/api-reference.mdx +0 -630
  89. package/mintlify-docs/core/exulu-reranker/configuration.mdx +0 -663
  90. package/mintlify-docs/core/exulu-reranker/introduction.mdx +0 -516
  91. package/mintlify-docs/core/exulu-tool/api-reference.mdx +0 -723
  92. package/mintlify-docs/core/exulu-tool/configuration.mdx +0 -805
  93. package/mintlify-docs/core/exulu-tool/introduction.mdx +0 -539
  94. package/mintlify-docs/core/exulu-variables/api-reference.mdx +0 -699
  95. package/mintlify-docs/core/exulu-variables/configuration.mdx +0 -736
  96. package/mintlify-docs/core/exulu-variables/introduction.mdx +0 -511
  97. package/mintlify-docs/development.mdx +0 -94
  98. package/mintlify-docs/docs.json +0 -248
  99. package/mintlify-docs/enterprise-edition.mdx +0 -538
  100. package/mintlify-docs/essentials/code.mdx +0 -35
  101. package/mintlify-docs/essentials/images.mdx +0 -59
  102. package/mintlify-docs/essentials/markdown.mdx +0 -88
  103. package/mintlify-docs/essentials/navigation.mdx +0 -87
  104. package/mintlify-docs/essentials/reusable-snippets.mdx +0 -110
  105. package/mintlify-docs/essentials/settings.mdx +0 -318
  106. package/mintlify-docs/favicon.svg +0 -3
  107. package/mintlify-docs/frontend/introduction.mdx +0 -39
  108. package/mintlify-docs/getting-started.mdx +0 -267
  109. package/mintlify-docs/guides/custom-agent.mdx +0 -608
  110. package/mintlify-docs/guides/first-agent.mdx +0 -315
  111. package/mintlify-docs/images/admin_ui.png +0 -0
  112. package/mintlify-docs/images/contexts.png +0 -0
  113. package/mintlify-docs/images/create_agents.png +0 -0
  114. package/mintlify-docs/images/evals.png +0 -0
  115. package/mintlify-docs/images/graphql.png +0 -0
  116. package/mintlify-docs/images/graphql_api.png +0 -0
  117. package/mintlify-docs/images/hero-dark.png +0 -0
  118. package/mintlify-docs/images/hero-light.png +0 -0
  119. package/mintlify-docs/images/hero.png +0 -0
  120. package/mintlify-docs/images/knowledge_sources.png +0 -0
  121. package/mintlify-docs/images/mcp.png +0 -0
  122. package/mintlify-docs/images/scaling.png +0 -0
  123. package/mintlify-docs/index.mdx +0 -411
  124. package/mintlify-docs/logo/dark.svg +0 -9
  125. package/mintlify-docs/logo/light.svg +0 -9
  126. package/mintlify-docs/partners.mdx +0 -558
  127. package/mintlify-docs/products.mdx +0 -77
  128. package/mintlify-docs/snippets/snippet-intro.mdx +0 -4
  129. package/mintlify-docs/styles.css +0 -207
  130. package/ngrok.bash +0 -1
  131. package/ngrok.md +0 -6
  132. package/ngrok.yml +0 -10
  133. package/release.config.cjs +0 -15
  134. package/skills-lock.json +0 -10
  135. package/types/context-processor.ts +0 -45
  136. package/types/enums/eval-types.ts +0 -5
  137. package/types/enums/field-types.ts +0 -1
  138. package/types/enums/jobs.ts +0 -11
  139. package/types/enums/statistics.ts +0 -13
  140. package/types/exulu-table-definition.ts +0 -79
  141. package/types/file-types.ts +0 -18
  142. package/types/models/agent-session.ts +0 -27
  143. package/types/models/agent.ts +0 -68
  144. package/types/models/context.ts +0 -53
  145. package/types/models/embedding.ts +0 -17
  146. package/types/models/eval-run.ts +0 -40
  147. package/types/models/exulu-agent-tool-config.ts +0 -11
  148. package/types/models/item.ts +0 -21
  149. package/types/models/job.ts +0 -8
  150. package/types/models/project.ts +0 -16
  151. package/types/models/rate-limiter-rules.ts +0 -7
  152. package/types/models/test-case.ts +0 -25
  153. package/types/models/tool.ts +0 -9
  154. package/types/models/user-role.ts +0 -12
  155. package/types/models/user.ts +0 -20
  156. package/types/models/variable.ts +0 -8
  157. package/types/models/vector-methods.ts +0 -7
  158. package/types/provider-config.ts +0 -21
  159. package/types/queue-config.ts +0 -16
  160. package/types/rbac-rights-modes.ts +0 -1
  161. package/types/statistics.ts +0 -20
  162. package/types/workflow.ts +0 -31
  163. /package/ee/{documents → python/documents}/THIRD_PARTY_LICENSES/docling.txt +0 -0
  164. /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>