@retrivora-ai/rag-engine 1.9.3 → 1.9.6

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 (60) hide show
  1. package/README.md +27 -0
  2. package/dist/{ILLMProvider-Bw2A28nU.d.mts → ILLMProvider-DNhyOYoK.d.mts} +41 -1
  3. package/dist/{ILLMProvider-Bw2A28nU.d.ts → ILLMProvider-DNhyOYoK.d.ts} +41 -1
  4. package/dist/handlers/index.d.mts +2 -2
  5. package/dist/handlers/index.d.ts +2 -2
  6. package/dist/handlers/index.js +552 -198
  7. package/dist/handlers/index.mjs +552 -198
  8. package/dist/index-C9v7-tWd.d.mts +183 -0
  9. package/dist/{index-B70ZLkfG.d.mts → index-CHL1jdYm.d.mts} +1 -1
  10. package/dist/index-CjQdL0cX.d.ts +183 -0
  11. package/dist/{index-DVu-mkAM.d.ts → index-Hgbwl9X4.d.ts} +1 -1
  12. package/dist/index.css +40 -0
  13. package/dist/index.d.mts +3 -3
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.js +326 -158
  16. package/dist/index.mjs +312 -151
  17. package/dist/server.d.mts +15 -94
  18. package/dist/server.d.ts +15 -94
  19. package/dist/server.js +612 -198
  20. package/dist/server.mjs +604 -198
  21. package/package.json +1 -1
  22. package/src/app/api/chat/route.ts +2 -2
  23. package/src/app/api/health/route.ts +3 -2
  24. package/src/app/api/ingest/route.ts +3 -2
  25. package/src/app/api/suggestions/route.ts +3 -2
  26. package/src/app/api/upload/route.ts +3 -2
  27. package/src/app/constants.tsx +85 -30
  28. package/src/app/layout.tsx +18 -7
  29. package/src/components/MessageBubble.tsx +39 -2
  30. package/src/components/ThinkingBlock.tsx +75 -0
  31. package/src/components/VisualizationRenderer.tsx +27 -1
  32. package/src/config/RagConfig.ts +47 -0
  33. package/src/config/serverConfig.ts +25 -0
  34. package/src/core/ConfigResolver.ts +56 -4
  35. package/src/core/ConfigValidator.ts +2 -1
  36. package/src/core/Pipeline.ts +224 -67
  37. package/src/core/ProviderRegistry.ts +11 -2
  38. package/src/core/QueryProcessor.ts +38 -4
  39. package/src/core/Retrivora.ts +51 -0
  40. package/src/exceptions/index.ts +59 -0
  41. package/src/handlers/index.ts +2 -0
  42. package/src/hooks/useRagChat.ts +26 -4
  43. package/src/index.ts +25 -1
  44. package/src/lib/plugin.ts +24 -0
  45. package/src/llm/LLMFactory.ts +4 -1
  46. package/src/llm/providers/AnthropicProvider.ts +70 -20
  47. package/src/llm/providers/GeminiProvider.ts +14 -15
  48. package/src/llm/providers/OllamaProvider.ts +13 -16
  49. package/src/llm/providers/OpenAIProvider.ts +9 -14
  50. package/src/llm/providers/UniversalLLMAdapter.ts +5 -5
  51. package/src/llm/utils.ts +46 -0
  52. package/src/providers/vectordb/MongoDBProvider.ts +9 -4
  53. package/src/providers/vectordb/MultiTablePostgresProvider.ts +45 -13
  54. package/src/rag/EntityExtractor.ts +2 -2
  55. package/src/rag/Reranker.ts +9 -16
  56. package/src/server.ts +27 -1
  57. package/src/types/chat.ts +7 -0
  58. package/src/utils/UITransformer.ts +73 -4
  59. package/dist/DocumentChunker-Dh9TvmGG.d.mts +0 -45
  60. package/dist/DocumentChunker-Dh9TvmGG.d.ts +0 -45
@@ -661,6 +661,11 @@ var init_MultiTablePostgresProvider = __esm({
661
661
  CREATE INDEX IF NOT EXISTS ${this.uploadTable}_embedding_idx
662
662
  ON ${this.uploadTable}
663
663
  USING hnsw (embedding vector_cosine_ops)
664
+ `);
665
+ await client.query(`
666
+ CREATE INDEX IF NOT EXISTS "${this.uploadTable}_fts_idx"
667
+ ON "${this.uploadTable}"
668
+ USING gin (to_tsvector('english', content))
664
669
  `);
665
670
  const res = await client.query(`
666
671
  SELECT table_name
@@ -731,6 +736,11 @@ var init_MultiTablePostgresProvider = __esm({
731
736
  CREATE INDEX IF NOT EXISTS "${tableName}_embedding_idx"
732
737
  ON "${tableName}"
733
738
  USING hnsw (embedding vector_cosine_ops)
739
+ `);
740
+ await client.query(`
741
+ CREATE INDEX IF NOT EXISTS "${tableName}_fts_idx"
742
+ ON "${tableName}"
743
+ USING gin (to_tsvector('english', content))
734
744
  `);
735
745
  if (!this.tables.includes(tableName)) {
736
746
  this.tables.push(tableName);
@@ -822,8 +832,11 @@ var init_MultiTablePostgresProvider = __esm({
822
832
  const paramIdx = baseOffset + filterParams.length;
823
833
  const synonyms = (_a3 = FIELD_SYNONYMS[key]) != null ? _a3 : [];
824
834
  const keysToCheck = [key, ...synonyms];
825
- const coalesceExprs = keysToCheck.map((k) => `LOWER(metadata->>'${k}')`);
826
- return `COALESCE(${coalesceExprs.join(", ")}) = LOWER($${paramIdx})`;
835
+ const coalesceExprs = keysToCheck.flatMap((k) => [
836
+ `metadata->>'${k}'`,
837
+ `to_jsonb(t)->>'${k}'`
838
+ ]);
839
+ return `COALESCE(${coalesceExprs.map((expr) => `LOWER(${expr})`).join(", ")}) = LOWER($${paramIdx})`;
827
840
  });
828
841
  whereClause = `WHERE ${conditions.join(" AND ")}`;
829
842
  }
@@ -834,16 +847,35 @@ var init_MultiTablePostgresProvider = __esm({
834
847
  CASE WHEN LOWER(val) IN (${entityHints.map((h) => `'${h.replace(/'/g, "''")}'`).join(", ")})
835
848
  THEN 1.0 ELSE 0.0 END
836
849
  ), 0)
837
- FROM jsonb_each_text(to_jsonb(t)) AS kv(key, val)
850
+ FROM jsonb_each_text(COALESCE(v.metadata, k.metadata)) AS kv(key, val)
838
851
  WHERE key IN (${this.searchFields.map((f) => `'${f}'`).join(", ")})
839
852
  ) * 3.0` : "";
840
853
  sqlQuery = `
841
- SELECT *,
842
- (1 - (embedding <=> $1::vector)) AS vector_score,
843
- COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
844
- ((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
845
- FROM "${table}" t
846
- ${whereClause}
854
+ WITH vector_search AS (
855
+ SELECT *, (1 - (embedding <=> $1::vector)) AS vector_score
856
+ FROM "${table}" t
857
+ ${whereClause}
858
+ ORDER BY embedding <=> $1::vector ASC
859
+ LIMIT ${tableLimit}
860
+ ),
861
+ keyword_search AS (
862
+ SELECT *, COALESCE(ts_rank(to_tsvector('english', content), to_tsquery('english', $2)), 0) AS keyword_score
863
+ FROM "${table}" t
864
+ WHERE ${whereClause ? whereClause.replace("WHERE", "") : "TRUE"}
865
+ AND to_tsvector('english', content) @@ to_tsquery('english', $2)
866
+ ORDER BY keyword_score DESC
867
+ LIMIT ${tableLimit}
868
+ )
869
+ SELECT
870
+ COALESCE(v.id, k.id) AS id,
871
+ COALESCE(v.namespace, k.namespace) AS namespace,
872
+ COALESCE(v.content, k.content) AS content,
873
+ COALESCE(v.metadata, k.metadata) AS metadata,
874
+ COALESCE(v.vector_score, 0) AS vector_score,
875
+ COALESCE(k.keyword_score, 0) AS keyword_score,
876
+ (COALESCE(v.vector_score, 0) + COALESCE(k.keyword_score, 0) * 2.0 ${exactNameScoreExpr}) AS hybrid_score
877
+ FROM vector_search v
878
+ FULL OUTER JOIN keyword_search k ON v.id = k.id
847
879
  ORDER BY hybrid_score DESC
848
880
  LIMIT ${tableLimit}
849
881
  `;
@@ -854,7 +886,7 @@ var init_MultiTablePostgresProvider = __esm({
854
886
  (1 - (embedding <=> $1::vector)) AS hybrid_score
855
887
  FROM "${table}" t
856
888
  ${whereClause}
857
- ORDER BY hybrid_score DESC
889
+ ORDER BY embedding <=> $1::vector ASC
858
890
  LIMIT ${tableLimit}
859
891
  `;
860
892
  params = [vectorLiteral, ...filterParams];
@@ -1060,7 +1092,11 @@ var init_MongoDBProvider = __esm({
1060
1092
  if (key === "namespace") {
1061
1093
  vectorSearchFilter.namespace = value;
1062
1094
  } else {
1063
- matchFilter[key] = value;
1095
+ if (typeof value === "string") {
1096
+ matchFilter[key] = { $regex: new RegExp(`^${value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "i") };
1097
+ } else {
1098
+ matchFilter[key] = value;
1099
+ }
1064
1100
  }
1065
1101
  }
1066
1102
  const pipeline = [
@@ -2149,7 +2185,7 @@ function readEnum(env, name, fallback, allowed) {
2149
2185
  throw new Error(`[getRagConfig] ${name} must be one of: ${allowed.join(", ")}`);
2150
2186
  }
2151
2187
  function getEnvConfig(env = process.env, base) {
2152
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa, _ba, _ca, _da, _ea, _fa, _ga, _ha, _ia, _ja, _ka, _la, _ma, _na, _oa, _pa, _qa, _ra, _sa, _ta, _ua, _va, _wa, _xa, _ya, _za, _Aa, _Ba, _Ca, _Da, _Ea, _Fa, _Ga;
2188
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa, _ba, _ca, _da, _ea, _fa, _ga, _ha, _ia, _ja, _ka, _la, _ma, _na, _oa, _pa, _qa, _ra, _sa, _ta, _ua, _va, _wa, _xa, _ya, _za, _Aa, _Ba, _Ca, _Da, _Ea, _Fa, _Ga, _Ha, _Ia, _Ja;
2153
2189
  const projectId = (_c = (_b = (_a = readString(env, "RAG_PROJECT_ID")) != null ? _a : readString(env, "NEXT_PUBLIC_PROJECT_ID")) != null ? _b : base == null ? void 0 : base.projectId) != null ? _c : "__default__";
2154
2190
  const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
2155
2191
  const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
@@ -2219,7 +2255,7 @@ function getEnvConfig(env = process.env, base) {
2219
2255
  rest: "default",
2220
2256
  custom: "default"
2221
2257
  };
2222
- return {
2258
+ return __spreadValues({
2223
2259
  projectId,
2224
2260
  vectorDb: {
2225
2261
  provider: vectorProvider,
@@ -2235,7 +2271,9 @@ function getEnvConfig(env = process.env, base) {
2235
2271
  maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
2236
2272
  temperature: readNumber(env, "LLM_TEMPERATURE", 0.7),
2237
2273
  options: {
2238
- profile: readString(env, "LLM_UNIVERSAL_PROFILE")
2274
+ profile: readString(env, "LLM_UNIVERSAL_PROFILE"),
2275
+ thinking: readString(env, "LLM_THINKING") === "true" || readString(env, "LLM_THINKING") === "enabled" ? true : readString(env, "LLM_THINKING") === "false" ? false : void 0,
2276
+ thinkingBudget: env.LLM_THINKING_BUDGET ? parseInt(env.LLM_THINKING_BUDGET, 10) : void 0
2239
2277
  }
2240
2278
  },
2241
2279
  embedding: {
@@ -2267,11 +2305,56 @@ function getEnvConfig(env = process.env, base) {
2267
2305
  topK: readNumber(env, "RAG_TOP_K", 5),
2268
2306
  scoreThreshold: readNumber(env, "RAG_SCORE_THRESHOLD", 0),
2269
2307
  chunkSize: readNumber(env, "RAG_CHUNK_SIZE", 1e3),
2270
- chunkOverlap: readNumber(env, "RAG_CHUNK_OVERLAP", 200)
2308
+ chunkOverlap: readNumber(env, "RAG_CHUNK_OVERLAP", 200),
2309
+ filterableFields: (_Ha = readString(env, "RAG_FILTERABLE_FIELDS")) == null ? void 0 : _Ha.split(",").map((f) => f.trim()),
2310
+ // Query pipeline toggles — read from .env.local
2311
+ useQueryTransformation: readString(env, "RAG_USE_QUERY_TRANSFORMATION") === "true",
2312
+ useReranking: readString(env, "RAG_USE_RERANKING") === "true",
2313
+ useGraphRetrieval: readString(env, "RAG_USE_GRAPH_RETRIEVAL") === "true",
2314
+ architecture: (_Ia = readString(env, "RAG_ARCHITECTURE")) != null ? _Ia : "simple",
2315
+ chunkingStrategy: (_Ja = readString(env, "RAG_CHUNKING_STRATEGY")) != null ? _Ja : "recursive",
2316
+ uiMapping: (() => {
2317
+ const raw = readString(env, "RAG_UI_MAPPING");
2318
+ if (!raw) return void 0;
2319
+ try {
2320
+ return JSON.parse(raw);
2321
+ } catch (e) {
2322
+ return void 0;
2323
+ }
2324
+ })()
2271
2325
  }
2272
- };
2326
+ }, readString(env, "GRAPH_DB_PROVIDER") ? {
2327
+ graphDb: {
2328
+ provider: readString(env, "GRAPH_DB_PROVIDER"),
2329
+ options: {
2330
+ uri: readString(env, "GRAPH_DB_URI"),
2331
+ username: readString(env, "GRAPH_DB_USERNAME"),
2332
+ password: readString(env, "GRAPH_DB_PASSWORD")
2333
+ }
2334
+ }
2335
+ } : {});
2273
2336
  }
2274
2337
 
2338
+ // src/exceptions/index.ts
2339
+ var RetrivoraError = class extends Error {
2340
+ constructor(message, code, details) {
2341
+ super(message);
2342
+ this.name = new.target.name;
2343
+ this.code = code;
2344
+ this.details = details;
2345
+ }
2346
+ };
2347
+ var ProviderNotFoundException = class extends RetrivoraError {
2348
+ constructor(providerType, provider, details) {
2349
+ super(`Unsupported ${providerType} provider: ${provider}`, "PROVIDER_NOT_FOUND", details);
2350
+ }
2351
+ };
2352
+ var ConfigurationException = class extends RetrivoraError {
2353
+ constructor(message, details) {
2354
+ super(message, "CONFIGURATION_ERROR", details);
2355
+ }
2356
+ };
2357
+
2275
2358
  // src/core/ConfigResolver.ts
2276
2359
  var ConfigResolver = class {
2277
2360
  /**
@@ -2298,24 +2381,81 @@ var ConfigResolver = class {
2298
2381
  rag: hostConfig.rag ? __spreadValues(__spreadValues({}, envConfig.rag), hostConfig.rag) : envConfig.rag
2299
2382
  });
2300
2383
  }
2384
+ /**
2385
+ * Resolves the public SDK config shape used by `new Retrivora({...})`.
2386
+ * Supports aliases from the product prompt while preserving existing env
2387
+ * fallback behavior.
2388
+ */
2389
+ static resolveUniversal(hostConfig, env = process.env) {
2390
+ var _a;
2391
+ if (!hostConfig) return this.resolve(void 0, env);
2392
+ const normalized = __spreadProps(__spreadValues({}, hostConfig), {
2393
+ vectorDb: (_a = hostConfig.vectorDb) != null ? _a : hostConfig.vectorDatabase,
2394
+ rag: this.mergeRetrievalWorkflow(hostConfig.rag, hostConfig.retrieval, hostConfig.workflow)
2395
+ });
2396
+ return this.resolve(normalized, env);
2397
+ }
2301
2398
  /**
2302
2399
  * Validates the configuration for required fields.
2303
2400
  */
2304
2401
  static validate(config) {
2305
2402
  if (!config.projectId) {
2306
- throw new Error("[ConfigResolver] projectId is required");
2403
+ throw new ConfigurationException("[ConfigResolver] projectId is required");
2307
2404
  }
2308
2405
  if (!config.vectorDb.provider) {
2309
- throw new Error("[ConfigResolver] vectorDb.provider is required");
2406
+ throw new ConfigurationException("[ConfigResolver] vectorDb.provider is required");
2310
2407
  }
2311
2408
  if (!config.llm.provider) {
2312
- throw new Error("[ConfigResolver] llm.provider is required");
2409
+ throw new ConfigurationException("[ConfigResolver] llm.provider is required");
2313
2410
  }
2314
2411
  }
2412
+ static mergeRetrievalWorkflow(rag, retrieval, workflow) {
2413
+ var _a, _b, _c, _d, _e;
2414
+ if (!rag && !retrieval && !workflow) return void 0;
2415
+ const normalized = __spreadValues({}, rag != null ? rag : {});
2416
+ if (retrieval) {
2417
+ normalized.topK = (_a = retrieval.topK) != null ? _a : normalized.topK;
2418
+ normalized.scoreThreshold = (_b = retrieval.scoreThreshold) != null ? _b : normalized.scoreThreshold;
2419
+ normalized.useReranking = (_c = retrieval.useReranking) != null ? _c : normalized.useReranking;
2420
+ if (retrieval.strategy === "hybrid") normalized.architecture = (_d = normalized.architecture) != null ? _d : "hybrid";
2421
+ if (retrieval.strategy === "agentic") normalized.architecture = "agentic";
2422
+ if (retrieval.strategy === "contextual-compression") normalized.useReranking = true;
2423
+ }
2424
+ if (workflow == null ? void 0 : workflow.type) {
2425
+ const type = workflow.type;
2426
+ if (type === "agentic" || type === "agentic-rag") normalized.architecture = "agentic";
2427
+ else if (type === "hybrid-rag") normalized.architecture = "hybrid";
2428
+ else if (type === "graph-rag") {
2429
+ normalized.architecture = "graph";
2430
+ normalized.useGraphRetrieval = true;
2431
+ } else if (type === "simple-rag" || type === "rag") {
2432
+ normalized.architecture = (_e = normalized.architecture) != null ? _e : "simple";
2433
+ }
2434
+ }
2435
+ return normalized;
2436
+ }
2315
2437
  };
2316
2438
 
2317
2439
  // src/llm/providers/OpenAIProvider.ts
2318
2440
  var import_openai = __toESM(require("openai"));
2441
+
2442
+ // src/llm/utils.ts
2443
+ function buildSystemContent(systemPrompt, context) {
2444
+ const noContext = !context || context.trim() === "" || context.trim() === "No relevant context found.";
2445
+ if (systemPrompt.includes("{{context}}")) {
2446
+ return systemPrompt.replace("{{context}}", noContext ? "" : context);
2447
+ }
2448
+ if (noContext) {
2449
+ return systemPrompt;
2450
+ }
2451
+ return `${systemPrompt}
2452
+
2453
+ ---
2454
+ Context:
2455
+ ${context}`;
2456
+ }
2457
+
2458
+ // src/llm/providers/OpenAIProvider.ts
2319
2459
  var OpenAIProvider = class {
2320
2460
  constructor(llmConfig, embeddingConfig) {
2321
2461
  if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
@@ -2379,16 +2519,10 @@ var OpenAIProvider = class {
2379
2519
  }
2380
2520
  async chat(messages, context, options) {
2381
2521
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2382
- const resolvedSystemPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Answer questions based on the provided context.
2383
-
2384
- Context:
2385
- ${context}`;
2522
+ const basePrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : "You are a helpful assistant. Answer questions based on the provided context.";
2386
2523
  const systemMessage = {
2387
2524
  role: "system",
2388
- content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
2389
-
2390
- Context:
2391
- ${context}`
2525
+ content: buildSystemContent(basePrompt, context)
2392
2526
  };
2393
2527
  const formattedMessages = [
2394
2528
  systemMessage,
@@ -2409,16 +2543,10 @@ ${context}`
2409
2543
  chatStream(messages, context, options) {
2410
2544
  return __asyncGenerator(this, null, function* () {
2411
2545
  var _a, _b, _c, _d, _e, _f, _g, _h;
2412
- const resolvedSystemPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Answer questions based on the provided context.
2413
-
2414
- Context:
2415
- ${context}`;
2546
+ const basePrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : "You are a helpful assistant. Answer questions based on the provided context.";
2416
2547
  const systemMessage = {
2417
2548
  role: "system",
2418
- content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
2419
-
2420
- Context:
2421
- ${context}`
2549
+ content: buildSystemContent(basePrompt, context)
2422
2550
  };
2423
2551
  const formattedMessages = [
2424
2552
  systemMessage,
@@ -2539,58 +2667,87 @@ var AnthropicProvider = class {
2539
2667
  };
2540
2668
  }
2541
2669
  async chat(messages, context, options) {
2542
- var _a, _b, _c, _d;
2543
- const resolvedPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Use the context below to answer the user's question accurately.
2544
-
2545
- Context:
2546
- ${context}`;
2547
- const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2548
-
2549
- Context:
2550
- ${context}`;
2670
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2671
+ const basePrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : "You are a helpful assistant. Use the context below to answer the user's question accurately.";
2672
+ const system = buildSystemContent(basePrompt, context);
2551
2673
  const anthropicMessages = messages.map((m) => ({
2552
2674
  role: m.role === "assistant" ? "assistant" : "user",
2553
2675
  content: m.content
2554
2676
  }));
2555
- const response = await this.client.messages.create({
2677
+ const isClaude37 = this.llmConfig.model.includes("claude-3-7-sonnet");
2678
+ const isThinkingEnabled = ((_c = this.llmConfig.options) == null ? void 0 : _c.thinking) === true || ((_d = this.llmConfig.options) == null ? void 0 : _d.thinking) === "enabled" || isClaude37 && ((_e = this.llmConfig.options) == null ? void 0 : _e.thinking) !== false;
2679
+ const extraParams = {};
2680
+ if (isThinkingEnabled && isClaude37) {
2681
+ extraParams.betas = ["interleaved-thinking-2025-05-14"];
2682
+ const maxTokens = (_g = (_f = options == null ? void 0 : options.maxTokens) != null ? _f : this.llmConfig.maxTokens) != null ? _g : 4096;
2683
+ const budget = Math.min(
2684
+ typeof ((_h = this.llmConfig.options) == null ? void 0 : _h.thinkingBudget) === "number" ? (_i = this.llmConfig.options) == null ? void 0 : _i.thinkingBudget : 2048,
2685
+ maxTokens - 1
2686
+ );
2687
+ extraParams.thinking = {
2688
+ type: "enabled",
2689
+ budget_tokens: budget
2690
+ };
2691
+ extraParams.max_tokens = Math.max(maxTokens, budget + 1024);
2692
+ }
2693
+ const response = await this.client.messages.create(__spreadValues({
2556
2694
  model: this.llmConfig.model,
2557
- max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
2695
+ max_tokens: (_k = (_j = options == null ? void 0 : options.maxTokens) != null ? _j : this.llmConfig.maxTokens) != null ? _k : 1024,
2558
2696
  system,
2559
2697
  messages: anthropicMessages
2560
- });
2561
- const block = response.content[0];
2562
- return block.type === "text" ? block.text : "";
2698
+ }, extraParams));
2699
+ let reply = "";
2700
+ for (const block of response.content) {
2701
+ if (block.type === "text") {
2702
+ reply += block.text;
2703
+ }
2704
+ }
2705
+ return reply;
2563
2706
  }
2564
2707
  chatStream(messages, context, options) {
2565
2708
  return __asyncGenerator(this, null, function* () {
2566
- var _a, _b, _c, _d;
2567
- const resolvedPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Use the context below to answer the user's question accurately.
2568
-
2569
- Context:
2570
- ${context}`;
2571
- const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2572
-
2573
- Context:
2574
- ${context}`;
2709
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2710
+ const basePrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : "You are a helpful assistant. Use the context below to answer the user's question accurately.";
2711
+ const system = buildSystemContent(basePrompt, context);
2575
2712
  const anthropicMessages = messages.map((m) => ({
2576
2713
  role: m.role === "assistant" ? "assistant" : "user",
2577
2714
  content: m.content
2578
2715
  }));
2579
- const stream = yield new __await(this.client.messages.create({
2716
+ const isClaude37 = this.llmConfig.model.includes("claude-3-7-sonnet");
2717
+ const isThinkingEnabled = ((_c = this.llmConfig.options) == null ? void 0 : _c.thinking) === true || ((_d = this.llmConfig.options) == null ? void 0 : _d.thinking) === "enabled" || isClaude37 && ((_e = this.llmConfig.options) == null ? void 0 : _e.thinking) !== false;
2718
+ const extraParams = {};
2719
+ if (isThinkingEnabled && isClaude37) {
2720
+ extraParams.betas = ["interleaved-thinking-2025-05-14"];
2721
+ const maxTokens = (_g = (_f = options == null ? void 0 : options.maxTokens) != null ? _f : this.llmConfig.maxTokens) != null ? _g : 4096;
2722
+ const budget = Math.min(
2723
+ typeof ((_h = this.llmConfig.options) == null ? void 0 : _h.thinkingBudget) === "number" ? (_i = this.llmConfig.options) == null ? void 0 : _i.thinkingBudget : 2048,
2724
+ maxTokens - 1
2725
+ );
2726
+ extraParams.thinking = {
2727
+ type: "enabled",
2728
+ budget_tokens: budget
2729
+ };
2730
+ extraParams.max_tokens = Math.max(maxTokens, budget + 1024);
2731
+ }
2732
+ const stream = yield new __await(this.client.messages.create(__spreadValues({
2580
2733
  model: this.llmConfig.model,
2581
- max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
2734
+ max_tokens: (_k = (_j = options == null ? void 0 : options.maxTokens) != null ? _j : this.llmConfig.maxTokens) != null ? _k : 1024,
2582
2735
  system,
2583
2736
  messages: anthropicMessages,
2584
2737
  stream: true
2585
- }));
2738
+ }, extraParams)));
2586
2739
  if (!stream) {
2587
2740
  throw new Error("[AnthropicProvider] messages.create stream is undefined");
2588
2741
  }
2589
2742
  try {
2590
2743
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
2591
2744
  const chunk = temp.value;
2592
- if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
2593
- yield chunk.delta.text;
2745
+ if (chunk.type === "content_block_delta") {
2746
+ if (chunk.delta.type === "text_delta") {
2747
+ yield chunk.delta.text;
2748
+ } else if (chunk.delta.type === "thinking_delta") {
2749
+ yield `\0THINKING\0${chunk.delta.thinking}`;
2750
+ }
2594
2751
  }
2595
2752
  }
2596
2753
  } catch (temp) {
@@ -2634,9 +2791,10 @@ ${context}`;
2634
2791
  var import_axios = __toESM(require("axios"));
2635
2792
  var OllamaProvider = class {
2636
2793
  constructor(llmConfig, embeddingConfig) {
2637
- var _a;
2794
+ var _a, _b;
2638
2795
  const baseURL = (_a = llmConfig.baseUrl) != null ? _a : "http://localhost:11434";
2639
- this.http = import_axios.default.create({ baseURL, timeout: 12e4 });
2796
+ const timeout = Number((_b = llmConfig.options) == null ? void 0 : _b.timeout) || 3e5;
2797
+ this.http = import_axios.default.create({ baseURL, timeout });
2640
2798
  this.llmConfig = llmConfig;
2641
2799
  this.embeddingConfig = embeddingConfig;
2642
2800
  }
@@ -2692,14 +2850,15 @@ var OllamaProvider = class {
2692
2850
  };
2693
2851
  }
2694
2852
  async chat(messages, context, options) {
2695
- var _a, _b, _c, _d;
2696
- const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
2853
+ var _a, _b, _c, _d, _e, _f;
2854
+ const basePrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : "You are a helpful assistant. Use the provided context to answer the user's question.";
2855
+ const system = buildSystemContent(basePrompt, context);
2697
2856
  const { data } = await this.http.post("/api/chat", {
2698
2857
  model: this.llmConfig.model,
2699
2858
  stream: false,
2700
2859
  options: {
2701
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
2702
- num_predict: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024
2860
+ temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : 0.7,
2861
+ num_predict: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : 1024
2703
2862
  },
2704
2863
  messages: [
2705
2864
  { role: "system", content: system },
@@ -2710,14 +2869,15 @@ var OllamaProvider = class {
2710
2869
  }
2711
2870
  chatStream(messages, context, options) {
2712
2871
  return __asyncGenerator(this, null, function* () {
2713
- var _a, _b, _c, _d, _e, _f;
2714
- const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
2872
+ var _a, _b, _c, _d, _e, _f, _g, _h;
2873
+ const basePrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : "You are a helpful assistant. Use the provided context to answer the user's question.";
2874
+ const system = buildSystemContent(basePrompt, context);
2715
2875
  const response = yield new __await(this.http.post("/api/chat", {
2716
2876
  model: this.llmConfig.model,
2717
2877
  stream: true,
2718
2878
  options: {
2719
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
2720
- num_predict: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024
2879
+ temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : 0.7,
2880
+ num_predict: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : 1024
2721
2881
  },
2722
2882
  messages: [
2723
2883
  { role: "system", content: system },
@@ -2739,7 +2899,7 @@ var OllamaProvider = class {
2739
2899
  if (!line.trim()) continue;
2740
2900
  try {
2741
2901
  const json = JSON.parse(line);
2742
- if ((_e = json.message) == null ? void 0 : _e.content) {
2902
+ if ((_g = json.message) == null ? void 0 : _g.content) {
2743
2903
  yield json.message.content;
2744
2904
  }
2745
2905
  if (json.done) return;
@@ -2761,26 +2921,12 @@ var OllamaProvider = class {
2761
2921
  if (lineBuffer.trim()) {
2762
2922
  try {
2763
2923
  const json = JSON.parse(lineBuffer);
2764
- if ((_f = json.message) == null ? void 0 : _f.content) yield json.message.content;
2924
+ if ((_h = json.message) == null ? void 0 : _h.content) yield json.message.content;
2765
2925
  } catch (e) {
2766
2926
  }
2767
2927
  }
2768
2928
  });
2769
2929
  }
2770
- buildSystemPrompt(context, overridePrompt) {
2771
- var _a;
2772
- if (overridePrompt) {
2773
- return overridePrompt;
2774
- }
2775
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
2776
-
2777
- Context:
2778
- ${context}`;
2779
- return systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2780
-
2781
- Context:
2782
- ${context}`;
2783
- }
2784
2930
  async embed(text, options) {
2785
2931
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2786
2932
  const model = (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "nomic-embed-text";
@@ -2927,21 +3073,6 @@ var GeminiProvider = class {
2927
3073
  var _a, _b;
2928
3074
  return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
2929
3075
  }
2930
- /**
2931
- * Build the system instruction string, inserting the RAG context either via
2932
- * the `{{context}}` placeholder or by appending it.
2933
- */
2934
- buildSystemInstruction(context) {
2935
- var _a;
2936
- const base = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
2937
-
2938
- Context:
2939
- ${context}`;
2940
- return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
2941
-
2942
- Context:
2943
- ${context}`;
2944
- }
2945
3076
  /**
2946
3077
  * Convert ChatMessage[] to the Gemini contents format.
2947
3078
  *
@@ -2960,16 +3091,17 @@ ${context}`;
2960
3091
  // ILLMProvider — chat
2961
3092
  // -------------------------------------------------------------------------
2962
3093
  async chat(messages, context, options) {
2963
- var _a, _b, _c, _d;
3094
+ var _a, _b, _c, _d, _e, _f;
3095
+ const basePrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : "You are a helpful assistant. Answer questions based on the provided context.";
2964
3096
  const model = this.client.getGenerativeModel({
2965
3097
  model: this.llmConfig.model,
2966
- systemInstruction: this.buildSystemInstruction(context)
3098
+ systemInstruction: buildSystemContent(basePrompt, context)
2967
3099
  });
2968
3100
  const result = await model.generateContent({
2969
3101
  contents: this.buildGeminiContents(messages),
2970
3102
  generationConfig: {
2971
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
2972
- maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
3103
+ temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : DEFAULT_TEMPERATURE,
3104
+ maxOutputTokens: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : DEFAULT_MAX_TOKENS,
2973
3105
  stopSequences: options == null ? void 0 : options.stop
2974
3106
  }
2975
3107
  });
@@ -2977,16 +3109,17 @@ ${context}`;
2977
3109
  }
2978
3110
  chatStream(messages, context, options) {
2979
3111
  return __asyncGenerator(this, null, function* () {
2980
- var _a, _b, _c, _d;
3112
+ var _a, _b, _c, _d, _e, _f;
3113
+ const basePrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : "You are a helpful assistant. Answer questions based on the provided context.";
2981
3114
  const model = this.client.getGenerativeModel({
2982
3115
  model: this.llmConfig.model,
2983
- systemInstruction: this.buildSystemInstruction(context)
3116
+ systemInstruction: buildSystemContent(basePrompt, context)
2984
3117
  });
2985
3118
  const result = yield new __await(model.generateContentStream({
2986
3119
  contents: this.buildGeminiContents(messages),
2987
3120
  generationConfig: {
2988
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
2989
- maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
3121
+ temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : DEFAULT_TEMPERATURE,
3122
+ maxOutputTokens: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : DEFAULT_MAX_TOKENS,
2990
3123
  stopSequences: options == null ? void 0 : options.stop
2991
3124
  }
2992
3125
  }));
@@ -3382,7 +3515,9 @@ var LLMFactory = class _LLMFactory {
3382
3515
  if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
3383
3516
  return new UniversalLLMAdapter(llmConfig);
3384
3517
  }
3385
- throw new Error(
3518
+ throw new ProviderNotFoundException(
3519
+ "LLM",
3520
+ String(llmConfig.provider),
3386
3521
  `[LLMFactory] Unknown provider "${llmConfig.provider}". Built-in providers: ${_LLMFactory.listProviders().join(", ")}. Register a custom provider with LLMFactory.register().`
3387
3522
  );
3388
3523
  }
@@ -3514,7 +3649,7 @@ var ProviderRegistry = class {
3514
3649
  return UniversalVectorProvider2;
3515
3650
  }
3516
3651
  default:
3517
- throw new Error(`Unsupported vector provider: ${provider}`);
3652
+ throw new ProviderNotFoundException("vector", provider);
3518
3653
  }
3519
3654
  }
3520
3655
  static async createVectorProvider(config) {
@@ -3532,12 +3667,18 @@ var ProviderRegistry = class {
3532
3667
  return new SimpleGraphProvider2(config);
3533
3668
  }
3534
3669
  default:
3535
- throw new Error(`Unsupported graph provider: ${provider}`);
3670
+ throw new ProviderNotFoundException("graph", provider);
3536
3671
  }
3537
3672
  }
3538
3673
  static createLLMProvider(llmConfig, embeddingConfig) {
3539
3674
  return LLMFactory.create(llmConfig, embeddingConfig);
3540
3675
  }
3676
+ static registerLLMProvider(name, factory) {
3677
+ LLMFactory.register(name, factory);
3678
+ }
3679
+ static createEmbeddingProvider(embeddingConfig) {
3680
+ return LLMFactory.createEmbeddingProvider(embeddingConfig);
3681
+ }
3541
3682
  };
3542
3683
  ProviderRegistry.vectorProviders = {};
3543
3684
  ProviderRegistry.graphProviders = {};
@@ -3684,8 +3825,8 @@ var ConfigValidator = class {
3684
3825
  const errorItems = errors.filter((e) => e.severity === "error");
3685
3826
  if (errorItems.length > 0) {
3686
3827
  const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
3687
- throw new Error(`[ConfigValidator] Configuration validation failed:
3688
- ${message}`);
3828
+ throw new ConfigurationException(`[ConfigValidator] Configuration validation failed:
3829
+ ${message}`, errorItems);
3689
3830
  }
3690
3831
  }
3691
3832
  };
@@ -3872,20 +4013,17 @@ var Reranker = class {
3872
4013
  }
3873
4014
  try {
3874
4015
  const topN = matches.slice(0, 10);
3875
- const prompt = `You are a relevance ranking expert.
3876
- Given the following user query and a list of retrieved document chunks, rank the chunks by relevance to the query.
3877
- Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant.
3878
- Use the indices provided in brackets like [0], [1], etc.
3879
- Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.
3880
-
3881
- Query: "${query}"
4016
+ const response = await llm.chat(
4017
+ [{ role: "user", content: `Query: "${query}"
3882
4018
 
3883
4019
  Documents:
3884
- ${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}`;
3885
- const response = await llm.chat(
3886
- [{ role: "user", content: prompt }],
4020
+ ${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}` }],
3887
4021
  "",
3888
- { temperature: 0, maxTokens: 50 }
4022
+ {
4023
+ systemPrompt: 'You are a relevance ranking expert. Given the user query and a list of retrieved document chunks, rank the chunks by relevance to the query. Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant. Use the indices provided in brackets like [0], [1], etc. Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.',
4024
+ temperature: 0,
4025
+ maxTokens: 50
4026
+ }
3889
4027
  );
3890
4028
  const cleanedResponse = response.trim().replace(/[\[\]\s]/g, "");
3891
4029
  const rankedIndices = cleanedResponse.split(",").map(Number).filter((n) => !isNaN(n));
@@ -4391,6 +4529,18 @@ var QueryProcessor = class {
4391
4529
  }, normalizedField ? { field: normalizedField } : {}));
4392
4530
  }
4393
4531
  };
4532
+ const activeValidFields = validFields.length > 0 ? validFields : ["brand", "category", "price", "rating", "stock", "stock_quantity", "status", "name", "title"];
4533
+ const resolveValidField = (fieldStr) => {
4534
+ const cleaned = this.normalizeHintValue(fieldStr).replace(/\b(?:provide|show|get|give|list|all|the|a|an|of|organizations?|companies?|records?|items?|whose|with|having|where|that|which|have|has|is|are|was|were)\b/gi, " ").replace(/\s+/g, " ").trim();
4535
+ const comparable = (v) => v.toLowerCase().replace(/[^a-z0-9]/g, "");
4536
+ const cleanedComparable = comparable(cleaned);
4537
+ if (!cleanedComparable) return null;
4538
+ const matchedField = activeValidFields.find((fieldName) => {
4539
+ const candidate = comparable(fieldName);
4540
+ return candidate === cleanedComparable || candidate.includes(cleanedComparable) || cleanedComparable.includes(candidate);
4541
+ });
4542
+ return matchedField != null ? matchedField : null;
4543
+ };
4394
4544
  for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
4395
4545
  addHint(match[1]);
4396
4546
  }
@@ -4415,7 +4565,13 @@ var QueryProcessor = class {
4415
4565
  const field = match[1];
4416
4566
  const value = match[2];
4417
4567
  if (field && !this.isLikelyPromptPhrase(field)) {
4418
- addHint(value, field);
4568
+ const resolvedField = resolveValidField(field);
4569
+ if (resolvedField) {
4570
+ addHint(value, resolvedField);
4571
+ } else {
4572
+ addHint(value);
4573
+ addHint(field);
4574
+ }
4419
4575
  }
4420
4576
  }
4421
4577
  if (validFields.length > 0) {
@@ -4461,12 +4617,16 @@ var QueryProcessor = class {
4461
4617
  ];
4462
4618
  for (const pattern of fieldValuePatterns) {
4463
4619
  for (const match of question.matchAll(pattern)) {
4464
- const field = this.normalizeHintValue((_c = match[1]) != null ? _c : "");
4620
+ const field = (_c = match[1]) != null ? _c : "";
4465
4621
  const value = (_d = match[2]) != null ? _d : "";
4466
- if (field && !this.isLikelyPromptPhrase(field)) {
4467
- addHint(value, field);
4622
+ const resolvedField = resolveValidField(field);
4623
+ if (resolvedField) {
4624
+ addHint(value, resolvedField);
4468
4625
  } else {
4469
4626
  addHint(value);
4627
+ if (field && !this.isLikelyPromptPhrase(field)) {
4628
+ addHint(field);
4629
+ }
4470
4630
  }
4471
4631
  }
4472
4632
  }
@@ -4693,7 +4853,7 @@ var LLMRouter = class {
4693
4853
 
4694
4854
  // src/utils/UITransformer.ts
4695
4855
  init_synonyms();
4696
- var UITransformer = class {
4856
+ var UITransformer = class _UITransformer {
4697
4857
  // ─── Public Entry Points ─────────────────────────────────────────────────
4698
4858
  /**
4699
4859
  * Heuristic-only transform (no LLM required).
@@ -4755,7 +4915,7 @@ var UITransformer = class {
4755
4915
  static async analyzeAndDecide(query, sources, llm) {
4756
4916
  let intent;
4757
4917
  try {
4758
- intent = await this.detectIntent(query, llm);
4918
+ intent = await this.detectIntent(query, llm, sources);
4759
4919
  console.debug("[UITransformer] Detected intent:", intent);
4760
4920
  } catch (err) {
4761
4921
  console.warn("[UITransformer] Intent detection failed; using heuristic.", err);
@@ -4773,11 +4933,23 @@ var UITransformer = class {
4773
4933
  try {
4774
4934
  const context = this.buildContextSummary(sources);
4775
4935
  const systemPrompt = this.buildVisualizationSystemPrompt();
4936
+ const profile = this.profileData(sources);
4937
+ const schemaContext = `
4938
+ RETRIEVED DATA SCHEMA:
4939
+ - Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4940
+ - Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4941
+ - Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4942
+ - Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4943
+ - Text/Other fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
4944
+ `;
4776
4945
  const userPrompt = [
4777
4946
  `USER QUESTION: ${query}`,
4778
4947
  "",
4779
4948
  `DETECTED INTENT (JSON): ${JSON.stringify(intent)}`,
4780
4949
  "",
4950
+ `RETRIEVED DATA SCHEMA:`,
4951
+ schemaContext,
4952
+ "",
4781
4953
  "RETRIEVED DATA (JSON):",
4782
4954
  context
4783
4955
  ].join("\n");
@@ -4788,6 +4960,20 @@ var UITransformer = class {
4788
4960
  );
4789
4961
  const parsed = this.parseTransformationResponse(rawResponse);
4790
4962
  if (parsed) {
4963
+ let isCompatible = true;
4964
+ if (parsed.type === "line_chart" && (profile.dateFields.length === 0 || profile.numericFields.length === 0)) {
4965
+ isCompatible = false;
4966
+ } else if (["bar_chart", "horizontal_bar", "pie_chart", "donut_chart"].includes(parsed.type) && (profile.categoricalFields.length === 0 && profile.numericFields.length === 0)) {
4967
+ isCompatible = false;
4968
+ } else if (parsed.type === "scatter_plot" && profile.numericFields.length < 2) {
4969
+ isCompatible = false;
4970
+ } else if (parsed.type === "metric_card" && profile.numericFields.length === 0) {
4971
+ isCompatible = false;
4972
+ }
4973
+ if (!isCompatible) {
4974
+ console.warn(`[UITransformer] LLM chose incompatible visualization type "${parsed.type}" for retrieved schema. Falling back to heuristic.`);
4975
+ return this.transform(query, sources, void 0, void 0, intent);
4976
+ }
4791
4977
  const intentAllowsTable = intent.wantsExplicitTable || ["tabular", "table", "geographic"].includes(intent.visualizationHint);
4792
4978
  const intentWantsPieLikeChart = ["pie_chart", "donut_chart"].includes(intent.recommendedChart) || ["composition", "category_breakdown"].includes(intent.visualizationHint);
4793
4979
  if (parsed.type === "table" && !intentAllowsTable) {
@@ -4817,7 +5003,26 @@ var UITransformer = class {
4817
5003
  * - The intent object can be reused across both the heuristic and LLM paths.
4818
5004
  * - It is easy to unit-test intent detection in isolation.
4819
5005
  */
4820
- static async detectIntent(query, llm) {
5006
+ static async detectIntent(query, llm, sources) {
5007
+ let schemaProfileText = "";
5008
+ if (sources && sources.length > 0) {
5009
+ const profile = this.profileData(sources);
5010
+ const hasProducts = sources.some((item) => this.isProductData(item));
5011
+ schemaProfileText = `
5012
+ RETRIEVED DATA SCHEMA:
5013
+ - Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5014
+ - Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5015
+ - Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5016
+ - Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5017
+ - Text fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
5018
+
5019
+ Please choose visualizationHint and recommendedChart dynamically based on the available schema.
5020
+ - Do NOT recommend "trend" or "line_chart" if there are no Date fields.
5021
+ - Do NOT recommend numeric-dependent visualizations (like bar_chart, line_chart, pie_chart, scatter_plot, histogram, metric_card) if there are no Numeric fields. Fall back to tabular/table or text.
5022
+ - If there are multiple metadata fields but no clear categories or numbers, "tabular"/"table" is a highly accurate default.
5023
+ ${hasProducts ? `- Note: The retrieved data matches product/catalog structure. If the user query is looking for, exploring, searching, browsing, or listing products/items, you MUST recommend visualizationHint: "product_browse" and recommendedChart: "text".` : ""}
5024
+ `;
5025
+ }
4821
5026
  const systemPrompt = `You are an intent classifier for a product-search RAG system.
4822
5027
  Given a user query, return ONLY a valid JSON object with this exact shape \u2014 no prose, no markdown:
4823
5028
 
@@ -4853,8 +5058,11 @@ RULES:
4853
5058
  - isTemporal: true if time words appear (trend, historical, over time, last year, monthly, etc.)
4854
5059
  - isComparison: true if user compares, ranks, or contrasts entities or categories.
4855
5060
  - language: detect from the query text itself; default "en" if uncertain.`;
5061
+ const userPrompt = `QUERY: ${query}${schemaProfileText ? `
5062
+
5063
+ ${schemaProfileText}` : ""}`;
4856
5064
  const rawResponse = await llm.chat(
4857
- [{ role: "user", content: `QUERY: ${query}` }],
5065
+ [{ role: "user", content: userPrompt }],
4858
5066
  "",
4859
5067
  { systemPrompt, temperature: 0 }
4860
5068
  );
@@ -5297,10 +5505,21 @@ RULES:
5297
5505
  const hasEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
5298
5506
  return asksForRecords && (hasNumericPredicate || hasEntityTerms);
5299
5507
  }
5508
+ /** @internal kept private for transform() internal logic */
5300
5509
  static isProductQuery(query) {
5510
+ return _UITransformer._productQueryTest(query);
5511
+ }
5512
+ /**
5513
+ * Public entry-point for external callers (e.g. Pipeline)
5514
+ * to check whether a query maps to product_browse without triggering an LLM call.
5515
+ */
5516
+ static isProductQueryPublic(query) {
5517
+ return _UITransformer._productQueryTest(query);
5518
+ }
5519
+ static _productQueryTest(query) {
5301
5520
  const q = query.toLowerCase();
5302
5521
  const productTerms = /\b(product|products|item|items|sku|catalog|catalogue|price|prices|brand|model|stock|inventory|in stock|out of stock|buy|shop)\b/.test(q);
5303
- const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list)\b/.test(q);
5522
+ const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list|explore|exploring)\b/.test(q);
5304
5523
  const nonProductEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
5305
5524
  return productTerms && productAction && !nonProductEntityTerms;
5306
5525
  }
@@ -6073,23 +6292,19 @@ function estimateCostUsd(promptTokens, completionTokens, model) {
6073
6292
  async function scoreHallucination(llm, answer, context) {
6074
6293
  const maxContextChars = 3e3;
6075
6294
  const truncatedContext = context.length > maxContextChars ? context.slice(0, maxContextChars) + "\n...[truncated]" : context;
6076
- const prompt = `You are an AI quality checker. Given the CONTEXT retrieved from a knowledge base and the ANSWER generated from it, rate how well-grounded the answer is.
6077
-
6078
- CONTEXT:
6295
+ try {
6296
+ const raw = await llm.chat(
6297
+ [{ role: "user", content: `CONTEXT:
6079
6298
  ${truncatedContext}
6080
6299
 
6081
6300
  ANSWER:
6082
- ${answer}
6083
-
6084
- Return ONLY a valid JSON object with no markdown fences:
6085
- {"score": <float 0-1>, "reason": "<one sentence>"}
6086
-
6087
- Where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.`;
6088
- try {
6089
- const raw = await llm.chat(
6090
- [{ role: "user", content: prompt }],
6301
+ ${answer}` }],
6091
6302
  "",
6092
- { temperature: 0, maxTokens: 120 }
6303
+ {
6304
+ systemPrompt: 'You are an AI quality checker. Given the CONTEXT retrieved from a knowledge base and the ANSWER generated from it, rate how well-grounded the answer is. Return ONLY a valid JSON object with no markdown fences: {"score": <float 0-1>, "reason": "<one sentence>"} where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.',
6305
+ temperature: 0,
6306
+ maxTokens: 120
6307
+ }
6093
6308
  );
6094
6309
  const jsonMatch = raw.match(/\{[\s\S]*\}/);
6095
6310
  if (!jsonMatch) return void 0;
@@ -6149,7 +6364,10 @@ var Pipeline = class {
6149
6364
  - For product descriptions, summarize customer-facing details only. Do NOT list internal catalog fields such as Handle, Body (HTML), Vendor, Type, Tags, Published, Option, Variant, SKU, or raw metadata labels.
6150
6365
  - Do NOT try to format product lists as tables.
6151
6366
  `;
6152
- this.config.llm.systemPrompt = chartInstruction;
6367
+ const userPromptPrefix = this.config.llm.systemPrompt ? `${this.config.llm.systemPrompt}
6368
+
6369
+ ` : "";
6370
+ this.config.llm.systemPrompt = `${userPromptPrefix}${chartInstruction}`;
6153
6371
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
6154
6372
  this.llmRouter = new LLMRouter(this.config);
6155
6373
  const { llmProvider: resolvedLLM, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
@@ -6327,11 +6545,11 @@ var Pipeline = class {
6327
6545
  */
6328
6546
  askStream(_0) {
6329
6547
  return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
6330
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
6548
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
6331
6549
  yield new __await(this.initialize());
6332
6550
  const ns = namespace != null ? namespace : this.config.projectId;
6333
6551
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
6334
- const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
6552
+ const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0.3;
6335
6553
  const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
6336
6554
  const requestStart = performance.now();
6337
6555
  try {
@@ -6350,12 +6568,13 @@ var Pipeline = class {
6350
6568
  const embedStart = performance.now();
6351
6569
  const cacheKey = `${ns}::${searchQuery}`;
6352
6570
  const cachedVector = this.embeddingCache.get(cacheKey);
6571
+ const useGraph = this.graphDB && ((_h = this.config.rag) == null ? void 0 : _h.useGraphRetrieval);
6353
6572
  const [strategyResult, embeddedVector] = yield new __await(Promise.all([
6354
6573
  QueryProcessor.determineRetrievalStrategy(
6355
6574
  searchQuery,
6356
- this.llmRouter.get("fast"),
6357
- (_h = this.config.rag) == null ? void 0 : _h.graphKeywords,
6358
- (_i = this.config.rag) == null ? void 0 : _i.vectorKeywords
6575
+ useGraph ? this.llmRouter.get("fast") : void 0,
6576
+ (_i = this.config.rag) == null ? void 0 : _i.graphKeywords,
6577
+ (_j = this.config.rag) == null ? void 0 : _j.vectorKeywords
6359
6578
  ),
6360
6579
  // Embed immediately regardless of strategy — costs nothing if cached.
6361
6580
  // If strategy turns out to be 'graph'-only we just won't use the vector.
@@ -6365,7 +6584,7 @@ var Pipeline = class {
6365
6584
  if (!cachedVector && queryVector.length > 0) {
6366
6585
  this.embeddingCache.set(cacheKey, queryVector);
6367
6586
  }
6368
- const graphData = (strategyResult === "graph" || strategyResult === "both") && this.graphDB && ((_j = this.config.rag) == null ? void 0 : _j.useGraphRetrieval) ? yield new __await(this.graphDB.query(searchQuery)) : void 0;
6587
+ const graphData = (strategyResult === "graph" || strategyResult === "both") && this.graphDB && ((_k = this.config.rag) == null ? void 0 : _k.useGraphRetrieval) ? yield new __await(this.graphDB.query(searchQuery)) : void 0;
6369
6588
  const hasMetadataFilter = filter.metadata && Object.keys(filter.metadata).length > 0;
6370
6589
  const hasNumericPredicates = Array.isArray(filter.__numericPredicates) && filter.__numericPredicates.length > 0;
6371
6590
  const wantsExhaustiveList = hasNumericPredicates || hasMetadataFilter || /\b(list|all|show|provide|give|get|browse|find|search|display)\b/i.test(question);
@@ -6378,7 +6597,7 @@ var Pipeline = class {
6378
6597
  const structuredSources = this.applyStructuredFilters(rawSources, filter);
6379
6598
  let fullSources = hasNumericPredicates ? structuredSources : structuredSources.filter((m) => m.score >= scoreThreshold);
6380
6599
  const rerankLimit = wantsExhaustiveList ? retrievalLimit : topK;
6381
- if (!hasNumericPredicates && ((_k = this.config.rag) == null ? void 0 : _k.useReranking)) {
6600
+ if (!hasNumericPredicates && ((_l = this.config.rag) == null ? void 0 : _l.useReranking)) {
6382
6601
  fullSources = yield new __await(this.reranker.rerank(fullSources, question, rerankLimit));
6383
6602
  } else if (!wantsExhaustiveList) {
6384
6603
  fullSources = fullSources.slice(0, topK);
@@ -6409,34 +6628,117 @@ VECTOR CONTEXT:
6409
6628
  ${context}`;
6410
6629
  }
6411
6630
  const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
6412
- const trainedSchemaPromise = allMetadataKeys.length > 0 ? SchemaMapper.train(this.llmProvider, ns, allMetadataKeys).catch(() => void 0) : Promise.resolve(void 0);
6413
- const uiTransformationPromise = trainedSchemaPromise.then(
6414
- (trainedSchema) => this.generateUiTransformation(
6631
+ const cachedSchema = allMetadataKeys.length > 0 ? SchemaMapper.getCached(ns, allMetadataKeys) : void 0;
6632
+ if (allMetadataKeys.length > 0 && !cachedSchema) {
6633
+ SchemaMapper.train(this.llmProvider, ns, allMetadataKeys).catch(() => {
6634
+ });
6635
+ }
6636
+ const isProductQ = UITransformer.isProductQueryPublic(question);
6637
+ const uiTransformationPromise = isProductQ ? Promise.resolve(
6638
+ UITransformer.transform(
6415
6639
  question,
6416
6640
  sources,
6417
- trainedSchema,
6418
- hasNumericPredicates
6641
+ this.config,
6642
+ cachedSchema,
6643
+ { visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
6419
6644
  )
6645
+ ) : this.generateUiTransformation(
6646
+ question,
6647
+ sources,
6648
+ cachedSchema,
6649
+ hasNumericPredicates
6420
6650
  ).catch((uiError) => {
6421
6651
  console.warn("[Pipeline] UI transformation failed concurrently:", uiError);
6422
- return UITransformer.transform(question, sources, this.config);
6652
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6423
6653
  });
6654
+ let hallucinationScoringPromise = Promise.resolve(void 0);
6424
6655
  const restrictionSuffix = "\n\n(IMPORTANT: Format your response beautifully using rich Markdown! Use bolding for emphasis, headings (##) for structure, bullet points for lists, and proper line breaks between paragraphs to make it highly readable. However, NEVER generate Markdown tables, HTML figures, or text charts (the UI renders requested charts separately, so NEVER say you cannot create, display, draw, render, or provide a chart/visualization). If listing products, use simple markdown bullet points or comma-separated names. NEVER use plus signs (+) to separate product names, category names, or list items. For product description/detail questions, provide customer-facing prose only and do NOT list internal catalog fields such as Handle, Body (HTML), Vendor, Type, Tags, Published, Option, Variant, SKU, or raw metadata labels. You CAN use numbers for years/counts like 2006 or 5800, but NEVER put a dollar sign ($) before them.)";
6656
+ const isClaude37 = this.config.llm.model.includes("claude-3-7-sonnet");
6657
+ const isNativeThinking = isClaude37 && (((_m = this.config.llm.options) == null ? void 0 : _m.thinking) === true || ((_n = this.config.llm.options) == null ? void 0 : _n.thinking) === "enabled" || ((_o = this.config.llm.options) == null ? void 0 : _o.thinking) !== false);
6658
+ const modelNameLower = this.config.llm.model.toLowerCase();
6659
+ const isReasoningModel = modelNameLower.includes("-r1") || modelNameLower.includes("deepseek-r1") || modelNameLower.includes("reasoning") || modelNameLower.includes("thinking");
6660
+ const isSimulatedThinking = !isNativeThinking && (((_p = this.config.llm.options) == null ? void 0 : _p.thinking) === true || ((_q = this.config.llm.options) == null ? void 0 : _q.thinking) === "enabled" || isReasoningModel && ((_r = this.config.llm.options) == null ? void 0 : _r.thinking) !== false);
6661
+ let finalRestrictionSuffix = restrictionSuffix;
6662
+ if (isSimulatedThinking) {
6663
+ finalRestrictionSuffix += "\n\n(IMPORTANT: You must think step-by-step before answering. Write your thinking process inside a `<think>...</think>` block. Write the `<think>` block first, then follow it with your final response. Keep the thinking process thorough and detailed. Example: `<think>Thinking details here...</think>Final answer text here.`)";
6664
+ }
6425
6665
  const hardenedHistory = [...history];
6426
- const userQuestion = { role: "user", content: question + restrictionSuffix };
6666
+ const userQuestion = { role: "user", content: question + finalRestrictionSuffix };
6427
6667
  const messages = [...hardenedHistory, userQuestion];
6428
- const systemPrompt = (_l = this.config.llm.systemPrompt) != null ? _l : "";
6668
+ const systemPrompt = (_s = this.config.llm.systemPrompt) != null ? _s : "";
6429
6669
  const userPrompt = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
6430
6670
  let fullReply = "";
6671
+ let thinkingText = "";
6672
+ let thinkingStartMs = performance.now();
6673
+ let thinkingDurationMs = 0;
6431
6674
  const generateStart = performance.now();
6432
6675
  if (this.llmProvider.chatStream) {
6433
6676
  const stream = this.llmProvider.chatStream(messages, context);
6434
6677
  if (!stream) throw new Error(`[Pipeline] ${this.config.llm.provider} chatStream returned undefined`);
6678
+ let inThinking = false;
6679
+ let textBuffer = "";
6435
6680
  try {
6436
6681
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
6437
6682
  const chunk = temp.value;
6438
- fullReply += chunk;
6439
- yield chunk;
6683
+ if (chunk.startsWith("\0THINKING\0")) {
6684
+ const thinkingDelta = chunk.slice("\0THINKING\0".length);
6685
+ thinkingText += thinkingDelta;
6686
+ thinkingDurationMs = performance.now() - thinkingStartMs;
6687
+ yield { type: "thinking", text: thinkingDelta };
6688
+ continue;
6689
+ }
6690
+ if (isSimulatedThinking) {
6691
+ textBuffer += chunk;
6692
+ while (textBuffer.length > 0) {
6693
+ if (!inThinking) {
6694
+ const thinkIndex = textBuffer.indexOf("<think>");
6695
+ if (thinkIndex !== -1) {
6696
+ const before = textBuffer.slice(0, thinkIndex);
6697
+ if (before) {
6698
+ fullReply += before;
6699
+ yield before;
6700
+ }
6701
+ inThinking = true;
6702
+ thinkingStartMs = performance.now();
6703
+ textBuffer = textBuffer.slice(thinkIndex + "<think>".length);
6704
+ } else {
6705
+ const maxSafeLength = textBuffer.length - "<think>".length;
6706
+ if (maxSafeLength > 0) {
6707
+ const safeText = textBuffer.slice(0, maxSafeLength);
6708
+ fullReply += safeText;
6709
+ yield safeText;
6710
+ textBuffer = textBuffer.slice(maxSafeLength);
6711
+ }
6712
+ break;
6713
+ }
6714
+ } else {
6715
+ const endThinkIndex = textBuffer.indexOf("</think>");
6716
+ if (endThinkIndex !== -1) {
6717
+ const thinkingDelta = textBuffer.slice(0, endThinkIndex);
6718
+ if (thinkingDelta) {
6719
+ thinkingText += thinkingDelta;
6720
+ thinkingDurationMs = performance.now() - thinkingStartMs;
6721
+ yield { type: "thinking", text: thinkingDelta };
6722
+ }
6723
+ inThinking = false;
6724
+ textBuffer = textBuffer.slice(endThinkIndex + "</think>".length);
6725
+ } else {
6726
+ const maxSafeLength = textBuffer.length - "</think>".length;
6727
+ if (maxSafeLength > 0) {
6728
+ const thinkingDelta = textBuffer.slice(0, maxSafeLength);
6729
+ thinkingText += thinkingDelta;
6730
+ thinkingDurationMs = performance.now() - thinkingStartMs;
6731
+ yield { type: "thinking", text: thinkingDelta };
6732
+ textBuffer = textBuffer.slice(maxSafeLength);
6733
+ }
6734
+ break;
6735
+ }
6736
+ }
6737
+ }
6738
+ } else {
6739
+ fullReply += chunk;
6740
+ yield chunk;
6741
+ }
6440
6742
  }
6441
6743
  } catch (temp) {
6442
6744
  error = [temp];
@@ -6448,17 +6750,42 @@ ${context}`;
6448
6750
  throw error[0];
6449
6751
  }
6450
6752
  }
6753
+ if (isSimulatedThinking && textBuffer.length > 0) {
6754
+ if (inThinking) {
6755
+ thinkingText += textBuffer;
6756
+ yield { type: "thinking", text: textBuffer };
6757
+ } else {
6758
+ fullReply += textBuffer;
6759
+ yield textBuffer;
6760
+ }
6761
+ }
6451
6762
  } else {
6452
6763
  const reply = yield new __await(this.llmProvider.chat(messages, context));
6453
- fullReply = reply;
6454
- yield reply;
6764
+ if (isSimulatedThinking) {
6765
+ const thinkStart = reply.indexOf("<think>");
6766
+ const thinkEnd = reply.indexOf("</think>");
6767
+ if (thinkStart !== -1 && thinkEnd !== -1 && thinkEnd > thinkStart) {
6768
+ thinkingText = reply.slice(thinkStart + "<think>".length, thinkEnd);
6769
+ fullReply = reply.slice(0, thinkStart) + reply.slice(thinkEnd + "</think>".length);
6770
+ thinkingDurationMs = 100;
6771
+ } else {
6772
+ fullReply = reply;
6773
+ }
6774
+ } else {
6775
+ fullReply = reply;
6776
+ }
6777
+ yield fullReply;
6778
+ }
6779
+ const runHallucination = ((_t = this.config.llm.options) == null ? void 0 : _t.hallucinationScoring) === true || this.config.llm.provider !== "ollama" && ((_u = this.config.llm.options) == null ? void 0 : _u.hallucinationScoring) !== false;
6780
+ if (runHallucination) {
6781
+ hallucinationScoringPromise = scoreHallucination(this.llmProvider, fullReply, context).catch(() => void 0);
6455
6782
  }
6456
6783
  const generateMs = performance.now() - generateStart;
6457
6784
  const totalMs = performance.now() - requestStart;
6458
6785
  const latency = {
6459
6786
  embedMs: Math.round(embedMs),
6460
6787
  retrieveMs: Math.round(retrieveMs),
6461
- rerankMs: ((_m = this.config.rag) == null ? void 0 : _m.useReranking) ? Math.round(rerankMs) : void 0,
6788
+ rerankMs: ((_v = this.config.rag) == null ? void 0 : _v.useReranking) ? Math.round(rerankMs) : void 0,
6462
6789
  generateMs: Math.round(generateMs),
6463
6790
  totalMs: Math.round(totalMs)
6464
6791
  };
@@ -6471,12 +6798,16 @@ ${context}`;
6471
6798
  totalTokens: promptTokens + completionTokens,
6472
6799
  estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model)
6473
6800
  };
6474
- const trace = {
6801
+ const [uiTransformation, hallucinationResult] = yield new __await(Promise.all([
6802
+ uiTransformationPromise,
6803
+ hallucinationScoringPromise
6804
+ ]));
6805
+ const trace = __spreadValues({
6475
6806
  requestId,
6476
6807
  query: question,
6477
6808
  rewrittenQuery,
6478
6809
  systemPrompt,
6479
- userPrompt: question + restrictionSuffix,
6810
+ userPrompt: question + finalRestrictionSuffix,
6480
6811
  chunks: sources.map((s) => {
6481
6812
  var _a2;
6482
6813
  return {
@@ -6490,22 +6821,19 @@ ${context}`;
6490
6821
  latency,
6491
6822
  tokens,
6492
6823
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6493
- };
6494
- const uiTransformation = yield new __await(uiTransformationPromise);
6824
+ }, hallucinationResult ? {
6825
+ hallucinationScore: hallucinationResult.score,
6826
+ hallucinationReason: hallucinationResult.reason
6827
+ } : {});
6495
6828
  yield {
6496
6829
  reply: "",
6497
6830
  sources,
6498
6831
  graphData,
6499
6832
  ui_transformation: uiTransformation,
6500
- trace
6833
+ trace,
6834
+ thinking: thinkingText || void 0,
6835
+ thinkingMs: thinkingDurationMs > 0 ? Math.round(thinkingDurationMs) : void 0
6501
6836
  };
6502
- scoreHallucination(this.llmProvider, fullReply, context).then((hallucinationResult) => {
6503
- if (hallucinationResult) {
6504
- trace.hallucinationScore = hallucinationResult.score;
6505
- trace.hallucinationReason = hallucinationResult.reason;
6506
- }
6507
- }).catch(() => {
6508
- });
6509
6837
  } catch (error2) {
6510
6838
  throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
6511
6839
  }
@@ -6515,18 +6843,30 @@ ${context}`;
6515
6843
  * Universal retrieval method combining all enabled providers.
6516
6844
  * Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
6517
6845
  */
6518
- async generateUiTransformation(question, sources, trainedSchema, forceDeterministic = false) {
6846
+ async generateUiTransformation(question, sources, cachedSchema, forceDeterministic = false) {
6847
+ var _a;
6519
6848
  if (!sources || sources.length === 0) {
6520
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6849
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6521
6850
  }
6522
- if (forceDeterministic) {
6523
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6851
+ if (UITransformer.isProductQueryPublic(question)) {
6852
+ return UITransformer.transform(
6853
+ question,
6854
+ sources,
6855
+ this.config,
6856
+ cachedSchema,
6857
+ { visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
6858
+ );
6859
+ }
6860
+ const isLocalProvider = this.config.llm.provider === "ollama";
6861
+ const disableLlmUiTransform = ((_a = this.config.llm.options) == null ? void 0 : _a.disableLlmUiTransform) === true;
6862
+ if (forceDeterministic || isLocalProvider || disableLlmUiTransform) {
6863
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6524
6864
  }
6525
6865
  try {
6526
6866
  return await UITransformer.analyzeAndDecide(question, sources, this.llmRouter.get("fast"));
6527
6867
  } catch (err) {
6528
6868
  console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
6529
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6869
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6530
6870
  }
6531
6871
  }
6532
6872
  applyStructuredFilters(sources, filter) {
@@ -6610,25 +6950,29 @@ ${context}`;
6610
6950
  return Number.isFinite(numeric) ? numeric : null;
6611
6951
  }
6612
6952
  async retrieve(query, options) {
6613
- var _a, _b, _c, _d, _e, _f;
6953
+ var _a, _b, _c, _d, _e, _f, _g;
6614
6954
  const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
6615
6955
  const topK = (_b = options.topK) != null ? _b : 5;
6616
6956
  const cacheKey = `${ns}::${query}`;
6617
6957
  let queryVector = this.embeddingCache.get(cacheKey);
6618
- const strategy = await QueryProcessor.determineRetrievalStrategy(query, this.llmRouter.get("fast"));
6958
+ const useGraph = this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval);
6959
+ const strategy = await QueryProcessor.determineRetrievalStrategy(
6960
+ query,
6961
+ useGraph ? this.llmRouter.get("fast") : void 0
6962
+ );
6619
6963
  console.debug(`[Pipeline] Determined retrieval strategy: ${strategy}`);
6620
6964
  const [retrievedVector, graphData] = await Promise.all([
6621
6965
  // Only embed if we need vector search (strategy is 'vector' or 'both')
6622
6966
  strategy === "vector" || strategy === "both" ? queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }) : Promise.resolve([]),
6623
6967
  // Only query graph if we need graph search (strategy is 'graph' or 'both')
6624
- (strategy === "graph" || strategy === "both") && this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
6968
+ (strategy === "graph" || strategy === "both") && this.graphDB && ((_d = this.config.rag) == null ? void 0 : _d.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
6625
6969
  ]);
6626
6970
  if ((strategy === "vector" || strategy === "both") && !queryVector && retrievedVector && retrievedVector.length > 0) {
6627
6971
  this.embeddingCache.set(cacheKey, retrievedVector);
6628
6972
  queryVector = retrievedVector;
6629
6973
  }
6630
- const baseFilter = __spreadProps(__spreadValues({}, (_d = options.filter) != null ? _d : {}), { queryText: query });
6631
- const numericPredicates = QueryProcessor.extractNumericPredicates(query, (_e = this.config.rag) == null ? void 0 : _e.filterableFields);
6974
+ const baseFilter = __spreadProps(__spreadValues({}, (_e = options.filter) != null ? _e : {}), { queryText: query });
6975
+ const numericPredicates = QueryProcessor.extractNumericPredicates(query, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
6632
6976
  if (numericPredicates.length > 0) {
6633
6977
  baseFilter.__numericPredicates = numericPredicates;
6634
6978
  }
@@ -6639,7 +6983,7 @@ ${context}`;
6639
6983
  ) : [];
6640
6984
  const resolvedSources = [];
6641
6985
  for (const source of sources) {
6642
- const parentId = (_f = source.metadata) == null ? void 0 : _f.parent_id;
6986
+ const parentId = (_g = source.metadata) == null ? void 0 : _g.parent_id;
6643
6987
  if (parentId) {
6644
6988
  console.log(`[Pipeline] Multi-Vector: Found child chunk. Parent ID: ${parentId}`);
6645
6989
  resolvedSources.push(source);
@@ -6662,10 +7006,13 @@ New Question: ${question}
6662
7006
  Optimized Search Query:`;
6663
7007
  const rewrite = await this.llmProvider.chat(
6664
7008
  [
6665
- { role: "system", content: "You are an assistant that optimizes search queries for RAG systems." },
6666
7009
  { role: "user", content: prompt }
6667
7010
  ],
6668
- ""
7011
+ "",
7012
+ {
7013
+ systemPrompt: "You are an assistant that optimizes search queries for RAG systems. Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.",
7014
+ temperature: 0
7015
+ }
6669
7016
  );
6670
7017
  return rewrite.trim() || question;
6671
7018
  }
@@ -6689,10 +7036,13 @@ ${context}
6689
7036
  Suggestions:`;
6690
7037
  const response = await this.llmProvider.chat(
6691
7038
  [
6692
- { role: "system", content: "You are a helpful assistant that generates search suggestions." },
6693
7039
  { role: "user", content: prompt }
6694
7040
  ],
6695
- ""
7041
+ "",
7042
+ {
7043
+ systemPrompt: "You are a helpful assistant that generates search suggestions based on the provided snippets. Focus on questions that can be answered by the context, keep each under 10 words, and return ONLY a JSON array of strings.",
7044
+ temperature: 0
7045
+ }
6696
7046
  );
6697
7047
  const match = response.match(/\[[\s\S]*\]/);
6698
7048
  if (match) {
@@ -7031,6 +7381,10 @@ function createStreamHandler(configOrPlugin) {
7031
7381
  if (!isActive) break;
7032
7382
  if (typeof chunk === "string") {
7033
7383
  enqueue(sseTextFrame(chunk));
7384
+ } else if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "thinking") {
7385
+ enqueue(`data: ${JSON.stringify(chunk)}
7386
+
7387
+ `);
7034
7388
  } else {
7035
7389
  enqueue(sseMetaFrame(chunk));
7036
7390
  const responseChunk = chunk;