@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
@@ -640,6 +640,11 @@ var init_MultiTablePostgresProvider = __esm({
640
640
  CREATE INDEX IF NOT EXISTS ${this.uploadTable}_embedding_idx
641
641
  ON ${this.uploadTable}
642
642
  USING hnsw (embedding vector_cosine_ops)
643
+ `);
644
+ await client.query(`
645
+ CREATE INDEX IF NOT EXISTS "${this.uploadTable}_fts_idx"
646
+ ON "${this.uploadTable}"
647
+ USING gin (to_tsvector('english', content))
643
648
  `);
644
649
  const res = await client.query(`
645
650
  SELECT table_name
@@ -710,6 +715,11 @@ var init_MultiTablePostgresProvider = __esm({
710
715
  CREATE INDEX IF NOT EXISTS "${tableName}_embedding_idx"
711
716
  ON "${tableName}"
712
717
  USING hnsw (embedding vector_cosine_ops)
718
+ `);
719
+ await client.query(`
720
+ CREATE INDEX IF NOT EXISTS "${tableName}_fts_idx"
721
+ ON "${tableName}"
722
+ USING gin (to_tsvector('english', content))
713
723
  `);
714
724
  if (!this.tables.includes(tableName)) {
715
725
  this.tables.push(tableName);
@@ -801,8 +811,11 @@ var init_MultiTablePostgresProvider = __esm({
801
811
  const paramIdx = baseOffset + filterParams.length;
802
812
  const synonyms = (_a3 = FIELD_SYNONYMS[key]) != null ? _a3 : [];
803
813
  const keysToCheck = [key, ...synonyms];
804
- const coalesceExprs = keysToCheck.map((k) => `LOWER(metadata->>'${k}')`);
805
- return `COALESCE(${coalesceExprs.join(", ")}) = LOWER($${paramIdx})`;
814
+ const coalesceExprs = keysToCheck.flatMap((k) => [
815
+ `metadata->>'${k}'`,
816
+ `to_jsonb(t)->>'${k}'`
817
+ ]);
818
+ return `COALESCE(${coalesceExprs.map((expr) => `LOWER(${expr})`).join(", ")}) = LOWER($${paramIdx})`;
806
819
  });
807
820
  whereClause = `WHERE ${conditions.join(" AND ")}`;
808
821
  }
@@ -813,16 +826,35 @@ var init_MultiTablePostgresProvider = __esm({
813
826
  CASE WHEN LOWER(val) IN (${entityHints.map((h) => `'${h.replace(/'/g, "''")}'`).join(", ")})
814
827
  THEN 1.0 ELSE 0.0 END
815
828
  ), 0)
816
- FROM jsonb_each_text(to_jsonb(t)) AS kv(key, val)
829
+ FROM jsonb_each_text(COALESCE(v.metadata, k.metadata)) AS kv(key, val)
817
830
  WHERE key IN (${this.searchFields.map((f) => `'${f}'`).join(", ")})
818
831
  ) * 3.0` : "";
819
832
  sqlQuery = `
820
- SELECT *,
821
- (1 - (embedding <=> $1::vector)) AS vector_score,
822
- COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
823
- ((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
824
- FROM "${table}" t
825
- ${whereClause}
833
+ WITH vector_search AS (
834
+ SELECT *, (1 - (embedding <=> $1::vector)) AS vector_score
835
+ FROM "${table}" t
836
+ ${whereClause}
837
+ ORDER BY embedding <=> $1::vector ASC
838
+ LIMIT ${tableLimit}
839
+ ),
840
+ keyword_search AS (
841
+ SELECT *, COALESCE(ts_rank(to_tsvector('english', content), to_tsquery('english', $2)), 0) AS keyword_score
842
+ FROM "${table}" t
843
+ WHERE ${whereClause ? whereClause.replace("WHERE", "") : "TRUE"}
844
+ AND to_tsvector('english', content) @@ to_tsquery('english', $2)
845
+ ORDER BY keyword_score DESC
846
+ LIMIT ${tableLimit}
847
+ )
848
+ SELECT
849
+ COALESCE(v.id, k.id) AS id,
850
+ COALESCE(v.namespace, k.namespace) AS namespace,
851
+ COALESCE(v.content, k.content) AS content,
852
+ COALESCE(v.metadata, k.metadata) AS metadata,
853
+ COALESCE(v.vector_score, 0) AS vector_score,
854
+ COALESCE(k.keyword_score, 0) AS keyword_score,
855
+ (COALESCE(v.vector_score, 0) + COALESCE(k.keyword_score, 0) * 2.0 ${exactNameScoreExpr}) AS hybrid_score
856
+ FROM vector_search v
857
+ FULL OUTER JOIN keyword_search k ON v.id = k.id
826
858
  ORDER BY hybrid_score DESC
827
859
  LIMIT ${tableLimit}
828
860
  `;
@@ -833,7 +865,7 @@ var init_MultiTablePostgresProvider = __esm({
833
865
  (1 - (embedding <=> $1::vector)) AS hybrid_score
834
866
  FROM "${table}" t
835
867
  ${whereClause}
836
- ORDER BY hybrid_score DESC
868
+ ORDER BY embedding <=> $1::vector ASC
837
869
  LIMIT ${tableLimit}
838
870
  `;
839
871
  params = [vectorLiteral, ...filterParams];
@@ -1039,7 +1071,11 @@ var init_MongoDBProvider = __esm({
1039
1071
  if (key === "namespace") {
1040
1072
  vectorSearchFilter.namespace = value;
1041
1073
  } else {
1042
- matchFilter[key] = value;
1074
+ if (typeof value === "string") {
1075
+ matchFilter[key] = { $regex: new RegExp(`^${value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "i") };
1076
+ } else {
1077
+ matchFilter[key] = value;
1078
+ }
1043
1079
  }
1044
1080
  }
1045
1081
  const pipeline = [
@@ -2112,7 +2148,7 @@ function readEnum(env, name, fallback, allowed) {
2112
2148
  throw new Error(`[getRagConfig] ${name} must be one of: ${allowed.join(", ")}`);
2113
2149
  }
2114
2150
  function getEnvConfig(env = process.env, base) {
2115
- 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;
2151
+ 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;
2116
2152
  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__";
2117
2153
  const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
2118
2154
  const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
@@ -2182,7 +2218,7 @@ function getEnvConfig(env = process.env, base) {
2182
2218
  rest: "default",
2183
2219
  custom: "default"
2184
2220
  };
2185
- return {
2221
+ return __spreadValues({
2186
2222
  projectId,
2187
2223
  vectorDb: {
2188
2224
  provider: vectorProvider,
@@ -2198,7 +2234,9 @@ function getEnvConfig(env = process.env, base) {
2198
2234
  maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
2199
2235
  temperature: readNumber(env, "LLM_TEMPERATURE", 0.7),
2200
2236
  options: {
2201
- profile: readString(env, "LLM_UNIVERSAL_PROFILE")
2237
+ profile: readString(env, "LLM_UNIVERSAL_PROFILE"),
2238
+ thinking: readString(env, "LLM_THINKING") === "true" || readString(env, "LLM_THINKING") === "enabled" ? true : readString(env, "LLM_THINKING") === "false" ? false : void 0,
2239
+ thinkingBudget: env.LLM_THINKING_BUDGET ? parseInt(env.LLM_THINKING_BUDGET, 10) : void 0
2202
2240
  }
2203
2241
  },
2204
2242
  embedding: {
@@ -2230,11 +2268,56 @@ function getEnvConfig(env = process.env, base) {
2230
2268
  topK: readNumber(env, "RAG_TOP_K", 5),
2231
2269
  scoreThreshold: readNumber(env, "RAG_SCORE_THRESHOLD", 0),
2232
2270
  chunkSize: readNumber(env, "RAG_CHUNK_SIZE", 1e3),
2233
- chunkOverlap: readNumber(env, "RAG_CHUNK_OVERLAP", 200)
2271
+ chunkOverlap: readNumber(env, "RAG_CHUNK_OVERLAP", 200),
2272
+ filterableFields: (_Ha = readString(env, "RAG_FILTERABLE_FIELDS")) == null ? void 0 : _Ha.split(",").map((f) => f.trim()),
2273
+ // Query pipeline toggles — read from .env.local
2274
+ useQueryTransformation: readString(env, "RAG_USE_QUERY_TRANSFORMATION") === "true",
2275
+ useReranking: readString(env, "RAG_USE_RERANKING") === "true",
2276
+ useGraphRetrieval: readString(env, "RAG_USE_GRAPH_RETRIEVAL") === "true",
2277
+ architecture: (_Ia = readString(env, "RAG_ARCHITECTURE")) != null ? _Ia : "simple",
2278
+ chunkingStrategy: (_Ja = readString(env, "RAG_CHUNKING_STRATEGY")) != null ? _Ja : "recursive",
2279
+ uiMapping: (() => {
2280
+ const raw = readString(env, "RAG_UI_MAPPING");
2281
+ if (!raw) return void 0;
2282
+ try {
2283
+ return JSON.parse(raw);
2284
+ } catch (e) {
2285
+ return void 0;
2286
+ }
2287
+ })()
2234
2288
  }
2235
- };
2289
+ }, readString(env, "GRAPH_DB_PROVIDER") ? {
2290
+ graphDb: {
2291
+ provider: readString(env, "GRAPH_DB_PROVIDER"),
2292
+ options: {
2293
+ uri: readString(env, "GRAPH_DB_URI"),
2294
+ username: readString(env, "GRAPH_DB_USERNAME"),
2295
+ password: readString(env, "GRAPH_DB_PASSWORD")
2296
+ }
2297
+ }
2298
+ } : {});
2236
2299
  }
2237
2300
 
2301
+ // src/exceptions/index.ts
2302
+ var RetrivoraError = class extends Error {
2303
+ constructor(message, code, details) {
2304
+ super(message);
2305
+ this.name = new.target.name;
2306
+ this.code = code;
2307
+ this.details = details;
2308
+ }
2309
+ };
2310
+ var ProviderNotFoundException = class extends RetrivoraError {
2311
+ constructor(providerType, provider, details) {
2312
+ super(`Unsupported ${providerType} provider: ${provider}`, "PROVIDER_NOT_FOUND", details);
2313
+ }
2314
+ };
2315
+ var ConfigurationException = class extends RetrivoraError {
2316
+ constructor(message, details) {
2317
+ super(message, "CONFIGURATION_ERROR", details);
2318
+ }
2319
+ };
2320
+
2238
2321
  // src/core/ConfigResolver.ts
2239
2322
  var ConfigResolver = class {
2240
2323
  /**
@@ -2261,24 +2344,81 @@ var ConfigResolver = class {
2261
2344
  rag: hostConfig.rag ? __spreadValues(__spreadValues({}, envConfig.rag), hostConfig.rag) : envConfig.rag
2262
2345
  });
2263
2346
  }
2347
+ /**
2348
+ * Resolves the public SDK config shape used by `new Retrivora({...})`.
2349
+ * Supports aliases from the product prompt while preserving existing env
2350
+ * fallback behavior.
2351
+ */
2352
+ static resolveUniversal(hostConfig, env = process.env) {
2353
+ var _a;
2354
+ if (!hostConfig) return this.resolve(void 0, env);
2355
+ const normalized = __spreadProps(__spreadValues({}, hostConfig), {
2356
+ vectorDb: (_a = hostConfig.vectorDb) != null ? _a : hostConfig.vectorDatabase,
2357
+ rag: this.mergeRetrievalWorkflow(hostConfig.rag, hostConfig.retrieval, hostConfig.workflow)
2358
+ });
2359
+ return this.resolve(normalized, env);
2360
+ }
2264
2361
  /**
2265
2362
  * Validates the configuration for required fields.
2266
2363
  */
2267
2364
  static validate(config) {
2268
2365
  if (!config.projectId) {
2269
- throw new Error("[ConfigResolver] projectId is required");
2366
+ throw new ConfigurationException("[ConfigResolver] projectId is required");
2270
2367
  }
2271
2368
  if (!config.vectorDb.provider) {
2272
- throw new Error("[ConfigResolver] vectorDb.provider is required");
2369
+ throw new ConfigurationException("[ConfigResolver] vectorDb.provider is required");
2273
2370
  }
2274
2371
  if (!config.llm.provider) {
2275
- throw new Error("[ConfigResolver] llm.provider is required");
2372
+ throw new ConfigurationException("[ConfigResolver] llm.provider is required");
2276
2373
  }
2277
2374
  }
2375
+ static mergeRetrievalWorkflow(rag, retrieval, workflow) {
2376
+ var _a, _b, _c, _d, _e;
2377
+ if (!rag && !retrieval && !workflow) return void 0;
2378
+ const normalized = __spreadValues({}, rag != null ? rag : {});
2379
+ if (retrieval) {
2380
+ normalized.topK = (_a = retrieval.topK) != null ? _a : normalized.topK;
2381
+ normalized.scoreThreshold = (_b = retrieval.scoreThreshold) != null ? _b : normalized.scoreThreshold;
2382
+ normalized.useReranking = (_c = retrieval.useReranking) != null ? _c : normalized.useReranking;
2383
+ if (retrieval.strategy === "hybrid") normalized.architecture = (_d = normalized.architecture) != null ? _d : "hybrid";
2384
+ if (retrieval.strategy === "agentic") normalized.architecture = "agentic";
2385
+ if (retrieval.strategy === "contextual-compression") normalized.useReranking = true;
2386
+ }
2387
+ if (workflow == null ? void 0 : workflow.type) {
2388
+ const type = workflow.type;
2389
+ if (type === "agentic" || type === "agentic-rag") normalized.architecture = "agentic";
2390
+ else if (type === "hybrid-rag") normalized.architecture = "hybrid";
2391
+ else if (type === "graph-rag") {
2392
+ normalized.architecture = "graph";
2393
+ normalized.useGraphRetrieval = true;
2394
+ } else if (type === "simple-rag" || type === "rag") {
2395
+ normalized.architecture = (_e = normalized.architecture) != null ? _e : "simple";
2396
+ }
2397
+ }
2398
+ return normalized;
2399
+ }
2278
2400
  };
2279
2401
 
2280
2402
  // src/llm/providers/OpenAIProvider.ts
2281
2403
  import OpenAI from "openai";
2404
+
2405
+ // src/llm/utils.ts
2406
+ function buildSystemContent(systemPrompt, context) {
2407
+ const noContext = !context || context.trim() === "" || context.trim() === "No relevant context found.";
2408
+ if (systemPrompt.includes("{{context}}")) {
2409
+ return systemPrompt.replace("{{context}}", noContext ? "" : context);
2410
+ }
2411
+ if (noContext) {
2412
+ return systemPrompt;
2413
+ }
2414
+ return `${systemPrompt}
2415
+
2416
+ ---
2417
+ Context:
2418
+ ${context}`;
2419
+ }
2420
+
2421
+ // src/llm/providers/OpenAIProvider.ts
2282
2422
  var OpenAIProvider = class {
2283
2423
  constructor(llmConfig, embeddingConfig) {
2284
2424
  if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
@@ -2342,16 +2482,10 @@ var OpenAIProvider = class {
2342
2482
  }
2343
2483
  async chat(messages, context, options) {
2344
2484
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2345
- 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.
2346
-
2347
- Context:
2348
- ${context}`;
2485
+ 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.";
2349
2486
  const systemMessage = {
2350
2487
  role: "system",
2351
- content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
2352
-
2353
- Context:
2354
- ${context}`
2488
+ content: buildSystemContent(basePrompt, context)
2355
2489
  };
2356
2490
  const formattedMessages = [
2357
2491
  systemMessage,
@@ -2372,16 +2506,10 @@ ${context}`
2372
2506
  chatStream(messages, context, options) {
2373
2507
  return __asyncGenerator(this, null, function* () {
2374
2508
  var _a, _b, _c, _d, _e, _f, _g, _h;
2375
- 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.
2376
-
2377
- Context:
2378
- ${context}`;
2509
+ 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.";
2379
2510
  const systemMessage = {
2380
2511
  role: "system",
2381
- content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
2382
-
2383
- Context:
2384
- ${context}`
2512
+ content: buildSystemContent(basePrompt, context)
2385
2513
  };
2386
2514
  const formattedMessages = [
2387
2515
  systemMessage,
@@ -2502,58 +2630,87 @@ var AnthropicProvider = class {
2502
2630
  };
2503
2631
  }
2504
2632
  async chat(messages, context, options) {
2505
- var _a, _b, _c, _d;
2506
- 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.
2507
-
2508
- Context:
2509
- ${context}`;
2510
- const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2511
-
2512
- Context:
2513
- ${context}`;
2633
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2634
+ 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.";
2635
+ const system = buildSystemContent(basePrompt, context);
2514
2636
  const anthropicMessages = messages.map((m) => ({
2515
2637
  role: m.role === "assistant" ? "assistant" : "user",
2516
2638
  content: m.content
2517
2639
  }));
2518
- const response = await this.client.messages.create({
2640
+ const isClaude37 = this.llmConfig.model.includes("claude-3-7-sonnet");
2641
+ 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;
2642
+ const extraParams = {};
2643
+ if (isThinkingEnabled && isClaude37) {
2644
+ extraParams.betas = ["interleaved-thinking-2025-05-14"];
2645
+ const maxTokens = (_g = (_f = options == null ? void 0 : options.maxTokens) != null ? _f : this.llmConfig.maxTokens) != null ? _g : 4096;
2646
+ const budget = Math.min(
2647
+ typeof ((_h = this.llmConfig.options) == null ? void 0 : _h.thinkingBudget) === "number" ? (_i = this.llmConfig.options) == null ? void 0 : _i.thinkingBudget : 2048,
2648
+ maxTokens - 1
2649
+ );
2650
+ extraParams.thinking = {
2651
+ type: "enabled",
2652
+ budget_tokens: budget
2653
+ };
2654
+ extraParams.max_tokens = Math.max(maxTokens, budget + 1024);
2655
+ }
2656
+ const response = await this.client.messages.create(__spreadValues({
2519
2657
  model: this.llmConfig.model,
2520
- max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
2658
+ max_tokens: (_k = (_j = options == null ? void 0 : options.maxTokens) != null ? _j : this.llmConfig.maxTokens) != null ? _k : 1024,
2521
2659
  system,
2522
2660
  messages: anthropicMessages
2523
- });
2524
- const block = response.content[0];
2525
- return block.type === "text" ? block.text : "";
2661
+ }, extraParams));
2662
+ let reply = "";
2663
+ for (const block of response.content) {
2664
+ if (block.type === "text") {
2665
+ reply += block.text;
2666
+ }
2667
+ }
2668
+ return reply;
2526
2669
  }
2527
2670
  chatStream(messages, context, options) {
2528
2671
  return __asyncGenerator(this, null, function* () {
2529
- var _a, _b, _c, _d;
2530
- 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.
2531
-
2532
- Context:
2533
- ${context}`;
2534
- const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2535
-
2536
- Context:
2537
- ${context}`;
2672
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2673
+ 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.";
2674
+ const system = buildSystemContent(basePrompt, context);
2538
2675
  const anthropicMessages = messages.map((m) => ({
2539
2676
  role: m.role === "assistant" ? "assistant" : "user",
2540
2677
  content: m.content
2541
2678
  }));
2542
- const stream = yield new __await(this.client.messages.create({
2679
+ const isClaude37 = this.llmConfig.model.includes("claude-3-7-sonnet");
2680
+ 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;
2681
+ const extraParams = {};
2682
+ if (isThinkingEnabled && isClaude37) {
2683
+ extraParams.betas = ["interleaved-thinking-2025-05-14"];
2684
+ const maxTokens = (_g = (_f = options == null ? void 0 : options.maxTokens) != null ? _f : this.llmConfig.maxTokens) != null ? _g : 4096;
2685
+ const budget = Math.min(
2686
+ typeof ((_h = this.llmConfig.options) == null ? void 0 : _h.thinkingBudget) === "number" ? (_i = this.llmConfig.options) == null ? void 0 : _i.thinkingBudget : 2048,
2687
+ maxTokens - 1
2688
+ );
2689
+ extraParams.thinking = {
2690
+ type: "enabled",
2691
+ budget_tokens: budget
2692
+ };
2693
+ extraParams.max_tokens = Math.max(maxTokens, budget + 1024);
2694
+ }
2695
+ const stream = yield new __await(this.client.messages.create(__spreadValues({
2543
2696
  model: this.llmConfig.model,
2544
- max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
2697
+ max_tokens: (_k = (_j = options == null ? void 0 : options.maxTokens) != null ? _j : this.llmConfig.maxTokens) != null ? _k : 1024,
2545
2698
  system,
2546
2699
  messages: anthropicMessages,
2547
2700
  stream: true
2548
- }));
2701
+ }, extraParams)));
2549
2702
  if (!stream) {
2550
2703
  throw new Error("[AnthropicProvider] messages.create stream is undefined");
2551
2704
  }
2552
2705
  try {
2553
2706
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
2554
2707
  const chunk = temp.value;
2555
- if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
2556
- yield chunk.delta.text;
2708
+ if (chunk.type === "content_block_delta") {
2709
+ if (chunk.delta.type === "text_delta") {
2710
+ yield chunk.delta.text;
2711
+ } else if (chunk.delta.type === "thinking_delta") {
2712
+ yield `\0THINKING\0${chunk.delta.thinking}`;
2713
+ }
2557
2714
  }
2558
2715
  }
2559
2716
  } catch (temp) {
@@ -2597,9 +2754,10 @@ ${context}`;
2597
2754
  import axios from "axios";
2598
2755
  var OllamaProvider = class {
2599
2756
  constructor(llmConfig, embeddingConfig) {
2600
- var _a;
2757
+ var _a, _b;
2601
2758
  const baseURL = (_a = llmConfig.baseUrl) != null ? _a : "http://localhost:11434";
2602
- this.http = axios.create({ baseURL, timeout: 12e4 });
2759
+ const timeout = Number((_b = llmConfig.options) == null ? void 0 : _b.timeout) || 3e5;
2760
+ this.http = axios.create({ baseURL, timeout });
2603
2761
  this.llmConfig = llmConfig;
2604
2762
  this.embeddingConfig = embeddingConfig;
2605
2763
  }
@@ -2655,14 +2813,15 @@ var OllamaProvider = class {
2655
2813
  };
2656
2814
  }
2657
2815
  async chat(messages, context, options) {
2658
- var _a, _b, _c, _d;
2659
- const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
2816
+ var _a, _b, _c, _d, _e, _f;
2817
+ 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.";
2818
+ const system = buildSystemContent(basePrompt, context);
2660
2819
  const { data } = await this.http.post("/api/chat", {
2661
2820
  model: this.llmConfig.model,
2662
2821
  stream: false,
2663
2822
  options: {
2664
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
2665
- num_predict: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024
2823
+ temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : 0.7,
2824
+ num_predict: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : 1024
2666
2825
  },
2667
2826
  messages: [
2668
2827
  { role: "system", content: system },
@@ -2673,14 +2832,15 @@ var OllamaProvider = class {
2673
2832
  }
2674
2833
  chatStream(messages, context, options) {
2675
2834
  return __asyncGenerator(this, null, function* () {
2676
- var _a, _b, _c, _d, _e, _f;
2677
- const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
2835
+ var _a, _b, _c, _d, _e, _f, _g, _h;
2836
+ 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.";
2837
+ const system = buildSystemContent(basePrompt, context);
2678
2838
  const response = yield new __await(this.http.post("/api/chat", {
2679
2839
  model: this.llmConfig.model,
2680
2840
  stream: true,
2681
2841
  options: {
2682
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
2683
- num_predict: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024
2842
+ temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : 0.7,
2843
+ num_predict: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : 1024
2684
2844
  },
2685
2845
  messages: [
2686
2846
  { role: "system", content: system },
@@ -2702,7 +2862,7 @@ var OllamaProvider = class {
2702
2862
  if (!line.trim()) continue;
2703
2863
  try {
2704
2864
  const json = JSON.parse(line);
2705
- if ((_e = json.message) == null ? void 0 : _e.content) {
2865
+ if ((_g = json.message) == null ? void 0 : _g.content) {
2706
2866
  yield json.message.content;
2707
2867
  }
2708
2868
  if (json.done) return;
@@ -2724,26 +2884,12 @@ var OllamaProvider = class {
2724
2884
  if (lineBuffer.trim()) {
2725
2885
  try {
2726
2886
  const json = JSON.parse(lineBuffer);
2727
- if ((_f = json.message) == null ? void 0 : _f.content) yield json.message.content;
2887
+ if ((_h = json.message) == null ? void 0 : _h.content) yield json.message.content;
2728
2888
  } catch (e) {
2729
2889
  }
2730
2890
  }
2731
2891
  });
2732
2892
  }
2733
- buildSystemPrompt(context, overridePrompt) {
2734
- var _a;
2735
- if (overridePrompt) {
2736
- return overridePrompt;
2737
- }
2738
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
2739
-
2740
- Context:
2741
- ${context}`;
2742
- return systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2743
-
2744
- Context:
2745
- ${context}`;
2746
- }
2747
2893
  async embed(text, options) {
2748
2894
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2749
2895
  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";
@@ -2890,21 +3036,6 @@ var GeminiProvider = class {
2890
3036
  var _a, _b;
2891
3037
  return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
2892
3038
  }
2893
- /**
2894
- * Build the system instruction string, inserting the RAG context either via
2895
- * the `{{context}}` placeholder or by appending it.
2896
- */
2897
- buildSystemInstruction(context) {
2898
- var _a;
2899
- const base = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
2900
-
2901
- Context:
2902
- ${context}`;
2903
- return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
2904
-
2905
- Context:
2906
- ${context}`;
2907
- }
2908
3039
  /**
2909
3040
  * Convert ChatMessage[] to the Gemini contents format.
2910
3041
  *
@@ -2923,16 +3054,17 @@ ${context}`;
2923
3054
  // ILLMProvider — chat
2924
3055
  // -------------------------------------------------------------------------
2925
3056
  async chat(messages, context, options) {
2926
- var _a, _b, _c, _d;
3057
+ var _a, _b, _c, _d, _e, _f;
3058
+ 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.";
2927
3059
  const model = this.client.getGenerativeModel({
2928
3060
  model: this.llmConfig.model,
2929
- systemInstruction: this.buildSystemInstruction(context)
3061
+ systemInstruction: buildSystemContent(basePrompt, context)
2930
3062
  });
2931
3063
  const result = await model.generateContent({
2932
3064
  contents: this.buildGeminiContents(messages),
2933
3065
  generationConfig: {
2934
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
2935
- maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
3066
+ temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : DEFAULT_TEMPERATURE,
3067
+ maxOutputTokens: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : DEFAULT_MAX_TOKENS,
2936
3068
  stopSequences: options == null ? void 0 : options.stop
2937
3069
  }
2938
3070
  });
@@ -2940,16 +3072,17 @@ ${context}`;
2940
3072
  }
2941
3073
  chatStream(messages, context, options) {
2942
3074
  return __asyncGenerator(this, null, function* () {
2943
- var _a, _b, _c, _d;
3075
+ var _a, _b, _c, _d, _e, _f;
3076
+ 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.";
2944
3077
  const model = this.client.getGenerativeModel({
2945
3078
  model: this.llmConfig.model,
2946
- systemInstruction: this.buildSystemInstruction(context)
3079
+ systemInstruction: buildSystemContent(basePrompt, context)
2947
3080
  });
2948
3081
  const result = yield new __await(model.generateContentStream({
2949
3082
  contents: this.buildGeminiContents(messages),
2950
3083
  generationConfig: {
2951
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
2952
- maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
3084
+ temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : DEFAULT_TEMPERATURE,
3085
+ maxOutputTokens: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : DEFAULT_MAX_TOKENS,
2953
3086
  stopSequences: options == null ? void 0 : options.stop
2954
3087
  }
2955
3088
  }));
@@ -3345,7 +3478,9 @@ var LLMFactory = class _LLMFactory {
3345
3478
  if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
3346
3479
  return new UniversalLLMAdapter(llmConfig);
3347
3480
  }
3348
- throw new Error(
3481
+ throw new ProviderNotFoundException(
3482
+ "LLM",
3483
+ String(llmConfig.provider),
3349
3484
  `[LLMFactory] Unknown provider "${llmConfig.provider}". Built-in providers: ${_LLMFactory.listProviders().join(", ")}. Register a custom provider with LLMFactory.register().`
3350
3485
  );
3351
3486
  }
@@ -3477,7 +3612,7 @@ var ProviderRegistry = class {
3477
3612
  return UniversalVectorProvider2;
3478
3613
  }
3479
3614
  default:
3480
- throw new Error(`Unsupported vector provider: ${provider}`);
3615
+ throw new ProviderNotFoundException("vector", provider);
3481
3616
  }
3482
3617
  }
3483
3618
  static async createVectorProvider(config) {
@@ -3495,12 +3630,18 @@ var ProviderRegistry = class {
3495
3630
  return new SimpleGraphProvider2(config);
3496
3631
  }
3497
3632
  default:
3498
- throw new Error(`Unsupported graph provider: ${provider}`);
3633
+ throw new ProviderNotFoundException("graph", provider);
3499
3634
  }
3500
3635
  }
3501
3636
  static createLLMProvider(llmConfig, embeddingConfig) {
3502
3637
  return LLMFactory.create(llmConfig, embeddingConfig);
3503
3638
  }
3639
+ static registerLLMProvider(name, factory) {
3640
+ LLMFactory.register(name, factory);
3641
+ }
3642
+ static createEmbeddingProvider(embeddingConfig) {
3643
+ return LLMFactory.createEmbeddingProvider(embeddingConfig);
3644
+ }
3504
3645
  };
3505
3646
  ProviderRegistry.vectorProviders = {};
3506
3647
  ProviderRegistry.graphProviders = {};
@@ -3647,8 +3788,8 @@ var ConfigValidator = class {
3647
3788
  const errorItems = errors.filter((e) => e.severity === "error");
3648
3789
  if (errorItems.length > 0) {
3649
3790
  const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
3650
- throw new Error(`[ConfigValidator] Configuration validation failed:
3651
- ${message}`);
3791
+ throw new ConfigurationException(`[ConfigValidator] Configuration validation failed:
3792
+ ${message}`, errorItems);
3652
3793
  }
3653
3794
  }
3654
3795
  };
@@ -3835,20 +3976,17 @@ var Reranker = class {
3835
3976
  }
3836
3977
  try {
3837
3978
  const topN = matches.slice(0, 10);
3838
- const prompt = `You are a relevance ranking expert.
3839
- Given the following user query and a list of retrieved document chunks, rank the chunks by relevance to the query.
3840
- Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant.
3841
- Use the indices provided in brackets like [0], [1], etc.
3842
- Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.
3843
-
3844
- Query: "${query}"
3979
+ const response = await llm.chat(
3980
+ [{ role: "user", content: `Query: "${query}"
3845
3981
 
3846
3982
  Documents:
3847
- ${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}`;
3848
- const response = await llm.chat(
3849
- [{ role: "user", content: prompt }],
3983
+ ${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}` }],
3850
3984
  "",
3851
- { temperature: 0, maxTokens: 50 }
3985
+ {
3986
+ 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.',
3987
+ temperature: 0,
3988
+ maxTokens: 50
3989
+ }
3852
3990
  );
3853
3991
  const cleanedResponse = response.trim().replace(/[\[\]\s]/g, "");
3854
3992
  const rankedIndices = cleanedResponse.split(",").map(Number).filter((n) => !isNaN(n));
@@ -4354,6 +4492,18 @@ var QueryProcessor = class {
4354
4492
  }, normalizedField ? { field: normalizedField } : {}));
4355
4493
  }
4356
4494
  };
4495
+ const activeValidFields = validFields.length > 0 ? validFields : ["brand", "category", "price", "rating", "stock", "stock_quantity", "status", "name", "title"];
4496
+ const resolveValidField = (fieldStr) => {
4497
+ 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();
4498
+ const comparable = (v) => v.toLowerCase().replace(/[^a-z0-9]/g, "");
4499
+ const cleanedComparable = comparable(cleaned);
4500
+ if (!cleanedComparable) return null;
4501
+ const matchedField = activeValidFields.find((fieldName) => {
4502
+ const candidate = comparable(fieldName);
4503
+ return candidate === cleanedComparable || candidate.includes(cleanedComparable) || cleanedComparable.includes(candidate);
4504
+ });
4505
+ return matchedField != null ? matchedField : null;
4506
+ };
4357
4507
  for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
4358
4508
  addHint(match[1]);
4359
4509
  }
@@ -4378,7 +4528,13 @@ var QueryProcessor = class {
4378
4528
  const field = match[1];
4379
4529
  const value = match[2];
4380
4530
  if (field && !this.isLikelyPromptPhrase(field)) {
4381
- addHint(value, field);
4531
+ const resolvedField = resolveValidField(field);
4532
+ if (resolvedField) {
4533
+ addHint(value, resolvedField);
4534
+ } else {
4535
+ addHint(value);
4536
+ addHint(field);
4537
+ }
4382
4538
  }
4383
4539
  }
4384
4540
  if (validFields.length > 0) {
@@ -4424,12 +4580,16 @@ var QueryProcessor = class {
4424
4580
  ];
4425
4581
  for (const pattern of fieldValuePatterns) {
4426
4582
  for (const match of question.matchAll(pattern)) {
4427
- const field = this.normalizeHintValue((_c = match[1]) != null ? _c : "");
4583
+ const field = (_c = match[1]) != null ? _c : "";
4428
4584
  const value = (_d = match[2]) != null ? _d : "";
4429
- if (field && !this.isLikelyPromptPhrase(field)) {
4430
- addHint(value, field);
4585
+ const resolvedField = resolveValidField(field);
4586
+ if (resolvedField) {
4587
+ addHint(value, resolvedField);
4431
4588
  } else {
4432
4589
  addHint(value);
4590
+ if (field && !this.isLikelyPromptPhrase(field)) {
4591
+ addHint(field);
4592
+ }
4433
4593
  }
4434
4594
  }
4435
4595
  }
@@ -4656,7 +4816,7 @@ var LLMRouter = class {
4656
4816
 
4657
4817
  // src/utils/UITransformer.ts
4658
4818
  init_synonyms();
4659
- var UITransformer = class {
4819
+ var UITransformer = class _UITransformer {
4660
4820
  // ─── Public Entry Points ─────────────────────────────────────────────────
4661
4821
  /**
4662
4822
  * Heuristic-only transform (no LLM required).
@@ -4718,7 +4878,7 @@ var UITransformer = class {
4718
4878
  static async analyzeAndDecide(query, sources, llm) {
4719
4879
  let intent;
4720
4880
  try {
4721
- intent = await this.detectIntent(query, llm);
4881
+ intent = await this.detectIntent(query, llm, sources);
4722
4882
  console.debug("[UITransformer] Detected intent:", intent);
4723
4883
  } catch (err) {
4724
4884
  console.warn("[UITransformer] Intent detection failed; using heuristic.", err);
@@ -4736,11 +4896,23 @@ var UITransformer = class {
4736
4896
  try {
4737
4897
  const context = this.buildContextSummary(sources);
4738
4898
  const systemPrompt = this.buildVisualizationSystemPrompt();
4899
+ const profile = this.profileData(sources);
4900
+ const schemaContext = `
4901
+ RETRIEVED DATA SCHEMA:
4902
+ - Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4903
+ - Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4904
+ - Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4905
+ - Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4906
+ - Text/Other fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
4907
+ `;
4739
4908
  const userPrompt = [
4740
4909
  `USER QUESTION: ${query}`,
4741
4910
  "",
4742
4911
  `DETECTED INTENT (JSON): ${JSON.stringify(intent)}`,
4743
4912
  "",
4913
+ `RETRIEVED DATA SCHEMA:`,
4914
+ schemaContext,
4915
+ "",
4744
4916
  "RETRIEVED DATA (JSON):",
4745
4917
  context
4746
4918
  ].join("\n");
@@ -4751,6 +4923,20 @@ var UITransformer = class {
4751
4923
  );
4752
4924
  const parsed = this.parseTransformationResponse(rawResponse);
4753
4925
  if (parsed) {
4926
+ let isCompatible = true;
4927
+ if (parsed.type === "line_chart" && (profile.dateFields.length === 0 || profile.numericFields.length === 0)) {
4928
+ isCompatible = false;
4929
+ } else if (["bar_chart", "horizontal_bar", "pie_chart", "donut_chart"].includes(parsed.type) && (profile.categoricalFields.length === 0 && profile.numericFields.length === 0)) {
4930
+ isCompatible = false;
4931
+ } else if (parsed.type === "scatter_plot" && profile.numericFields.length < 2) {
4932
+ isCompatible = false;
4933
+ } else if (parsed.type === "metric_card" && profile.numericFields.length === 0) {
4934
+ isCompatible = false;
4935
+ }
4936
+ if (!isCompatible) {
4937
+ console.warn(`[UITransformer] LLM chose incompatible visualization type "${parsed.type}" for retrieved schema. Falling back to heuristic.`);
4938
+ return this.transform(query, sources, void 0, void 0, intent);
4939
+ }
4754
4940
  const intentAllowsTable = intent.wantsExplicitTable || ["tabular", "table", "geographic"].includes(intent.visualizationHint);
4755
4941
  const intentWantsPieLikeChart = ["pie_chart", "donut_chart"].includes(intent.recommendedChart) || ["composition", "category_breakdown"].includes(intent.visualizationHint);
4756
4942
  if (parsed.type === "table" && !intentAllowsTable) {
@@ -4780,7 +4966,26 @@ var UITransformer = class {
4780
4966
  * - The intent object can be reused across both the heuristic and LLM paths.
4781
4967
  * - It is easy to unit-test intent detection in isolation.
4782
4968
  */
4783
- static async detectIntent(query, llm) {
4969
+ static async detectIntent(query, llm, sources) {
4970
+ let schemaProfileText = "";
4971
+ if (sources && sources.length > 0) {
4972
+ const profile = this.profileData(sources);
4973
+ const hasProducts = sources.some((item) => this.isProductData(item));
4974
+ schemaProfileText = `
4975
+ RETRIEVED DATA SCHEMA:
4976
+ - Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4977
+ - Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4978
+ - Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4979
+ - Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
4980
+ - Text fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
4981
+
4982
+ Please choose visualizationHint and recommendedChart dynamically based on the available schema.
4983
+ - Do NOT recommend "trend" or "line_chart" if there are no Date fields.
4984
+ - 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.
4985
+ - If there are multiple metadata fields but no clear categories or numbers, "tabular"/"table" is a highly accurate default.
4986
+ ${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".` : ""}
4987
+ `;
4988
+ }
4784
4989
  const systemPrompt = `You are an intent classifier for a product-search RAG system.
4785
4990
  Given a user query, return ONLY a valid JSON object with this exact shape \u2014 no prose, no markdown:
4786
4991
 
@@ -4816,8 +5021,11 @@ RULES:
4816
5021
  - isTemporal: true if time words appear (trend, historical, over time, last year, monthly, etc.)
4817
5022
  - isComparison: true if user compares, ranks, or contrasts entities or categories.
4818
5023
  - language: detect from the query text itself; default "en" if uncertain.`;
5024
+ const userPrompt = `QUERY: ${query}${schemaProfileText ? `
5025
+
5026
+ ${schemaProfileText}` : ""}`;
4819
5027
  const rawResponse = await llm.chat(
4820
- [{ role: "user", content: `QUERY: ${query}` }],
5028
+ [{ role: "user", content: userPrompt }],
4821
5029
  "",
4822
5030
  { systemPrompt, temperature: 0 }
4823
5031
  );
@@ -5260,10 +5468,21 @@ RULES:
5260
5468
  const hasEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
5261
5469
  return asksForRecords && (hasNumericPredicate || hasEntityTerms);
5262
5470
  }
5471
+ /** @internal kept private for transform() internal logic */
5263
5472
  static isProductQuery(query) {
5473
+ return _UITransformer._productQueryTest(query);
5474
+ }
5475
+ /**
5476
+ * Public entry-point for external callers (e.g. Pipeline)
5477
+ * to check whether a query maps to product_browse without triggering an LLM call.
5478
+ */
5479
+ static isProductQueryPublic(query) {
5480
+ return _UITransformer._productQueryTest(query);
5481
+ }
5482
+ static _productQueryTest(query) {
5264
5483
  const q = query.toLowerCase();
5265
5484
  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);
5266
- const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list)\b/.test(q);
5485
+ const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list|explore|exploring)\b/.test(q);
5267
5486
  const nonProductEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
5268
5487
  return productTerms && productAction && !nonProductEntityTerms;
5269
5488
  }
@@ -6036,23 +6255,19 @@ function estimateCostUsd(promptTokens, completionTokens, model) {
6036
6255
  async function scoreHallucination(llm, answer, context) {
6037
6256
  const maxContextChars = 3e3;
6038
6257
  const truncatedContext = context.length > maxContextChars ? context.slice(0, maxContextChars) + "\n...[truncated]" : context;
6039
- 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.
6040
-
6041
- CONTEXT:
6258
+ try {
6259
+ const raw = await llm.chat(
6260
+ [{ role: "user", content: `CONTEXT:
6042
6261
  ${truncatedContext}
6043
6262
 
6044
6263
  ANSWER:
6045
- ${answer}
6046
-
6047
- Return ONLY a valid JSON object with no markdown fences:
6048
- {"score": <float 0-1>, "reason": "<one sentence>"}
6049
-
6050
- Where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.`;
6051
- try {
6052
- const raw = await llm.chat(
6053
- [{ role: "user", content: prompt }],
6264
+ ${answer}` }],
6054
6265
  "",
6055
- { temperature: 0, maxTokens: 120 }
6266
+ {
6267
+ 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.',
6268
+ temperature: 0,
6269
+ maxTokens: 120
6270
+ }
6056
6271
  );
6057
6272
  const jsonMatch = raw.match(/\{[\s\S]*\}/);
6058
6273
  if (!jsonMatch) return void 0;
@@ -6112,7 +6327,10 @@ var Pipeline = class {
6112
6327
  - 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.
6113
6328
  - Do NOT try to format product lists as tables.
6114
6329
  `;
6115
- this.config.llm.systemPrompt = chartInstruction;
6330
+ const userPromptPrefix = this.config.llm.systemPrompt ? `${this.config.llm.systemPrompt}
6331
+
6332
+ ` : "";
6333
+ this.config.llm.systemPrompt = `${userPromptPrefix}${chartInstruction}`;
6116
6334
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
6117
6335
  this.llmRouter = new LLMRouter(this.config);
6118
6336
  const { llmProvider: resolvedLLM, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
@@ -6290,11 +6508,11 @@ var Pipeline = class {
6290
6508
  */
6291
6509
  askStream(_0) {
6292
6510
  return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
6293
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
6511
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
6294
6512
  yield new __await(this.initialize());
6295
6513
  const ns = namespace != null ? namespace : this.config.projectId;
6296
6514
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
6297
- const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
6515
+ const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0.3;
6298
6516
  const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
6299
6517
  const requestStart = performance.now();
6300
6518
  try {
@@ -6313,12 +6531,13 @@ var Pipeline = class {
6313
6531
  const embedStart = performance.now();
6314
6532
  const cacheKey = `${ns}::${searchQuery}`;
6315
6533
  const cachedVector = this.embeddingCache.get(cacheKey);
6534
+ const useGraph = this.graphDB && ((_h = this.config.rag) == null ? void 0 : _h.useGraphRetrieval);
6316
6535
  const [strategyResult, embeddedVector] = yield new __await(Promise.all([
6317
6536
  QueryProcessor.determineRetrievalStrategy(
6318
6537
  searchQuery,
6319
- this.llmRouter.get("fast"),
6320
- (_h = this.config.rag) == null ? void 0 : _h.graphKeywords,
6321
- (_i = this.config.rag) == null ? void 0 : _i.vectorKeywords
6538
+ useGraph ? this.llmRouter.get("fast") : void 0,
6539
+ (_i = this.config.rag) == null ? void 0 : _i.graphKeywords,
6540
+ (_j = this.config.rag) == null ? void 0 : _j.vectorKeywords
6322
6541
  ),
6323
6542
  // Embed immediately regardless of strategy — costs nothing if cached.
6324
6543
  // If strategy turns out to be 'graph'-only we just won't use the vector.
@@ -6328,7 +6547,7 @@ var Pipeline = class {
6328
6547
  if (!cachedVector && queryVector.length > 0) {
6329
6548
  this.embeddingCache.set(cacheKey, queryVector);
6330
6549
  }
6331
- 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;
6550
+ 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;
6332
6551
  const hasMetadataFilter = filter.metadata && Object.keys(filter.metadata).length > 0;
6333
6552
  const hasNumericPredicates = Array.isArray(filter.__numericPredicates) && filter.__numericPredicates.length > 0;
6334
6553
  const wantsExhaustiveList = hasNumericPredicates || hasMetadataFilter || /\b(list|all|show|provide|give|get|browse|find|search|display)\b/i.test(question);
@@ -6341,7 +6560,7 @@ var Pipeline = class {
6341
6560
  const structuredSources = this.applyStructuredFilters(rawSources, filter);
6342
6561
  let fullSources = hasNumericPredicates ? structuredSources : structuredSources.filter((m) => m.score >= scoreThreshold);
6343
6562
  const rerankLimit = wantsExhaustiveList ? retrievalLimit : topK;
6344
- if (!hasNumericPredicates && ((_k = this.config.rag) == null ? void 0 : _k.useReranking)) {
6563
+ if (!hasNumericPredicates && ((_l = this.config.rag) == null ? void 0 : _l.useReranking)) {
6345
6564
  fullSources = yield new __await(this.reranker.rerank(fullSources, question, rerankLimit));
6346
6565
  } else if (!wantsExhaustiveList) {
6347
6566
  fullSources = fullSources.slice(0, topK);
@@ -6372,34 +6591,117 @@ VECTOR CONTEXT:
6372
6591
  ${context}`;
6373
6592
  }
6374
6593
  const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
6375
- const trainedSchemaPromise = allMetadataKeys.length > 0 ? SchemaMapper.train(this.llmProvider, ns, allMetadataKeys).catch(() => void 0) : Promise.resolve(void 0);
6376
- const uiTransformationPromise = trainedSchemaPromise.then(
6377
- (trainedSchema) => this.generateUiTransformation(
6594
+ const cachedSchema = allMetadataKeys.length > 0 ? SchemaMapper.getCached(ns, allMetadataKeys) : void 0;
6595
+ if (allMetadataKeys.length > 0 && !cachedSchema) {
6596
+ SchemaMapper.train(this.llmProvider, ns, allMetadataKeys).catch(() => {
6597
+ });
6598
+ }
6599
+ const isProductQ = UITransformer.isProductQueryPublic(question);
6600
+ const uiTransformationPromise = isProductQ ? Promise.resolve(
6601
+ UITransformer.transform(
6378
6602
  question,
6379
6603
  sources,
6380
- trainedSchema,
6381
- hasNumericPredicates
6604
+ this.config,
6605
+ cachedSchema,
6606
+ { visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
6382
6607
  )
6608
+ ) : this.generateUiTransformation(
6609
+ question,
6610
+ sources,
6611
+ cachedSchema,
6612
+ hasNumericPredicates
6383
6613
  ).catch((uiError) => {
6384
6614
  console.warn("[Pipeline] UI transformation failed concurrently:", uiError);
6385
- return UITransformer.transform(question, sources, this.config);
6615
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6386
6616
  });
6617
+ let hallucinationScoringPromise = Promise.resolve(void 0);
6387
6618
  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.)";
6619
+ const isClaude37 = this.config.llm.model.includes("claude-3-7-sonnet");
6620
+ 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);
6621
+ const modelNameLower = this.config.llm.model.toLowerCase();
6622
+ const isReasoningModel = modelNameLower.includes("-r1") || modelNameLower.includes("deepseek-r1") || modelNameLower.includes("reasoning") || modelNameLower.includes("thinking");
6623
+ 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);
6624
+ let finalRestrictionSuffix = restrictionSuffix;
6625
+ if (isSimulatedThinking) {
6626
+ 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.`)";
6627
+ }
6388
6628
  const hardenedHistory = [...history];
6389
- const userQuestion = { role: "user", content: question + restrictionSuffix };
6629
+ const userQuestion = { role: "user", content: question + finalRestrictionSuffix };
6390
6630
  const messages = [...hardenedHistory, userQuestion];
6391
- const systemPrompt = (_l = this.config.llm.systemPrompt) != null ? _l : "";
6631
+ const systemPrompt = (_s = this.config.llm.systemPrompt) != null ? _s : "";
6392
6632
  const userPrompt = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
6393
6633
  let fullReply = "";
6634
+ let thinkingText = "";
6635
+ let thinkingStartMs = performance.now();
6636
+ let thinkingDurationMs = 0;
6394
6637
  const generateStart = performance.now();
6395
6638
  if (this.llmProvider.chatStream) {
6396
6639
  const stream = this.llmProvider.chatStream(messages, context);
6397
6640
  if (!stream) throw new Error(`[Pipeline] ${this.config.llm.provider} chatStream returned undefined`);
6641
+ let inThinking = false;
6642
+ let textBuffer = "";
6398
6643
  try {
6399
6644
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
6400
6645
  const chunk = temp.value;
6401
- fullReply += chunk;
6402
- yield chunk;
6646
+ if (chunk.startsWith("\0THINKING\0")) {
6647
+ const thinkingDelta = chunk.slice("\0THINKING\0".length);
6648
+ thinkingText += thinkingDelta;
6649
+ thinkingDurationMs = performance.now() - thinkingStartMs;
6650
+ yield { type: "thinking", text: thinkingDelta };
6651
+ continue;
6652
+ }
6653
+ if (isSimulatedThinking) {
6654
+ textBuffer += chunk;
6655
+ while (textBuffer.length > 0) {
6656
+ if (!inThinking) {
6657
+ const thinkIndex = textBuffer.indexOf("<think>");
6658
+ if (thinkIndex !== -1) {
6659
+ const before = textBuffer.slice(0, thinkIndex);
6660
+ if (before) {
6661
+ fullReply += before;
6662
+ yield before;
6663
+ }
6664
+ inThinking = true;
6665
+ thinkingStartMs = performance.now();
6666
+ textBuffer = textBuffer.slice(thinkIndex + "<think>".length);
6667
+ } else {
6668
+ const maxSafeLength = textBuffer.length - "<think>".length;
6669
+ if (maxSafeLength > 0) {
6670
+ const safeText = textBuffer.slice(0, maxSafeLength);
6671
+ fullReply += safeText;
6672
+ yield safeText;
6673
+ textBuffer = textBuffer.slice(maxSafeLength);
6674
+ }
6675
+ break;
6676
+ }
6677
+ } else {
6678
+ const endThinkIndex = textBuffer.indexOf("</think>");
6679
+ if (endThinkIndex !== -1) {
6680
+ const thinkingDelta = textBuffer.slice(0, endThinkIndex);
6681
+ if (thinkingDelta) {
6682
+ thinkingText += thinkingDelta;
6683
+ thinkingDurationMs = performance.now() - thinkingStartMs;
6684
+ yield { type: "thinking", text: thinkingDelta };
6685
+ }
6686
+ inThinking = false;
6687
+ textBuffer = textBuffer.slice(endThinkIndex + "</think>".length);
6688
+ } else {
6689
+ const maxSafeLength = textBuffer.length - "</think>".length;
6690
+ if (maxSafeLength > 0) {
6691
+ const thinkingDelta = textBuffer.slice(0, maxSafeLength);
6692
+ thinkingText += thinkingDelta;
6693
+ thinkingDurationMs = performance.now() - thinkingStartMs;
6694
+ yield { type: "thinking", text: thinkingDelta };
6695
+ textBuffer = textBuffer.slice(maxSafeLength);
6696
+ }
6697
+ break;
6698
+ }
6699
+ }
6700
+ }
6701
+ } else {
6702
+ fullReply += chunk;
6703
+ yield chunk;
6704
+ }
6403
6705
  }
6404
6706
  } catch (temp) {
6405
6707
  error = [temp];
@@ -6411,17 +6713,42 @@ ${context}`;
6411
6713
  throw error[0];
6412
6714
  }
6413
6715
  }
6716
+ if (isSimulatedThinking && textBuffer.length > 0) {
6717
+ if (inThinking) {
6718
+ thinkingText += textBuffer;
6719
+ yield { type: "thinking", text: textBuffer };
6720
+ } else {
6721
+ fullReply += textBuffer;
6722
+ yield textBuffer;
6723
+ }
6724
+ }
6414
6725
  } else {
6415
6726
  const reply = yield new __await(this.llmProvider.chat(messages, context));
6416
- fullReply = reply;
6417
- yield reply;
6727
+ if (isSimulatedThinking) {
6728
+ const thinkStart = reply.indexOf("<think>");
6729
+ const thinkEnd = reply.indexOf("</think>");
6730
+ if (thinkStart !== -1 && thinkEnd !== -1 && thinkEnd > thinkStart) {
6731
+ thinkingText = reply.slice(thinkStart + "<think>".length, thinkEnd);
6732
+ fullReply = reply.slice(0, thinkStart) + reply.slice(thinkEnd + "</think>".length);
6733
+ thinkingDurationMs = 100;
6734
+ } else {
6735
+ fullReply = reply;
6736
+ }
6737
+ } else {
6738
+ fullReply = reply;
6739
+ }
6740
+ yield fullReply;
6741
+ }
6742
+ 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;
6743
+ if (runHallucination) {
6744
+ hallucinationScoringPromise = scoreHallucination(this.llmProvider, fullReply, context).catch(() => void 0);
6418
6745
  }
6419
6746
  const generateMs = performance.now() - generateStart;
6420
6747
  const totalMs = performance.now() - requestStart;
6421
6748
  const latency = {
6422
6749
  embedMs: Math.round(embedMs),
6423
6750
  retrieveMs: Math.round(retrieveMs),
6424
- rerankMs: ((_m = this.config.rag) == null ? void 0 : _m.useReranking) ? Math.round(rerankMs) : void 0,
6751
+ rerankMs: ((_v = this.config.rag) == null ? void 0 : _v.useReranking) ? Math.round(rerankMs) : void 0,
6425
6752
  generateMs: Math.round(generateMs),
6426
6753
  totalMs: Math.round(totalMs)
6427
6754
  };
@@ -6434,12 +6761,16 @@ ${context}`;
6434
6761
  totalTokens: promptTokens + completionTokens,
6435
6762
  estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model)
6436
6763
  };
6437
- const trace = {
6764
+ const [uiTransformation, hallucinationResult] = yield new __await(Promise.all([
6765
+ uiTransformationPromise,
6766
+ hallucinationScoringPromise
6767
+ ]));
6768
+ const trace = __spreadValues({
6438
6769
  requestId,
6439
6770
  query: question,
6440
6771
  rewrittenQuery,
6441
6772
  systemPrompt,
6442
- userPrompt: question + restrictionSuffix,
6773
+ userPrompt: question + finalRestrictionSuffix,
6443
6774
  chunks: sources.map((s) => {
6444
6775
  var _a2;
6445
6776
  return {
@@ -6453,22 +6784,19 @@ ${context}`;
6453
6784
  latency,
6454
6785
  tokens,
6455
6786
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6456
- };
6457
- const uiTransformation = yield new __await(uiTransformationPromise);
6787
+ }, hallucinationResult ? {
6788
+ hallucinationScore: hallucinationResult.score,
6789
+ hallucinationReason: hallucinationResult.reason
6790
+ } : {});
6458
6791
  yield {
6459
6792
  reply: "",
6460
6793
  sources,
6461
6794
  graphData,
6462
6795
  ui_transformation: uiTransformation,
6463
- trace
6796
+ trace,
6797
+ thinking: thinkingText || void 0,
6798
+ thinkingMs: thinkingDurationMs > 0 ? Math.round(thinkingDurationMs) : void 0
6464
6799
  };
6465
- scoreHallucination(this.llmProvider, fullReply, context).then((hallucinationResult) => {
6466
- if (hallucinationResult) {
6467
- trace.hallucinationScore = hallucinationResult.score;
6468
- trace.hallucinationReason = hallucinationResult.reason;
6469
- }
6470
- }).catch(() => {
6471
- });
6472
6800
  } catch (error2) {
6473
6801
  throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
6474
6802
  }
@@ -6478,18 +6806,30 @@ ${context}`;
6478
6806
  * Universal retrieval method combining all enabled providers.
6479
6807
  * Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
6480
6808
  */
6481
- async generateUiTransformation(question, sources, trainedSchema, forceDeterministic = false) {
6809
+ async generateUiTransformation(question, sources, cachedSchema, forceDeterministic = false) {
6810
+ var _a;
6482
6811
  if (!sources || sources.length === 0) {
6483
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6812
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6484
6813
  }
6485
- if (forceDeterministic) {
6486
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6814
+ if (UITransformer.isProductQueryPublic(question)) {
6815
+ return UITransformer.transform(
6816
+ question,
6817
+ sources,
6818
+ this.config,
6819
+ cachedSchema,
6820
+ { visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
6821
+ );
6822
+ }
6823
+ const isLocalProvider = this.config.llm.provider === "ollama";
6824
+ const disableLlmUiTransform = ((_a = this.config.llm.options) == null ? void 0 : _a.disableLlmUiTransform) === true;
6825
+ if (forceDeterministic || isLocalProvider || disableLlmUiTransform) {
6826
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6487
6827
  }
6488
6828
  try {
6489
6829
  return await UITransformer.analyzeAndDecide(question, sources, this.llmRouter.get("fast"));
6490
6830
  } catch (err) {
6491
6831
  console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
6492
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6832
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6493
6833
  }
6494
6834
  }
6495
6835
  applyStructuredFilters(sources, filter) {
@@ -6573,25 +6913,29 @@ ${context}`;
6573
6913
  return Number.isFinite(numeric) ? numeric : null;
6574
6914
  }
6575
6915
  async retrieve(query, options) {
6576
- var _a, _b, _c, _d, _e, _f;
6916
+ var _a, _b, _c, _d, _e, _f, _g;
6577
6917
  const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
6578
6918
  const topK = (_b = options.topK) != null ? _b : 5;
6579
6919
  const cacheKey = `${ns}::${query}`;
6580
6920
  let queryVector = this.embeddingCache.get(cacheKey);
6581
- const strategy = await QueryProcessor.determineRetrievalStrategy(query, this.llmRouter.get("fast"));
6921
+ const useGraph = this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval);
6922
+ const strategy = await QueryProcessor.determineRetrievalStrategy(
6923
+ query,
6924
+ useGraph ? this.llmRouter.get("fast") : void 0
6925
+ );
6582
6926
  console.debug(`[Pipeline] Determined retrieval strategy: ${strategy}`);
6583
6927
  const [retrievedVector, graphData] = await Promise.all([
6584
6928
  // Only embed if we need vector search (strategy is 'vector' or 'both')
6585
6929
  strategy === "vector" || strategy === "both" ? queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }) : Promise.resolve([]),
6586
6930
  // Only query graph if we need graph search (strategy is 'graph' or 'both')
6587
- (strategy === "graph" || strategy === "both") && this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
6931
+ (strategy === "graph" || strategy === "both") && this.graphDB && ((_d = this.config.rag) == null ? void 0 : _d.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
6588
6932
  ]);
6589
6933
  if ((strategy === "vector" || strategy === "both") && !queryVector && retrievedVector && retrievedVector.length > 0) {
6590
6934
  this.embeddingCache.set(cacheKey, retrievedVector);
6591
6935
  queryVector = retrievedVector;
6592
6936
  }
6593
- const baseFilter = __spreadProps(__spreadValues({}, (_d = options.filter) != null ? _d : {}), { queryText: query });
6594
- const numericPredicates = QueryProcessor.extractNumericPredicates(query, (_e = this.config.rag) == null ? void 0 : _e.filterableFields);
6937
+ const baseFilter = __spreadProps(__spreadValues({}, (_e = options.filter) != null ? _e : {}), { queryText: query });
6938
+ const numericPredicates = QueryProcessor.extractNumericPredicates(query, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
6595
6939
  if (numericPredicates.length > 0) {
6596
6940
  baseFilter.__numericPredicates = numericPredicates;
6597
6941
  }
@@ -6602,7 +6946,7 @@ ${context}`;
6602
6946
  ) : [];
6603
6947
  const resolvedSources = [];
6604
6948
  for (const source of sources) {
6605
- const parentId = (_f = source.metadata) == null ? void 0 : _f.parent_id;
6949
+ const parentId = (_g = source.metadata) == null ? void 0 : _g.parent_id;
6606
6950
  if (parentId) {
6607
6951
  console.log(`[Pipeline] Multi-Vector: Found child chunk. Parent ID: ${parentId}`);
6608
6952
  resolvedSources.push(source);
@@ -6625,10 +6969,13 @@ New Question: ${question}
6625
6969
  Optimized Search Query:`;
6626
6970
  const rewrite = await this.llmProvider.chat(
6627
6971
  [
6628
- { role: "system", content: "You are an assistant that optimizes search queries for RAG systems." },
6629
6972
  { role: "user", content: prompt }
6630
6973
  ],
6631
- ""
6974
+ "",
6975
+ {
6976
+ 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.",
6977
+ temperature: 0
6978
+ }
6632
6979
  );
6633
6980
  return rewrite.trim() || question;
6634
6981
  }
@@ -6652,10 +6999,13 @@ ${context}
6652
6999
  Suggestions:`;
6653
7000
  const response = await this.llmProvider.chat(
6654
7001
  [
6655
- { role: "system", content: "You are a helpful assistant that generates search suggestions." },
6656
7002
  { role: "user", content: prompt }
6657
7003
  ],
6658
- ""
7004
+ "",
7005
+ {
7006
+ 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.",
7007
+ temperature: 0
7008
+ }
6659
7009
  );
6660
7010
  const match = response.match(/\[[\s\S]*\]/);
6661
7011
  if (match) {
@@ -6994,6 +7344,10 @@ function createStreamHandler(configOrPlugin) {
6994
7344
  if (!isActive) break;
6995
7345
  if (typeof chunk === "string") {
6996
7346
  enqueue(sseTextFrame(chunk));
7347
+ } else if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "thinking") {
7348
+ enqueue(`data: ${JSON.stringify(chunk)}
7349
+
7350
+ `);
6997
7351
  } else {
6998
7352
  enqueue(sseMetaFrame(chunk));
6999
7353
  const responseChunk = chunk;