@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.
@@ -594,6 +594,7 @@ function cleanApiKeyOverride(apiKeyOverride) {
594
594
  async function dispatchChatCompletion(req, apiKeyOverride) {
595
595
  const effectiveKey = cleanApiKeyOverride(apiKeyOverride);
596
596
  const provider = resolveProvider(req.model);
597
+ 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)}`);
597
598
  try {
598
599
  switch (provider) {
599
600
  case "groq":
@@ -612,6 +613,10 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
612
613
  throw new Error(`Unsupported LLM provider for model: ${req.model}`);
613
614
  }
614
615
  } catch (error) {
616
+ console.error(`[LLM Gateway Router] Provider '${provider}' failed for model '${req.model}':`, {
617
+ message: error.message,
618
+ stack: error.stack
619
+ });
615
620
  if (/rate[- ]?limit|429|exhausted/i.test(error.message)) {
616
621
  const isGroqKey = process.env.GROQ_API_KEY || effectiveKey && effectiveKey.startsWith("gsk_");
617
622
  const reqModelLower = req.model.toLowerCase();
@@ -619,7 +624,8 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
619
624
  console.warn(`[LLM Gateway] Provider ${provider} rate limited (${error.message}). Falling back to secondary Groq model (llama-3.3-70b-versatile)...`);
620
625
  try {
621
626
  return await handleGroqRequest(__spreadProps(__spreadValues({}, req), { model: "llama-3.3-70b-versatile" }), effectiveKey);
622
- } catch (e) {
627
+ } catch (fErr) {
628
+ console.error("[LLM Gateway] Fallback groq/llama-3.3-70b-versatile failed:", fErr.message);
623
629
  }
624
630
  }
625
631
  const geminiKey = process.env.GEMINI_API_KEY || effectiveKey;
@@ -627,14 +633,16 @@ async function dispatchChatCompletion(req, apiKeyOverride) {
627
633
  console.warn(`[LLM Gateway] Provider ${provider} rate limited (${error.message}). Falling back to Google Gemini (gemini-2.5-flash)...`);
628
634
  try {
629
635
  return await handleGeminiRequest(__spreadProps(__spreadValues({}, req), { model: "gemini-2.5-flash" }), effectiveKey);
630
- } catch (e) {
636
+ } catch (fErr) {
637
+ console.error("[LLM Gateway] Fallback gemini-2.5-flash failed:", fErr.message);
631
638
  }
632
639
  }
633
640
  if (provider !== "huggingface" && (process.env.HF_TOKEN || process.env.HUGGINGFACE_API_KEY)) {
634
641
  console.warn(`[LLM Gateway] Provider ${provider} rate limited (${error.message}). Falling back to Hugging Face (Qwen/Qwen2.5-Coder-32B-Instruct)...`);
635
642
  try {
636
643
  return await handleHuggingFaceChatRequest(__spreadProps(__spreadValues({}, req), { model: "Qwen/Qwen2.5-Coder-32B-Instruct" }), effectiveKey);
637
- } catch (e) {
644
+ } catch (fErr) {
645
+ console.error("[LLM Gateway] Fallback HuggingFace Qwen failed:", fErr.message);
638
646
  }
639
647
  }
640
648
  if (provider !== "openai" && process.env.OPENAI_API_KEY) {
@@ -3428,12 +3436,13 @@ var OpenAIProvider = class {
3428
3436
  role: "system",
3429
3437
  content: buildSystemContent(basePrompt, context)
3430
3438
  };
3439
+ const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
3440
+ role: m.role || "user",
3441
+ content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
3442
+ }));
3431
3443
  const formattedMessages = [
3432
3444
  systemMessage,
3433
- ...messages.map((m) => ({
3434
- role: m.role,
3435
- content: m.content
3436
- }))
3445
+ ...safeMessages
3437
3446
  ];
3438
3447
  const completion = await this.client.chat.completions.create({
3439
3448
  model: this.llmConfig.model,
@@ -3452,12 +3461,13 @@ var OpenAIProvider = class {
3452
3461
  role: "system",
3453
3462
  content: buildSystemContent(basePrompt, context)
3454
3463
  };
3464
+ const safeStreamMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
3465
+ role: m.role || "user",
3466
+ content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
3467
+ }));
3455
3468
  const formattedMessages = [
3456
3469
  systemMessage,
3457
- ...messages.map((m) => ({
3458
- role: m.role,
3459
- content: m.content
3460
- }))
3470
+ ...safeStreamMessages
3461
3471
  ];
3462
3472
  const stream = yield new __await(this.client.chat.completions.create({
3463
3473
  model: this.llmConfig.model,
@@ -3988,9 +3998,9 @@ var GeminiProvider = class {
3988
3998
  * - Messages must strictly alternate between `user` and `model`.
3989
3999
  */
3990
4000
  buildGeminiContents(messages) {
3991
- return messages.filter((m) => m.role !== "system").map((m) => ({
4001
+ return (messages || []).filter((m) => Boolean(m && typeof m === "object" && m.role !== "system")).map((m) => ({
3992
4002
  role: m.role === "assistant" ? "model" : "user",
3993
- parts: [{ text: m.content }]
4003
+ parts: [{ text: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : "" }]
3994
4004
  }));
3995
4005
  }
3996
4006
  // -------------------------------------------------------------------------
@@ -4193,12 +4203,13 @@ var GroqProvider = class {
4193
4203
  role: "system",
4194
4204
  content: buildSystemContent(basePrompt, context)
4195
4205
  };
4206
+ const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
4207
+ role: m.role || "user",
4208
+ content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
4209
+ }));
4196
4210
  const formattedMessages = [
4197
4211
  systemMessage,
4198
- ...messages.map((m) => ({
4199
- role: m.role,
4200
- content: m.content
4201
- }))
4212
+ ...safeMessages
4202
4213
  ];
4203
4214
  const completion = await this.client.chat.completions.create({
4204
4215
  model: this.llmConfig.model,
@@ -4217,12 +4228,13 @@ var GroqProvider = class {
4217
4228
  role: "system",
4218
4229
  content: buildSystemContent(basePrompt, context)
4219
4230
  };
4231
+ const safeStreamMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
4232
+ role: m.role || "user",
4233
+ content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
4234
+ }));
4220
4235
  const formattedMessages = [
4221
4236
  systemMessage,
4222
- ...messages.map((m) => ({
4223
- role: m.role,
4224
- content: m.content
4225
- }))
4237
+ ...safeStreamMessages
4226
4238
  ];
4227
4239
  const stream = yield new __await(this.client.chat.completions.create({
4228
4240
  model: this.llmConfig.model,
@@ -4351,12 +4363,13 @@ var QwenProvider = class {
4351
4363
  role: "system",
4352
4364
  content: buildSystemContent(basePrompt, context)
4353
4365
  };
4366
+ const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
4367
+ role: m.role || "user",
4368
+ content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
4369
+ }));
4354
4370
  const formattedMessages = [
4355
4371
  systemMessage,
4356
- ...messages.map((m) => ({
4357
- role: m.role,
4358
- content: m.content
4359
- }))
4372
+ ...safeMessages
4360
4373
  ];
4361
4374
  const completion = await this.client.chat.completions.create({
4362
4375
  model: this.llmConfig.model,
@@ -4375,12 +4388,13 @@ var QwenProvider = class {
4375
4388
  role: "system",
4376
4389
  content: buildSystemContent(basePrompt, context)
4377
4390
  };
4391
+ const safeStreamMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
4392
+ role: m.role || "user",
4393
+ content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
4394
+ }));
4378
4395
  const formattedMessages = [
4379
4396
  systemMessage,
4380
- ...messages.map((m) => ({
4381
- role: m.role,
4382
- content: m.content
4383
- }))
4397
+ ...safeStreamMessages
4384
4398
  ];
4385
4399
  const stream = yield new __await(this.client.chat.completions.create({
4386
4400
  model: this.llmConfig.model,
@@ -4521,14 +4535,18 @@ var UniversalLLMAdapter = class {
4521
4535
  });
4522
4536
  }
4523
4537
  async chat(messages, context) {
4524
- var _a2, _b, _c;
4538
+ var _a2, _b, _c, _d, _e;
4525
4539
  const path2 = (_a2 = this.opts.chatPath) != null ? _a2 : "/chat/completions";
4540
+ const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
4541
+ role: m.role || "user",
4542
+ content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
4543
+ }));
4526
4544
  const formattedMessages = [
4527
4545
  { role: "system", content: `${this.systemPrompt}
4528
4546
 
4529
4547
  Context:
4530
4548
  ${context != null ? context : "None"}` },
4531
- ...messages
4549
+ ...safeMessages
4532
4550
  ];
4533
4551
  let payload;
4534
4552
  if (this.opts.chatPayloadTemplate) {
@@ -4546,8 +4564,15 @@ ${context != null ? context : "None"}` },
4546
4564
  temperature: this.temperature
4547
4565
  };
4548
4566
  }
4549
- const isSelfHost = this.baseUrl.includes("retrivora.com") || this.baseUrl.includes("localhost");
4550
- if (isSelfHost || Boolean(process.env.VERCEL)) {
4567
+ try {
4568
+ const { data: data2 } = await this.http.post(path2, payload);
4569
+ const extractPath2 = (_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content";
4570
+ const result2 = (_c = resolvePath(data2, extractPath2)) != null ? _c : extractContent(data2);
4571
+ if (result2 !== void 0) {
4572
+ return String(result2);
4573
+ }
4574
+ } catch (httpErr) {
4575
+ console.warn(`[UniversalLLMAdapter] Direct HTTP POST to ${this.baseUrl}${path2} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
4551
4576
  const _g2 = globalThis;
4552
4577
  let dispatch = _g2.__retrivoraDispatchChat;
4553
4578
  if (typeof dispatch !== "function") {
@@ -4562,28 +4587,22 @@ ${context != null ? context : "None"}` },
4562
4587
  }
4563
4588
  }
4564
4589
  if (typeof dispatch === "function") {
4565
- try {
4566
- const res = await dispatch({
4567
- model: this.model,
4568
- messages: formattedMessages,
4569
- max_tokens: this.maxTokens,
4570
- temperature: this.temperature
4571
- }, this.apiKey);
4572
- const content = extractContent(res == null ? void 0 : res.response);
4573
- if (content !== void 0) {
4574
- return content;
4575
- }
4576
- throw new Error(`[UniversalLLMAdapter] In-process dispatch returned empty content for model: ${this.model}`);
4577
- } catch (dispatchErr) {
4578
- throw dispatchErr;
4590
+ const res = await dispatch({
4591
+ model: this.model,
4592
+ messages: formattedMessages,
4593
+ max_tokens: this.maxTokens,
4594
+ temperature: this.temperature
4595
+ }, this.apiKey);
4596
+ const content = extractContent(res == null ? void 0 : res.response);
4597
+ if (content !== void 0) {
4598
+ return content;
4579
4599
  }
4580
- } else {
4581
- 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.`);
4582
4600
  }
4601
+ throw httpErr;
4583
4602
  }
4584
4603
  const { data } = await this.http.post(path2, payload);
4585
- const extractPath = (_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content";
4586
- const result = (_c = resolvePath(data, extractPath)) != null ? _c : extractContent(data);
4604
+ const extractPath = (_d = this.opts.responseExtractPath) != null ? _d : "choices[0].message.content";
4605
+ const result = (_e = resolvePath(data, extractPath)) != null ? _e : extractContent(data);
4587
4606
  if (result === void 0) {
4588
4607
  throw new Error(`[UniversalLLMAdapter] Could not extract text from path '${extractPath}' in response.`);
4589
4608
  }
@@ -4600,12 +4619,16 @@ ${context != null ? context : "None"}` },
4600
4619
  const path2 = (_a2 = this.opts.chatPath) != null ? _a2 : "/chat/completions";
4601
4620
  const url = `${this.baseUrl.replace(/\/$/, "")}${path2}`;
4602
4621
  const extractPath = ((_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content").replace("message.content", "delta.content");
4622
+ const safeMessages = (Array.isArray(messages) ? messages : []).filter((m) => Boolean(m && typeof m === "object")).map((m) => ({
4623
+ role: m.role || "user",
4624
+ content: typeof m.content === "string" ? m.content : m.content != null ? String(m.content) : ""
4625
+ }));
4603
4626
  const formattedMessages = [
4604
4627
  { role: "system", content: `${this.systemPrompt}
4605
4628
 
4606
4629
  Context:
4607
4630
  ${context != null ? context : "None"}` },
4608
- ...messages
4631
+ ...safeMessages
4609
4632
  ];
4610
4633
  let payload;
4611
4634
  if (this.opts.chatPayloadTemplate) {
@@ -4721,7 +4744,7 @@ ${context != null ? context : "None"}` },
4721
4744
  });
4722
4745
  }
4723
4746
  async embed(text) {
4724
- var _a2, _b, _c, _d;
4747
+ var _a2, _b, _c, _d, _e;
4725
4748
  const path2 = (_a2 = this.opts.embedPath) != null ? _a2 : "/embeddings";
4726
4749
  let payload;
4727
4750
  if (this.opts.embedPayloadTemplate) {
@@ -4735,8 +4758,15 @@ ${context != null ? context : "None"}` },
4735
4758
  input: text
4736
4759
  };
4737
4760
  }
4738
- const isSelfHost = this.baseUrl.includes("retrivora.com") || this.baseUrl.includes("localhost");
4739
- if (isSelfHost || Boolean(process.env.VERCEL)) {
4761
+ try {
4762
+ const { data: data2 } = await this.http.post(path2, payload);
4763
+ const extractPath2 = (_b = this.opts.embedExtractPath) != null ? _b : "data[0].embedding";
4764
+ const vector2 = resolvePath(data2, extractPath2);
4765
+ if (Array.isArray(vector2)) {
4766
+ return vector2;
4767
+ }
4768
+ } catch (httpErr) {
4769
+ console.warn(`[UniversalLLMAdapter] Direct HTTP embedding POST to ${this.baseUrl}${path2} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
4740
4770
  const _g2 = globalThis;
4741
4771
  let dispatch = _g2.__retrivoraDispatchEmbedding;
4742
4772
  if (typeof dispatch !== "function") {
@@ -4751,24 +4781,18 @@ ${context != null ? context : "None"}` },
4751
4781
  }
4752
4782
  }
4753
4783
  if (typeof dispatch === "function") {
4754
- try {
4755
- const res = await dispatch({
4756
- model: this.model,
4757
- input: text
4758
- }, this.apiKey);
4759
- if ((_c = (_b = res == null ? void 0 : res.data) == null ? void 0 : _b[0]) == null ? void 0 : _c.embedding) {
4760
- return res.data[0].embedding;
4761
- }
4762
- throw new Error(`[UniversalLLMAdapter] In-process embedding dispatch returned invalid vector for model: ${this.model}`);
4763
- } catch (dispatchErr) {
4764
- throw dispatchErr;
4784
+ const res = await dispatch({
4785
+ model: this.model,
4786
+ input: text
4787
+ }, this.apiKey);
4788
+ if ((_d = (_c = res == null ? void 0 : res.data) == null ? void 0 : _c[0]) == null ? void 0 : _d.embedding) {
4789
+ return res.data[0].embedding;
4765
4790
  }
4766
- } else {
4767
- 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.`);
4768
4791
  }
4792
+ throw httpErr;
4769
4793
  }
4770
4794
  const { data } = await this.http.post(path2, payload);
4771
- const extractPath = (_d = this.opts.embedExtractPath) != null ? _d : "data[0].embedding";
4795
+ const extractPath = (_e = this.opts.embedExtractPath) != null ? _e : "data[0].embedding";
4772
4796
  const vector = resolvePath(data, extractPath);
4773
4797
  if (!Array.isArray(vector)) {
4774
4798
  throw new Error(`[UniversalLLMAdapter] Expected a number array at '${extractPath}' for embeddings.`);
@@ -6743,8 +6767,8 @@ var IntentClassifier = class {
6743
6767
  }
6744
6768
  // ─── Intent Heuristic Checkers ─────────────────────────────────────────────
6745
6769
  static isInformationLookup(query) {
6746
- 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)) {
6747
- if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top selling|best)\b/i.test(query)) {
6770
+ 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)) {
6771
+ if (!/\b(compare|versus|vs\.?|trend|monthly|revenue|top 10|top selling|best|chart|graph|plot|breakdown|analytics)\b/i.test(query)) {
6748
6772
  return true;
6749
6773
  }
6750
6774
  }
@@ -6781,7 +6805,7 @@ var Rule1SpecificInfoRule = class {
6781
6805
  }
6782
6806
  evaluate(context, intent) {
6783
6807
  const q = (context.userQuery || "").toLowerCase().trim();
6784
- 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);
6808
+ 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);
6785
6809
  const isNonVisual = !/\b(compare|versus|vs\.?|trend|monthly|revenue|best|top 10|chart|table|list|grid)\b/i.test(q);
6786
6810
  if (isFactQuery && isNonVisual) {
6787
6811
  return {
@@ -8060,6 +8084,11 @@ ${schemaProfileText}` : ""}`;
8060
8084
  }
8061
8085
  static chooseAutomaticVisualization(data, profile, query) {
8062
8086
  if (profile.records.length === 0) return null;
8087
+ const q = query.toLowerCase().trim();
8088
+ 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);
8089
+ if (!isExplicitVisualOrAnalytic) {
8090
+ return null;
8091
+ }
8063
8092
  if (profile.dateFields.length > 0 && profile.numericFields.length > 0) {
8064
8093
  return this.transformToLineChart(profile);
8065
8094
  }
@@ -8175,6 +8204,7 @@ ${schemaProfileText}` : ""}`;
8175
8204
  return Array.from(categories);
8176
8205
  }
8177
8206
  static getProductCategory(item) {
8207
+ if (!item) return null;
8178
8208
  const meta = item.metadata || {};
8179
8209
  const metadataCategory = resolveMetadataValue(meta, "category");
8180
8210
  if (this.isUsableCategory(metadataCategory)) return String(metadataCategory).trim();
@@ -8291,6 +8321,7 @@ ${schemaProfileText}` : ""}`;
8291
8321
  if (!fields.has(normalized)) fields.set(normalized, clean);
8292
8322
  };
8293
8323
  data.forEach((item) => {
8324
+ if (!item) return;
8294
8325
  Object.keys(item.metadata || {}).forEach(addField);
8295
8326
  for (const match of (item.content || "").matchAll(/(?:^|\n)\s*([A-Za-z][A-Za-z0-9_ /-]{1,50})\s*[:\-–]\s*([^\n]+)/g)) {
8296
8327
  addField(match[1]);
@@ -8407,6 +8438,7 @@ ${schemaProfileText}` : ""}`;
8407
8438
  return true;
8408
8439
  }
8409
8440
  static extractStockQuantity(item) {
8441
+ if (!item) return null;
8410
8442
  const meta = item.metadata || {};
8411
8443
  const stockValue = resolveMetadataValue(meta, "stock");
8412
8444
  const numericStock = this.toFiniteNumber(stockValue);
@@ -9667,7 +9699,10 @@ ${context}`;
9667
9699
  Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
9668
9700
 
9669
9701
  History:
9670
- ${history.map((m) => `${m.role}: ${m.content}`).join("\n")}
9702
+ ${(history || []).map((m) => {
9703
+ var _a2;
9704
+ return `${(m == null ? void 0 : m.role) || "user"}: ${(_a2 = m == null ? void 0 : m.content) != null ? _a2 : ""}`;
9705
+ }).join("\n")}
9671
9706
 
9672
9707
  New Question: ${question}
9673
9708
 
@@ -9691,8 +9726,11 @@ Optimized Search Query:`;
9691
9726
  const ns = formatNamespace(namespace != null ? namespace : this.config.projectId);
9692
9727
  try {
9693
9728
  const { sources } = await this.retrieve(query, { namespace: ns, topK: 5 });
9694
- if (sources.length === 0) return [];
9695
- const context = sources.map((s) => s.content).join("\n\n---\n\n");
9729
+ if (!sources || sources.length === 0) return [];
9730
+ const context = sources.map((s) => {
9731
+ var _a2;
9732
+ return (_a2 = s == null ? void 0 : s.content) != null ? _a2 : "";
9733
+ }).join("\n\n---\n\n");
9696
9734
  const prompt = `Based on the following snippets from a document, what are at least 5 short, relevant questions a user might ask?
9697
9735
  Focus on questions that can be answered by the context.
9698
9736
  Keep each question under 10 words and make them very specific to the content.