@retrivora-ai/rag-engine 1.9.8 → 2.0.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/README.md +36 -28
- package/dist/{ILLMProvider-B8ROITYK.d.mts → ILLMProvider-DMxLyTdq.d.mts} +2 -2
- package/dist/{ILLMProvider-B8ROITYK.d.ts → ILLMProvider-DMxLyTdq.d.ts} +2 -2
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +248 -74
- package/dist/handlers/index.mjs +254 -74
- package/dist/{index-DNvoi-sV.d.ts → index-CfkqZd2Y.d.ts} +1 -1
- package/dist/{index-BCPKUAVL.d.ts → index-DHsFLJXQ.d.mts} +2 -31
- package/dist/{index-CrxCy36A.d.mts → index-tzwc7UhY.d.ts} +2 -31
- package/dist/{index-B8PbEFSY.d.mts → index-xygonxpW.d.mts} +1 -1
- package/dist/index.css +485 -557
- package/dist/index.d.mts +16 -10
- package/dist/index.d.ts +16 -10
- package/dist/index.js +36 -768
- package/dist/index.mjs +32 -782
- package/dist/server.d.mts +8 -5
- package/dist/server.d.ts +8 -5
- package/dist/server.js +249 -74
- package/dist/server.mjs +255 -74
- package/package.json +3 -2
- package/src/app/page.tsx +2 -0
- package/src/components/ChatWindow.tsx +14 -3
- package/src/components/constants.tsx +224 -0
- package/src/config/RagConfig.ts +2 -2
- package/src/config/serverConfig.ts +14 -1
- package/src/core/ConfigResolver.ts +32 -6
- package/src/core/LicenseVerifier.ts +106 -0
- package/src/core/Pipeline.ts +11 -8
- package/src/core/Retrivora.ts +1 -0
- package/src/handlers/index.ts +84 -48
- package/src/index.ts +3 -5
- package/src/components/AmbientBackground.tsx +0 -29
- package/src/components/ArchitectureCard.tsx +0 -53
- package/src/components/ArchitectureCardsSection.tsx +0 -48
- package/src/components/DocViewer.tsx +0 -97
- package/src/components/Documentation.tsx +0 -141
- package/src/components/Hero.tsx +0 -142
- package/src/components/Lifecycle.tsx +0 -77
- package/src/components/Navbar.tsx +0 -55
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
|
|
5
5
|
"author": "Abhinav Alkuchi",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"rag",
|
|
9
9
|
"retrieval-augmented-generation",
|
|
@@ -126,6 +126,7 @@
|
|
|
126
126
|
"dotenv": "^17.4.2",
|
|
127
127
|
"eslint": "^9",
|
|
128
128
|
"eslint-config-next": "16.2.4",
|
|
129
|
+
"next": "16.2.10",
|
|
129
130
|
"tailwindcss": "^4",
|
|
130
131
|
"tsup": "^8.5.1",
|
|
131
132
|
"typescript": "^5"
|
package/src/app/page.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { Navbar } from '@/components/Navbar';
|
|
|
6
6
|
import { Hero } from '@/components/Hero';
|
|
7
7
|
import { Lifecycle } from '@/components/Lifecycle';
|
|
8
8
|
import { ArchitectureCardsSection } from '@/components/ArchitectureCardsSection';
|
|
9
|
+
import { RagPatternsSection } from '@/components/RagPatternsSection';
|
|
9
10
|
import { Documentation } from '@/components/Documentation';
|
|
10
11
|
import { ChatWidget } from '@/components/ChatWidget';
|
|
11
12
|
|
|
@@ -17,6 +18,7 @@ export default function HomePage() {
|
|
|
17
18
|
<div className="relative z-10 container mx-auto px-6 py-12">
|
|
18
19
|
<Hero />
|
|
19
20
|
<Lifecycle />
|
|
21
|
+
<RagPatternsSection />
|
|
20
22
|
<ArchitectureCardsSection />
|
|
21
23
|
<Documentation />
|
|
22
24
|
</div>
|
|
@@ -217,11 +217,22 @@ export function ChatWindow({
|
|
|
217
217
|
const timer = setTimeout(async () => {
|
|
218
218
|
setIsSuggesting(true);
|
|
219
219
|
try {
|
|
220
|
-
const
|
|
220
|
+
const base = retrivoraApiBase || '/api/retrivora';
|
|
221
|
+
const primaryUrl = `${base}/suggestions`;
|
|
222
|
+
let response = await fetch(primaryUrl, {
|
|
221
223
|
method: 'POST',
|
|
222
|
-
headers: { 'Content-Type': 'application/json' },
|
|
224
|
+
headers: { 'Content-Type': 'application/json', ...(headers || {}) },
|
|
223
225
|
body: JSON.stringify({ query: input, namespace: projectId }),
|
|
224
226
|
});
|
|
227
|
+
|
|
228
|
+
if (!response.ok && response.status === 404) {
|
|
229
|
+
response = await fetch('/api/suggestions', {
|
|
230
|
+
method: 'POST',
|
|
231
|
+
headers: { 'Content-Type': 'application/json', ...(headers || {}) },
|
|
232
|
+
body: JSON.stringify({ query: input, namespace: projectId }),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
225
236
|
const data = await response.json();
|
|
226
237
|
if (data.suggestions) {
|
|
227
238
|
setSuggestions(data.suggestions);
|
|
@@ -234,7 +245,7 @@ export function ChatWindow({
|
|
|
234
245
|
}, 800); // 800ms debounce to save costs/latency
|
|
235
246
|
|
|
236
247
|
return () => clearTimeout(timer);
|
|
237
|
-
}, [input, projectId]);
|
|
248
|
+
}, [input, projectId, retrivoraApiBase, headers]);
|
|
238
249
|
|
|
239
250
|
return (
|
|
240
251
|
<div
|
|
@@ -277,3 +277,227 @@ export const BORDER_RADIUS_MAP: Record<string, string> = {
|
|
|
277
277
|
xl: 'rounded-xl',
|
|
278
278
|
full: 'rounded-3xl',
|
|
279
279
|
};
|
|
280
|
+
|
|
281
|
+
export interface RagPatternStep {
|
|
282
|
+
label: string;
|
|
283
|
+
type: 'input' | 'process' | 'database' | 'model' | 'output';
|
|
284
|
+
color: string;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export interface RagPattern {
|
|
288
|
+
id: string;
|
|
289
|
+
name: string;
|
|
290
|
+
tagline: string;
|
|
291
|
+
description: string;
|
|
292
|
+
pros: string[];
|
|
293
|
+
cons: string[];
|
|
294
|
+
snippet: string;
|
|
295
|
+
color: string;
|
|
296
|
+
flowSteps: RagPatternStep[];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export const RAG_PATTERNS: RagPattern[] = [
|
|
300
|
+
{
|
|
301
|
+
id: 'naive',
|
|
302
|
+
name: 'Naive RAG',
|
|
303
|
+
tagline: 'Standard semantic search pipeline',
|
|
304
|
+
description: 'The foundation pattern: splits documents into text chunks, embeddings are stored in a vector database, and relevant context is fetched via direct query similarity to augment the model prompt.',
|
|
305
|
+
pros: ['Very low latency', 'Low resource cost', 'Simple implementation'],
|
|
306
|
+
cons: ['Struggles with complex multi-hop queries', 'No ranking optimization', 'Can return irrelevant context chunks'],
|
|
307
|
+
color: '#d97706', // Amber/Brown
|
|
308
|
+
snippet: `import { Retrivora } from '@retrivora-ai/rag-engine/server';
|
|
309
|
+
|
|
310
|
+
const ai = new Retrivora({
|
|
311
|
+
projectId: 'naive-rag-demo',
|
|
312
|
+
vectorDatabase: {
|
|
313
|
+
provider: 'pinecone',
|
|
314
|
+
indexName: 'knowledge-base',
|
|
315
|
+
},
|
|
316
|
+
workflow: {
|
|
317
|
+
type: 'simple-rag'
|
|
318
|
+
}
|
|
319
|
+
});`,
|
|
320
|
+
flowSteps: [
|
|
321
|
+
{ label: 'Documents', type: 'input', color: 'bg-amber-100 text-amber-800 dark:bg-amber-950/40 dark:text-amber-300' },
|
|
322
|
+
{ label: 'Chunking', type: 'process', color: 'bg-amber-50 text-amber-700 dark:bg-amber-900/20 dark:text-amber-400' },
|
|
323
|
+
{ label: 'Embedding Model', type: 'model', color: 'bg-amber-100 text-amber-900 dark:bg-amber-800/30 dark:text-amber-200' },
|
|
324
|
+
{ label: 'Vector Store', type: 'database', color: 'bg-amber-200 text-amber-950 dark:bg-amber-700/40 dark:text-amber-100' },
|
|
325
|
+
{ label: 'Context Prompt', type: 'process', color: 'bg-amber-50 text-amber-700 dark:bg-amber-900/20 dark:text-amber-400' },
|
|
326
|
+
{ label: 'LLM Generator', type: 'model', color: 'bg-indigo-100 text-indigo-900 dark:bg-indigo-800/30 dark:text-indigo-200' },
|
|
327
|
+
]
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
id: 'retrieve-and-rerank',
|
|
331
|
+
name: 'Retrieve-and-rerank',
|
|
332
|
+
tagline: 'High-precision contextual relevance',
|
|
333
|
+
description: 'Enhances standard RAG by fetching a larger pool of vector candidates and feeding them through a cross-encoder Reranker model. The reranker scores context chunks dynamically relative to query intent, filtering noise.',
|
|
334
|
+
pros: ['Extremely high context precision', 'Better generation accuracy', 'Mitigates hallucination rates'],
|
|
335
|
+
cons: ['Adds slight latency bottleneck (~100-300ms)', 'Higher computational requirements'],
|
|
336
|
+
color: '#2563eb', // Blue
|
|
337
|
+
snippet: `import { Retrivora } from '@retrivora-ai/rag-engine/server';
|
|
338
|
+
|
|
339
|
+
const ai = new Retrivora({
|
|
340
|
+
projectId: 'rerank-rag-demo',
|
|
341
|
+
vectorDatabase: {
|
|
342
|
+
provider: 'pinecone',
|
|
343
|
+
indexName: 'knowledge-base',
|
|
344
|
+
},
|
|
345
|
+
retrieval: {
|
|
346
|
+
strategy: 'contextual-compression',
|
|
347
|
+
useReranking: true, // Enables cross-encoder re-evaluation
|
|
348
|
+
topK: 5,
|
|
349
|
+
}
|
|
350
|
+
});`,
|
|
351
|
+
flowSteps: [
|
|
352
|
+
{ label: 'Query Input', type: 'input', color: 'bg-blue-100 text-blue-800 dark:bg-blue-950/40 dark:text-blue-300' },
|
|
353
|
+
{ label: 'Vector Retrieval', type: 'database', color: 'bg-blue-200 text-blue-950 dark:bg-blue-700/40 dark:text-blue-100' },
|
|
354
|
+
{ label: 'Reranker Model', type: 'model', color: 'bg-indigo-100 text-indigo-950 dark:bg-indigo-700/40 dark:text-indigo-100' },
|
|
355
|
+
{ label: 'Filtered Context', type: 'process', color: 'bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400' },
|
|
356
|
+
{ label: 'Generative Model', type: 'model', color: 'bg-violet-100 text-violet-900 dark:bg-violet-800/30 dark:text-violet-200' },
|
|
357
|
+
]
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
id: 'multimodal',
|
|
361
|
+
name: 'Multimodal RAG',
|
|
362
|
+
tagline: 'Search and reason across text and media',
|
|
363
|
+
description: 'Ingests both unstructured text documents and visual/audio/structured assets. It queries multi-modal embeddings, feeding retrieved visuals and matched context fragments to multimodal LLMs (like Gemini or Claude) to support diagrams, tables, and media outputs.',
|
|
364
|
+
pros: ['Handles images, PDFs, charts, audio', 'Deeper context retrieval', 'Generates visual answers'],
|
|
365
|
+
cons: ['Multimodal embeddings are computationally expensive', 'Higher token payload sizes'],
|
|
366
|
+
color: '#7c3aed', // Purple
|
|
367
|
+
snippet: `import { Retrivora } from '@retrivora-ai/rag-engine/server';
|
|
368
|
+
|
|
369
|
+
const ai = new Retrivora({
|
|
370
|
+
projectId: 'multimodal-rag-demo',
|
|
371
|
+
llm: {
|
|
372
|
+
provider: 'gemini',
|
|
373
|
+
model: 'gemini-2.5-flash', // Multimodal reasoning model
|
|
374
|
+
},
|
|
375
|
+
embedding: {
|
|
376
|
+
provider: 'gemini',
|
|
377
|
+
model: 'text-embedding-004',
|
|
378
|
+
},
|
|
379
|
+
workflow: {
|
|
380
|
+
type: 'multimodal-rag'
|
|
381
|
+
}
|
|
382
|
+
});`,
|
|
383
|
+
flowSteps: [
|
|
384
|
+
{ label: 'Multi-modal Docs', type: 'input', color: 'bg-purple-100 text-purple-800 dark:bg-purple-950/40 dark:text-purple-300' },
|
|
385
|
+
{ label: 'Multi-modal Embed', type: 'model', color: 'bg-purple-50 text-purple-700 dark:bg-purple-900/20 dark:text-purple-400' },
|
|
386
|
+
{ label: 'Vector Store', type: 'database', color: 'bg-purple-200 text-purple-950 dark:bg-purple-700/40 dark:text-purple-100' },
|
|
387
|
+
{ label: 'Media Context', type: 'process', color: 'bg-purple-100 text-purple-950 dark:bg-purple-800/40 dark:text-purple-100' },
|
|
388
|
+
{ label: 'Multimodal LLM', type: 'model', color: 'bg-violet-100 text-violet-900 dark:bg-violet-800/30 dark:text-violet-200' },
|
|
389
|
+
]
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
id: 'graph',
|
|
393
|
+
name: 'Graph RAG',
|
|
394
|
+
tagline: 'Entity-relationship semantic reasoning',
|
|
395
|
+
description: 'Builds a knowledge graph structure from raw documents using an LLM extractor. Entity-to-entity and entity-to-document relationships are resolved, allowing queries to retrieve structured semantic contexts (nodes & relationships) to resolve structural, relational, and hierarchy-based questions.',
|
|
396
|
+
pros: ['Excellent for tracing connections', 'Resolves hierarchy and relationships', 'Grounded fact validation'],
|
|
397
|
+
cons: ['Heavy write-time pipeline ingestion cost', 'Requires graph database setup'],
|
|
398
|
+
color: '#059669', // Emerald
|
|
399
|
+
snippet: `import { Retrivora } from '@retrivora-ai/rag-engine/server';
|
|
400
|
+
|
|
401
|
+
const ai = new Retrivora({
|
|
402
|
+
projectId: 'graph-rag-demo',
|
|
403
|
+
graphDb: {
|
|
404
|
+
provider: 'neo4j',
|
|
405
|
+
options: { uri: 'neo4j://localhost:7687' },
|
|
406
|
+
},
|
|
407
|
+
workflow: {
|
|
408
|
+
type: 'graph-rag'
|
|
409
|
+
}
|
|
410
|
+
});`,
|
|
411
|
+
flowSteps: [
|
|
412
|
+
{ label: 'Documents', type: 'input', color: 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300' },
|
|
413
|
+
{ label: 'LLM Extractor', type: 'model', color: 'bg-emerald-50 text-emerald-700 dark:bg-emerald-900/20 dark:text-emerald-400' },
|
|
414
|
+
{ label: 'Graph Database', type: 'database', color: 'bg-emerald-200 text-emerald-950 dark:bg-emerald-700/40 dark:text-emerald-100' },
|
|
415
|
+
{ label: 'Relation Search', type: 'process', color: 'bg-emerald-100 text-emerald-950 dark:bg-emerald-800/40 dark:text-emerald-100' },
|
|
416
|
+
{ label: 'LLM Reasoner', type: 'model', color: 'bg-indigo-100 text-indigo-900 dark:bg-indigo-800/30 dark:text-indigo-200' },
|
|
417
|
+
]
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
id: 'hybrid',
|
|
421
|
+
name: 'Hybrid RAG',
|
|
422
|
+
tagline: 'Best of both worlds: Vector + Graph',
|
|
423
|
+
description: 'Queries both a vector database for semantic match proximity and a graph database for topological linkages. It joins vector search results and sub-graph entity contexts into a unified prompt, resolving complex interconnected schemas.',
|
|
424
|
+
pros: ['Unparalleled query coverage', 'Combines semantic proximity and graph relationships', 'Robust grounding'],
|
|
425
|
+
cons: ['Most complex RAG configuration', 'Double database query overhead'],
|
|
426
|
+
color: '#d97706', // Orange
|
|
427
|
+
snippet: `import { Retrivora } from '@retrivora-ai/rag-engine/server';
|
|
428
|
+
|
|
429
|
+
const ai = new Retrivora({
|
|
430
|
+
projectId: 'hybrid-rag-demo',
|
|
431
|
+
vectorDatabase: { provider: 'mongodb', indexName: 'default' },
|
|
432
|
+
graphDb: { provider: 'neo4j', options: { uri: 'neo4j://...' } },
|
|
433
|
+
workflow: {
|
|
434
|
+
type: 'hybrid-rag'
|
|
435
|
+
}
|
|
436
|
+
});`,
|
|
437
|
+
flowSteps: [
|
|
438
|
+
{ label: 'Query', type: 'input', color: 'bg-amber-100 text-amber-800 dark:bg-amber-950/40 dark:text-amber-300' },
|
|
439
|
+
{ label: 'Vector Store', type: 'database', color: 'bg-orange-100 text-orange-950 dark:bg-orange-700/40 dark:text-orange-100' },
|
|
440
|
+
{ label: 'Graph Database', type: 'database', color: 'bg-emerald-100 text-emerald-950 dark:bg-emerald-700/40 dark:text-emerald-100' },
|
|
441
|
+
{ label: 'Unified Context', type: 'process', color: 'bg-amber-50 text-amber-700 dark:bg-amber-900/20 dark:text-amber-400' },
|
|
442
|
+
{ label: 'LLM Reasoner', type: 'model', color: 'bg-indigo-100 text-indigo-900 dark:bg-indigo-800/30 dark:text-indigo-200' },
|
|
443
|
+
]
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
id: 'agentic-router',
|
|
447
|
+
name: 'Agentic RAG (Router)',
|
|
448
|
+
tagline: 'Dynamic intent-based query routing',
|
|
449
|
+
description: 'Uses an intelligent fast router agent to analyze the intent of the incoming query. The router dynamically decides whether to fetch data from the Vector DB, query a Graph DB, fallback to web search, or trigger external API tools.',
|
|
450
|
+
pros: ['Highly adaptive workflow', 'Saves tokens on simple queries', 'Automatically invokes external API tools'],
|
|
451
|
+
cons: ['Adds slight routing classification latency', 'Dependent on router accuracy'],
|
|
452
|
+
color: '#0891b2', // Cyan
|
|
453
|
+
snippet: `import { Retrivora } from '@retrivora-ai/rag-engine/server';
|
|
454
|
+
|
|
455
|
+
const ai = new Retrivora({
|
|
456
|
+
projectId: 'agentic-router-demo',
|
|
457
|
+
workflow: {
|
|
458
|
+
type: 'agentic-router'
|
|
459
|
+
}
|
|
460
|
+
});`,
|
|
461
|
+
flowSteps: [
|
|
462
|
+
{ label: 'User Query', type: 'input', color: 'bg-cyan-100 text-cyan-800 dark:bg-cyan-950/40 dark:text-cyan-300' },
|
|
463
|
+
{ label: 'Router Agent', type: 'model', color: 'bg-indigo-100 text-indigo-900 dark:bg-indigo-800/30 dark:text-indigo-200' },
|
|
464
|
+
{ label: 'Vector / Graph / Tools', type: 'database', color: 'bg-cyan-200 text-cyan-950 dark:bg-cyan-700/40 dark:text-cyan-100' },
|
|
465
|
+
{ label: 'Selected Context', type: 'process', color: 'bg-cyan-50 text-cyan-700 dark:bg-cyan-900/20 dark:text-cyan-400' },
|
|
466
|
+
{ label: 'LLM Generator', type: 'model', color: 'bg-violet-100 text-violet-900 dark:bg-violet-800/30 dark:text-violet-200' },
|
|
467
|
+
]
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: 'multi-agent',
|
|
471
|
+
name: 'Agent RAG (Multi-Agent)',
|
|
472
|
+
tagline: 'Collaborative swarm-based problem solving',
|
|
473
|
+
description: 'Deploys a supervisor agent that coordinates a group of specialized worker agents (e.g., search agent, tool executor agent, web crawler agent). Agents collaborate, pass information, check each other\'s facts, and compile a final answer.',
|
|
474
|
+
pros: ['Excellent for complex research tasks', 'Self-correcting query pipelines', 'Multi-step action sequences'],
|
|
475
|
+
cons: ['High latency (requires multiple LLM cycles)', 'Higher cost per query', 'Complex state synchronization'],
|
|
476
|
+
color: '#ec4899', // Pink
|
|
477
|
+
snippet: `import { Retrivora } from '@retrivora-ai/rag-engine/server';
|
|
478
|
+
|
|
479
|
+
const ai = new Retrivora({
|
|
480
|
+
projectId: 'multi-agent-demo',
|
|
481
|
+
workflow: {
|
|
482
|
+
type: 'multi-agent'
|
|
483
|
+
},
|
|
484
|
+
mcpServers: [
|
|
485
|
+
{
|
|
486
|
+
name: 'search-engine',
|
|
487
|
+
transport: 'stdio',
|
|
488
|
+
command: 'npx',
|
|
489
|
+
args: ['-y', '@modelcontextprotocol/server-web-search']
|
|
490
|
+
}
|
|
491
|
+
]
|
|
492
|
+
});`,
|
|
493
|
+
flowSteps: [
|
|
494
|
+
{ label: 'Goal Query', type: 'input', color: 'bg-pink-100 text-pink-800 dark:bg-pink-950/40 dark:text-pink-300' },
|
|
495
|
+
{ label: 'Supervisor Agent', type: 'model', color: 'bg-pink-200 text-pink-950 dark:bg-pink-800/30 dark:text-pink-100' },
|
|
496
|
+
{ label: 'Specialist Agents', type: 'model', color: 'bg-indigo-100 text-indigo-900 dark:bg-indigo-900/30 dark:text-indigo-200' },
|
|
497
|
+
{ label: 'Tools / Vector DBs', type: 'database', color: 'bg-pink-200 text-pink-950 dark:bg-pink-700/40 dark:text-pink-100' },
|
|
498
|
+
{ label: 'Consensus Context', type: 'process', color: 'bg-pink-50 text-pink-700 dark:bg-pink-900/20 dark:text-pink-400' },
|
|
499
|
+
{ label: 'Final Output', type: 'output', color: 'bg-indigo-600 text-white shadow-sm' },
|
|
500
|
+
]
|
|
501
|
+
}
|
|
502
|
+
];
|
|
503
|
+
|
package/src/config/RagConfig.ts
CHANGED
|
@@ -182,7 +182,7 @@ export interface RAGConfig {
|
|
|
182
182
|
/** Characters used to split text, in order of priority */
|
|
183
183
|
separators?: string[];
|
|
184
184
|
/** Overall RAG architecture pattern */
|
|
185
|
-
architecture?: 'simple' | 'graph' | 'hybrid' | 'agentic';
|
|
185
|
+
architecture?: 'simple' | 'graph' | 'hybrid' | 'agentic' | 'naive' | 'retrieve-and-rerank' | 'multimodal' | 'agentic-router' | 'multi-agent';
|
|
186
186
|
/** Chunking strategy to use during ingestion */
|
|
187
187
|
chunkingStrategy?: 'recursive' | 'markdown' | 'semantic' | 'llamaindex';
|
|
188
188
|
/** Whether to use query transformation (e.g. HyDE) */
|
|
@@ -249,7 +249,7 @@ export interface RetrievalConfig {
|
|
|
249
249
|
|
|
250
250
|
export interface WorkflowConfig {
|
|
251
251
|
/** High-level workflow selector from the SDK prompt. */
|
|
252
|
-
type?: 'simple-rag' | 'rag' | 'hybrid-rag' | 'graph-rag' | 'agentic-rag' | 'agentic';
|
|
252
|
+
type?: 'simple-rag' | 'rag' | 'hybrid-rag' | 'graph-rag' | 'agentic-rag' | 'agentic' | 'naive-rag' | 'retrieve-and-rerank' | 'multimodal-rag' | 'agentic-router' | 'multi-agent-rag' | 'multi-agent';
|
|
253
253
|
/** Future workflow/provider-specific options. */
|
|
254
254
|
options?: Record<string, unknown>;
|
|
255
255
|
}
|
|
@@ -64,6 +64,19 @@ export function getEnvConfig(env: Record<string, string | undefined> = process.e
|
|
|
64
64
|
base?.projectId ??
|
|
65
65
|
'__default__';
|
|
66
66
|
|
|
67
|
+
const licenseKey =
|
|
68
|
+
readString(env, 'RAG_LICENSE_KEY') ??
|
|
69
|
+
readString(env, 'RETRIVORA_LICENSE_KEY') ??
|
|
70
|
+
readString(env, 'NEXT_PUBLIC_RETRIVORA_LICENSE_KEY') ??
|
|
71
|
+
readString(env, 'LICENSE_KEY') ??
|
|
72
|
+
base?.licenseKey;
|
|
73
|
+
|
|
74
|
+
const telemetryEnabled =
|
|
75
|
+
readString(env, 'TELEMETRY_ENABLED') === 'true' ||
|
|
76
|
+
readString(env, 'NEXT_PUBLIC_TELEMETRY_ENABLED') === 'true' ||
|
|
77
|
+
base?.telemetry?.enabled ||
|
|
78
|
+
Boolean(licenseKey);
|
|
79
|
+
|
|
67
80
|
const vectorProvider = readEnum(env, 'VECTOR_DB_PROVIDER', 'pinecone', VECTOR_DB_PROVIDERS);
|
|
68
81
|
const llmProvider = readEnum(env, 'LLM_PROVIDER', 'openai', LLM_PROVIDERS);
|
|
69
82
|
const embeddingProvider = readEnum(env, 'EMBEDDING_PROVIDER', 'openai', EMBEDDING_PROVIDERS);
|
|
@@ -219,7 +232,7 @@ export function getEnvConfig(env: Record<string, string | undefined> = process.e
|
|
|
219
232
|
})(),
|
|
220
233
|
},
|
|
221
234
|
telemetry: {
|
|
222
|
-
enabled:
|
|
235
|
+
enabled: telemetryEnabled,
|
|
223
236
|
url: readString(env, 'TELEMETRY_URL') ?? readString(env, 'NEXT_PUBLIC_TELEMETRY_URL') ?? base?.telemetry?.url ?? '/api/telemetry',
|
|
224
237
|
},
|
|
225
238
|
// Optional graph DB — driven by GRAPH_DB_PROVIDER env var
|
|
@@ -117,22 +117,48 @@ export class ConfigResolver {
|
|
|
117
117
|
normalized.useReranking = retrieval.useReranking ?? normalized.useReranking;
|
|
118
118
|
|
|
119
119
|
if (retrieval.strategy === 'hybrid') normalized.architecture = normalized.architecture ?? 'hybrid';
|
|
120
|
-
if (retrieval.strategy === 'agentic') normalized.architecture = '
|
|
121
|
-
if (retrieval.strategy === 'contextual-compression')
|
|
120
|
+
if (retrieval.strategy === 'agentic') normalized.architecture = 'multi-agent';
|
|
121
|
+
if (retrieval.strategy === 'contextual-compression') {
|
|
122
|
+
normalized.useReranking = true;
|
|
123
|
+
normalized.architecture = normalized.architecture ?? 'retrieve-and-rerank';
|
|
124
|
+
}
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
if (workflow?.type) {
|
|
125
128
|
const type = workflow.type;
|
|
126
|
-
if (type === '
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
if (type === 'naive-rag') {
|
|
130
|
+
normalized.architecture = 'naive';
|
|
131
|
+
normalized.useReranking = false;
|
|
132
|
+
normalized.useGraphRetrieval = false;
|
|
133
|
+
} else if (type === 'retrieve-and-rerank') {
|
|
134
|
+
normalized.architecture = 'retrieve-and-rerank';
|
|
135
|
+
normalized.useReranking = true;
|
|
136
|
+
} else if (type === 'multimodal-rag') {
|
|
137
|
+
normalized.architecture = 'multimodal';
|
|
138
|
+
} else if (type === 'graph-rag') {
|
|
129
139
|
normalized.architecture = 'graph';
|
|
130
140
|
normalized.useGraphRetrieval = true;
|
|
141
|
+
} else if (type === 'hybrid-rag') {
|
|
142
|
+
normalized.architecture = 'hybrid';
|
|
143
|
+
} else if (type === 'agentic-router') {
|
|
144
|
+
normalized.architecture = 'agentic-router';
|
|
145
|
+
} else if (type === 'multi-agent-rag' || type === 'multi-agent' || type === 'agentic' || type === 'agentic-rag') {
|
|
146
|
+
normalized.architecture = 'multi-agent';
|
|
131
147
|
} else if (type === 'simple-rag' || type === 'rag') {
|
|
132
|
-
normalized.architecture = normalized.architecture ?? '
|
|
148
|
+
normalized.architecture = normalized.architecture ?? 'naive';
|
|
133
149
|
}
|
|
134
150
|
}
|
|
135
151
|
|
|
152
|
+
// Default fallbacks based on architecture configuration
|
|
153
|
+
if (normalized.architecture === 'retrieve-and-rerank') {
|
|
154
|
+
normalized.useReranking = true;
|
|
155
|
+
} else if (normalized.architecture === 'graph') {
|
|
156
|
+
normalized.useGraphRetrieval = true;
|
|
157
|
+
} else if (normalized.architecture === 'naive') {
|
|
158
|
+
normalized.useReranking = false;
|
|
159
|
+
normalized.useGraphRetrieval = false;
|
|
160
|
+
}
|
|
161
|
+
|
|
136
162
|
return normalized;
|
|
137
163
|
}
|
|
138
164
|
}
|
|
@@ -12,6 +12,13 @@ export interface LicensePayload {
|
|
|
12
12
|
* Enables zero-latency local license validation without external network calls.
|
|
13
13
|
*/
|
|
14
14
|
export class LicenseVerifier {
|
|
15
|
+
// A simple in-memory cache to save CPU overhead of cryptographic signature checks.
|
|
16
|
+
private static readonly cache = new Map<
|
|
17
|
+
string,
|
|
18
|
+
{ payload: LicensePayload; cachedAt: number }
|
|
19
|
+
>();
|
|
20
|
+
private static readonly CACHE_TTL_MS = 60 * 1000; // Cache verified license for 60 seconds
|
|
21
|
+
|
|
15
22
|
// Retrivora's Public Key used to verify the license key signature.
|
|
16
23
|
// In production, this matches the private key held by Retrivora SaaS.
|
|
17
24
|
private static readonly PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
@@ -38,6 +45,22 @@ MwIDAQAB
|
|
|
38
45
|
publicKeyOverride?: string
|
|
39
46
|
): LicensePayload {
|
|
40
47
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
|
|
50
|
+
// Check verification cache to bypass crypto computations on hot paths
|
|
51
|
+
if (licenseKey) {
|
|
52
|
+
const cacheKey = `${licenseKey}:${currentProjectId}:${provider || ''}:${publicKeyOverride || ''}`;
|
|
53
|
+
const cached = this.cache.get(cacheKey);
|
|
54
|
+
if (cached && now - cached.cachedAt < this.CACHE_TTL_MS) {
|
|
55
|
+
// Enforce expiration check even for cached items
|
|
56
|
+
const currentTimestampSec = Math.floor(now / 1000);
|
|
57
|
+
if (cached.payload.expiresAt && currentTimestampSec > cached.payload.expiresAt) {
|
|
58
|
+
this.cache.delete(cacheKey);
|
|
59
|
+
} else {
|
|
60
|
+
return cached.payload;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
41
64
|
|
|
42
65
|
// 1. Development Fallback (Fail-open locally but log warning)
|
|
43
66
|
if (!licenseKey) {
|
|
@@ -143,6 +166,11 @@ MwIDAQAB
|
|
|
143
166
|
// Enterprise has access to all databases (including redis, weaviate, custom ones)
|
|
144
167
|
}
|
|
145
168
|
|
|
169
|
+
if (licenseKey) {
|
|
170
|
+
const cacheKey = `${licenseKey}:${currentProjectId}:${provider || ''}:${publicKeyOverride || ''}`;
|
|
171
|
+
this.cache.set(cacheKey, { payload, cachedAt: now });
|
|
172
|
+
}
|
|
173
|
+
|
|
146
174
|
return payload;
|
|
147
175
|
} catch (err) {
|
|
148
176
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -151,4 +179,82 @@ MwIDAQAB
|
|
|
151
179
|
);
|
|
152
180
|
}
|
|
153
181
|
}
|
|
182
|
+
|
|
183
|
+
static async verifyIP(licenseKey: string | undefined): Promise<void> {
|
|
184
|
+
if (!licenseKey) return;
|
|
185
|
+
try {
|
|
186
|
+
const parts = licenseKey.split('.');
|
|
187
|
+
if (parts.length !== 3) return;
|
|
188
|
+
const [, payloadB64] = parts;
|
|
189
|
+
const payload: LicensePayload & { ipv4?: string; ipv6?: string } = JSON.parse(
|
|
190
|
+
Buffer.from(payloadB64, 'base64url').toString('utf8')
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const tier = (payload.tier || '').toLowerCase();
|
|
194
|
+
// Skip check if Enterprise or Custom
|
|
195
|
+
if (tier === 'enterprise' || tier === 'custom') {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// If Hobby (free_trial) or Developer Pro, perform the check
|
|
200
|
+
if (payload.ipv4 || payload.ipv6) {
|
|
201
|
+
let currentIpv4 = '';
|
|
202
|
+
let currentIpv6 = '';
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const res = await fetch('https://api4.ipify.org?format=json', { signal: AbortSignal.timeout(3000) });
|
|
206
|
+
const data = await res.json();
|
|
207
|
+
currentIpv4 = data.ip;
|
|
208
|
+
} catch (e) {}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const res = await fetch('https://api6.ipify.org?format=json', { signal: AbortSignal.timeout(3000) });
|
|
212
|
+
const data = await res.json();
|
|
213
|
+
currentIpv6 = data.ip;
|
|
214
|
+
} catch (e) {}
|
|
215
|
+
|
|
216
|
+
const localIps: string[] = [];
|
|
217
|
+
try {
|
|
218
|
+
const osModule = require('os');
|
|
219
|
+
const interfaces = osModule.networkInterfaces();
|
|
220
|
+
for (const name of Object.keys(interfaces)) {
|
|
221
|
+
for (const iface of interfaces[name] || []) {
|
|
222
|
+
if (!iface.internal) {
|
|
223
|
+
localIps.push(iface.address);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {}
|
|
228
|
+
|
|
229
|
+
const isIpv4Match = payload.ipv4 && (
|
|
230
|
+
currentIpv4 === payload.ipv4 ||
|
|
231
|
+
localIps.includes(payload.ipv4)
|
|
232
|
+
);
|
|
233
|
+
const isIpv6Match = payload.ipv6 && (
|
|
234
|
+
currentIpv6 === payload.ipv6 ||
|
|
235
|
+
localIps.includes(payload.ipv6)
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
if (payload.ipv4 && payload.ipv6) {
|
|
239
|
+
if (!isIpv4Match && !isIpv6Match) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`License key access restricted. This license key was created for a different system (authorized IPv4: ${payload.ipv4}, IPv6: ${payload.ipv6}) and cannot be used on this machine.`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
} else if (payload.ipv4 && !isIpv4Match) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`License key access restricted. This license key was created for a different system (authorized IPv4: ${payload.ipv4}) and cannot be used on this machine.`
|
|
247
|
+
);
|
|
248
|
+
} else if (payload.ipv6 && !isIpv6Match) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`License key access restricted. This license key was created for a different system (authorized IPv6: ${payload.ipv6}) and cannot be used on this machine.`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch (err: any) {
|
|
255
|
+
if (err.message.includes('License key access restricted')) {
|
|
256
|
+
throw new ConfigurationException(`[Retrivora SDK] ${err.message}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
154
260
|
}
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -256,7 +256,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
256
256
|
|
|
257
257
|
await this.vectorDB.initialize();
|
|
258
258
|
|
|
259
|
-
if (this.config.rag?.architecture === 'agentic' || (this.config.mcpServers && this.config.mcpServers.length > 0)) {
|
|
259
|
+
if (this.config.rag?.architecture === 'agentic' || this.config.rag?.architecture === 'multi-agent' || (this.config.mcpServers && this.config.mcpServers.length > 0)) {
|
|
260
260
|
this.mcpRegistry = new MCPRegistry(this.config.mcpServers || []);
|
|
261
261
|
this.multiAgentCoordinator = new MultiAgentCoordinator({
|
|
262
262
|
llmProvider: this.llmProvider,
|
|
@@ -448,7 +448,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
448
448
|
async ask(question: string, history: ChatMessage[] = [], namespace?: string): Promise<ChatResponse> {
|
|
449
449
|
await this.initialize();
|
|
450
450
|
|
|
451
|
-
if ((this.config.rag?.architecture === 'agentic' || (this.config.mcpServers && this.config.mcpServers.length > 0)) && this.multiAgentCoordinator) {
|
|
451
|
+
if ((this.config.rag?.architecture === 'agentic' || this.config.rag?.architecture === 'multi-agent' || (this.config.mcpServers && this.config.mcpServers.length > 0)) && this.multiAgentCoordinator) {
|
|
452
452
|
return await this.multiAgentCoordinator.run(question, history);
|
|
453
453
|
}
|
|
454
454
|
|
|
@@ -475,7 +475,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
475
475
|
|
|
476
476
|
async *askStream(question: string, history: ChatMessage[] = [], namespace?: string): AsyncIterable<string | ChatResponse> {
|
|
477
477
|
await this.initialize();
|
|
478
|
-
if ((this.config.rag?.architecture === 'agentic' || (this.config.mcpServers && this.config.mcpServers.length > 0)) && this.multiAgentCoordinator) {
|
|
478
|
+
if ((this.config.rag?.architecture === 'agentic' || this.config.rag?.architecture === 'multi-agent' || (this.config.mcpServers && this.config.mcpServers.length > 0)) && this.multiAgentCoordinator) {
|
|
479
479
|
const stream = this.multiAgentCoordinator.runStream(question, history);
|
|
480
480
|
for await (const chunk of stream) {
|
|
481
481
|
yield chunk;
|
|
@@ -535,7 +535,8 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
535
535
|
const cacheKey = `${ns}::${searchQuery}`;
|
|
536
536
|
const cachedVector = this.embeddingCache.get(cacheKey);
|
|
537
537
|
|
|
538
|
-
const
|
|
538
|
+
const arch = this.config.rag?.architecture;
|
|
539
|
+
const useGraph = arch !== 'naive' && arch !== 'retrieve-and-rerank' && this.graphDB && this.config.rag?.useGraphRetrieval;
|
|
539
540
|
const [strategyResult, embeddedVector] = await Promise.all([
|
|
540
541
|
QueryProcessor.determineRetrievalStrategy(
|
|
541
542
|
searchQuery,
|
|
@@ -579,7 +580,8 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
579
580
|
? structuredSources
|
|
580
581
|
: structuredSources.filter((m) => m.score >= scoreThreshold);
|
|
581
582
|
const rerankLimit = wantsExhaustiveList ? retrievalLimit : topK;
|
|
582
|
-
|
|
583
|
+
const useReranking = arch !== 'naive' && (arch === 'retrieve-and-rerank' || this.config.rag?.useReranking);
|
|
584
|
+
if (!hasNumericPredicates && useReranking) {
|
|
583
585
|
fullSources = await this.reranker.rerank(fullSources, question, rerankLimit);
|
|
584
586
|
} else if (!wantsExhaustiveList) {
|
|
585
587
|
fullSources = fullSources.slice(0, topK);
|
|
@@ -830,7 +832,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
830
832
|
const latency: LatencyBreakdown = {
|
|
831
833
|
embedMs: Math.round(embedMs),
|
|
832
834
|
retrieveMs: Math.round(retrieveMs),
|
|
833
|
-
rerankMs: this.config.rag?.useReranking ? Math.round(rerankMs) : undefined,
|
|
835
|
+
rerankMs: (arch !== 'naive' && (arch === 'retrieve-and-rerank' || this.config.rag?.useReranking)) ? Math.round(rerankMs) : undefined,
|
|
834
836
|
generateMs: Math.round(generateMs),
|
|
835
837
|
totalMs: Math.round(totalMs),
|
|
836
838
|
};
|
|
@@ -879,8 +881,9 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
879
881
|
const trace = buildTrace(hallucinationResult);
|
|
880
882
|
|
|
881
883
|
// Asynchronously trigger telemetry logging without blocking response yield
|
|
882
|
-
|
|
883
|
-
|
|
884
|
+
const isTelemetryActive = this.config.telemetry?.enabled ?? Boolean(this.config.licenseKey);
|
|
885
|
+
if (isTelemetryActive) {
|
|
886
|
+
const telemetryUrl = this.config.telemetry?.url || '/api/telemetry';
|
|
884
887
|
const absoluteUrl = telemetryUrl.startsWith('http')
|
|
885
888
|
? telemetryUrl
|
|
886
889
|
: (process.env.NEXT_PUBLIC_APP_URL || `http://localhost:${process.env.PORT || 3000}`) + telemetryUrl;
|