@retrivora-ai/rag-engine 1.9.2 → 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 +563 -205
  7. package/dist/handlers/index.mjs +563 -205
  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 +623 -205
  20. package/dist/server.mjs +615 -205
  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 +226 -68
  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 +19 -7
  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 = [
@@ -1090,12 +1126,15 @@ var init_MongoDBProvider = __esm({
1090
1126
  }
1091
1127
  );
1092
1128
  const results = await this.collection.aggregate(pipeline).toArray();
1093
- return results.map((res) => ({
1094
- id: res._id,
1095
- content: res[this.contentKey],
1096
- metadata: res[this.metadataKey] || {},
1097
- score: res.score
1098
- }));
1129
+ return results.map((res) => {
1130
+ const normalizedScore = res.score * 2 - 1;
1131
+ return {
1132
+ id: res._id,
1133
+ content: res[this.contentKey],
1134
+ metadata: res[this.metadataKey] || {},
1135
+ score: normalizedScore
1136
+ };
1137
+ });
1099
1138
  }
1100
1139
  async delete(id, namespace) {
1101
1140
  await this.collection.deleteOne(__spreadValues({ _id: id }, namespace ? { namespace } : {}));
@@ -2146,7 +2185,7 @@ function readEnum(env, name, fallback, allowed) {
2146
2185
  throw new Error(`[getRagConfig] ${name} must be one of: ${allowed.join(", ")}`);
2147
2186
  }
2148
2187
  function getEnvConfig(env = process.env, base) {
2149
- 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;
2150
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__";
2151
2190
  const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
2152
2191
  const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
@@ -2216,7 +2255,7 @@ function getEnvConfig(env = process.env, base) {
2216
2255
  rest: "default",
2217
2256
  custom: "default"
2218
2257
  };
2219
- return {
2258
+ return __spreadValues({
2220
2259
  projectId,
2221
2260
  vectorDb: {
2222
2261
  provider: vectorProvider,
@@ -2232,7 +2271,9 @@ function getEnvConfig(env = process.env, base) {
2232
2271
  maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
2233
2272
  temperature: readNumber(env, "LLM_TEMPERATURE", 0.7),
2234
2273
  options: {
2235
- 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
2236
2277
  }
2237
2278
  },
2238
2279
  embedding: {
@@ -2264,11 +2305,56 @@ function getEnvConfig(env = process.env, base) {
2264
2305
  topK: readNumber(env, "RAG_TOP_K", 5),
2265
2306
  scoreThreshold: readNumber(env, "RAG_SCORE_THRESHOLD", 0),
2266
2307
  chunkSize: readNumber(env, "RAG_CHUNK_SIZE", 1e3),
2267
- 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
+ })()
2268
2325
  }
2269
- };
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
+ } : {});
2270
2336
  }
2271
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
+
2272
2358
  // src/core/ConfigResolver.ts
2273
2359
  var ConfigResolver = class {
2274
2360
  /**
@@ -2295,24 +2381,81 @@ var ConfigResolver = class {
2295
2381
  rag: hostConfig.rag ? __spreadValues(__spreadValues({}, envConfig.rag), hostConfig.rag) : envConfig.rag
2296
2382
  });
2297
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
+ }
2298
2398
  /**
2299
2399
  * Validates the configuration for required fields.
2300
2400
  */
2301
2401
  static validate(config) {
2302
2402
  if (!config.projectId) {
2303
- throw new Error("[ConfigResolver] projectId is required");
2403
+ throw new ConfigurationException("[ConfigResolver] projectId is required");
2304
2404
  }
2305
2405
  if (!config.vectorDb.provider) {
2306
- throw new Error("[ConfigResolver] vectorDb.provider is required");
2406
+ throw new ConfigurationException("[ConfigResolver] vectorDb.provider is required");
2307
2407
  }
2308
2408
  if (!config.llm.provider) {
2309
- throw new Error("[ConfigResolver] llm.provider is required");
2409
+ throw new ConfigurationException("[ConfigResolver] llm.provider is required");
2310
2410
  }
2311
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
+ }
2312
2437
  };
2313
2438
 
2314
2439
  // src/llm/providers/OpenAIProvider.ts
2315
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
2316
2459
  var OpenAIProvider = class {
2317
2460
  constructor(llmConfig, embeddingConfig) {
2318
2461
  if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
@@ -2376,16 +2519,10 @@ var OpenAIProvider = class {
2376
2519
  }
2377
2520
  async chat(messages, context, options) {
2378
2521
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2379
- 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.
2380
-
2381
- Context:
2382
- ${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.";
2383
2523
  const systemMessage = {
2384
2524
  role: "system",
2385
- content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
2386
-
2387
- Context:
2388
- ${context}`
2525
+ content: buildSystemContent(basePrompt, context)
2389
2526
  };
2390
2527
  const formattedMessages = [
2391
2528
  systemMessage,
@@ -2406,16 +2543,10 @@ ${context}`
2406
2543
  chatStream(messages, context, options) {
2407
2544
  return __asyncGenerator(this, null, function* () {
2408
2545
  var _a, _b, _c, _d, _e, _f, _g, _h;
2409
- 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.
2410
-
2411
- Context:
2412
- ${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.";
2413
2547
  const systemMessage = {
2414
2548
  role: "system",
2415
- content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
2416
-
2417
- Context:
2418
- ${context}`
2549
+ content: buildSystemContent(basePrompt, context)
2419
2550
  };
2420
2551
  const formattedMessages = [
2421
2552
  systemMessage,
@@ -2536,58 +2667,87 @@ var AnthropicProvider = class {
2536
2667
  };
2537
2668
  }
2538
2669
  async chat(messages, context, options) {
2539
- var _a, _b, _c, _d;
2540
- 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.
2541
-
2542
- Context:
2543
- ${context}`;
2544
- const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2545
-
2546
- Context:
2547
- ${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);
2548
2673
  const anthropicMessages = messages.map((m) => ({
2549
2674
  role: m.role === "assistant" ? "assistant" : "user",
2550
2675
  content: m.content
2551
2676
  }));
2552
- 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({
2553
2694
  model: this.llmConfig.model,
2554
- 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,
2555
2696
  system,
2556
2697
  messages: anthropicMessages
2557
- });
2558
- const block = response.content[0];
2559
- 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;
2560
2706
  }
2561
2707
  chatStream(messages, context, options) {
2562
2708
  return __asyncGenerator(this, null, function* () {
2563
- var _a, _b, _c, _d;
2564
- 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.
2565
-
2566
- Context:
2567
- ${context}`;
2568
- const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2569
-
2570
- Context:
2571
- ${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);
2572
2712
  const anthropicMessages = messages.map((m) => ({
2573
2713
  role: m.role === "assistant" ? "assistant" : "user",
2574
2714
  content: m.content
2575
2715
  }));
2576
- 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({
2577
2733
  model: this.llmConfig.model,
2578
- 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,
2579
2735
  system,
2580
2736
  messages: anthropicMessages,
2581
2737
  stream: true
2582
- }));
2738
+ }, extraParams)));
2583
2739
  if (!stream) {
2584
2740
  throw new Error("[AnthropicProvider] messages.create stream is undefined");
2585
2741
  }
2586
2742
  try {
2587
2743
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
2588
2744
  const chunk = temp.value;
2589
- if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
2590
- 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
+ }
2591
2751
  }
2592
2752
  }
2593
2753
  } catch (temp) {
@@ -2631,9 +2791,10 @@ ${context}`;
2631
2791
  var import_axios = __toESM(require("axios"));
2632
2792
  var OllamaProvider = class {
2633
2793
  constructor(llmConfig, embeddingConfig) {
2634
- var _a;
2794
+ var _a, _b;
2635
2795
  const baseURL = (_a = llmConfig.baseUrl) != null ? _a : "http://localhost:11434";
2636
- 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 });
2637
2798
  this.llmConfig = llmConfig;
2638
2799
  this.embeddingConfig = embeddingConfig;
2639
2800
  }
@@ -2689,14 +2850,15 @@ var OllamaProvider = class {
2689
2850
  };
2690
2851
  }
2691
2852
  async chat(messages, context, options) {
2692
- var _a, _b, _c, _d;
2693
- 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);
2694
2856
  const { data } = await this.http.post("/api/chat", {
2695
2857
  model: this.llmConfig.model,
2696
2858
  stream: false,
2697
2859
  options: {
2698
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
2699
- 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
2700
2862
  },
2701
2863
  messages: [
2702
2864
  { role: "system", content: system },
@@ -2707,14 +2869,15 @@ var OllamaProvider = class {
2707
2869
  }
2708
2870
  chatStream(messages, context, options) {
2709
2871
  return __asyncGenerator(this, null, function* () {
2710
- var _a, _b, _c, _d, _e, _f;
2711
- 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);
2712
2875
  const response = yield new __await(this.http.post("/api/chat", {
2713
2876
  model: this.llmConfig.model,
2714
2877
  stream: true,
2715
2878
  options: {
2716
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
2717
- 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
2718
2881
  },
2719
2882
  messages: [
2720
2883
  { role: "system", content: system },
@@ -2736,7 +2899,7 @@ var OllamaProvider = class {
2736
2899
  if (!line.trim()) continue;
2737
2900
  try {
2738
2901
  const json = JSON.parse(line);
2739
- if ((_e = json.message) == null ? void 0 : _e.content) {
2902
+ if ((_g = json.message) == null ? void 0 : _g.content) {
2740
2903
  yield json.message.content;
2741
2904
  }
2742
2905
  if (json.done) return;
@@ -2758,26 +2921,12 @@ var OllamaProvider = class {
2758
2921
  if (lineBuffer.trim()) {
2759
2922
  try {
2760
2923
  const json = JSON.parse(lineBuffer);
2761
- 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;
2762
2925
  } catch (e) {
2763
2926
  }
2764
2927
  }
2765
2928
  });
2766
2929
  }
2767
- buildSystemPrompt(context, overridePrompt) {
2768
- var _a;
2769
- if (overridePrompt) {
2770
- return overridePrompt;
2771
- }
2772
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
2773
-
2774
- Context:
2775
- ${context}`;
2776
- return systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2777
-
2778
- Context:
2779
- ${context}`;
2780
- }
2781
2930
  async embed(text, options) {
2782
2931
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2783
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";
@@ -2924,21 +3073,6 @@ var GeminiProvider = class {
2924
3073
  var _a, _b;
2925
3074
  return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
2926
3075
  }
2927
- /**
2928
- * Build the system instruction string, inserting the RAG context either via
2929
- * the `{{context}}` placeholder or by appending it.
2930
- */
2931
- buildSystemInstruction(context) {
2932
- var _a;
2933
- const base = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
2934
-
2935
- Context:
2936
- ${context}`;
2937
- return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
2938
-
2939
- Context:
2940
- ${context}`;
2941
- }
2942
3076
  /**
2943
3077
  * Convert ChatMessage[] to the Gemini contents format.
2944
3078
  *
@@ -2957,16 +3091,17 @@ ${context}`;
2957
3091
  // ILLMProvider — chat
2958
3092
  // -------------------------------------------------------------------------
2959
3093
  async chat(messages, context, options) {
2960
- 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.";
2961
3096
  const model = this.client.getGenerativeModel({
2962
3097
  model: this.llmConfig.model,
2963
- systemInstruction: this.buildSystemInstruction(context)
3098
+ systemInstruction: buildSystemContent(basePrompt, context)
2964
3099
  });
2965
3100
  const result = await model.generateContent({
2966
3101
  contents: this.buildGeminiContents(messages),
2967
3102
  generationConfig: {
2968
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
2969
- 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,
2970
3105
  stopSequences: options == null ? void 0 : options.stop
2971
3106
  }
2972
3107
  });
@@ -2974,16 +3109,17 @@ ${context}`;
2974
3109
  }
2975
3110
  chatStream(messages, context, options) {
2976
3111
  return __asyncGenerator(this, null, function* () {
2977
- 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.";
2978
3114
  const model = this.client.getGenerativeModel({
2979
3115
  model: this.llmConfig.model,
2980
- systemInstruction: this.buildSystemInstruction(context)
3116
+ systemInstruction: buildSystemContent(basePrompt, context)
2981
3117
  });
2982
3118
  const result = yield new __await(model.generateContentStream({
2983
3119
  contents: this.buildGeminiContents(messages),
2984
3120
  generationConfig: {
2985
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
2986
- 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,
2987
3123
  stopSequences: options == null ? void 0 : options.stop
2988
3124
  }
2989
3125
  }));
@@ -3379,7 +3515,9 @@ var LLMFactory = class _LLMFactory {
3379
3515
  if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
3380
3516
  return new UniversalLLMAdapter(llmConfig);
3381
3517
  }
3382
- throw new Error(
3518
+ throw new ProviderNotFoundException(
3519
+ "LLM",
3520
+ String(llmConfig.provider),
3383
3521
  `[LLMFactory] Unknown provider "${llmConfig.provider}". Built-in providers: ${_LLMFactory.listProviders().join(", ")}. Register a custom provider with LLMFactory.register().`
3384
3522
  );
3385
3523
  }
@@ -3511,7 +3649,7 @@ var ProviderRegistry = class {
3511
3649
  return UniversalVectorProvider2;
3512
3650
  }
3513
3651
  default:
3514
- throw new Error(`Unsupported vector provider: ${provider}`);
3652
+ throw new ProviderNotFoundException("vector", provider);
3515
3653
  }
3516
3654
  }
3517
3655
  static async createVectorProvider(config) {
@@ -3529,12 +3667,18 @@ var ProviderRegistry = class {
3529
3667
  return new SimpleGraphProvider2(config);
3530
3668
  }
3531
3669
  default:
3532
- throw new Error(`Unsupported graph provider: ${provider}`);
3670
+ throw new ProviderNotFoundException("graph", provider);
3533
3671
  }
3534
3672
  }
3535
3673
  static createLLMProvider(llmConfig, embeddingConfig) {
3536
3674
  return LLMFactory.create(llmConfig, embeddingConfig);
3537
3675
  }
3676
+ static registerLLMProvider(name, factory) {
3677
+ LLMFactory.register(name, factory);
3678
+ }
3679
+ static createEmbeddingProvider(embeddingConfig) {
3680
+ return LLMFactory.createEmbeddingProvider(embeddingConfig);
3681
+ }
3538
3682
  };
3539
3683
  ProviderRegistry.vectorProviders = {};
3540
3684
  ProviderRegistry.graphProviders = {};
@@ -3681,8 +3825,8 @@ var ConfigValidator = class {
3681
3825
  const errorItems = errors.filter((e) => e.severity === "error");
3682
3826
  if (errorItems.length > 0) {
3683
3827
  const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
3684
- throw new Error(`[ConfigValidator] Configuration validation failed:
3685
- ${message}`);
3828
+ throw new ConfigurationException(`[ConfigValidator] Configuration validation failed:
3829
+ ${message}`, errorItems);
3686
3830
  }
3687
3831
  }
3688
3832
  };
@@ -3869,20 +4013,17 @@ var Reranker = class {
3869
4013
  }
3870
4014
  try {
3871
4015
  const topN = matches.slice(0, 10);
3872
- const prompt = `You are a relevance ranking expert.
3873
- Given the following user query and a list of retrieved document chunks, rank the chunks by relevance to the query.
3874
- Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant.
3875
- Use the indices provided in brackets like [0], [1], etc.
3876
- Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.
3877
-
3878
- Query: "${query}"
4016
+ const response = await llm.chat(
4017
+ [{ role: "user", content: `Query: "${query}"
3879
4018
 
3880
4019
  Documents:
3881
- ${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}`;
3882
- const response = await llm.chat(
3883
- [{ role: "user", content: prompt }],
4020
+ ${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}` }],
3884
4021
  "",
3885
- { 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
+ }
3886
4027
  );
3887
4028
  const cleanedResponse = response.trim().replace(/[\[\]\s]/g, "");
3888
4029
  const rankedIndices = cleanedResponse.split(",").map(Number).filter((n) => !isNaN(n));
@@ -4388,6 +4529,18 @@ var QueryProcessor = class {
4388
4529
  }, normalizedField ? { field: normalizedField } : {}));
4389
4530
  }
4390
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
+ };
4391
4544
  for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
4392
4545
  addHint(match[1]);
4393
4546
  }
@@ -4412,7 +4565,13 @@ var QueryProcessor = class {
4412
4565
  const field = match[1];
4413
4566
  const value = match[2];
4414
4567
  if (field && !this.isLikelyPromptPhrase(field)) {
4415
- addHint(value, field);
4568
+ const resolvedField = resolveValidField(field);
4569
+ if (resolvedField) {
4570
+ addHint(value, resolvedField);
4571
+ } else {
4572
+ addHint(value);
4573
+ addHint(field);
4574
+ }
4416
4575
  }
4417
4576
  }
4418
4577
  if (validFields.length > 0) {
@@ -4458,12 +4617,16 @@ var QueryProcessor = class {
4458
4617
  ];
4459
4618
  for (const pattern of fieldValuePatterns) {
4460
4619
  for (const match of question.matchAll(pattern)) {
4461
- const field = this.normalizeHintValue((_c = match[1]) != null ? _c : "");
4620
+ const field = (_c = match[1]) != null ? _c : "";
4462
4621
  const value = (_d = match[2]) != null ? _d : "";
4463
- if (field && !this.isLikelyPromptPhrase(field)) {
4464
- addHint(value, field);
4622
+ const resolvedField = resolveValidField(field);
4623
+ if (resolvedField) {
4624
+ addHint(value, resolvedField);
4465
4625
  } else {
4466
4626
  addHint(value);
4627
+ if (field && !this.isLikelyPromptPhrase(field)) {
4628
+ addHint(field);
4629
+ }
4467
4630
  }
4468
4631
  }
4469
4632
  }
@@ -4690,7 +4853,7 @@ var LLMRouter = class {
4690
4853
 
4691
4854
  // src/utils/UITransformer.ts
4692
4855
  init_synonyms();
4693
- var UITransformer = class {
4856
+ var UITransformer = class _UITransformer {
4694
4857
  // ─── Public Entry Points ─────────────────────────────────────────────────
4695
4858
  /**
4696
4859
  * Heuristic-only transform (no LLM required).
@@ -4752,7 +4915,7 @@ var UITransformer = class {
4752
4915
  static async analyzeAndDecide(query, sources, llm) {
4753
4916
  let intent;
4754
4917
  try {
4755
- intent = await this.detectIntent(query, llm);
4918
+ intent = await this.detectIntent(query, llm, sources);
4756
4919
  console.debug("[UITransformer] Detected intent:", intent);
4757
4920
  } catch (err) {
4758
4921
  console.warn("[UITransformer] Intent detection failed; using heuristic.", err);
@@ -4770,11 +4933,23 @@ var UITransformer = class {
4770
4933
  try {
4771
4934
  const context = this.buildContextSummary(sources);
4772
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
+ `;
4773
4945
  const userPrompt = [
4774
4946
  `USER QUESTION: ${query}`,
4775
4947
  "",
4776
4948
  `DETECTED INTENT (JSON): ${JSON.stringify(intent)}`,
4777
4949
  "",
4950
+ `RETRIEVED DATA SCHEMA:`,
4951
+ schemaContext,
4952
+ "",
4778
4953
  "RETRIEVED DATA (JSON):",
4779
4954
  context
4780
4955
  ].join("\n");
@@ -4785,6 +4960,20 @@ var UITransformer = class {
4785
4960
  );
4786
4961
  const parsed = this.parseTransformationResponse(rawResponse);
4787
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
+ }
4788
4977
  const intentAllowsTable = intent.wantsExplicitTable || ["tabular", "table", "geographic"].includes(intent.visualizationHint);
4789
4978
  const intentWantsPieLikeChart = ["pie_chart", "donut_chart"].includes(intent.recommendedChart) || ["composition", "category_breakdown"].includes(intent.visualizationHint);
4790
4979
  if (parsed.type === "table" && !intentAllowsTable) {
@@ -4814,7 +5003,26 @@ var UITransformer = class {
4814
5003
  * - The intent object can be reused across both the heuristic and LLM paths.
4815
5004
  * - It is easy to unit-test intent detection in isolation.
4816
5005
  */
4817
- 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
+ }
4818
5026
  const systemPrompt = `You are an intent classifier for a product-search RAG system.
4819
5027
  Given a user query, return ONLY a valid JSON object with this exact shape \u2014 no prose, no markdown:
4820
5028
 
@@ -4850,8 +5058,11 @@ RULES:
4850
5058
  - isTemporal: true if time words appear (trend, historical, over time, last year, monthly, etc.)
4851
5059
  - isComparison: true if user compares, ranks, or contrasts entities or categories.
4852
5060
  - language: detect from the query text itself; default "en" if uncertain.`;
5061
+ const userPrompt = `QUERY: ${query}${schemaProfileText ? `
5062
+
5063
+ ${schemaProfileText}` : ""}`;
4853
5064
  const rawResponse = await llm.chat(
4854
- [{ role: "user", content: `QUERY: ${query}` }],
5065
+ [{ role: "user", content: userPrompt }],
4855
5066
  "",
4856
5067
  { systemPrompt, temperature: 0 }
4857
5068
  );
@@ -5294,10 +5505,21 @@ RULES:
5294
5505
  const hasEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
5295
5506
  return asksForRecords && (hasNumericPredicate || hasEntityTerms);
5296
5507
  }
5508
+ /** @internal kept private for transform() internal logic */
5297
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) {
5298
5520
  const q = query.toLowerCase();
5299
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);
5300
- 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);
5301
5523
  const nonProductEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
5302
5524
  return productTerms && productAction && !nonProductEntityTerms;
5303
5525
  }
@@ -6070,23 +6292,19 @@ function estimateCostUsd(promptTokens, completionTokens, model) {
6070
6292
  async function scoreHallucination(llm, answer, context) {
6071
6293
  const maxContextChars = 3e3;
6072
6294
  const truncatedContext = context.length > maxContextChars ? context.slice(0, maxContextChars) + "\n...[truncated]" : context;
6073
- 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.
6074
-
6075
- CONTEXT:
6295
+ try {
6296
+ const raw = await llm.chat(
6297
+ [{ role: "user", content: `CONTEXT:
6076
6298
  ${truncatedContext}
6077
6299
 
6078
6300
  ANSWER:
6079
- ${answer}
6080
-
6081
- Return ONLY a valid JSON object with no markdown fences:
6082
- {"score": <float 0-1>, "reason": "<one sentence>"}
6083
-
6084
- Where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.`;
6085
- try {
6086
- const raw = await llm.chat(
6087
- [{ role: "user", content: prompt }],
6301
+ ${answer}` }],
6088
6302
  "",
6089
- { 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
+ }
6090
6308
  );
6091
6309
  const jsonMatch = raw.match(/\{[\s\S]*\}/);
6092
6310
  if (!jsonMatch) return void 0;
@@ -6146,7 +6364,10 @@ var Pipeline = class {
6146
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.
6147
6365
  - Do NOT try to format product lists as tables.
6148
6366
  `;
6149
- 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}`;
6150
6371
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
6151
6372
  this.llmRouter = new LLMRouter(this.config);
6152
6373
  const { llmProvider: resolvedLLM, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
@@ -6324,11 +6545,11 @@ var Pipeline = class {
6324
6545
  */
6325
6546
  askStream(_0) {
6326
6547
  return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
6327
- 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;
6328
6549
  yield new __await(this.initialize());
6329
6550
  const ns = namespace != null ? namespace : this.config.projectId;
6330
6551
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
6331
- 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;
6332
6553
  const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
6333
6554
  const requestStart = performance.now();
6334
6555
  try {
@@ -6347,12 +6568,13 @@ var Pipeline = class {
6347
6568
  const embedStart = performance.now();
6348
6569
  const cacheKey = `${ns}::${searchQuery}`;
6349
6570
  const cachedVector = this.embeddingCache.get(cacheKey);
6571
+ const useGraph = this.graphDB && ((_h = this.config.rag) == null ? void 0 : _h.useGraphRetrieval);
6350
6572
  const [strategyResult, embeddedVector] = yield new __await(Promise.all([
6351
6573
  QueryProcessor.determineRetrievalStrategy(
6352
6574
  searchQuery,
6353
- this.llmRouter.get("fast"),
6354
- (_h = this.config.rag) == null ? void 0 : _h.graphKeywords,
6355
- (_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
6356
6578
  ),
6357
6579
  // Embed immediately regardless of strategy — costs nothing if cached.
6358
6580
  // If strategy turns out to be 'graph'-only we just won't use the vector.
@@ -6362,7 +6584,7 @@ var Pipeline = class {
6362
6584
  if (!cachedVector && queryVector.length > 0) {
6363
6585
  this.embeddingCache.set(cacheKey, queryVector);
6364
6586
  }
6365
- 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;
6366
6588
  const hasMetadataFilter = filter.metadata && Object.keys(filter.metadata).length > 0;
6367
6589
  const hasNumericPredicates = Array.isArray(filter.__numericPredicates) && filter.__numericPredicates.length > 0;
6368
6590
  const wantsExhaustiveList = hasNumericPredicates || hasMetadataFilter || /\b(list|all|show|provide|give|get|browse|find|search|display)\b/i.test(question);
@@ -6375,7 +6597,7 @@ var Pipeline = class {
6375
6597
  const structuredSources = this.applyStructuredFilters(rawSources, filter);
6376
6598
  let fullSources = hasNumericPredicates ? structuredSources : structuredSources.filter((m) => m.score >= scoreThreshold);
6377
6599
  const rerankLimit = wantsExhaustiveList ? retrievalLimit : topK;
6378
- if (!hasNumericPredicates && ((_k = this.config.rag) == null ? void 0 : _k.useReranking)) {
6600
+ if (!hasNumericPredicates && ((_l = this.config.rag) == null ? void 0 : _l.useReranking)) {
6379
6601
  fullSources = yield new __await(this.reranker.rerank(fullSources, question, rerankLimit));
6380
6602
  } else if (!wantsExhaustiveList) {
6381
6603
  fullSources = fullSources.slice(0, topK);
@@ -6387,7 +6609,8 @@ ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
6387
6609
  if (hasMetadataFilter) {
6388
6610
  displayCount = fullSources.length;
6389
6611
  } else {
6390
- const highlyRelevant = fullSources.filter((m) => m.score >= 0.4);
6612
+ const baseThreshold = Math.max(scoreThreshold, 0.4);
6613
+ const highlyRelevant = fullSources.filter((m) => m.score >= baseThreshold);
6391
6614
  displayCount = Math.max(highlyRelevant.length, topK);
6392
6615
  if (displayCount > 15) {
6393
6616
  displayCount = 15;
@@ -6405,34 +6628,117 @@ VECTOR CONTEXT:
6405
6628
  ${context}`;
6406
6629
  }
6407
6630
  const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
6408
- const trainedSchemaPromise = allMetadataKeys.length > 0 ? SchemaMapper.train(this.llmProvider, ns, allMetadataKeys).catch(() => void 0) : Promise.resolve(void 0);
6409
- const uiTransformationPromise = trainedSchemaPromise.then(
6410
- (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(
6411
6639
  question,
6412
6640
  sources,
6413
- trainedSchema,
6414
- hasNumericPredicates
6641
+ this.config,
6642
+ cachedSchema,
6643
+ { visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
6415
6644
  )
6645
+ ) : this.generateUiTransformation(
6646
+ question,
6647
+ sources,
6648
+ cachedSchema,
6649
+ hasNumericPredicates
6416
6650
  ).catch((uiError) => {
6417
6651
  console.warn("[Pipeline] UI transformation failed concurrently:", uiError);
6418
- return UITransformer.transform(question, sources, this.config);
6652
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6419
6653
  });
6654
+ let hallucinationScoringPromise = Promise.resolve(void 0);
6420
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
+ }
6421
6665
  const hardenedHistory = [...history];
6422
- const userQuestion = { role: "user", content: question + restrictionSuffix };
6666
+ const userQuestion = { role: "user", content: question + finalRestrictionSuffix };
6423
6667
  const messages = [...hardenedHistory, userQuestion];
6424
- const systemPrompt = (_l = this.config.llm.systemPrompt) != null ? _l : "";
6668
+ const systemPrompt = (_s = this.config.llm.systemPrompt) != null ? _s : "";
6425
6669
  const userPrompt = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
6426
6670
  let fullReply = "";
6671
+ let thinkingText = "";
6672
+ let thinkingStartMs = performance.now();
6673
+ let thinkingDurationMs = 0;
6427
6674
  const generateStart = performance.now();
6428
6675
  if (this.llmProvider.chatStream) {
6429
6676
  const stream = this.llmProvider.chatStream(messages, context);
6430
6677
  if (!stream) throw new Error(`[Pipeline] ${this.config.llm.provider} chatStream returned undefined`);
6678
+ let inThinking = false;
6679
+ let textBuffer = "";
6431
6680
  try {
6432
6681
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
6433
6682
  const chunk = temp.value;
6434
- fullReply += chunk;
6435
- 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
+ }
6436
6742
  }
6437
6743
  } catch (temp) {
6438
6744
  error = [temp];
@@ -6444,17 +6750,42 @@ ${context}`;
6444
6750
  throw error[0];
6445
6751
  }
6446
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
+ }
6447
6762
  } else {
6448
6763
  const reply = yield new __await(this.llmProvider.chat(messages, context));
6449
- fullReply = reply;
6450
- 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);
6451
6782
  }
6452
6783
  const generateMs = performance.now() - generateStart;
6453
6784
  const totalMs = performance.now() - requestStart;
6454
6785
  const latency = {
6455
6786
  embedMs: Math.round(embedMs),
6456
6787
  retrieveMs: Math.round(retrieveMs),
6457
- 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,
6458
6789
  generateMs: Math.round(generateMs),
6459
6790
  totalMs: Math.round(totalMs)
6460
6791
  };
@@ -6467,12 +6798,16 @@ ${context}`;
6467
6798
  totalTokens: promptTokens + completionTokens,
6468
6799
  estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model)
6469
6800
  };
6470
- const trace = {
6801
+ const [uiTransformation, hallucinationResult] = yield new __await(Promise.all([
6802
+ uiTransformationPromise,
6803
+ hallucinationScoringPromise
6804
+ ]));
6805
+ const trace = __spreadValues({
6471
6806
  requestId,
6472
6807
  query: question,
6473
6808
  rewrittenQuery,
6474
6809
  systemPrompt,
6475
- userPrompt: question + restrictionSuffix,
6810
+ userPrompt: question + finalRestrictionSuffix,
6476
6811
  chunks: sources.map((s) => {
6477
6812
  var _a2;
6478
6813
  return {
@@ -6486,22 +6821,19 @@ ${context}`;
6486
6821
  latency,
6487
6822
  tokens,
6488
6823
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6489
- };
6490
- const uiTransformation = yield new __await(uiTransformationPromise);
6824
+ }, hallucinationResult ? {
6825
+ hallucinationScore: hallucinationResult.score,
6826
+ hallucinationReason: hallucinationResult.reason
6827
+ } : {});
6491
6828
  yield {
6492
6829
  reply: "",
6493
6830
  sources,
6494
6831
  graphData,
6495
6832
  ui_transformation: uiTransformation,
6496
- trace
6833
+ trace,
6834
+ thinking: thinkingText || void 0,
6835
+ thinkingMs: thinkingDurationMs > 0 ? Math.round(thinkingDurationMs) : void 0
6497
6836
  };
6498
- scoreHallucination(this.llmProvider, fullReply, context).then((hallucinationResult) => {
6499
- if (hallucinationResult) {
6500
- trace.hallucinationScore = hallucinationResult.score;
6501
- trace.hallucinationReason = hallucinationResult.reason;
6502
- }
6503
- }).catch(() => {
6504
- });
6505
6837
  } catch (error2) {
6506
6838
  throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
6507
6839
  }
@@ -6511,18 +6843,30 @@ ${context}`;
6511
6843
  * Universal retrieval method combining all enabled providers.
6512
6844
  * Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
6513
6845
  */
6514
- async generateUiTransformation(question, sources, trainedSchema, forceDeterministic = false) {
6846
+ async generateUiTransformation(question, sources, cachedSchema, forceDeterministic = false) {
6847
+ var _a;
6515
6848
  if (!sources || sources.length === 0) {
6516
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6849
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6517
6850
  }
6518
- if (forceDeterministic) {
6519
- 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);
6520
6864
  }
6521
6865
  try {
6522
6866
  return await UITransformer.analyzeAndDecide(question, sources, this.llmRouter.get("fast"));
6523
6867
  } catch (err) {
6524
6868
  console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
6525
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6869
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6526
6870
  }
6527
6871
  }
6528
6872
  applyStructuredFilters(sources, filter) {
@@ -6606,25 +6950,29 @@ ${context}`;
6606
6950
  return Number.isFinite(numeric) ? numeric : null;
6607
6951
  }
6608
6952
  async retrieve(query, options) {
6609
- var _a, _b, _c, _d, _e, _f;
6953
+ var _a, _b, _c, _d, _e, _f, _g;
6610
6954
  const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
6611
6955
  const topK = (_b = options.topK) != null ? _b : 5;
6612
6956
  const cacheKey = `${ns}::${query}`;
6613
6957
  let queryVector = this.embeddingCache.get(cacheKey);
6614
- 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
+ );
6615
6963
  console.debug(`[Pipeline] Determined retrieval strategy: ${strategy}`);
6616
6964
  const [retrievedVector, graphData] = await Promise.all([
6617
6965
  // Only embed if we need vector search (strategy is 'vector' or 'both')
6618
6966
  strategy === "vector" || strategy === "both" ? queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }) : Promise.resolve([]),
6619
6967
  // Only query graph if we need graph search (strategy is 'graph' or 'both')
6620
- (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)
6621
6969
  ]);
6622
6970
  if ((strategy === "vector" || strategy === "both") && !queryVector && retrievedVector && retrievedVector.length > 0) {
6623
6971
  this.embeddingCache.set(cacheKey, retrievedVector);
6624
6972
  queryVector = retrievedVector;
6625
6973
  }
6626
- const baseFilter = __spreadProps(__spreadValues({}, (_d = options.filter) != null ? _d : {}), { queryText: query });
6627
- 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);
6628
6976
  if (numericPredicates.length > 0) {
6629
6977
  baseFilter.__numericPredicates = numericPredicates;
6630
6978
  }
@@ -6635,7 +6983,7 @@ ${context}`;
6635
6983
  ) : [];
6636
6984
  const resolvedSources = [];
6637
6985
  for (const source of sources) {
6638
- const parentId = (_f = source.metadata) == null ? void 0 : _f.parent_id;
6986
+ const parentId = (_g = source.metadata) == null ? void 0 : _g.parent_id;
6639
6987
  if (parentId) {
6640
6988
  console.log(`[Pipeline] Multi-Vector: Found child chunk. Parent ID: ${parentId}`);
6641
6989
  resolvedSources.push(source);
@@ -6658,10 +7006,13 @@ New Question: ${question}
6658
7006
  Optimized Search Query:`;
6659
7007
  const rewrite = await this.llmProvider.chat(
6660
7008
  [
6661
- { role: "system", content: "You are an assistant that optimizes search queries for RAG systems." },
6662
7009
  { role: "user", content: prompt }
6663
7010
  ],
6664
- ""
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
+ }
6665
7016
  );
6666
7017
  return rewrite.trim() || question;
6667
7018
  }
@@ -6685,10 +7036,13 @@ ${context}
6685
7036
  Suggestions:`;
6686
7037
  const response = await this.llmProvider.chat(
6687
7038
  [
6688
- { role: "system", content: "You are a helpful assistant that generates search suggestions." },
6689
7039
  { role: "user", content: prompt }
6690
7040
  ],
6691
- ""
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
+ }
6692
7046
  );
6693
7047
  const match = response.match(/\[[\s\S]*\]/);
6694
7048
  if (match) {
@@ -7027,6 +7381,10 @@ function createStreamHandler(configOrPlugin) {
7027
7381
  if (!isActive) break;
7028
7382
  if (typeof chunk === "string") {
7029
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
+ `);
7030
7388
  } else {
7031
7389
  enqueue(sseMetaFrame(chunk));
7032
7390
  const responseChunk = chunk;