@retrivora-ai/rag-engine 2.0.8 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/handlers/index.js +113 -75
- package/dist/handlers/index.mjs +113 -75
- package/dist/index.js +19 -12
- package/dist/index.mjs +19 -12
- package/dist/server.js +113 -75
- package/dist/server.mjs +113 -75
- package/package.json +1 -1
- package/src/components/ChatWindow.tsx +22 -22
- package/src/core/Pipeline.ts +3 -3
- package/src/llm/providers/GeminiProvider.ts +4 -4
- package/src/llm/providers/GroqProvider.ts +16 -8
- package/src/llm/providers/OpenAIProvider.ts +16 -8
- package/src/llm/providers/QwenProvider.ts +16 -8
- package/src/llm/providers/UniversalLLMAdapter.ts +56 -36
- package/src/rendering/IntentClassifier.ts +3 -3
- package/src/rendering/rules/Rule1SpecificInfoRule.ts +1 -1
- package/src/utils/ProductExtractor.ts +4 -3
- package/src/utils/UITransformer.ts +12 -0
package/dist/server.mjs
CHANGED
|
@@ -579,6 +579,7 @@ function cleanApiKeyOverride(apiKeyOverride) {
|
|
|
579
579
|
async function dispatchChatCompletion(req, apiKeyOverride) {
|
|
580
580
|
const effectiveKey = cleanApiKeyOverride(apiKeyOverride);
|
|
581
581
|
const provider = resolveProvider(req.model);
|
|
582
|
+
console.log(`[LLM Gateway Router] Dispatching chat request: model=${req.model}, provider=${provider}, hasGroqKey=${Boolean(process.env.GROQ_API_KEY)}, hasGeminiKey=${Boolean(process.env.GROQ_API_KEY || process.env.GEMINI_API_KEY)}, hasHFToken=${Boolean(process.env.HF_TOKEN)}`);
|
|
582
583
|
try {
|
|
583
584
|
switch (provider) {
|
|
584
585
|
case "groq":
|
|
@@ -597,6 +598,10 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
|
|
|
597
598
|
throw new Error(`Unsupported LLM provider for model: ${req.model}`);
|
|
598
599
|
}
|
|
599
600
|
} catch (error) {
|
|
601
|
+
console.error(`[LLM Gateway Router] Provider '${provider}' failed for model '${req.model}':`, {
|
|
602
|
+
message: error.message,
|
|
603
|
+
stack: error.stack
|
|
604
|
+
});
|
|
600
605
|
if (/rate[- ]?limit|429|exhausted/i.test(error.message)) {
|
|
601
606
|
const isGroqKey = process.env.GROQ_API_KEY || effectiveKey && effectiveKey.startsWith("gsk_");
|
|
602
607
|
const reqModelLower = req.model.toLowerCase();
|
|
@@ -604,7 +609,8 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
|
|
|
604
609
|
console.warn(`[LLM Gateway] Provider ${provider} rate limited (${error.message}). Falling back to secondary Groq model (llama-3.3-70b-versatile)...`);
|
|
605
610
|
try {
|
|
606
611
|
return await handleGroqRequest(__spreadProps(__spreadValues({}, req), { model: "llama-3.3-70b-versatile" }), effectiveKey);
|
|
607
|
-
} catch (
|
|
612
|
+
} catch (fErr) {
|
|
613
|
+
console.error("[LLM Gateway] Fallback groq/llama-3.3-70b-versatile failed:", fErr.message);
|
|
608
614
|
}
|
|
609
615
|
}
|
|
610
616
|
const geminiKey = process.env.GEMINI_API_KEY || effectiveKey;
|
|
@@ -612,14 +618,16 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
|
|
|
612
618
|
console.warn(`[LLM Gateway] Provider ${provider} rate limited (${error.message}). Falling back to Google Gemini (gemini-2.5-flash)...`);
|
|
613
619
|
try {
|
|
614
620
|
return await handleGeminiRequest(__spreadProps(__spreadValues({}, req), { model: "gemini-2.5-flash" }), effectiveKey);
|
|
615
|
-
} catch (
|
|
621
|
+
} catch (fErr) {
|
|
622
|
+
console.error("[LLM Gateway] Fallback gemini-2.5-flash failed:", fErr.message);
|
|
616
623
|
}
|
|
617
624
|
}
|
|
618
625
|
if (provider !== "huggingface" && (process.env.HF_TOKEN || process.env.HUGGINGFACE_API_KEY)) {
|
|
619
626
|
console.warn(`[LLM Gateway] Provider ${provider} rate limited (${error.message}). Falling back to Hugging Face (Qwen/Qwen2.5-Coder-32B-Instruct)...`);
|
|
620
627
|
try {
|
|
621
628
|
return await handleHuggingFaceChatRequest(__spreadProps(__spreadValues({}, req), { model: "Qwen/Qwen2.5-Coder-32B-Instruct" }), effectiveKey);
|
|
622
|
-
} catch (
|
|
629
|
+
} catch (fErr) {
|
|
630
|
+
console.error("[LLM Gateway] Fallback HuggingFace Qwen failed:", fErr.message);
|
|
623
631
|
}
|
|
624
632
|
}
|
|
625
633
|
if (provider !== "openai" && process.env.OPENAI_API_KEY) {
|
|
@@ -3393,12 +3401,13 @@ var OpenAIProvider = class {
|
|
|
3393
3401
|
role: "system",
|
|
3394
3402
|
content: buildSystemContent(basePrompt, context)
|
|
3395
3403
|
};
|
|
3404
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
3405
|
+
role: m.role || "user",
|
|
3406
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
3407
|
+
}));
|
|
3396
3408
|
const formattedMessages = [
|
|
3397
3409
|
systemMessage,
|
|
3398
|
-
...
|
|
3399
|
-
role: m.role,
|
|
3400
|
-
content: m.content
|
|
3401
|
-
}))
|
|
3410
|
+
...safeMessages
|
|
3402
3411
|
];
|
|
3403
3412
|
const completion = await this.client.chat.completions.create({
|
|
3404
3413
|
model: this.llmConfig.model,
|
|
@@ -3417,12 +3426,13 @@ var OpenAIProvider = class {
|
|
|
3417
3426
|
role: "system",
|
|
3418
3427
|
content: buildSystemContent(basePrompt, context)
|
|
3419
3428
|
};
|
|
3429
|
+
const safeStreamMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
3430
|
+
role: m.role || "user",
|
|
3431
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
3432
|
+
}));
|
|
3420
3433
|
const formattedMessages = [
|
|
3421
3434
|
systemMessage,
|
|
3422
|
-
...
|
|
3423
|
-
role: m.role,
|
|
3424
|
-
content: m.content
|
|
3425
|
-
}))
|
|
3435
|
+
...safeStreamMessages
|
|
3426
3436
|
];
|
|
3427
3437
|
const stream = yield new __await(this.client.chat.completions.create({
|
|
3428
3438
|
model: this.llmConfig.model,
|
|
@@ -3953,9 +3963,9 @@ var GeminiProvider = class {
|
|
|
3953
3963
|
* - Messages must strictly alternate between `user` and `model`.
|
|
3954
3964
|
*/
|
|
3955
3965
|
buildGeminiContents(messages) {
|
|
3956
|
-
return messages.filter((m) => m.role !== "system").map((m) => ({
|
|
3966
|
+
return (messages || []).filter((m) => Boolean(m && typeof m === "object" && m.role !== "system")).map((m) => ({
|
|
3957
3967
|
role: m.role === "assistant" ? "model" : "user",
|
|
3958
|
-
parts: [{ text: m.content }]
|
|
3968
|
+
parts: [{ text: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : "" }]
|
|
3959
3969
|
}));
|
|
3960
3970
|
}
|
|
3961
3971
|
// -------------------------------------------------------------------------
|
|
@@ -4158,12 +4168,13 @@ var GroqProvider = class {
|
|
|
4158
4168
|
role: "system",
|
|
4159
4169
|
content: buildSystemContent(basePrompt, context)
|
|
4160
4170
|
};
|
|
4171
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4172
|
+
role: m.role || "user",
|
|
4173
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4174
|
+
}));
|
|
4161
4175
|
const formattedMessages = [
|
|
4162
4176
|
systemMessage,
|
|
4163
|
-
...
|
|
4164
|
-
role: m.role,
|
|
4165
|
-
content: m.content
|
|
4166
|
-
}))
|
|
4177
|
+
...safeMessages
|
|
4167
4178
|
];
|
|
4168
4179
|
const completion = await this.client.chat.completions.create({
|
|
4169
4180
|
model: this.llmConfig.model,
|
|
@@ -4182,12 +4193,13 @@ var GroqProvider = class {
|
|
|
4182
4193
|
role: "system",
|
|
4183
4194
|
content: buildSystemContent(basePrompt, context)
|
|
4184
4195
|
};
|
|
4196
|
+
const safeStreamMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4197
|
+
role: m.role || "user",
|
|
4198
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4199
|
+
}));
|
|
4185
4200
|
const formattedMessages = [
|
|
4186
4201
|
systemMessage,
|
|
4187
|
-
...
|
|
4188
|
-
role: m.role,
|
|
4189
|
-
content: m.content
|
|
4190
|
-
}))
|
|
4202
|
+
...safeStreamMessages
|
|
4191
4203
|
];
|
|
4192
4204
|
const stream = yield new __await(this.client.chat.completions.create({
|
|
4193
4205
|
model: this.llmConfig.model,
|
|
@@ -4316,12 +4328,13 @@ var QwenProvider = class {
|
|
|
4316
4328
|
role: "system",
|
|
4317
4329
|
content: buildSystemContent(basePrompt, context)
|
|
4318
4330
|
};
|
|
4331
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4332
|
+
role: m.role || "user",
|
|
4333
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4334
|
+
}));
|
|
4319
4335
|
const formattedMessages = [
|
|
4320
4336
|
systemMessage,
|
|
4321
|
-
...
|
|
4322
|
-
role: m.role,
|
|
4323
|
-
content: m.content
|
|
4324
|
-
}))
|
|
4337
|
+
...safeMessages
|
|
4325
4338
|
];
|
|
4326
4339
|
const completion = await this.client.chat.completions.create({
|
|
4327
4340
|
model: this.llmConfig.model,
|
|
@@ -4340,12 +4353,13 @@ var QwenProvider = class {
|
|
|
4340
4353
|
role: "system",
|
|
4341
4354
|
content: buildSystemContent(basePrompt, context)
|
|
4342
4355
|
};
|
|
4356
|
+
const safeStreamMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4357
|
+
role: m.role || "user",
|
|
4358
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4359
|
+
}));
|
|
4343
4360
|
const formattedMessages = [
|
|
4344
4361
|
systemMessage,
|
|
4345
|
-
...
|
|
4346
|
-
role: m.role,
|
|
4347
|
-
content: m.content
|
|
4348
|
-
}))
|
|
4362
|
+
...safeStreamMessages
|
|
4349
4363
|
];
|
|
4350
4364
|
const stream = yield new __await(this.client.chat.completions.create({
|
|
4351
4365
|
model: this.llmConfig.model,
|
|
@@ -4525,14 +4539,18 @@ var UniversalLLMAdapter = class {
|
|
|
4525
4539
|
});
|
|
4526
4540
|
}
|
|
4527
4541
|
async chat(messages, context) {
|
|
4528
|
-
var _a2, _b, _c;
|
|
4542
|
+
var _a2, _b, _c, _d, _e;
|
|
4529
4543
|
const path2 = (_a2 = this.opts.chatPath) != null ? _a2 : "/chat/completions";
|
|
4544
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4545
|
+
role: m.role || "user",
|
|
4546
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4547
|
+
}));
|
|
4530
4548
|
const formattedMessages = [
|
|
4531
4549
|
{ role: "system", content: `${this.systemPrompt}
|
|
4532
4550
|
|
|
4533
4551
|
Context:
|
|
4534
4552
|
${context != null ? context : "None"}` },
|
|
4535
|
-
...
|
|
4553
|
+
...safeMessages
|
|
4536
4554
|
];
|
|
4537
4555
|
let payload;
|
|
4538
4556
|
if (this.opts.chatPayloadTemplate) {
|
|
@@ -4550,8 +4568,15 @@ ${context != null ? context : "None"}` },
|
|
|
4550
4568
|
temperature: this.temperature
|
|
4551
4569
|
};
|
|
4552
4570
|
}
|
|
4553
|
-
|
|
4554
|
-
|
|
4571
|
+
try {
|
|
4572
|
+
const { data: data2 } = await this.http.post(path2, payload);
|
|
4573
|
+
const extractPath2 = (_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content";
|
|
4574
|
+
const result2 = (_c = resolvePath(data2, extractPath2)) != null ? _c : extractContent(data2);
|
|
4575
|
+
if (result2 !== void 0) {
|
|
4576
|
+
return String(result2);
|
|
4577
|
+
}
|
|
4578
|
+
} catch (httpErr) {
|
|
4579
|
+
console.warn(`[UniversalLLMAdapter] Direct HTTP POST to ${this.baseUrl}${path2} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
|
|
4555
4580
|
const _g2 = globalThis;
|
|
4556
4581
|
let dispatch = _g2.__retrivoraDispatchChat;
|
|
4557
4582
|
if (typeof dispatch !== "function") {
|
|
@@ -4566,28 +4591,22 @@ ${context != null ? context : "None"}` },
|
|
|
4566
4591
|
}
|
|
4567
4592
|
}
|
|
4568
4593
|
if (typeof dispatch === "function") {
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
return content;
|
|
4579
|
-
}
|
|
4580
|
-
throw new Error(`[UniversalLLMAdapter] In-process dispatch returned empty content for model: ${this.model}`);
|
|
4581
|
-
} catch (dispatchErr) {
|
|
4582
|
-
throw dispatchErr;
|
|
4594
|
+
const res = await dispatch({
|
|
4595
|
+
model: this.model,
|
|
4596
|
+
messages: formattedMessages,
|
|
4597
|
+
max_tokens: this.maxTokens,
|
|
4598
|
+
temperature: this.temperature
|
|
4599
|
+
}, this.apiKey);
|
|
4600
|
+
const content = extractContent(res == null ? void 0 : res.response);
|
|
4601
|
+
if (content !== void 0) {
|
|
4602
|
+
return content;
|
|
4583
4603
|
}
|
|
4584
|
-
} else {
|
|
4585
|
-
throw new Error(`[UniversalLLMAdapter] In-process gateway dispatch not registered. Direct self-referential HTTP calls to ${this.baseUrl} are disabled on Vercel to prevent HTTP 508 Loop Detected.`);
|
|
4586
4604
|
}
|
|
4605
|
+
throw httpErr;
|
|
4587
4606
|
}
|
|
4588
4607
|
const { data } = await this.http.post(path2, payload);
|
|
4589
|
-
const extractPath = (
|
|
4590
|
-
const result = (
|
|
4608
|
+
const extractPath = (_d = this.opts.responseExtractPath) != null ? _d : "choices[0].message.content";
|
|
4609
|
+
const result = (_e = resolvePath(data, extractPath)) != null ? _e : extractContent(data);
|
|
4591
4610
|
if (result === void 0) {
|
|
4592
4611
|
throw new Error(`[UniversalLLMAdapter] Could not extract text from path '${extractPath}' in response.`);
|
|
4593
4612
|
}
|
|
@@ -4604,12 +4623,16 @@ ${context != null ? context : "None"}` },
|
|
|
4604
4623
|
const path2 = (_a2 = this.opts.chatPath) != null ? _a2 : "/chat/completions";
|
|
4605
4624
|
const url = `${this.baseUrl.replace(/\/$/, "")}${path2}`;
|
|
4606
4625
|
const extractPath = ((_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content").replace("message.content", "delta.content");
|
|
4626
|
+
const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
|
|
4627
|
+
role: m.role || "user",
|
|
4628
|
+
content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
|
|
4629
|
+
}));
|
|
4607
4630
|
const formattedMessages = [
|
|
4608
4631
|
{ role: "system", content: `${this.systemPrompt}
|
|
4609
4632
|
|
|
4610
4633
|
Context:
|
|
4611
4634
|
${context != null ? context : "None"}` },
|
|
4612
|
-
...
|
|
4635
|
+
...safeMessages
|
|
4613
4636
|
];
|
|
4614
4637
|
let payload;
|
|
4615
4638
|
if (this.opts.chatPayloadTemplate) {
|
|
@@ -4725,7 +4748,7 @@ ${context != null ? context : "None"}` },
|
|
|
4725
4748
|
});
|
|
4726
4749
|
}
|
|
4727
4750
|
async embed(text) {
|
|
4728
|
-
var _a2, _b, _c, _d;
|
|
4751
|
+
var _a2, _b, _c, _d, _e;
|
|
4729
4752
|
const path2 = (_a2 = this.opts.embedPath) != null ? _a2 : "/embeddings";
|
|
4730
4753
|
let payload;
|
|
4731
4754
|
if (this.opts.embedPayloadTemplate) {
|
|
@@ -4739,8 +4762,15 @@ ${context != null ? context : "None"}` },
|
|
|
4739
4762
|
input: text
|
|
4740
4763
|
};
|
|
4741
4764
|
}
|
|
4742
|
-
|
|
4743
|
-
|
|
4765
|
+
try {
|
|
4766
|
+
const { data: data2 } = await this.http.post(path2, payload);
|
|
4767
|
+
const extractPath2 = (_b = this.opts.embedExtractPath) != null ? _b : "data[0].embedding";
|
|
4768
|
+
const vector2 = resolvePath(data2, extractPath2);
|
|
4769
|
+
if (Array.isArray(vector2)) {
|
|
4770
|
+
return vector2;
|
|
4771
|
+
}
|
|
4772
|
+
} catch (httpErr) {
|
|
4773
|
+
console.warn(`[UniversalLLMAdapter] Direct HTTP embedding POST to ${this.baseUrl}${path2} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
|
|
4744
4774
|
const _g2 = globalThis;
|
|
4745
4775
|
let dispatch = _g2.__retrivoraDispatchEmbedding;
|
|
4746
4776
|
if (typeof dispatch !== "function") {
|
|
@@ -4755,24 +4785,18 @@ ${context != null ? context : "None"}` },
|
|
|
4755
4785
|
}
|
|
4756
4786
|
}
|
|
4757
4787
|
if (typeof dispatch === "function") {
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
return res.data[0].embedding;
|
|
4765
|
-
}
|
|
4766
|
-
throw new Error(`[UniversalLLMAdapter] In-process embedding dispatch returned invalid vector for model: ${this.model}`);
|
|
4767
|
-
} catch (dispatchErr) {
|
|
4768
|
-
throw dispatchErr;
|
|
4788
|
+
const res = await dispatch({
|
|
4789
|
+
model: this.model,
|
|
4790
|
+
input: text
|
|
4791
|
+
}, this.apiKey);
|
|
4792
|
+
if ((_d = (_c = res == null ? void 0 : res.data) == null ? void 0 : _c[0]) == null ? void 0 : _d.embedding) {
|
|
4793
|
+
return res.data[0].embedding;
|
|
4769
4794
|
}
|
|
4770
|
-
} else {
|
|
4771
|
-
throw new Error(`[UniversalLLMAdapter] In-process gateway dispatch not registered. Direct self-referential HTTP calls to ${this.baseUrl} are disabled on Vercel to prevent HTTP 508 Loop Detected.`);
|
|
4772
4795
|
}
|
|
4796
|
+
throw httpErr;
|
|
4773
4797
|
}
|
|
4774
4798
|
const { data } = await this.http.post(path2, payload);
|
|
4775
|
-
const extractPath = (
|
|
4799
|
+
const extractPath = (_e = this.opts.embedExtractPath) != null ? _e : "data[0].embedding";
|
|
4776
4800
|
const vector = resolvePath(data, extractPath);
|
|
4777
4801
|
if (!Array.isArray(vector)) {
|
|
4778
4802
|
throw new Error(`[UniversalLLMAdapter] Expected a number array at '${extractPath}' for embeddings.`);
|
|
@@ -6753,8 +6777,8 @@ var IntentClassifier = class {
|
|
|
6753
6777
|
}
|
|
6754
6778
|
// ─── Intent Heuristic Checkers ─────────────────────────────────────────────
|
|
6755
6779
|
static isInformationLookup(query) {
|
|
6756
|
-
if (/^(what|who|explain|define|where|when|why|how|how does|can|could|does|do|is|are|has|have|will|should|would|meaning of)\s+/i.test(query)) {
|
|
6757
|
-
if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top selling|best)\b/i.test(query)) {
|
|
6780
|
+
if (/^(what|who|explain|explore|define|where|when|why|how|how does|can|could|does|do|is|are|has|have|will|should|would|meaning of|tell me about|show details of|overview of|summarize)\s+/i.test(query)) {
|
|
6781
|
+
if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top 10|top selling|best|chart|graph|plot|breakdown|analytics)\b/i.test(query)) {
|
|
6758
6782
|
return true;
|
|
6759
6783
|
}
|
|
6760
6784
|
}
|
|
@@ -6791,7 +6815,7 @@ var Rule1SpecificInfoRule = class {
|
|
|
6791
6815
|
}
|
|
6792
6816
|
evaluate(context, intent) {
|
|
6793
6817
|
const q = (context.userQuery || "").toLowerCase().trim();
|
|
6794
|
-
const isFactQuery = intent === "information_lookup" || /^(what|who|explain|define|where|when|why|meaning of)\s+/i.test(q) || /^(what is the price of|price of|cost of)\b/i.test(q);
|
|
6818
|
+
const isFactQuery = intent === "information_lookup" || /^(what|who|explain|explore|define|where|when|why|meaning of|tell me about|show details of|overview of|summarize)\s+/i.test(q) || /^(what is the price of|price of|cost of)\b/i.test(q);
|
|
6795
6819
|
const isNonVisual = !/\b(compare|versus|vs\.?|trend|monthly|revenue|best|top 10|chart|table|list|grid)\b/i.test(q);
|
|
6796
6820
|
if (isFactQuery && isNonVisual) {
|
|
6797
6821
|
return {
|
|
@@ -8078,6 +8102,11 @@ ${schemaProfileText}` : ""}`;
|
|
|
8078
8102
|
}
|
|
8079
8103
|
static chooseAutomaticVisualization(data, profile, query) {
|
|
8080
8104
|
if (profile.records.length === 0) return null;
|
|
8105
|
+
const q = query.toLowerCase().trim();
|
|
8106
|
+
const isExplicitVisualOrAnalytic = /\b(chart|graph|plot|visualize|trend|monthly|revenue|top\s*\d+|breakdown|analytics|compare|versus|vs\.?|histogram|pie|bar|line|distribution|percentage|share|summary of metrics)\b/i.test(q);
|
|
8107
|
+
if (!isExplicitVisualOrAnalytic) {
|
|
8108
|
+
return null;
|
|
8109
|
+
}
|
|
8081
8110
|
if (profile.dateFields.length > 0 && profile.numericFields.length > 0) {
|
|
8082
8111
|
return this.transformToLineChart(profile);
|
|
8083
8112
|
}
|
|
@@ -8193,6 +8222,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
8193
8222
|
return Array.from(categories);
|
|
8194
8223
|
}
|
|
8195
8224
|
static getProductCategory(item) {
|
|
8225
|
+
if (!item) return null;
|
|
8196
8226
|
const meta = item.metadata || {};
|
|
8197
8227
|
const metadataCategory = resolveMetadataValue(meta, "category");
|
|
8198
8228
|
if (this.isUsableCategory(metadataCategory)) return String(metadataCategory).trim();
|
|
@@ -8309,6 +8339,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
8309
8339
|
if (!fields.has(normalized)) fields.set(normalized, clean);
|
|
8310
8340
|
};
|
|
8311
8341
|
data.forEach((item) => {
|
|
8342
|
+
if (!item) return;
|
|
8312
8343
|
Object.keys(item.metadata || {}).forEach(addField);
|
|
8313
8344
|
for (const match of (item.content || "").matchAll(/(?:^|\n)\s*([A-Za-z][A-Za-z0-9_ /-]{1,50})\s*[:\-–]\s*([^\n]+)/g)) {
|
|
8314
8345
|
addField(match[1]);
|
|
@@ -8425,6 +8456,7 @@ ${schemaProfileText}` : ""}`;
|
|
|
8425
8456
|
return true;
|
|
8426
8457
|
}
|
|
8427
8458
|
static extractStockQuantity(item) {
|
|
8459
|
+
if (!item) return null;
|
|
8428
8460
|
const meta = item.metadata || {};
|
|
8429
8461
|
const stockValue = resolveMetadataValue(meta, "stock");
|
|
8430
8462
|
const numericStock = this.toFiniteNumber(stockValue);
|
|
@@ -9685,7 +9717,10 @@ ${context}`;
|
|
|
9685
9717
|
Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
|
|
9686
9718
|
|
|
9687
9719
|
History:
|
|
9688
|
-
${history.map((m) =>
|
|
9720
|
+
${(history || []).map((m) => {
|
|
9721
|
+
var _a2;
|
|
9722
|
+
return `${(m == null ? void 0 : m.role) || "user"}: ${(_a2 = m == null ? void 0 : m.content) != null ? _a2 : ""}`;
|
|
9723
|
+
}).join("\n")}
|
|
9689
9724
|
|
|
9690
9725
|
New Question: ${question}
|
|
9691
9726
|
|
|
@@ -9709,8 +9744,11 @@ Optimized Search Query:`;
|
|
|
9709
9744
|
const ns = formatNamespace(namespace != null ? namespace : this.config.projectId);
|
|
9710
9745
|
try {
|
|
9711
9746
|
const { sources } = await this.retrieve(query, { namespace: ns, topK: 5 });
|
|
9712
|
-
if (sources.length === 0) return [];
|
|
9713
|
-
const context = sources.map((s) =>
|
|
9747
|
+
if (!sources || sources.length === 0) return [];
|
|
9748
|
+
const context = sources.map((s) => {
|
|
9749
|
+
var _a2;
|
|
9750
|
+
return (_a2 = s == null ? void 0 : s.content) != null ? _a2 : "";
|
|
9751
|
+
}).join("\n\n---\n\n");
|
|
9714
9752
|
const prompt = `Based on the following snippets from a document, what are at least 5 short, relevant questions a user might ask?
|
|
9715
9753
|
Focus on questions that can be answered by the context.
|
|
9716
9754
|
Keep each question under 10 words and make them very specific to the content.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.2",
|
|
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
6
|
"license": "UNLICENSED",
|
|
@@ -52,10 +52,10 @@ interface WindowWithSpeech extends Window {
|
|
|
52
52
|
webkitSpeechRecognition?: new () => ISpeechRecognition;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export function ChatWindow({
|
|
56
|
-
className = '',
|
|
57
|
-
style,
|
|
58
|
-
onClose,
|
|
55
|
+
export function ChatWindow({
|
|
56
|
+
className = '',
|
|
57
|
+
style,
|
|
58
|
+
onClose,
|
|
59
59
|
showClose = false,
|
|
60
60
|
onResizeStart,
|
|
61
61
|
onResetResize,
|
|
@@ -129,7 +129,7 @@ export function ChatWindow({
|
|
|
129
129
|
alert('Speech recognition is not supported in your browser.');
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
if (isListening) {
|
|
134
134
|
recognitionRef.current.stop();
|
|
135
135
|
} else {
|
|
@@ -178,7 +178,7 @@ export function ChatWindow({
|
|
|
178
178
|
const sendMessage = useCallback(async () => {
|
|
179
179
|
const text = input.trim();
|
|
180
180
|
if (!text || isLoading) return;
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
if (isListening) {
|
|
183
183
|
recognitionRef.current?.stop();
|
|
184
184
|
}
|
|
@@ -250,11 +250,10 @@ export function ChatWindow({
|
|
|
250
250
|
return (
|
|
251
251
|
<div
|
|
252
252
|
ref={windowRef}
|
|
253
|
-
className={`relative flex flex-col h-full flex-1 border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${
|
|
254
|
-
|
|
255
|
-
? 'bg-white/90 dark:bg-[#0f0f1a]/90 backdrop-blur-xl'
|
|
253
|
+
className={`relative flex flex-col h-full flex-1 border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${isGlass
|
|
254
|
+
? 'bg-white/90 dark:bg-[#0f0f1a]/90 backdrop-blur-xl'
|
|
256
255
|
: 'bg-white dark:bg-[#0f0f1a]'
|
|
257
|
-
|
|
256
|
+
} ${className}`}
|
|
258
257
|
style={{ '--primary': ui.primaryColor, '--accent': ui.accentColor, ...style } as React.CSSProperties}
|
|
259
258
|
>
|
|
260
259
|
{/* Resize Handle (Top-Left) */}
|
|
@@ -293,11 +292,13 @@ export function ChatWindow({
|
|
|
293
292
|
</div>
|
|
294
293
|
|
|
295
294
|
<div className="flex items-center gap-1.5">
|
|
296
|
-
{/* Online indicator */}
|
|
297
|
-
<
|
|
298
|
-
<span className="
|
|
299
|
-
|
|
300
|
-
|
|
295
|
+
{/* Online indicator & Deployment ID badge */}
|
|
296
|
+
<div className="flex items-center gap-1.5 mr-2">
|
|
297
|
+
<span className="flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400">
|
|
298
|
+
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400 animate-pulse" />
|
|
299
|
+
Online
|
|
300
|
+
</span>
|
|
301
|
+
</div>
|
|
301
302
|
|
|
302
303
|
<div className="flex items-center bg-slate-100/50 dark:bg-white/5 rounded-lg p-0.5 border border-slate-200/50 dark:border-white/5">
|
|
303
304
|
{mounted && messages.length > 0 && (
|
|
@@ -427,9 +428,9 @@ export function ChatWindow({
|
|
|
427
428
|
>
|
|
428
429
|
<X className="w-5 h-5" />
|
|
429
430
|
</button>
|
|
430
|
-
<DocumentUpload
|
|
431
|
-
namespace={projectId}
|
|
432
|
-
onUploadComplete={() => setIsUploadModalOpen(false)}
|
|
431
|
+
<DocumentUpload
|
|
432
|
+
namespace={projectId}
|
|
433
|
+
onUploadComplete={() => setIsUploadModalOpen(false)}
|
|
433
434
|
/>
|
|
434
435
|
</div>
|
|
435
436
|
</div>
|
|
@@ -437,7 +438,7 @@ export function ChatWindow({
|
|
|
437
438
|
|
|
438
439
|
{/* ── Input ── */}
|
|
439
440
|
<div className={`px-4 pb-4 pt-2 border-t border-slate-200 dark:border-white/10 ${isGlass ? 'bg-transparent' : 'bg-white dark:bg-[#0f0f1a]'}`}>
|
|
440
|
-
|
|
441
|
+
|
|
441
442
|
{/* Dynamic Suggestions */}
|
|
442
443
|
{(suggestions.length > 0 || isSuggesting) && !isLoading && (
|
|
443
444
|
<div className="mb-3 flex flex-wrap gap-2 animate-in fade-in slide-in-from-bottom-2 duration-300">
|
|
@@ -488,11 +489,10 @@ export function ChatWindow({
|
|
|
488
489
|
type="button"
|
|
489
490
|
onClick={toggleListening}
|
|
490
491
|
disabled={isLoading}
|
|
491
|
-
className={`flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:active:scale-100 ${
|
|
492
|
-
isListening
|
|
492
|
+
className={`flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:active:scale-100 ${isListening
|
|
493
493
|
? 'bg-rose-500 text-white animate-pulse shadow-md shadow-rose-500/20'
|
|
494
494
|
: 'text-slate-400 dark:text-white/40 hover:bg-slate-200 dark:hover:bg-white/10'
|
|
495
|
-
|
|
495
|
+
}`}
|
|
496
496
|
title={isListening ? 'Stop listening' : 'Start voice input'}
|
|
497
497
|
>
|
|
498
498
|
{isListening ? <MicOff className="w-4 h-4" /> : <Mic className="w-4 h-4" />}
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -1193,7 +1193,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
|
|
|
1193
1193
|
Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
|
|
1194
1194
|
|
|
1195
1195
|
History:
|
|
1196
|
-
${history.map(m => `${m
|
|
1196
|
+
${(history || []).map(m => `${m?.role || 'user'}: ${m?.content ?? ''}`).join('\n')}
|
|
1197
1197
|
|
|
1198
1198
|
New Question: ${question}
|
|
1199
1199
|
|
|
@@ -1222,9 +1222,9 @@ Optimized Search Query:`;
|
|
|
1222
1222
|
|
|
1223
1223
|
try {
|
|
1224
1224
|
const { sources } = await this.retrieve(query, { namespace: ns, topK: 5 });
|
|
1225
|
-
if (sources.length === 0) return [];
|
|
1225
|
+
if (!sources || sources.length === 0) return [];
|
|
1226
1226
|
|
|
1227
|
-
const context = sources.map((s) => s
|
|
1227
|
+
const context = sources.map((s) => s?.content ?? '').join('\n\n---\n\n');
|
|
1228
1228
|
const prompt = `Based on the following snippets from a document, what are at least 5 short, relevant questions a user might ask?
|
|
1229
1229
|
Focus on questions that can be answered by the context.
|
|
1230
1230
|
Keep each question under 10 words and make them very specific to the content.
|
|
@@ -175,11 +175,11 @@ export class GeminiProvider implements ILLMProvider {
|
|
|
175
175
|
* - Messages must strictly alternate between `user` and `model`.
|
|
176
176
|
*/
|
|
177
177
|
private buildGeminiContents(messages: ChatMessage[]): Array<{ role: 'user' | 'model'; parts: [{ text: string }] }> {
|
|
178
|
-
return messages
|
|
179
|
-
.filter((m) => m.role !== 'system')
|
|
178
|
+
return (messages || [])
|
|
179
|
+
.filter((m): m is ChatMessage => Boolean(m && typeof m === 'object' && m.role !== 'system'))
|
|
180
180
|
.map((m) => ({
|
|
181
|
-
role: m.role === 'assistant' ?
|
|
182
|
-
parts: [{ text: m.content
|
|
181
|
+
role: m.role === 'assistant' ? 'model' : 'user',
|
|
182
|
+
parts: [{ text: typeof m.content === 'string' ? m.content : (m.content != null ? String(m.content) : '') }],
|
|
183
183
|
}));
|
|
184
184
|
}
|
|
185
185
|
|
|
@@ -95,12 +95,16 @@ export class GroqProvider implements ILLMProvider {
|
|
|
95
95
|
content: buildSystemContent(basePrompt, context),
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
+
const safeMessages = (Array.isArray(messages) ? messages : [])
|
|
99
|
+
.filter((m): m is ChatMessage => Boolean(m && typeof m === 'object'))
|
|
100
|
+
.map((m) => ({
|
|
101
|
+
role: (m.role || 'user') as 'user' | 'assistant',
|
|
102
|
+
content: typeof m.content === 'string' ? m.content : (m.content != null ? String(m.content) : ''),
|
|
103
|
+
}));
|
|
104
|
+
|
|
98
105
|
const formattedMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
|
99
106
|
systemMessage,
|
|
100
|
-
...
|
|
101
|
-
role: m.role as 'user' | 'assistant',
|
|
102
|
-
content: m.content,
|
|
103
|
-
})),
|
|
107
|
+
...safeMessages,
|
|
104
108
|
];
|
|
105
109
|
|
|
106
110
|
const completion = await this.client.chat.completions.create({
|
|
@@ -125,12 +129,16 @@ export class GroqProvider implements ILLMProvider {
|
|
|
125
129
|
content: buildSystemContent(basePrompt, context),
|
|
126
130
|
};
|
|
127
131
|
|
|
132
|
+
const safeStreamMessages = (Array.isArray(messages) ? messages : [])
|
|
133
|
+
.filter((m): m is ChatMessage => Boolean(m && typeof m === 'object'))
|
|
134
|
+
.map((m) => ({
|
|
135
|
+
role: (m.role || 'user') as 'user' | 'assistant',
|
|
136
|
+
content: typeof m.content === 'string' ? m.content : (m.content != null ? String(m.content) : ''),
|
|
137
|
+
}));
|
|
138
|
+
|
|
128
139
|
const formattedMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
|
129
140
|
systemMessage,
|
|
130
|
-
...
|
|
131
|
-
role: m.role as 'user' | 'assistant',
|
|
132
|
-
content: m.content,
|
|
133
|
-
})),
|
|
141
|
+
...safeStreamMessages,
|
|
134
142
|
];
|
|
135
143
|
|
|
136
144
|
const stream = await this.client.chat.completions.create({
|
|
@@ -91,12 +91,16 @@ export class OpenAIProvider implements ILLMProvider {
|
|
|
91
91
|
content: buildSystemContent(basePrompt, context),
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
+
const safeMessages = (Array.isArray(messages) ? messages : [])
|
|
95
|
+
.filter((m): m is ChatMessage => Boolean(m && typeof m === 'object'))
|
|
96
|
+
.map((m) => ({
|
|
97
|
+
role: (m.role || 'user') as 'user' | 'assistant',
|
|
98
|
+
content: typeof m.content === 'string' ? m.content : (m.content != null ? String(m.content) : ''),
|
|
99
|
+
}));
|
|
100
|
+
|
|
94
101
|
const formattedMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
|
95
102
|
systemMessage,
|
|
96
|
-
...
|
|
97
|
-
role: m.role as 'user' | 'assistant',
|
|
98
|
-
content: m.content,
|
|
99
|
-
})),
|
|
103
|
+
...safeMessages,
|
|
100
104
|
];
|
|
101
105
|
|
|
102
106
|
const completion = await this.client.chat.completions.create({
|
|
@@ -121,12 +125,16 @@ export class OpenAIProvider implements ILLMProvider {
|
|
|
121
125
|
content: buildSystemContent(basePrompt, context),
|
|
122
126
|
};
|
|
123
127
|
|
|
128
|
+
const safeStreamMessages = (Array.isArray(messages) ? messages : [])
|
|
129
|
+
.filter((m): m is ChatMessage => Boolean(m && typeof m === 'object'))
|
|
130
|
+
.map((m) => ({
|
|
131
|
+
role: (m.role || 'user') as 'user' | 'assistant',
|
|
132
|
+
content: typeof m.content === 'string' ? m.content : (m.content != null ? String(m.content) : ''),
|
|
133
|
+
}));
|
|
134
|
+
|
|
124
135
|
const formattedMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
|
125
136
|
systemMessage,
|
|
126
|
-
...
|
|
127
|
-
role: m.role as 'user' | 'assistant',
|
|
128
|
-
content: m.content,
|
|
129
|
-
})),
|
|
137
|
+
...safeStreamMessages,
|
|
130
138
|
];
|
|
131
139
|
|
|
132
140
|
const stream = await this.client.chat.completions.create({
|