@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
@@ -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 = [
@@ -1069,12 +1105,15 @@ var init_MongoDBProvider = __esm({
1069
1105
  }
1070
1106
  );
1071
1107
  const results = await this.collection.aggregate(pipeline).toArray();
1072
- return results.map((res) => ({
1073
- id: res._id,
1074
- content: res[this.contentKey],
1075
- metadata: res[this.metadataKey] || {},
1076
- score: res.score
1077
- }));
1108
+ return results.map((res) => {
1109
+ const normalizedScore = res.score * 2 - 1;
1110
+ return {
1111
+ id: res._id,
1112
+ content: res[this.contentKey],
1113
+ metadata: res[this.metadataKey] || {},
1114
+ score: normalizedScore
1115
+ };
1116
+ });
1078
1117
  }
1079
1118
  async delete(id, namespace) {
1080
1119
  await this.collection.deleteOne(__spreadValues({ _id: id }, namespace ? { namespace } : {}));
@@ -2109,7 +2148,7 @@ function readEnum(env, name, fallback, allowed) {
2109
2148
  throw new Error(`[getRagConfig] ${name} must be one of: ${allowed.join(", ")}`);
2110
2149
  }
2111
2150
  function getEnvConfig(env = process.env, base) {
2112
- 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;
2113
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__";
2114
2153
  const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
2115
2154
  const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
@@ -2179,7 +2218,7 @@ function getEnvConfig(env = process.env, base) {
2179
2218
  rest: "default",
2180
2219
  custom: "default"
2181
2220
  };
2182
- return {
2221
+ return __spreadValues({
2183
2222
  projectId,
2184
2223
  vectorDb: {
2185
2224
  provider: vectorProvider,
@@ -2195,7 +2234,9 @@ function getEnvConfig(env = process.env, base) {
2195
2234
  maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
2196
2235
  temperature: readNumber(env, "LLM_TEMPERATURE", 0.7),
2197
2236
  options: {
2198
- 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
2199
2240
  }
2200
2241
  },
2201
2242
  embedding: {
@@ -2227,11 +2268,56 @@ function getEnvConfig(env = process.env, base) {
2227
2268
  topK: readNumber(env, "RAG_TOP_K", 5),
2228
2269
  scoreThreshold: readNumber(env, "RAG_SCORE_THRESHOLD", 0),
2229
2270
  chunkSize: readNumber(env, "RAG_CHUNK_SIZE", 1e3),
2230
- 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
+ })()
2231
2288
  }
2232
- };
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
+ } : {});
2233
2299
  }
2234
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
+
2235
2321
  // src/core/ConfigResolver.ts
2236
2322
  var ConfigResolver = class {
2237
2323
  /**
@@ -2258,24 +2344,81 @@ var ConfigResolver = class {
2258
2344
  rag: hostConfig.rag ? __spreadValues(__spreadValues({}, envConfig.rag), hostConfig.rag) : envConfig.rag
2259
2345
  });
2260
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
+ }
2261
2361
  /**
2262
2362
  * Validates the configuration for required fields.
2263
2363
  */
2264
2364
  static validate(config) {
2265
2365
  if (!config.projectId) {
2266
- throw new Error("[ConfigResolver] projectId is required");
2366
+ throw new ConfigurationException("[ConfigResolver] projectId is required");
2267
2367
  }
2268
2368
  if (!config.vectorDb.provider) {
2269
- throw new Error("[ConfigResolver] vectorDb.provider is required");
2369
+ throw new ConfigurationException("[ConfigResolver] vectorDb.provider is required");
2270
2370
  }
2271
2371
  if (!config.llm.provider) {
2272
- throw new Error("[ConfigResolver] llm.provider is required");
2372
+ throw new ConfigurationException("[ConfigResolver] llm.provider is required");
2273
2373
  }
2274
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
+ }
2275
2400
  };
2276
2401
 
2277
2402
  // src/llm/providers/OpenAIProvider.ts
2278
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
2279
2422
  var OpenAIProvider = class {
2280
2423
  constructor(llmConfig, embeddingConfig) {
2281
2424
  if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
@@ -2339,16 +2482,10 @@ var OpenAIProvider = class {
2339
2482
  }
2340
2483
  async chat(messages, context, options) {
2341
2484
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2342
- 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.
2343
-
2344
- Context:
2345
- ${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.";
2346
2486
  const systemMessage = {
2347
2487
  role: "system",
2348
- content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
2349
-
2350
- Context:
2351
- ${context}`
2488
+ content: buildSystemContent(basePrompt, context)
2352
2489
  };
2353
2490
  const formattedMessages = [
2354
2491
  systemMessage,
@@ -2369,16 +2506,10 @@ ${context}`
2369
2506
  chatStream(messages, context, options) {
2370
2507
  return __asyncGenerator(this, null, function* () {
2371
2508
  var _a, _b, _c, _d, _e, _f, _g, _h;
2372
- 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.
2373
-
2374
- Context:
2375
- ${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.";
2376
2510
  const systemMessage = {
2377
2511
  role: "system",
2378
- content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
2379
-
2380
- Context:
2381
- ${context}`
2512
+ content: buildSystemContent(basePrompt, context)
2382
2513
  };
2383
2514
  const formattedMessages = [
2384
2515
  systemMessage,
@@ -2499,58 +2630,87 @@ var AnthropicProvider = class {
2499
2630
  };
2500
2631
  }
2501
2632
  async chat(messages, context, options) {
2502
- var _a, _b, _c, _d;
2503
- 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.
2504
-
2505
- Context:
2506
- ${context}`;
2507
- const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2508
-
2509
- Context:
2510
- ${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);
2511
2636
  const anthropicMessages = messages.map((m) => ({
2512
2637
  role: m.role === "assistant" ? "assistant" : "user",
2513
2638
  content: m.content
2514
2639
  }));
2515
- 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({
2516
2657
  model: this.llmConfig.model,
2517
- 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,
2518
2659
  system,
2519
2660
  messages: anthropicMessages
2520
- });
2521
- const block = response.content[0];
2522
- 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;
2523
2669
  }
2524
2670
  chatStream(messages, context, options) {
2525
2671
  return __asyncGenerator(this, null, function* () {
2526
- var _a, _b, _c, _d;
2527
- 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.
2528
-
2529
- Context:
2530
- ${context}`;
2531
- const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2532
-
2533
- Context:
2534
- ${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);
2535
2675
  const anthropicMessages = messages.map((m) => ({
2536
2676
  role: m.role === "assistant" ? "assistant" : "user",
2537
2677
  content: m.content
2538
2678
  }));
2539
- 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({
2540
2696
  model: this.llmConfig.model,
2541
- 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,
2542
2698
  system,
2543
2699
  messages: anthropicMessages,
2544
2700
  stream: true
2545
- }));
2701
+ }, extraParams)));
2546
2702
  if (!stream) {
2547
2703
  throw new Error("[AnthropicProvider] messages.create stream is undefined");
2548
2704
  }
2549
2705
  try {
2550
2706
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
2551
2707
  const chunk = temp.value;
2552
- if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
2553
- 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
+ }
2554
2714
  }
2555
2715
  }
2556
2716
  } catch (temp) {
@@ -2594,9 +2754,10 @@ ${context}`;
2594
2754
  import axios from "axios";
2595
2755
  var OllamaProvider = class {
2596
2756
  constructor(llmConfig, embeddingConfig) {
2597
- var _a;
2757
+ var _a, _b;
2598
2758
  const baseURL = (_a = llmConfig.baseUrl) != null ? _a : "http://localhost:11434";
2599
- 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 });
2600
2761
  this.llmConfig = llmConfig;
2601
2762
  this.embeddingConfig = embeddingConfig;
2602
2763
  }
@@ -2652,14 +2813,15 @@ var OllamaProvider = class {
2652
2813
  };
2653
2814
  }
2654
2815
  async chat(messages, context, options) {
2655
- var _a, _b, _c, _d;
2656
- 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);
2657
2819
  const { data } = await this.http.post("/api/chat", {
2658
2820
  model: this.llmConfig.model,
2659
2821
  stream: false,
2660
2822
  options: {
2661
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
2662
- 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
2663
2825
  },
2664
2826
  messages: [
2665
2827
  { role: "system", content: system },
@@ -2670,14 +2832,15 @@ var OllamaProvider = class {
2670
2832
  }
2671
2833
  chatStream(messages, context, options) {
2672
2834
  return __asyncGenerator(this, null, function* () {
2673
- var _a, _b, _c, _d, _e, _f;
2674
- 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);
2675
2838
  const response = yield new __await(this.http.post("/api/chat", {
2676
2839
  model: this.llmConfig.model,
2677
2840
  stream: true,
2678
2841
  options: {
2679
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
2680
- 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
2681
2844
  },
2682
2845
  messages: [
2683
2846
  { role: "system", content: system },
@@ -2699,7 +2862,7 @@ var OllamaProvider = class {
2699
2862
  if (!line.trim()) continue;
2700
2863
  try {
2701
2864
  const json = JSON.parse(line);
2702
- if ((_e = json.message) == null ? void 0 : _e.content) {
2865
+ if ((_g = json.message) == null ? void 0 : _g.content) {
2703
2866
  yield json.message.content;
2704
2867
  }
2705
2868
  if (json.done) return;
@@ -2721,26 +2884,12 @@ var OllamaProvider = class {
2721
2884
  if (lineBuffer.trim()) {
2722
2885
  try {
2723
2886
  const json = JSON.parse(lineBuffer);
2724
- 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;
2725
2888
  } catch (e) {
2726
2889
  }
2727
2890
  }
2728
2891
  });
2729
2892
  }
2730
- buildSystemPrompt(context, overridePrompt) {
2731
- var _a;
2732
- if (overridePrompt) {
2733
- return overridePrompt;
2734
- }
2735
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
2736
-
2737
- Context:
2738
- ${context}`;
2739
- return systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2740
-
2741
- Context:
2742
- ${context}`;
2743
- }
2744
2893
  async embed(text, options) {
2745
2894
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2746
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";
@@ -2887,21 +3036,6 @@ var GeminiProvider = class {
2887
3036
  var _a, _b;
2888
3037
  return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
2889
3038
  }
2890
- /**
2891
- * Build the system instruction string, inserting the RAG context either via
2892
- * the `{{context}}` placeholder or by appending it.
2893
- */
2894
- buildSystemInstruction(context) {
2895
- var _a;
2896
- const base = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
2897
-
2898
- Context:
2899
- ${context}`;
2900
- return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
2901
-
2902
- Context:
2903
- ${context}`;
2904
- }
2905
3039
  /**
2906
3040
  * Convert ChatMessage[] to the Gemini contents format.
2907
3041
  *
@@ -2920,16 +3054,17 @@ ${context}`;
2920
3054
  // ILLMProvider — chat
2921
3055
  // -------------------------------------------------------------------------
2922
3056
  async chat(messages, context, options) {
2923
- 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.";
2924
3059
  const model = this.client.getGenerativeModel({
2925
3060
  model: this.llmConfig.model,
2926
- systemInstruction: this.buildSystemInstruction(context)
3061
+ systemInstruction: buildSystemContent(basePrompt, context)
2927
3062
  });
2928
3063
  const result = await model.generateContent({
2929
3064
  contents: this.buildGeminiContents(messages),
2930
3065
  generationConfig: {
2931
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
2932
- 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,
2933
3068
  stopSequences: options == null ? void 0 : options.stop
2934
3069
  }
2935
3070
  });
@@ -2937,16 +3072,17 @@ ${context}`;
2937
3072
  }
2938
3073
  chatStream(messages, context, options) {
2939
3074
  return __asyncGenerator(this, null, function* () {
2940
- 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.";
2941
3077
  const model = this.client.getGenerativeModel({
2942
3078
  model: this.llmConfig.model,
2943
- systemInstruction: this.buildSystemInstruction(context)
3079
+ systemInstruction: buildSystemContent(basePrompt, context)
2944
3080
  });
2945
3081
  const result = yield new __await(model.generateContentStream({
2946
3082
  contents: this.buildGeminiContents(messages),
2947
3083
  generationConfig: {
2948
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
2949
- 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,
2950
3086
  stopSequences: options == null ? void 0 : options.stop
2951
3087
  }
2952
3088
  }));
@@ -3342,7 +3478,9 @@ var LLMFactory = class _LLMFactory {
3342
3478
  if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
3343
3479
  return new UniversalLLMAdapter(llmConfig);
3344
3480
  }
3345
- throw new Error(
3481
+ throw new ProviderNotFoundException(
3482
+ "LLM",
3483
+ String(llmConfig.provider),
3346
3484
  `[LLMFactory] Unknown provider "${llmConfig.provider}". Built-in providers: ${_LLMFactory.listProviders().join(", ")}. Register a custom provider with LLMFactory.register().`
3347
3485
  );
3348
3486
  }
@@ -3474,7 +3612,7 @@ var ProviderRegistry = class {
3474
3612
  return UniversalVectorProvider2;
3475
3613
  }
3476
3614
  default:
3477
- throw new Error(`Unsupported vector provider: ${provider}`);
3615
+ throw new ProviderNotFoundException("vector", provider);
3478
3616
  }
3479
3617
  }
3480
3618
  static async createVectorProvider(config) {
@@ -3492,12 +3630,18 @@ var ProviderRegistry = class {
3492
3630
  return new SimpleGraphProvider2(config);
3493
3631
  }
3494
3632
  default:
3495
- throw new Error(`Unsupported graph provider: ${provider}`);
3633
+ throw new ProviderNotFoundException("graph", provider);
3496
3634
  }
3497
3635
  }
3498
3636
  static createLLMProvider(llmConfig, embeddingConfig) {
3499
3637
  return LLMFactory.create(llmConfig, embeddingConfig);
3500
3638
  }
3639
+ static registerLLMProvider(name, factory) {
3640
+ LLMFactory.register(name, factory);
3641
+ }
3642
+ static createEmbeddingProvider(embeddingConfig) {
3643
+ return LLMFactory.createEmbeddingProvider(embeddingConfig);
3644
+ }
3501
3645
  };
3502
3646
  ProviderRegistry.vectorProviders = {};
3503
3647
  ProviderRegistry.graphProviders = {};
@@ -3644,8 +3788,8 @@ var ConfigValidator = class {
3644
3788
  const errorItems = errors.filter((e) => e.severity === "error");
3645
3789
  if (errorItems.length > 0) {
3646
3790
  const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
3647
- throw new Error(`[ConfigValidator] Configuration validation failed:
3648
- ${message}`);
3791
+ throw new ConfigurationException(`[ConfigValidator] Configuration validation failed:
3792
+ ${message}`, errorItems);
3649
3793
  }
3650
3794
  }
3651
3795
  };
@@ -3832,20 +3976,17 @@ var Reranker = class {
3832
3976
  }
3833
3977
  try {
3834
3978
  const topN = matches.slice(0, 10);
3835
- const prompt = `You are a relevance ranking expert.
3836
- Given the following user query and a list of retrieved document chunks, rank the chunks by relevance to the query.
3837
- Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant.
3838
- Use the indices provided in brackets like [0], [1], etc.
3839
- Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.
3840
-
3841
- Query: "${query}"
3979
+ const response = await llm.chat(
3980
+ [{ role: "user", content: `Query: "${query}"
3842
3981
 
3843
3982
  Documents:
3844
- ${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}`;
3845
- const response = await llm.chat(
3846
- [{ role: "user", content: prompt }],
3983
+ ${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}` }],
3847
3984
  "",
3848
- { 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
+ }
3849
3990
  );
3850
3991
  const cleanedResponse = response.trim().replace(/[\[\]\s]/g, "");
3851
3992
  const rankedIndices = cleanedResponse.split(",").map(Number).filter((n) => !isNaN(n));
@@ -4351,6 +4492,18 @@ var QueryProcessor = class {
4351
4492
  }, normalizedField ? { field: normalizedField } : {}));
4352
4493
  }
4353
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
+ };
4354
4507
  for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
4355
4508
  addHint(match[1]);
4356
4509
  }
@@ -4375,7 +4528,13 @@ var QueryProcessor = class {
4375
4528
  const field = match[1];
4376
4529
  const value = match[2];
4377
4530
  if (field && !this.isLikelyPromptPhrase(field)) {
4378
- addHint(value, field);
4531
+ const resolvedField = resolveValidField(field);
4532
+ if (resolvedField) {
4533
+ addHint(value, resolvedField);
4534
+ } else {
4535
+ addHint(value);
4536
+ addHint(field);
4537
+ }
4379
4538
  }
4380
4539
  }
4381
4540
  if (validFields.length > 0) {
@@ -4421,12 +4580,16 @@ var QueryProcessor = class {
4421
4580
  ];
4422
4581
  for (const pattern of fieldValuePatterns) {
4423
4582
  for (const match of question.matchAll(pattern)) {
4424
- const field = this.normalizeHintValue((_c = match[1]) != null ? _c : "");
4583
+ const field = (_c = match[1]) != null ? _c : "";
4425
4584
  const value = (_d = match[2]) != null ? _d : "";
4426
- if (field && !this.isLikelyPromptPhrase(field)) {
4427
- addHint(value, field);
4585
+ const resolvedField = resolveValidField(field);
4586
+ if (resolvedField) {
4587
+ addHint(value, resolvedField);
4428
4588
  } else {
4429
4589
  addHint(value);
4590
+ if (field && !this.isLikelyPromptPhrase(field)) {
4591
+ addHint(field);
4592
+ }
4430
4593
  }
4431
4594
  }
4432
4595
  }
@@ -4653,7 +4816,7 @@ var LLMRouter = class {
4653
4816
 
4654
4817
  // src/utils/UITransformer.ts
4655
4818
  init_synonyms();
4656
- var UITransformer = class {
4819
+ var UITransformer = class _UITransformer {
4657
4820
  // ─── Public Entry Points ─────────────────────────────────────────────────
4658
4821
  /**
4659
4822
  * Heuristic-only transform (no LLM required).
@@ -4715,7 +4878,7 @@ var UITransformer = class {
4715
4878
  static async analyzeAndDecide(query, sources, llm) {
4716
4879
  let intent;
4717
4880
  try {
4718
- intent = await this.detectIntent(query, llm);
4881
+ intent = await this.detectIntent(query, llm, sources);
4719
4882
  console.debug("[UITransformer] Detected intent:", intent);
4720
4883
  } catch (err) {
4721
4884
  console.warn("[UITransformer] Intent detection failed; using heuristic.", err);
@@ -4733,11 +4896,23 @@ var UITransformer = class {
4733
4896
  try {
4734
4897
  const context = this.buildContextSummary(sources);
4735
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
+ `;
4736
4908
  const userPrompt = [
4737
4909
  `USER QUESTION: ${query}`,
4738
4910
  "",
4739
4911
  `DETECTED INTENT (JSON): ${JSON.stringify(intent)}`,
4740
4912
  "",
4913
+ `RETRIEVED DATA SCHEMA:`,
4914
+ schemaContext,
4915
+ "",
4741
4916
  "RETRIEVED DATA (JSON):",
4742
4917
  context
4743
4918
  ].join("\n");
@@ -4748,6 +4923,20 @@ var UITransformer = class {
4748
4923
  );
4749
4924
  const parsed = this.parseTransformationResponse(rawResponse);
4750
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
+ }
4751
4940
  const intentAllowsTable = intent.wantsExplicitTable || ["tabular", "table", "geographic"].includes(intent.visualizationHint);
4752
4941
  const intentWantsPieLikeChart = ["pie_chart", "donut_chart"].includes(intent.recommendedChart) || ["composition", "category_breakdown"].includes(intent.visualizationHint);
4753
4942
  if (parsed.type === "table" && !intentAllowsTable) {
@@ -4777,7 +4966,26 @@ var UITransformer = class {
4777
4966
  * - The intent object can be reused across both the heuristic and LLM paths.
4778
4967
  * - It is easy to unit-test intent detection in isolation.
4779
4968
  */
4780
- 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
+ }
4781
4989
  const systemPrompt = `You are an intent classifier for a product-search RAG system.
4782
4990
  Given a user query, return ONLY a valid JSON object with this exact shape \u2014 no prose, no markdown:
4783
4991
 
@@ -4813,8 +5021,11 @@ RULES:
4813
5021
  - isTemporal: true if time words appear (trend, historical, over time, last year, monthly, etc.)
4814
5022
  - isComparison: true if user compares, ranks, or contrasts entities or categories.
4815
5023
  - language: detect from the query text itself; default "en" if uncertain.`;
5024
+ const userPrompt = `QUERY: ${query}${schemaProfileText ? `
5025
+
5026
+ ${schemaProfileText}` : ""}`;
4816
5027
  const rawResponse = await llm.chat(
4817
- [{ role: "user", content: `QUERY: ${query}` }],
5028
+ [{ role: "user", content: userPrompt }],
4818
5029
  "",
4819
5030
  { systemPrompt, temperature: 0 }
4820
5031
  );
@@ -5257,10 +5468,21 @@ RULES:
5257
5468
  const hasEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
5258
5469
  return asksForRecords && (hasNumericPredicate || hasEntityTerms);
5259
5470
  }
5471
+ /** @internal kept private for transform() internal logic */
5260
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) {
5261
5483
  const q = query.toLowerCase();
5262
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);
5263
- 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);
5264
5486
  const nonProductEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
5265
5487
  return productTerms && productAction && !nonProductEntityTerms;
5266
5488
  }
@@ -6033,23 +6255,19 @@ function estimateCostUsd(promptTokens, completionTokens, model) {
6033
6255
  async function scoreHallucination(llm, answer, context) {
6034
6256
  const maxContextChars = 3e3;
6035
6257
  const truncatedContext = context.length > maxContextChars ? context.slice(0, maxContextChars) + "\n...[truncated]" : context;
6036
- 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.
6037
-
6038
- CONTEXT:
6258
+ try {
6259
+ const raw = await llm.chat(
6260
+ [{ role: "user", content: `CONTEXT:
6039
6261
  ${truncatedContext}
6040
6262
 
6041
6263
  ANSWER:
6042
- ${answer}
6043
-
6044
- Return ONLY a valid JSON object with no markdown fences:
6045
- {"score": <float 0-1>, "reason": "<one sentence>"}
6046
-
6047
- Where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.`;
6048
- try {
6049
- const raw = await llm.chat(
6050
- [{ role: "user", content: prompt }],
6264
+ ${answer}` }],
6051
6265
  "",
6052
- { 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
+ }
6053
6271
  );
6054
6272
  const jsonMatch = raw.match(/\{[\s\S]*\}/);
6055
6273
  if (!jsonMatch) return void 0;
@@ -6109,7 +6327,10 @@ var Pipeline = class {
6109
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.
6110
6328
  - Do NOT try to format product lists as tables.
6111
6329
  `;
6112
- 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}`;
6113
6334
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
6114
6335
  this.llmRouter = new LLMRouter(this.config);
6115
6336
  const { llmProvider: resolvedLLM, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
@@ -6287,11 +6508,11 @@ var Pipeline = class {
6287
6508
  */
6288
6509
  askStream(_0) {
6289
6510
  return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
6290
- 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;
6291
6512
  yield new __await(this.initialize());
6292
6513
  const ns = namespace != null ? namespace : this.config.projectId;
6293
6514
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
6294
- 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;
6295
6516
  const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
6296
6517
  const requestStart = performance.now();
6297
6518
  try {
@@ -6310,12 +6531,13 @@ var Pipeline = class {
6310
6531
  const embedStart = performance.now();
6311
6532
  const cacheKey = `${ns}::${searchQuery}`;
6312
6533
  const cachedVector = this.embeddingCache.get(cacheKey);
6534
+ const useGraph = this.graphDB && ((_h = this.config.rag) == null ? void 0 : _h.useGraphRetrieval);
6313
6535
  const [strategyResult, embeddedVector] = yield new __await(Promise.all([
6314
6536
  QueryProcessor.determineRetrievalStrategy(
6315
6537
  searchQuery,
6316
- this.llmRouter.get("fast"),
6317
- (_h = this.config.rag) == null ? void 0 : _h.graphKeywords,
6318
- (_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
6319
6541
  ),
6320
6542
  // Embed immediately regardless of strategy — costs nothing if cached.
6321
6543
  // If strategy turns out to be 'graph'-only we just won't use the vector.
@@ -6325,7 +6547,7 @@ var Pipeline = class {
6325
6547
  if (!cachedVector && queryVector.length > 0) {
6326
6548
  this.embeddingCache.set(cacheKey, queryVector);
6327
6549
  }
6328
- 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;
6329
6551
  const hasMetadataFilter = filter.metadata && Object.keys(filter.metadata).length > 0;
6330
6552
  const hasNumericPredicates = Array.isArray(filter.__numericPredicates) && filter.__numericPredicates.length > 0;
6331
6553
  const wantsExhaustiveList = hasNumericPredicates || hasMetadataFilter || /\b(list|all|show|provide|give|get|browse|find|search|display)\b/i.test(question);
@@ -6338,7 +6560,7 @@ var Pipeline = class {
6338
6560
  const structuredSources = this.applyStructuredFilters(rawSources, filter);
6339
6561
  let fullSources = hasNumericPredicates ? structuredSources : structuredSources.filter((m) => m.score >= scoreThreshold);
6340
6562
  const rerankLimit = wantsExhaustiveList ? retrievalLimit : topK;
6341
- if (!hasNumericPredicates && ((_k = this.config.rag) == null ? void 0 : _k.useReranking)) {
6563
+ if (!hasNumericPredicates && ((_l = this.config.rag) == null ? void 0 : _l.useReranking)) {
6342
6564
  fullSources = yield new __await(this.reranker.rerank(fullSources, question, rerankLimit));
6343
6565
  } else if (!wantsExhaustiveList) {
6344
6566
  fullSources = fullSources.slice(0, topK);
@@ -6350,7 +6572,8 @@ ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
6350
6572
  if (hasMetadataFilter) {
6351
6573
  displayCount = fullSources.length;
6352
6574
  } else {
6353
- const highlyRelevant = fullSources.filter((m) => m.score >= 0.4);
6575
+ const baseThreshold = Math.max(scoreThreshold, 0.4);
6576
+ const highlyRelevant = fullSources.filter((m) => m.score >= baseThreshold);
6354
6577
  displayCount = Math.max(highlyRelevant.length, topK);
6355
6578
  if (displayCount > 15) {
6356
6579
  displayCount = 15;
@@ -6368,34 +6591,117 @@ VECTOR CONTEXT:
6368
6591
  ${context}`;
6369
6592
  }
6370
6593
  const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
6371
- const trainedSchemaPromise = allMetadataKeys.length > 0 ? SchemaMapper.train(this.llmProvider, ns, allMetadataKeys).catch(() => void 0) : Promise.resolve(void 0);
6372
- const uiTransformationPromise = trainedSchemaPromise.then(
6373
- (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(
6374
6602
  question,
6375
6603
  sources,
6376
- trainedSchema,
6377
- hasNumericPredicates
6604
+ this.config,
6605
+ cachedSchema,
6606
+ { visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
6378
6607
  )
6608
+ ) : this.generateUiTransformation(
6609
+ question,
6610
+ sources,
6611
+ cachedSchema,
6612
+ hasNumericPredicates
6379
6613
  ).catch((uiError) => {
6380
6614
  console.warn("[Pipeline] UI transformation failed concurrently:", uiError);
6381
- return UITransformer.transform(question, sources, this.config);
6615
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6382
6616
  });
6617
+ let hallucinationScoringPromise = Promise.resolve(void 0);
6383
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
+ }
6384
6628
  const hardenedHistory = [...history];
6385
- const userQuestion = { role: "user", content: question + restrictionSuffix };
6629
+ const userQuestion = { role: "user", content: question + finalRestrictionSuffix };
6386
6630
  const messages = [...hardenedHistory, userQuestion];
6387
- const systemPrompt = (_l = this.config.llm.systemPrompt) != null ? _l : "";
6631
+ const systemPrompt = (_s = this.config.llm.systemPrompt) != null ? _s : "";
6388
6632
  const userPrompt = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
6389
6633
  let fullReply = "";
6634
+ let thinkingText = "";
6635
+ let thinkingStartMs = performance.now();
6636
+ let thinkingDurationMs = 0;
6390
6637
  const generateStart = performance.now();
6391
6638
  if (this.llmProvider.chatStream) {
6392
6639
  const stream = this.llmProvider.chatStream(messages, context);
6393
6640
  if (!stream) throw new Error(`[Pipeline] ${this.config.llm.provider} chatStream returned undefined`);
6641
+ let inThinking = false;
6642
+ let textBuffer = "";
6394
6643
  try {
6395
6644
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
6396
6645
  const chunk = temp.value;
6397
- fullReply += chunk;
6398
- 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
+ }
6399
6705
  }
6400
6706
  } catch (temp) {
6401
6707
  error = [temp];
@@ -6407,17 +6713,42 @@ ${context}`;
6407
6713
  throw error[0];
6408
6714
  }
6409
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
+ }
6410
6725
  } else {
6411
6726
  const reply = yield new __await(this.llmProvider.chat(messages, context));
6412
- fullReply = reply;
6413
- 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);
6414
6745
  }
6415
6746
  const generateMs = performance.now() - generateStart;
6416
6747
  const totalMs = performance.now() - requestStart;
6417
6748
  const latency = {
6418
6749
  embedMs: Math.round(embedMs),
6419
6750
  retrieveMs: Math.round(retrieveMs),
6420
- 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,
6421
6752
  generateMs: Math.round(generateMs),
6422
6753
  totalMs: Math.round(totalMs)
6423
6754
  };
@@ -6430,12 +6761,16 @@ ${context}`;
6430
6761
  totalTokens: promptTokens + completionTokens,
6431
6762
  estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model)
6432
6763
  };
6433
- const trace = {
6764
+ const [uiTransformation, hallucinationResult] = yield new __await(Promise.all([
6765
+ uiTransformationPromise,
6766
+ hallucinationScoringPromise
6767
+ ]));
6768
+ const trace = __spreadValues({
6434
6769
  requestId,
6435
6770
  query: question,
6436
6771
  rewrittenQuery,
6437
6772
  systemPrompt,
6438
- userPrompt: question + restrictionSuffix,
6773
+ userPrompt: question + finalRestrictionSuffix,
6439
6774
  chunks: sources.map((s) => {
6440
6775
  var _a2;
6441
6776
  return {
@@ -6449,22 +6784,19 @@ ${context}`;
6449
6784
  latency,
6450
6785
  tokens,
6451
6786
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6452
- };
6453
- const uiTransformation = yield new __await(uiTransformationPromise);
6787
+ }, hallucinationResult ? {
6788
+ hallucinationScore: hallucinationResult.score,
6789
+ hallucinationReason: hallucinationResult.reason
6790
+ } : {});
6454
6791
  yield {
6455
6792
  reply: "",
6456
6793
  sources,
6457
6794
  graphData,
6458
6795
  ui_transformation: uiTransformation,
6459
- trace
6796
+ trace,
6797
+ thinking: thinkingText || void 0,
6798
+ thinkingMs: thinkingDurationMs > 0 ? Math.round(thinkingDurationMs) : void 0
6460
6799
  };
6461
- scoreHallucination(this.llmProvider, fullReply, context).then((hallucinationResult) => {
6462
- if (hallucinationResult) {
6463
- trace.hallucinationScore = hallucinationResult.score;
6464
- trace.hallucinationReason = hallucinationResult.reason;
6465
- }
6466
- }).catch(() => {
6467
- });
6468
6800
  } catch (error2) {
6469
6801
  throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
6470
6802
  }
@@ -6474,18 +6806,30 @@ ${context}`;
6474
6806
  * Universal retrieval method combining all enabled providers.
6475
6807
  * Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
6476
6808
  */
6477
- async generateUiTransformation(question, sources, trainedSchema, forceDeterministic = false) {
6809
+ async generateUiTransformation(question, sources, cachedSchema, forceDeterministic = false) {
6810
+ var _a;
6478
6811
  if (!sources || sources.length === 0) {
6479
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6812
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6480
6813
  }
6481
- if (forceDeterministic) {
6482
- 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);
6483
6827
  }
6484
6828
  try {
6485
6829
  return await UITransformer.analyzeAndDecide(question, sources, this.llmRouter.get("fast"));
6486
6830
  } catch (err) {
6487
6831
  console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
6488
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6832
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6489
6833
  }
6490
6834
  }
6491
6835
  applyStructuredFilters(sources, filter) {
@@ -6569,25 +6913,29 @@ ${context}`;
6569
6913
  return Number.isFinite(numeric) ? numeric : null;
6570
6914
  }
6571
6915
  async retrieve(query, options) {
6572
- var _a, _b, _c, _d, _e, _f;
6916
+ var _a, _b, _c, _d, _e, _f, _g;
6573
6917
  const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
6574
6918
  const topK = (_b = options.topK) != null ? _b : 5;
6575
6919
  const cacheKey = `${ns}::${query}`;
6576
6920
  let queryVector = this.embeddingCache.get(cacheKey);
6577
- 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
+ );
6578
6926
  console.debug(`[Pipeline] Determined retrieval strategy: ${strategy}`);
6579
6927
  const [retrievedVector, graphData] = await Promise.all([
6580
6928
  // Only embed if we need vector search (strategy is 'vector' or 'both')
6581
6929
  strategy === "vector" || strategy === "both" ? queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }) : Promise.resolve([]),
6582
6930
  // Only query graph if we need graph search (strategy is 'graph' or 'both')
6583
- (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)
6584
6932
  ]);
6585
6933
  if ((strategy === "vector" || strategy === "both") && !queryVector && retrievedVector && retrievedVector.length > 0) {
6586
6934
  this.embeddingCache.set(cacheKey, retrievedVector);
6587
6935
  queryVector = retrievedVector;
6588
6936
  }
6589
- const baseFilter = __spreadProps(__spreadValues({}, (_d = options.filter) != null ? _d : {}), { queryText: query });
6590
- 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);
6591
6939
  if (numericPredicates.length > 0) {
6592
6940
  baseFilter.__numericPredicates = numericPredicates;
6593
6941
  }
@@ -6598,7 +6946,7 @@ ${context}`;
6598
6946
  ) : [];
6599
6947
  const resolvedSources = [];
6600
6948
  for (const source of sources) {
6601
- const parentId = (_f = source.metadata) == null ? void 0 : _f.parent_id;
6949
+ const parentId = (_g = source.metadata) == null ? void 0 : _g.parent_id;
6602
6950
  if (parentId) {
6603
6951
  console.log(`[Pipeline] Multi-Vector: Found child chunk. Parent ID: ${parentId}`);
6604
6952
  resolvedSources.push(source);
@@ -6621,10 +6969,13 @@ New Question: ${question}
6621
6969
  Optimized Search Query:`;
6622
6970
  const rewrite = await this.llmProvider.chat(
6623
6971
  [
6624
- { role: "system", content: "You are an assistant that optimizes search queries for RAG systems." },
6625
6972
  { role: "user", content: prompt }
6626
6973
  ],
6627
- ""
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
+ }
6628
6979
  );
6629
6980
  return rewrite.trim() || question;
6630
6981
  }
@@ -6648,10 +6999,13 @@ ${context}
6648
6999
  Suggestions:`;
6649
7000
  const response = await this.llmProvider.chat(
6650
7001
  [
6651
- { role: "system", content: "You are a helpful assistant that generates search suggestions." },
6652
7002
  { role: "user", content: prompt }
6653
7003
  ],
6654
- ""
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
+ }
6655
7009
  );
6656
7010
  const match = response.match(/\[[\s\S]*\]/);
6657
7011
  if (match) {
@@ -6990,6 +7344,10 @@ function createStreamHandler(configOrPlugin) {
6990
7344
  if (!isActive) break;
6991
7345
  if (typeof chunk === "string") {
6992
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
+ `);
6993
7351
  } else {
6994
7352
  enqueue(sseMetaFrame(chunk));
6995
7353
  const responseChunk = chunk;