@retrivora-ai/rag-engine 1.9.7 → 1.9.8
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 +127 -135
- package/dist/{ILLMProvider-Bhk6zJOK.d.mts → ILLMProvider-B8ROITYK.d.mts} +57 -2
- package/dist/{ILLMProvider-Bhk6zJOK.d.ts → ILLMProvider-B8ROITYK.d.ts} +57 -2
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +2198 -457
- package/dist/handlers/index.mjs +2195 -457
- package/dist/{index-BJ4cd-t5.d.mts → index-B8PbEFSY.d.mts} +12 -2
- package/dist/{index-Bu7T6xgr.d.ts → index-BCPKUAVL.d.ts} +27 -52
- package/dist/{index-C3SVtPYg.d.mts → index-CrxCy36A.d.mts} +27 -52
- package/dist/{index-B9J_XEh0.d.ts → index-DNvoi-sV.d.ts} +12 -2
- package/dist/index.css +578 -273
- package/dist/index.d.mts +29 -7
- package/dist/index.d.ts +29 -7
- package/dist/index.js +1160 -282
- package/dist/index.mjs +1185 -292
- package/dist/server.d.mts +62 -6
- package/dist/server.d.ts +62 -6
- package/dist/server.js +2061 -306
- package/dist/server.mjs +2055 -306
- package/package.json +11 -7
- package/src/app/constants.tsx +37 -7
- package/src/components/AmbientBackground.tsx +10 -10
- package/src/components/ArchitectureCard.tsx +43 -7
- package/src/components/ArchitectureCardsSection.tsx +37 -4
- package/src/components/ChatWidget.tsx +2 -1
- package/src/components/ChatWindow.tsx +4 -1
- package/src/components/CodeViewer.tsx +19 -14
- package/src/components/DocViewer.tsx +43 -49
- package/src/components/Documentation.tsx +71 -51
- package/src/components/Hero.tsx +103 -20
- package/src/components/Lifecycle.tsx +65 -25
- package/src/components/MarkdownComponents.tsx +44 -1
- package/src/components/MessageBubble.tsx +162 -50
- package/src/components/constants.tsx +4 -0
- package/src/config/RagConfig.ts +46 -0
- package/src/config/constants.ts +4 -0
- package/src/config/serverConfig.ts +15 -0
- package/src/core/ConfigResolver.ts +6 -0
- package/src/core/DatabaseStorage.ts +469 -0
- package/src/core/LicenseVerifier.ts +154 -0
- package/src/core/MultiAgentCoordinator.ts +239 -0
- package/src/core/Pipeline.ts +146 -15
- package/src/core/Retrivora.ts +6 -0
- package/src/core/VectorPlugin.ts +12 -3
- package/src/core/mcp.ts +261 -0
- package/src/handlers/index.ts +449 -63
- package/src/hooks/useRagChat.ts +96 -42
- package/src/hooks/useStoredMessages.ts +15 -4
- package/src/index.ts +5 -0
- package/src/llm/LLMFactory.ts +9 -1
- package/src/llm/providers/GroqProvider.ts +176 -0
- package/src/llm/providers/QwenProvider.ts +191 -0
- package/src/server.ts +7 -0
- package/src/types/chat.ts +14 -0
- package/src/types/props.ts +12 -0
- package/.env.example +0 -80
- package/LICENSE.txt +0 -21
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { ILLMProvider } from '../llm/ILLMProvider';
|
|
2
|
+
import { MCPRegistry } from './mcp';
|
|
3
|
+
import { ChatMessage, ChatResponse } from '../types';
|
|
4
|
+
|
|
5
|
+
interface CoordinatorOptions {
|
|
6
|
+
llmProvider: ILLMProvider;
|
|
7
|
+
mcpRegistry: MCPRegistry;
|
|
8
|
+
documentSearch: (query: string) => Promise<{ reply: string; sources: any[] }>;
|
|
9
|
+
maxIterations?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class MultiAgentCoordinator {
|
|
13
|
+
private llmProvider: ILLMProvider;
|
|
14
|
+
private mcpRegistry: MCPRegistry;
|
|
15
|
+
private documentSearch: (query: string) => Promise<{ reply: string; sources: any[] }>;
|
|
16
|
+
private maxIterations: number;
|
|
17
|
+
|
|
18
|
+
constructor(options: CoordinatorOptions) {
|
|
19
|
+
this.llmProvider = options.llmProvider;
|
|
20
|
+
this.mcpRegistry = options.mcpRegistry;
|
|
21
|
+
this.documentSearch = options.documentSearch;
|
|
22
|
+
this.maxIterations = options.maxIterations ?? 5;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Run the multi-agent coordination loop synchronously and return the final response.
|
|
27
|
+
*/
|
|
28
|
+
async run(question: string, history: ChatMessage[] = []): Promise<ChatResponse> {
|
|
29
|
+
const generator = this.runStream(question, history);
|
|
30
|
+
let finalReply = '';
|
|
31
|
+
const sources: any[] = [];
|
|
32
|
+
|
|
33
|
+
for await (const chunk of generator) {
|
|
34
|
+
if (typeof chunk === 'string') {
|
|
35
|
+
finalReply += chunk;
|
|
36
|
+
} else if (chunk && typeof chunk === 'object') {
|
|
37
|
+
if ('reply' in chunk && chunk.reply) {
|
|
38
|
+
finalReply += chunk.reply;
|
|
39
|
+
}
|
|
40
|
+
if ('sources' in chunk && chunk.sources) {
|
|
41
|
+
sources.push(...chunk.sources);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
reply: finalReply,
|
|
48
|
+
sources,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Run the multi-agent coordination loop as an async stream, yielding intermediate thought blocks and tool execution events.
|
|
54
|
+
*/
|
|
55
|
+
async *runStream(question: string, history: ChatMessage[] = []): AsyncGenerator<string | ChatResponse> {
|
|
56
|
+
// 1. Gather all tools
|
|
57
|
+
const mcpTools = await this.mcpRegistry.getAllTools();
|
|
58
|
+
|
|
59
|
+
// Construct system instructions with tools
|
|
60
|
+
let systemPrompt = `You are a goal-driven supervisor agent coordinating a RAG search engine and external MCP servers to solve the user's task.
|
|
61
|
+
You must think step-by-step before acting. Write your internal thinking process inside a \`<think>...</think>\` block first, then act or answer.
|
|
62
|
+
|
|
63
|
+
Available Tools:
|
|
64
|
+
- document_search(query: string): Search the vector database and knowledge base for documents.
|
|
65
|
+
|
|
66
|
+
${mcpTools.length > 0 ? 'MCP Server Tools:' : ''}
|
|
67
|
+
${mcpTools.map(mt => `- ${mt.tool.name}(${JSON.stringify(mt.tool.inputSchema?.properties || {})}): ${mt.tool.description || 'No description provided.'}`).join('\n')}
|
|
68
|
+
|
|
69
|
+
Tool Calling Protocol:
|
|
70
|
+
If you need to call a tool, write:
|
|
71
|
+
TOOL_CALL: <toolName>({ "argName": "value" })
|
|
72
|
+
And then STOP generating immediately.
|
|
73
|
+
|
|
74
|
+
Once you have gathered enough information to fully satisfy the user's query, write your final response directly after the closing \`</think>\` block.
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
// 2. Setup message history for the supervisor
|
|
78
|
+
const supervisorHistory: ChatMessage[] = [
|
|
79
|
+
{ role: 'system' as any, content: systemPrompt },
|
|
80
|
+
...history,
|
|
81
|
+
{ role: 'user', content: question }
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
let iteration = 0;
|
|
85
|
+
const allSources: any[] = [];
|
|
86
|
+
|
|
87
|
+
while (iteration < this.maxIterations) {
|
|
88
|
+
iteration++;
|
|
89
|
+
|
|
90
|
+
const context = ''; // Context is populated by tool call results directly in message history
|
|
91
|
+
let fullModelResponse = '';
|
|
92
|
+
let textBuffer = '';
|
|
93
|
+
let inThinking = false;
|
|
94
|
+
let toolCallDetected: { name: string; args: any } | null = null;
|
|
95
|
+
|
|
96
|
+
if (!this.llmProvider.chatStream) {
|
|
97
|
+
// Fallback for non-streaming providers
|
|
98
|
+
const reply = await this.llmProvider.chat(supervisorHistory, context);
|
|
99
|
+
fullModelResponse = reply;
|
|
100
|
+
|
|
101
|
+
// Parse simulated thinking / tool call
|
|
102
|
+
const thinkIndex = reply.indexOf('<think>');
|
|
103
|
+
const endThinkIndex = reply.indexOf('</think>');
|
|
104
|
+
|
|
105
|
+
let thinkingText = '';
|
|
106
|
+
let answerText = reply;
|
|
107
|
+
|
|
108
|
+
if (thinkIndex !== -1 && endThinkIndex !== -1) {
|
|
109
|
+
thinkingText = reply.substring(thinkIndex + 7, endThinkIndex);
|
|
110
|
+
answerText = reply.substring(endThinkIndex + 8);
|
|
111
|
+
yield { type: 'thinking', text: thinkingText } as any;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const toolMatch = answerText.match(/TOOL_CALL:\s*(\w+)\s*\(([\s\S]*)\)/);
|
|
115
|
+
if (toolMatch) {
|
|
116
|
+
const toolName = toolMatch[1];
|
|
117
|
+
const toolArgsStr = toolMatch[2];
|
|
118
|
+
try {
|
|
119
|
+
const toolArgs = JSON.parse(toolArgsStr.trim());
|
|
120
|
+
toolCallDetected = { name: toolName, args: toolArgs };
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.error('[MultiAgentCoordinator] Failed to parse tool args:', toolArgsStr, e);
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
yield answerText;
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
// Run with streaming completion
|
|
129
|
+
const stream = this.llmProvider.chatStream(supervisorHistory, context);
|
|
130
|
+
|
|
131
|
+
for await (const chunk of stream) {
|
|
132
|
+
fullModelResponse += chunk;
|
|
133
|
+
textBuffer += chunk;
|
|
134
|
+
|
|
135
|
+
// Check for <think> tag
|
|
136
|
+
if (!inThinking && textBuffer.includes('<think>')) {
|
|
137
|
+
inThinking = true;
|
|
138
|
+
const idx = textBuffer.indexOf('<think>');
|
|
139
|
+
textBuffer = textBuffer.slice(idx + 7);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Check for </think> tag
|
|
143
|
+
if (inThinking && textBuffer.includes('</think>')) {
|
|
144
|
+
inThinking = false;
|
|
145
|
+
const idx = textBuffer.indexOf('</think>');
|
|
146
|
+
const thinkingDelta = textBuffer.slice(0, idx);
|
|
147
|
+
yield { type: 'thinking', text: thinkingDelta } as any;
|
|
148
|
+
textBuffer = textBuffer.slice(idx + 8);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// If we are in thinking mode, yield thinking delta
|
|
152
|
+
if (inThinking && textBuffer.length > 0) {
|
|
153
|
+
yield { type: 'thinking', text: textBuffer } as any;
|
|
154
|
+
textBuffer = '';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check for TOOL_CALL during stream
|
|
158
|
+
if (!inThinking && textBuffer.includes('TOOL_CALL:')) {
|
|
159
|
+
const idx = textBuffer.indexOf('TOOL_CALL:');
|
|
160
|
+
const precedingText = textBuffer.slice(0, idx);
|
|
161
|
+
if (precedingText.trim()) {
|
|
162
|
+
yield precedingText;
|
|
163
|
+
}
|
|
164
|
+
// Await remainder of stream to get the full tool call signature
|
|
165
|
+
textBuffer = textBuffer.slice(idx);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// If not in thinking and no tool call prefix is blocking, yield final answer text
|
|
169
|
+
if (!inThinking && !textBuffer.includes('TOOL_CALL:') && textBuffer.length > 0) {
|
|
170
|
+
yield textBuffer;
|
|
171
|
+
textBuffer = '';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Flush any remaining buffer text
|
|
176
|
+
if (textBuffer.trim()) {
|
|
177
|
+
const toolMatch = textBuffer.match(/TOOL_CALL:\s*(\w+)\s*\(([\s\S]*)\)/);
|
|
178
|
+
if (toolMatch) {
|
|
179
|
+
const toolName = toolMatch[1];
|
|
180
|
+
const toolArgsStr = toolMatch[2];
|
|
181
|
+
try {
|
|
182
|
+
const toolArgs = JSON.parse(toolArgsStr.trim());
|
|
183
|
+
toolCallDetected = { name: toolName, args: toolArgs };
|
|
184
|
+
} catch (e) {
|
|
185
|
+
console.error('[MultiAgentCoordinator] Failed to parse tool args:', toolArgsStr, e);
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
yield textBuffer;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 3. Append the model response to the supervisor history
|
|
194
|
+
supervisorHistory.push({ role: 'assistant', content: fullModelResponse });
|
|
195
|
+
|
|
196
|
+
// 4. Handle tool execution if requested, else break the loop
|
|
197
|
+
if (toolCallDetected) {
|
|
198
|
+
const { name: toolName, args: toolArgs } = toolCallDetected;
|
|
199
|
+
|
|
200
|
+
yield {
|
|
201
|
+
type: 'thinking',
|
|
202
|
+
text: `\n[Agent Call] Executing tool "${toolName}" with parameters ${JSON.stringify(toolArgs)}...\n`
|
|
203
|
+
} as any;
|
|
204
|
+
|
|
205
|
+
let toolResultText = '';
|
|
206
|
+
try {
|
|
207
|
+
if (toolName === 'document_search') {
|
|
208
|
+
const searchRes = await this.documentSearch(toolArgs.query || '');
|
|
209
|
+
toolResultText = `document_search returned:\n${searchRes.reply}\n\nSources count: ${searchRes.sources.length}`;
|
|
210
|
+
if (searchRes.sources && searchRes.sources.length > 0) {
|
|
211
|
+
allSources.push(...searchRes.sources);
|
|
212
|
+
yield { reply: '', sources: searchRes.sources };
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
// Call MCP tool
|
|
216
|
+
const mcpRes = await this.mcpRegistry.callTool(toolName, toolArgs);
|
|
217
|
+
toolResultText = `MCP Tool "${toolName}" returned:\n${JSON.stringify(mcpRes.content)}`;
|
|
218
|
+
}
|
|
219
|
+
} catch (err: any) {
|
|
220
|
+
toolResultText = `Error calling tool "${toolName}": ${err.message}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Append tool output to history
|
|
224
|
+
supervisorHistory.push({
|
|
225
|
+
role: 'user',
|
|
226
|
+
content: `TOOL_RESULT for ${toolName}:\n${toolResultText}`
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
yield {
|
|
230
|
+
type: 'thinking',
|
|
231
|
+
text: `[Agent Output] Tool "${toolName}" executed. Continuing reasoning...\n`
|
|
232
|
+
} as any;
|
|
233
|
+
} else {
|
|
234
|
+
// No tool call requested — response is complete
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -9,6 +9,8 @@ import { Reranker } from '../rag/Reranker';
|
|
|
9
9
|
import { LlamaIndexIngestor } from '../rag/LlamaIndexIngestor';
|
|
10
10
|
import { LangChainAgent } from './LangChainAgent';
|
|
11
11
|
import { RagConfig } from '../config/RagConfig';
|
|
12
|
+
import { MCPRegistry } from './mcp';
|
|
13
|
+
import { MultiAgentCoordinator } from './MultiAgentCoordinator';
|
|
12
14
|
import { ProviderRegistry } from './ProviderRegistry';
|
|
13
15
|
import { BatchProcessor, BatchOptions } from './BatchProcessor';
|
|
14
16
|
import { EmbeddingStrategyResolver } from '../config/EmbeddingStrategy';
|
|
@@ -165,9 +167,13 @@ export class Pipeline {
|
|
|
165
167
|
private entityExtractor?: EntityExtractor;
|
|
166
168
|
private reranker: Reranker;
|
|
167
169
|
private agent?: LangChainAgent;
|
|
170
|
+
private mcpRegistry?: MCPRegistry;
|
|
171
|
+
private multiAgentCoordinator?: MultiAgentCoordinator;
|
|
168
172
|
/** LRU-bounded cache: avoids re-embedding identical queries within the same process. */
|
|
169
173
|
private embeddingCache = new LRUEmbeddingCache(500);
|
|
170
174
|
private initialised = false;
|
|
175
|
+
/** Namespace-specific static cold context cache for CAG */
|
|
176
|
+
private coldContexts = new Map<string, string>();
|
|
171
177
|
|
|
172
178
|
constructor(private config: RagConfig) {
|
|
173
179
|
this.chunker = new DocumentChunker(
|
|
@@ -250,14 +256,52 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
250
256
|
|
|
251
257
|
await this.vectorDB.initialize();
|
|
252
258
|
|
|
253
|
-
if (this.config.rag?.architecture === 'agentic') {
|
|
259
|
+
if (this.config.rag?.architecture === 'agentic' || (this.config.mcpServers && this.config.mcpServers.length > 0)) {
|
|
260
|
+
this.mcpRegistry = new MCPRegistry(this.config.mcpServers || []);
|
|
261
|
+
this.multiAgentCoordinator = new MultiAgentCoordinator({
|
|
262
|
+
llmProvider: this.llmProvider,
|
|
263
|
+
mcpRegistry: this.mcpRegistry,
|
|
264
|
+
documentSearch: async (query: string) => {
|
|
265
|
+
return this.runNormalQuery(query);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
254
268
|
this.agent = new LangChainAgent(this, this.config);
|
|
255
269
|
await this.agent.initialize(this.llmProvider as unknown);
|
|
256
270
|
}
|
|
257
271
|
|
|
272
|
+
if (this.config.rag?.cag?.enabled) {
|
|
273
|
+
await this.loadColdContext(this.config.projectId);
|
|
274
|
+
}
|
|
275
|
+
|
|
258
276
|
this.initialised = true;
|
|
259
277
|
}
|
|
260
278
|
|
|
279
|
+
private async loadColdContext(ns: string): Promise<void> {
|
|
280
|
+
const cagConfig = this.config.rag?.cag;
|
|
281
|
+
if (!cagConfig || !cagConfig.enabled) return;
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const coldNs = cagConfig.coldNamespace || ns;
|
|
285
|
+
const limit = cagConfig.coldLimit ?? 20;
|
|
286
|
+
|
|
287
|
+
const queryText = cagConfig.coldQuery || 'documentation';
|
|
288
|
+
const queryVector = await this.embeddingProvider.embed(queryText, { taskType: 'query' });
|
|
289
|
+
const coldMatches = await this.vectorDB.query(queryVector, limit, coldNs);
|
|
290
|
+
|
|
291
|
+
if (coldMatches.length > 0) {
|
|
292
|
+
const formatted = coldMatches.map((m, i) => `[Cold Source ${i + 1}]\n${m.content}`).join('\n\n---\n\n');
|
|
293
|
+
this.coldContexts.set(ns, formatted);
|
|
294
|
+
console.log(`[Pipeline] Cold RAG (CAG) loaded ${coldMatches.length} documents into cached context for namespace: ${ns}`);
|
|
295
|
+
} else {
|
|
296
|
+
this.coldContexts.set(ns, 'No cold context found.');
|
|
297
|
+
console.warn(`[Pipeline] Cold RAG (CAG) initialized but no documents were found in namespace: ${coldNs}`);
|
|
298
|
+
}
|
|
299
|
+
} catch (err) {
|
|
300
|
+
console.error(`[Pipeline] Failed to load Cold RAG (CAG) context for namespace ${ns}:`, err);
|
|
301
|
+
this.coldContexts.set(ns, 'Failed to load cold context.');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
261
305
|
/**
|
|
262
306
|
* Ingest documents with automatic chunking, embedding, and batch upsert.
|
|
263
307
|
*/
|
|
@@ -386,12 +430,26 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
386
430
|
);
|
|
387
431
|
}
|
|
388
432
|
|
|
433
|
+
async runNormalQuery(question: string, history: ChatMessage[] = [], namespace?: string): Promise<{ reply: string; sources: any[] }> {
|
|
434
|
+
const stream = this.askStreamInternal(question, history, namespace);
|
|
435
|
+
let reply = '';
|
|
436
|
+
let sources: any[] = [];
|
|
437
|
+
for await (const chunk of stream) {
|
|
438
|
+
if (typeof chunk === 'string') {
|
|
439
|
+
reply += chunk;
|
|
440
|
+
} else if (typeof chunk === 'object' && chunk !== null) {
|
|
441
|
+
if ('reply' in chunk && chunk.reply) reply += chunk.reply;
|
|
442
|
+
if ('sources' in chunk && chunk.sources) sources = chunk.sources;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return { reply, sources };
|
|
446
|
+
}
|
|
447
|
+
|
|
389
448
|
async ask(question: string, history: ChatMessage[] = [], namespace?: string): Promise<ChatResponse> {
|
|
390
449
|
await this.initialize();
|
|
391
450
|
|
|
392
|
-
if (this.config.rag?.architecture === 'agentic' && this.
|
|
393
|
-
|
|
394
|
-
return { reply: agentReply, sources: [] };
|
|
451
|
+
if ((this.config.rag?.architecture === 'agentic' || (this.config.mcpServers && this.config.mcpServers.length > 0)) && this.multiAgentCoordinator) {
|
|
452
|
+
return await this.multiAgentCoordinator.run(question, history);
|
|
395
453
|
}
|
|
396
454
|
|
|
397
455
|
const stream = this.askStream(question, history, namespace);
|
|
@@ -415,6 +473,21 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
415
473
|
return { reply, sources, graphData, ui_transformation: uiTransformation, trace };
|
|
416
474
|
}
|
|
417
475
|
|
|
476
|
+
async *askStream(question: string, history: ChatMessage[] = [], namespace?: string): AsyncIterable<string | ChatResponse> {
|
|
477
|
+
await this.initialize();
|
|
478
|
+
if ((this.config.rag?.architecture === 'agentic' || (this.config.mcpServers && this.config.mcpServers.length > 0)) && this.multiAgentCoordinator) {
|
|
479
|
+
const stream = this.multiAgentCoordinator.runStream(question, history);
|
|
480
|
+
for await (const chunk of stream) {
|
|
481
|
+
yield chunk;
|
|
482
|
+
}
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const stream = this.askStreamInternal(question, history, namespace);
|
|
486
|
+
for await (const chunk of stream) {
|
|
487
|
+
yield chunk;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
418
491
|
/**
|
|
419
492
|
* High-performance streaming RAG flow.
|
|
420
493
|
* Yields text chunks first, then the retrieval metadata + observability trace at the end.
|
|
@@ -425,7 +498,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
425
498
|
* - UITransformation is computed after text streaming and emitted with metadata
|
|
426
499
|
* - SchemaMapper.train runs while answer generation streams
|
|
427
500
|
*/
|
|
428
|
-
async *
|
|
501
|
+
async *askStreamInternal(question: string, history: ChatMessage[] = [], namespace?: string): AsyncIterable<string | ChatResponse> {
|
|
429
502
|
await this.initialize();
|
|
430
503
|
const ns = namespace ?? this.config.projectId;
|
|
431
504
|
const topK = this.config.rag?.topK ?? 5;
|
|
@@ -547,6 +620,28 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
547
620
|
context = `GRAPH KNOWLEDGE:\n${graphContext}\n\nVECTOR CONTEXT:\n${context}`;
|
|
548
621
|
}
|
|
549
622
|
|
|
623
|
+
if (this.config.rag?.cag?.enabled) {
|
|
624
|
+
if (!this.coldContexts.has(ns)) {
|
|
625
|
+
await this.loadColdContext(ns);
|
|
626
|
+
}
|
|
627
|
+
const coldContext = this.coldContexts.get(ns);
|
|
628
|
+
if (coldContext && coldContext !== 'No cold context found.' && coldContext !== 'Failed to load cold context.') {
|
|
629
|
+
context = `COLD CONTEXT (STATIC KNOWLEDGE):\n${coldContext}\n\nRETRIEVED HOT CONTEXT (REAL-TIME KNOWLEDGE):\n${context}`;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Yield sources immediately to support early UI citation rendering (highly responsive)
|
|
634
|
+
yield {
|
|
635
|
+
reply: '',
|
|
636
|
+
sources: sources.map((s) => ({
|
|
637
|
+
id: s.id,
|
|
638
|
+
score: s.score,
|
|
639
|
+
content: s.content,
|
|
640
|
+
metadata: s.metadata ?? {},
|
|
641
|
+
namespace: ns,
|
|
642
|
+
} satisfies RetrievedChunk)),
|
|
643
|
+
} as ChatResponse;
|
|
644
|
+
|
|
550
645
|
// 4.5 Schema Training — true fire-and-forget, never gates anything.
|
|
551
646
|
const allMetadataKeys = Array.from(new Set(sources.flatMap(s => Object.keys(s.metadata || {}))));
|
|
552
647
|
const cachedSchema = allMetadataKeys.length > 0
|
|
@@ -751,15 +846,15 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
751
846
|
estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model),
|
|
752
847
|
};
|
|
753
848
|
|
|
754
|
-
// 7. Await
|
|
755
|
-
|
|
756
|
-
|
|
849
|
+
// 7. Await UI transformation and optionally hallucination score before yielding the metadata frame.
|
|
850
|
+
const awaitHallucination = this.config.llm.options?.awaitHallucination === true;
|
|
851
|
+
|
|
757
852
|
const [uiTransformation, hallucinationResult] = await Promise.all([
|
|
758
853
|
uiTransformationPromise,
|
|
759
|
-
hallucinationScoringPromise,
|
|
854
|
+
awaitHallucination ? hallucinationScoringPromise : Promise.resolve(undefined),
|
|
760
855
|
]);
|
|
761
856
|
|
|
762
|
-
const
|
|
857
|
+
const buildTrace = (hScore?: { score: number; reason: string }): ObservabilityTrace => ({
|
|
763
858
|
requestId,
|
|
764
859
|
query: question,
|
|
765
860
|
rewrittenQuery,
|
|
@@ -775,12 +870,48 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
775
870
|
latency,
|
|
776
871
|
tokens,
|
|
777
872
|
timestamp: new Date().toISOString(),
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
hallucinationReason: hallucinationResult.reason,
|
|
873
|
+
...(hScore ? {
|
|
874
|
+
hallucinationScore: hScore.score,
|
|
875
|
+
hallucinationReason: hScore.reason,
|
|
782
876
|
} : {}),
|
|
783
|
-
};
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
const trace = buildTrace(hallucinationResult);
|
|
880
|
+
|
|
881
|
+
// 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 absoluteUrl = telemetryUrl.startsWith('http')
|
|
885
|
+
? telemetryUrl
|
|
886
|
+
: (process.env.NEXT_PUBLIC_APP_URL || `http://localhost:${process.env.PORT || 3000}`) + telemetryUrl;
|
|
887
|
+
|
|
888
|
+
// Start background process for telemetry, awaiting hallucination scoring if it was backgrounded
|
|
889
|
+
(async () => {
|
|
890
|
+
try {
|
|
891
|
+
let finalTrace = trace;
|
|
892
|
+
if (!awaitHallucination && runHallucination) {
|
|
893
|
+
const backgroundScoreResult = await hallucinationScoringPromise;
|
|
894
|
+
if (backgroundScoreResult) {
|
|
895
|
+
finalTrace = buildTrace(backgroundScoreResult);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
await fetch(absoluteUrl, {
|
|
900
|
+
method: 'POST',
|
|
901
|
+
headers: {
|
|
902
|
+
'Content-Type': 'application/json',
|
|
903
|
+
},
|
|
904
|
+
body: JSON.stringify({
|
|
905
|
+
trace: finalTrace,
|
|
906
|
+
licenseKey: this.config.licenseKey,
|
|
907
|
+
projectId: ns
|
|
908
|
+
})
|
|
909
|
+
});
|
|
910
|
+
} catch (err: any) {
|
|
911
|
+
console.warn(`[Pipeline Telemetry] Failed to send trace async to ${absoluteUrl}:`, err.message);
|
|
912
|
+
}
|
|
913
|
+
})();
|
|
914
|
+
}
|
|
784
915
|
|
|
785
916
|
yield {
|
|
786
917
|
reply: '',
|
package/src/core/Retrivora.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { RagConfig, RetrievalConfig, UniversalRagConfig } from '../config/RagCon
|
|
|
2
2
|
import { ChatMessage, ChatResponse, IngestDocument } from '../types';
|
|
3
3
|
import { ConfigResolver } from './ConfigResolver';
|
|
4
4
|
import { ConfigValidator } from './ConfigValidator';
|
|
5
|
+
import { LicenseVerifier } from './LicenseVerifier';
|
|
5
6
|
import { Pipeline } from './Pipeline';
|
|
6
7
|
import { wrapError, RetrivoraErrorCode } from '../exceptions';
|
|
7
8
|
|
|
@@ -23,6 +24,11 @@ export class Retrivora {
|
|
|
23
24
|
async initialize(): Promise<void> {
|
|
24
25
|
try {
|
|
25
26
|
await ConfigValidator.validateAndThrow(this.config);
|
|
27
|
+
LicenseVerifier.verify(
|
|
28
|
+
this.config.licenseKey,
|
|
29
|
+
this.config.projectId,
|
|
30
|
+
this.config.vectorDb?.provider
|
|
31
|
+
);
|
|
26
32
|
} catch (err) {
|
|
27
33
|
throw wrapError(err, 'CONFIGURATION_ERROR');
|
|
28
34
|
}
|
package/src/core/VectorPlugin.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Pipeline } from './Pipeline';
|
|
|
5
5
|
import { ProviderHealthCheck, HealthCheckResult } from './ProviderHealthCheck';
|
|
6
6
|
import { IngestDocument, ChatResponse } from '../types';
|
|
7
7
|
import { ChatMessage } from '../types';
|
|
8
|
+
import { LicenseVerifier } from './LicenseVerifier';
|
|
8
9
|
import { wrapError, RetrivoraErrorCode } from '../exceptions';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -24,14 +25,22 @@ export class VectorPlugin {
|
|
|
24
25
|
private validationPromise: Promise<void>;
|
|
25
26
|
|
|
26
27
|
constructor(hostConfig?: Partial<RagConfig>) {
|
|
27
|
-
|
|
28
|
+
const resolvedConfig = ConfigResolver.resolve(hostConfig);
|
|
29
|
+
this.config = resolvedConfig;
|
|
28
30
|
|
|
29
31
|
// Start validation early
|
|
30
|
-
this.validationPromise =
|
|
32
|
+
this.validationPromise = (async () => {
|
|
33
|
+
await ConfigValidator.validateAndThrow(resolvedConfig);
|
|
34
|
+
LicenseVerifier.verify(
|
|
35
|
+
resolvedConfig.licenseKey,
|
|
36
|
+
resolvedConfig.projectId,
|
|
37
|
+
resolvedConfig.vectorDb?.provider
|
|
38
|
+
);
|
|
39
|
+
})();
|
|
31
40
|
// Prevent unhandled promise rejection warning
|
|
32
41
|
this.validationPromise.catch(() => {});
|
|
33
42
|
|
|
34
|
-
this.pipeline = new Pipeline(
|
|
43
|
+
this.pipeline = new Pipeline(resolvedConfig);
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
/**
|