@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.
Files changed (40) hide show
  1. package/README.md +36 -28
  2. package/dist/{ILLMProvider-B8ROITYK.d.mts → ILLMProvider-DMxLyTdq.d.mts} +2 -2
  3. package/dist/{ILLMProvider-B8ROITYK.d.ts → ILLMProvider-DMxLyTdq.d.ts} +2 -2
  4. package/dist/handlers/index.d.mts +2 -2
  5. package/dist/handlers/index.d.ts +2 -2
  6. package/dist/handlers/index.js +248 -74
  7. package/dist/handlers/index.mjs +254 -74
  8. package/dist/{index-DNvoi-sV.d.ts → index-CfkqZd2Y.d.ts} +1 -1
  9. package/dist/{index-BCPKUAVL.d.ts → index-DHsFLJXQ.d.mts} +2 -31
  10. package/dist/{index-CrxCy36A.d.mts → index-tzwc7UhY.d.ts} +2 -31
  11. package/dist/{index-B8PbEFSY.d.mts → index-xygonxpW.d.mts} +1 -1
  12. package/dist/index.css +485 -557
  13. package/dist/index.d.mts +16 -10
  14. package/dist/index.d.ts +16 -10
  15. package/dist/index.js +36 -768
  16. package/dist/index.mjs +32 -782
  17. package/dist/server.d.mts +8 -5
  18. package/dist/server.d.ts +8 -5
  19. package/dist/server.js +249 -74
  20. package/dist/server.mjs +255 -74
  21. package/package.json +3 -2
  22. package/src/app/page.tsx +2 -0
  23. package/src/components/ChatWindow.tsx +14 -3
  24. package/src/components/constants.tsx +224 -0
  25. package/src/config/RagConfig.ts +2 -2
  26. package/src/config/serverConfig.ts +14 -1
  27. package/src/core/ConfigResolver.ts +32 -6
  28. package/src/core/LicenseVerifier.ts +106 -0
  29. package/src/core/Pipeline.ts +11 -8
  30. package/src/core/Retrivora.ts +1 -0
  31. package/src/handlers/index.ts +84 -48
  32. package/src/index.ts +3 -5
  33. package/src/components/AmbientBackground.tsx +0 -29
  34. package/src/components/ArchitectureCard.tsx +0 -53
  35. package/src/components/ArchitectureCardsSection.tsx +0 -48
  36. package/src/components/DocViewer.tsx +0 -97
  37. package/src/components/Documentation.tsx +0 -141
  38. package/src/components/Hero.tsx +0 -142
  39. package/src/components/Lifecycle.tsx +0 -77
  40. 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": "1.9.8",
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": "MIT",
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 response = await fetch('/api/suggestions', {
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
+
@@ -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: readString(env, 'TELEMETRY_ENABLED') === 'true' || readString(env, 'NEXT_PUBLIC_TELEMETRY_ENABLED') === 'true' || base?.telemetry?.enabled || false,
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 = 'agentic';
121
- if (retrieval.strategy === 'contextual-compression') normalized.useReranking = true;
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 === 'agentic' || type === 'agentic-rag') normalized.architecture = 'agentic';
127
- else if (type === 'hybrid-rag') normalized.architecture = 'hybrid';
128
- else if (type === 'graph-rag') {
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 ?? 'simple';
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
  }
@@ -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 useGraph = this.graphDB && this.config.rag?.useGraphRetrieval;
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
- if (!hasNumericPredicates && this.config.rag?.useReranking) {
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
- if (this.config.telemetry?.enabled) {
883
- const telemetryUrl = this.config.telemetry.url || '/api/telemetry';
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;
@@ -29,6 +29,7 @@ export class Retrivora {
29
29
  this.config.projectId,
30
30
  this.config.vectorDb?.provider
31
31
  );
32
+ await LicenseVerifier.verifyIP(this.config.licenseKey);
32
33
  } catch (err) {
33
34
  throw wrapError(err, 'CONFIGURATION_ERROR');
34
35
  }