@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
package/dist/server.js CHANGED
@@ -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 } : {}));
@@ -2063,14 +2102,17 @@ var init_SimpleGraphProvider = __esm({
2063
2102
  var server_exports = {};
2064
2103
  __export(server_exports, {
2065
2104
  AnthropicProvider: () => AnthropicProvider,
2105
+ AuthenticationException: () => AuthenticationException,
2066
2106
  BaseVectorProvider: () => BaseVectorProvider,
2067
2107
  BatchProcessor: () => BatchProcessor,
2068
2108
  ChromaDBProvider: () => ChromaDBProvider,
2069
2109
  ConfigBuilder: () => ConfigBuilder,
2070
2110
  ConfigResolver: () => ConfigResolver,
2071
2111
  ConfigValidator: () => ConfigValidator,
2112
+ ConfigurationException: () => ConfigurationException,
2072
2113
  DocumentChunker: () => DocumentChunker,
2073
2114
  DocumentParser: () => DocumentParser,
2115
+ EmbeddingFailedException: () => EmbeddingFailedException,
2074
2116
  EmbeddingStrategy: () => EmbeddingStrategy,
2075
2117
  EmbeddingStrategyResolver: () => EmbeddingStrategyResolver,
2076
2118
  LLMFactory: () => LLMFactory,
@@ -2085,9 +2127,14 @@ __export(server_exports, {
2085
2127
  Pipeline: () => Pipeline,
2086
2128
  PostgreSQLProvider: () => PostgreSQLProvider,
2087
2129
  ProviderHealthCheck: () => ProviderHealthCheck,
2130
+ ProviderNotFoundException: () => ProviderNotFoundException,
2088
2131
  ProviderRegistry: () => ProviderRegistry,
2089
2132
  QdrantProvider: () => QdrantProvider,
2133
+ RateLimitException: () => RateLimitException,
2090
2134
  RedisProvider: () => RedisProvider,
2135
+ RetrievalException: () => RetrievalException,
2136
+ Retrivora: () => Retrivora,
2137
+ RetrivoraError: () => RetrivoraError,
2091
2138
  UniversalLLMAdapter: () => UniversalLLMAdapter,
2092
2139
  UniversalVectorProvider: () => UniversalVectorProvider,
2093
2140
  VECTOR_PROFILES: () => VECTOR_PROFILES,
@@ -2178,7 +2225,7 @@ function getRagConfig(baseConfig, env = process.env) {
2178
2225
  return getEnvConfig(env, baseConfig);
2179
2226
  }
2180
2227
  function getEnvConfig(env = process.env, base) {
2181
- 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;
2228
+ 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;
2182
2229
  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__";
2183
2230
  const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
2184
2231
  const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
@@ -2248,7 +2295,7 @@ function getEnvConfig(env = process.env, base) {
2248
2295
  rest: "default",
2249
2296
  custom: "default"
2250
2297
  };
2251
- return {
2298
+ return __spreadValues({
2252
2299
  projectId,
2253
2300
  vectorDb: {
2254
2301
  provider: vectorProvider,
@@ -2264,7 +2311,9 @@ function getEnvConfig(env = process.env, base) {
2264
2311
  maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
2265
2312
  temperature: readNumber(env, "LLM_TEMPERATURE", 0.7),
2266
2313
  options: {
2267
- profile: readString(env, "LLM_UNIVERSAL_PROFILE")
2314
+ profile: readString(env, "LLM_UNIVERSAL_PROFILE"),
2315
+ thinking: readString(env, "LLM_THINKING") === "true" || readString(env, "LLM_THINKING") === "enabled" ? true : readString(env, "LLM_THINKING") === "false" ? false : void 0,
2316
+ thinkingBudget: env.LLM_THINKING_BUDGET ? parseInt(env.LLM_THINKING_BUDGET, 10) : void 0
2268
2317
  }
2269
2318
  },
2270
2319
  embedding: {
@@ -2296,11 +2345,76 @@ function getEnvConfig(env = process.env, base) {
2296
2345
  topK: readNumber(env, "RAG_TOP_K", 5),
2297
2346
  scoreThreshold: readNumber(env, "RAG_SCORE_THRESHOLD", 0),
2298
2347
  chunkSize: readNumber(env, "RAG_CHUNK_SIZE", 1e3),
2299
- chunkOverlap: readNumber(env, "RAG_CHUNK_OVERLAP", 200)
2348
+ chunkOverlap: readNumber(env, "RAG_CHUNK_OVERLAP", 200),
2349
+ filterableFields: (_Ha = readString(env, "RAG_FILTERABLE_FIELDS")) == null ? void 0 : _Ha.split(",").map((f) => f.trim()),
2350
+ // Query pipeline toggles — read from .env.local
2351
+ useQueryTransformation: readString(env, "RAG_USE_QUERY_TRANSFORMATION") === "true",
2352
+ useReranking: readString(env, "RAG_USE_RERANKING") === "true",
2353
+ useGraphRetrieval: readString(env, "RAG_USE_GRAPH_RETRIEVAL") === "true",
2354
+ architecture: (_Ia = readString(env, "RAG_ARCHITECTURE")) != null ? _Ia : "simple",
2355
+ chunkingStrategy: (_Ja = readString(env, "RAG_CHUNKING_STRATEGY")) != null ? _Ja : "recursive",
2356
+ uiMapping: (() => {
2357
+ const raw = readString(env, "RAG_UI_MAPPING");
2358
+ if (!raw) return void 0;
2359
+ try {
2360
+ return JSON.parse(raw);
2361
+ } catch (e) {
2362
+ return void 0;
2363
+ }
2364
+ })()
2300
2365
  }
2301
- };
2366
+ }, readString(env, "GRAPH_DB_PROVIDER") ? {
2367
+ graphDb: {
2368
+ provider: readString(env, "GRAPH_DB_PROVIDER"),
2369
+ options: {
2370
+ uri: readString(env, "GRAPH_DB_URI"),
2371
+ username: readString(env, "GRAPH_DB_USERNAME"),
2372
+ password: readString(env, "GRAPH_DB_PASSWORD")
2373
+ }
2374
+ }
2375
+ } : {});
2302
2376
  }
2303
2377
 
2378
+ // src/exceptions/index.ts
2379
+ var RetrivoraError = class extends Error {
2380
+ constructor(message, code, details) {
2381
+ super(message);
2382
+ this.name = new.target.name;
2383
+ this.code = code;
2384
+ this.details = details;
2385
+ }
2386
+ };
2387
+ var ProviderNotFoundException = class extends RetrivoraError {
2388
+ constructor(providerType, provider, details) {
2389
+ super(`Unsupported ${providerType} provider: ${provider}`, "PROVIDER_NOT_FOUND", details);
2390
+ }
2391
+ };
2392
+ var EmbeddingFailedException = class extends RetrivoraError {
2393
+ constructor(message = "Embedding generation failed", details) {
2394
+ super(message, "EMBEDDING_FAILED", details);
2395
+ }
2396
+ };
2397
+ var RetrievalException = class extends RetrivoraError {
2398
+ constructor(message = "Retrieval failed", details) {
2399
+ super(message, "RETRIEVAL_FAILED", details);
2400
+ }
2401
+ };
2402
+ var RateLimitException = class extends RetrivoraError {
2403
+ constructor(message = "Provider rate limit exceeded", details) {
2404
+ super(message, "RATE_LIMITED", details);
2405
+ }
2406
+ };
2407
+ var ConfigurationException = class extends RetrivoraError {
2408
+ constructor(message, details) {
2409
+ super(message, "CONFIGURATION_ERROR", details);
2410
+ }
2411
+ };
2412
+ var AuthenticationException = class extends RetrivoraError {
2413
+ constructor(message = "Provider authentication failed", details) {
2414
+ super(message, "AUTHENTICATION_ERROR", details);
2415
+ }
2416
+ };
2417
+
2304
2418
  // src/core/ConfigResolver.ts
2305
2419
  var ConfigResolver = class {
2306
2420
  /**
@@ -2327,24 +2441,81 @@ var ConfigResolver = class {
2327
2441
  rag: hostConfig.rag ? __spreadValues(__spreadValues({}, envConfig.rag), hostConfig.rag) : envConfig.rag
2328
2442
  });
2329
2443
  }
2444
+ /**
2445
+ * Resolves the public SDK config shape used by `new Retrivora({...})`.
2446
+ * Supports aliases from the product prompt while preserving existing env
2447
+ * fallback behavior.
2448
+ */
2449
+ static resolveUniversal(hostConfig, env = process.env) {
2450
+ var _a;
2451
+ if (!hostConfig) return this.resolve(void 0, env);
2452
+ const normalized = __spreadProps(__spreadValues({}, hostConfig), {
2453
+ vectorDb: (_a = hostConfig.vectorDb) != null ? _a : hostConfig.vectorDatabase,
2454
+ rag: this.mergeRetrievalWorkflow(hostConfig.rag, hostConfig.retrieval, hostConfig.workflow)
2455
+ });
2456
+ return this.resolve(normalized, env);
2457
+ }
2330
2458
  /**
2331
2459
  * Validates the configuration for required fields.
2332
2460
  */
2333
2461
  static validate(config) {
2334
2462
  if (!config.projectId) {
2335
- throw new Error("[ConfigResolver] projectId is required");
2463
+ throw new ConfigurationException("[ConfigResolver] projectId is required");
2336
2464
  }
2337
2465
  if (!config.vectorDb.provider) {
2338
- throw new Error("[ConfigResolver] vectorDb.provider is required");
2466
+ throw new ConfigurationException("[ConfigResolver] vectorDb.provider is required");
2339
2467
  }
2340
2468
  if (!config.llm.provider) {
2341
- throw new Error("[ConfigResolver] llm.provider is required");
2469
+ throw new ConfigurationException("[ConfigResolver] llm.provider is required");
2342
2470
  }
2343
2471
  }
2472
+ static mergeRetrievalWorkflow(rag, retrieval, workflow) {
2473
+ var _a, _b, _c, _d, _e;
2474
+ if (!rag && !retrieval && !workflow) return void 0;
2475
+ const normalized = __spreadValues({}, rag != null ? rag : {});
2476
+ if (retrieval) {
2477
+ normalized.topK = (_a = retrieval.topK) != null ? _a : normalized.topK;
2478
+ normalized.scoreThreshold = (_b = retrieval.scoreThreshold) != null ? _b : normalized.scoreThreshold;
2479
+ normalized.useReranking = (_c = retrieval.useReranking) != null ? _c : normalized.useReranking;
2480
+ if (retrieval.strategy === "hybrid") normalized.architecture = (_d = normalized.architecture) != null ? _d : "hybrid";
2481
+ if (retrieval.strategy === "agentic") normalized.architecture = "agentic";
2482
+ if (retrieval.strategy === "contextual-compression") normalized.useReranking = true;
2483
+ }
2484
+ if (workflow == null ? void 0 : workflow.type) {
2485
+ const type = workflow.type;
2486
+ if (type === "agentic" || type === "agentic-rag") normalized.architecture = "agentic";
2487
+ else if (type === "hybrid-rag") normalized.architecture = "hybrid";
2488
+ else if (type === "graph-rag") {
2489
+ normalized.architecture = "graph";
2490
+ normalized.useGraphRetrieval = true;
2491
+ } else if (type === "simple-rag" || type === "rag") {
2492
+ normalized.architecture = (_e = normalized.architecture) != null ? _e : "simple";
2493
+ }
2494
+ }
2495
+ return normalized;
2496
+ }
2344
2497
  };
2345
2498
 
2346
2499
  // src/llm/providers/OpenAIProvider.ts
2347
2500
  var import_openai = __toESM(require("openai"));
2501
+
2502
+ // src/llm/utils.ts
2503
+ function buildSystemContent(systemPrompt, context) {
2504
+ const noContext = !context || context.trim() === "" || context.trim() === "No relevant context found.";
2505
+ if (systemPrompt.includes("{{context}}")) {
2506
+ return systemPrompt.replace("{{context}}", noContext ? "" : context);
2507
+ }
2508
+ if (noContext) {
2509
+ return systemPrompt;
2510
+ }
2511
+ return `${systemPrompt}
2512
+
2513
+ ---
2514
+ Context:
2515
+ ${context}`;
2516
+ }
2517
+
2518
+ // src/llm/providers/OpenAIProvider.ts
2348
2519
  var OpenAIProvider = class {
2349
2520
  constructor(llmConfig, embeddingConfig) {
2350
2521
  if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
@@ -2408,16 +2579,10 @@ var OpenAIProvider = class {
2408
2579
  }
2409
2580
  async chat(messages, context, options) {
2410
2581
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2411
- 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.
2412
-
2413
- Context:
2414
- ${context}`;
2582
+ 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.";
2415
2583
  const systemMessage = {
2416
2584
  role: "system",
2417
- content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
2418
-
2419
- Context:
2420
- ${context}`
2585
+ content: buildSystemContent(basePrompt, context)
2421
2586
  };
2422
2587
  const formattedMessages = [
2423
2588
  systemMessage,
@@ -2438,16 +2603,10 @@ ${context}`
2438
2603
  chatStream(messages, context, options) {
2439
2604
  return __asyncGenerator(this, null, function* () {
2440
2605
  var _a, _b, _c, _d, _e, _f, _g, _h;
2441
- 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.
2442
-
2443
- Context:
2444
- ${context}`;
2606
+ 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.";
2445
2607
  const systemMessage = {
2446
2608
  role: "system",
2447
- content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
2448
-
2449
- Context:
2450
- ${context}`
2609
+ content: buildSystemContent(basePrompt, context)
2451
2610
  };
2452
2611
  const formattedMessages = [
2453
2612
  systemMessage,
@@ -2568,58 +2727,87 @@ var AnthropicProvider = class {
2568
2727
  };
2569
2728
  }
2570
2729
  async chat(messages, context, options) {
2571
- var _a, _b, _c, _d;
2572
- 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.
2573
-
2574
- Context:
2575
- ${context}`;
2576
- const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2577
-
2578
- Context:
2579
- ${context}`;
2730
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2731
+ 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.";
2732
+ const system = buildSystemContent(basePrompt, context);
2580
2733
  const anthropicMessages = messages.map((m) => ({
2581
2734
  role: m.role === "assistant" ? "assistant" : "user",
2582
2735
  content: m.content
2583
2736
  }));
2584
- const response = await this.client.messages.create({
2737
+ const isClaude37 = this.llmConfig.model.includes("claude-3-7-sonnet");
2738
+ 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;
2739
+ const extraParams = {};
2740
+ if (isThinkingEnabled && isClaude37) {
2741
+ extraParams.betas = ["interleaved-thinking-2025-05-14"];
2742
+ const maxTokens = (_g = (_f = options == null ? void 0 : options.maxTokens) != null ? _f : this.llmConfig.maxTokens) != null ? _g : 4096;
2743
+ const budget = Math.min(
2744
+ typeof ((_h = this.llmConfig.options) == null ? void 0 : _h.thinkingBudget) === "number" ? (_i = this.llmConfig.options) == null ? void 0 : _i.thinkingBudget : 2048,
2745
+ maxTokens - 1
2746
+ );
2747
+ extraParams.thinking = {
2748
+ type: "enabled",
2749
+ budget_tokens: budget
2750
+ };
2751
+ extraParams.max_tokens = Math.max(maxTokens, budget + 1024);
2752
+ }
2753
+ const response = await this.client.messages.create(__spreadValues({
2585
2754
  model: this.llmConfig.model,
2586
- max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
2755
+ max_tokens: (_k = (_j = options == null ? void 0 : options.maxTokens) != null ? _j : this.llmConfig.maxTokens) != null ? _k : 1024,
2587
2756
  system,
2588
2757
  messages: anthropicMessages
2589
- });
2590
- const block = response.content[0];
2591
- return block.type === "text" ? block.text : "";
2758
+ }, extraParams));
2759
+ let reply = "";
2760
+ for (const block of response.content) {
2761
+ if (block.type === "text") {
2762
+ reply += block.text;
2763
+ }
2764
+ }
2765
+ return reply;
2592
2766
  }
2593
2767
  chatStream(messages, context, options) {
2594
2768
  return __asyncGenerator(this, null, function* () {
2595
- var _a, _b, _c, _d;
2596
- 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.
2597
-
2598
- Context:
2599
- ${context}`;
2600
- const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
2601
-
2602
- Context:
2603
- ${context}`;
2769
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2770
+ 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.";
2771
+ const system = buildSystemContent(basePrompt, context);
2604
2772
  const anthropicMessages = messages.map((m) => ({
2605
2773
  role: m.role === "assistant" ? "assistant" : "user",
2606
2774
  content: m.content
2607
2775
  }));
2608
- const stream = yield new __await(this.client.messages.create({
2776
+ const isClaude37 = this.llmConfig.model.includes("claude-3-7-sonnet");
2777
+ 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;
2778
+ const extraParams = {};
2779
+ if (isThinkingEnabled && isClaude37) {
2780
+ extraParams.betas = ["interleaved-thinking-2025-05-14"];
2781
+ const maxTokens = (_g = (_f = options == null ? void 0 : options.maxTokens) != null ? _f : this.llmConfig.maxTokens) != null ? _g : 4096;
2782
+ const budget = Math.min(
2783
+ typeof ((_h = this.llmConfig.options) == null ? void 0 : _h.thinkingBudget) === "number" ? (_i = this.llmConfig.options) == null ? void 0 : _i.thinkingBudget : 2048,
2784
+ maxTokens - 1
2785
+ );
2786
+ extraParams.thinking = {
2787
+ type: "enabled",
2788
+ budget_tokens: budget
2789
+ };
2790
+ extraParams.max_tokens = Math.max(maxTokens, budget + 1024);
2791
+ }
2792
+ const stream = yield new __await(this.client.messages.create(__spreadValues({
2609
2793
  model: this.llmConfig.model,
2610
- max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
2794
+ max_tokens: (_k = (_j = options == null ? void 0 : options.maxTokens) != null ? _j : this.llmConfig.maxTokens) != null ? _k : 1024,
2611
2795
  system,
2612
2796
  messages: anthropicMessages,
2613
2797
  stream: true
2614
- }));
2798
+ }, extraParams)));
2615
2799
  if (!stream) {
2616
2800
  throw new Error("[AnthropicProvider] messages.create stream is undefined");
2617
2801
  }
2618
2802
  try {
2619
2803
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
2620
2804
  const chunk = temp.value;
2621
- if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
2622
- yield chunk.delta.text;
2805
+ if (chunk.type === "content_block_delta") {
2806
+ if (chunk.delta.type === "text_delta") {
2807
+ yield chunk.delta.text;
2808
+ } else if (chunk.delta.type === "thinking_delta") {
2809
+ yield `\0THINKING\0${chunk.delta.thinking}`;
2810
+ }
2623
2811
  }
2624
2812
  }
2625
2813
  } catch (temp) {
@@ -2663,9 +2851,10 @@ ${context}`;
2663
2851
  var import_axios = __toESM(require("axios"));
2664
2852
  var OllamaProvider = class {
2665
2853
  constructor(llmConfig, embeddingConfig) {
2666
- var _a;
2854
+ var _a, _b;
2667
2855
  const baseURL = (_a = llmConfig.baseUrl) != null ? _a : "http://localhost:11434";
2668
- this.http = import_axios.default.create({ baseURL, timeout: 12e4 });
2856
+ const timeout = Number((_b = llmConfig.options) == null ? void 0 : _b.timeout) || 3e5;
2857
+ this.http = import_axios.default.create({ baseURL, timeout });
2669
2858
  this.llmConfig = llmConfig;
2670
2859
  this.embeddingConfig = embeddingConfig;
2671
2860
  }
@@ -2721,14 +2910,15 @@ var OllamaProvider = class {
2721
2910
  };
2722
2911
  }
2723
2912
  async chat(messages, context, options) {
2724
- var _a, _b, _c, _d;
2725
- const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
2913
+ var _a, _b, _c, _d, _e, _f;
2914
+ 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.";
2915
+ const system = buildSystemContent(basePrompt, context);
2726
2916
  const { data } = await this.http.post("/api/chat", {
2727
2917
  model: this.llmConfig.model,
2728
2918
  stream: false,
2729
2919
  options: {
2730
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
2731
- num_predict: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024
2920
+ temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : 0.7,
2921
+ num_predict: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : 1024
2732
2922
  },
2733
2923
  messages: [
2734
2924
  { role: "system", content: system },
@@ -2739,14 +2929,15 @@ var OllamaProvider = class {
2739
2929
  }
2740
2930
  chatStream(messages, context, options) {
2741
2931
  return __asyncGenerator(this, null, function* () {
2742
- var _a, _b, _c, _d, _e, _f;
2743
- const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
2932
+ var _a, _b, _c, _d, _e, _f, _g, _h;
2933
+ 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.";
2934
+ const system = buildSystemContent(basePrompt, context);
2744
2935
  const response = yield new __await(this.http.post("/api/chat", {
2745
2936
  model: this.llmConfig.model,
2746
2937
  stream: true,
2747
2938
  options: {
2748
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : 0.7,
2749
- num_predict: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024
2939
+ temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : 0.7,
2940
+ num_predict: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : 1024
2750
2941
  },
2751
2942
  messages: [
2752
2943
  { role: "system", content: system },
@@ -2768,7 +2959,7 @@ var OllamaProvider = class {
2768
2959
  if (!line.trim()) continue;
2769
2960
  try {
2770
2961
  const json = JSON.parse(line);
2771
- if ((_e = json.message) == null ? void 0 : _e.content) {
2962
+ if ((_g = json.message) == null ? void 0 : _g.content) {
2772
2963
  yield json.message.content;
2773
2964
  }
2774
2965
  if (json.done) return;
@@ -2790,26 +2981,12 @@ var OllamaProvider = class {
2790
2981
  if (lineBuffer.trim()) {
2791
2982
  try {
2792
2983
  const json = JSON.parse(lineBuffer);
2793
- if ((_f = json.message) == null ? void 0 : _f.content) yield json.message.content;
2984
+ if ((_h = json.message) == null ? void 0 : _h.content) yield json.message.content;
2794
2985
  } catch (e) {
2795
2986
  }
2796
2987
  }
2797
2988
  });
2798
2989
  }
2799
- buildSystemPrompt(context, overridePrompt) {
2800
- var _a;
2801
- if (overridePrompt) {
2802
- return overridePrompt;
2803
- }
2804
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
2805
-
2806
- Context:
2807
- ${context}`;
2808
- return systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
2809
-
2810
- Context:
2811
- ${context}`;
2812
- }
2813
2990
  async embed(text, options) {
2814
2991
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2815
2992
  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";
@@ -2956,21 +3133,6 @@ var GeminiProvider = class {
2956
3133
  var _a, _b;
2957
3134
  return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
2958
3135
  }
2959
- /**
2960
- * Build the system instruction string, inserting the RAG context either via
2961
- * the `{{context}}` placeholder or by appending it.
2962
- */
2963
- buildSystemInstruction(context) {
2964
- var _a;
2965
- const base = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
2966
-
2967
- Context:
2968
- ${context}`;
2969
- return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
2970
-
2971
- Context:
2972
- ${context}`;
2973
- }
2974
3136
  /**
2975
3137
  * Convert ChatMessage[] to the Gemini contents format.
2976
3138
  *
@@ -2989,16 +3151,17 @@ ${context}`;
2989
3151
  // ILLMProvider — chat
2990
3152
  // -------------------------------------------------------------------------
2991
3153
  async chat(messages, context, options) {
2992
- var _a, _b, _c, _d;
3154
+ var _a, _b, _c, _d, _e, _f;
3155
+ 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.";
2993
3156
  const model = this.client.getGenerativeModel({
2994
3157
  model: this.llmConfig.model,
2995
- systemInstruction: this.buildSystemInstruction(context)
3158
+ systemInstruction: buildSystemContent(basePrompt, context)
2996
3159
  });
2997
3160
  const result = await model.generateContent({
2998
3161
  contents: this.buildGeminiContents(messages),
2999
3162
  generationConfig: {
3000
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
3001
- maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
3163
+ temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : DEFAULT_TEMPERATURE,
3164
+ maxOutputTokens: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : DEFAULT_MAX_TOKENS,
3002
3165
  stopSequences: options == null ? void 0 : options.stop
3003
3166
  }
3004
3167
  });
@@ -3006,16 +3169,17 @@ ${context}`;
3006
3169
  }
3007
3170
  chatStream(messages, context, options) {
3008
3171
  return __asyncGenerator(this, null, function* () {
3009
- var _a, _b, _c, _d;
3172
+ var _a, _b, _c, _d, _e, _f;
3173
+ 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.";
3010
3174
  const model = this.client.getGenerativeModel({
3011
3175
  model: this.llmConfig.model,
3012
- systemInstruction: this.buildSystemInstruction(context)
3176
+ systemInstruction: buildSystemContent(basePrompt, context)
3013
3177
  });
3014
3178
  const result = yield new __await(model.generateContentStream({
3015
3179
  contents: this.buildGeminiContents(messages),
3016
3180
  generationConfig: {
3017
- temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
3018
- maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
3181
+ temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : DEFAULT_TEMPERATURE,
3182
+ maxOutputTokens: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : DEFAULT_MAX_TOKENS,
3019
3183
  stopSequences: options == null ? void 0 : options.stop
3020
3184
  }
3021
3185
  }));
@@ -3450,7 +3614,9 @@ var LLMFactory = class _LLMFactory {
3450
3614
  if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
3451
3615
  return new UniversalLLMAdapter(llmConfig);
3452
3616
  }
3453
- throw new Error(
3617
+ throw new ProviderNotFoundException(
3618
+ "LLM",
3619
+ String(llmConfig.provider),
3454
3620
  `[LLMFactory] Unknown provider "${llmConfig.provider}". Built-in providers: ${_LLMFactory.listProviders().join(", ")}. Register a custom provider with LLMFactory.register().`
3455
3621
  );
3456
3622
  }
@@ -3582,7 +3748,7 @@ var ProviderRegistry = class {
3582
3748
  return UniversalVectorProvider2;
3583
3749
  }
3584
3750
  default:
3585
- throw new Error(`Unsupported vector provider: ${provider}`);
3751
+ throw new ProviderNotFoundException("vector", provider);
3586
3752
  }
3587
3753
  }
3588
3754
  static async createVectorProvider(config) {
@@ -3600,12 +3766,18 @@ var ProviderRegistry = class {
3600
3766
  return new SimpleGraphProvider2(config);
3601
3767
  }
3602
3768
  default:
3603
- throw new Error(`Unsupported graph provider: ${provider}`);
3769
+ throw new ProviderNotFoundException("graph", provider);
3604
3770
  }
3605
3771
  }
3606
3772
  static createLLMProvider(llmConfig, embeddingConfig) {
3607
3773
  return LLMFactory.create(llmConfig, embeddingConfig);
3608
3774
  }
3775
+ static registerLLMProvider(name, factory) {
3776
+ LLMFactory.register(name, factory);
3777
+ }
3778
+ static createEmbeddingProvider(embeddingConfig) {
3779
+ return LLMFactory.createEmbeddingProvider(embeddingConfig);
3780
+ }
3609
3781
  };
3610
3782
  ProviderRegistry.vectorProviders = {};
3611
3783
  ProviderRegistry.graphProviders = {};
@@ -3752,8 +3924,8 @@ var ConfigValidator = class {
3752
3924
  const errorItems = errors.filter((e) => e.severity === "error");
3753
3925
  if (errorItems.length > 0) {
3754
3926
  const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
3755
- throw new Error(`[ConfigValidator] Configuration validation failed:
3756
- ${message}`);
3927
+ throw new ConfigurationException(`[ConfigValidator] Configuration validation failed:
3928
+ ${message}`, errorItems);
3757
3929
  }
3758
3930
  }
3759
3931
  };
@@ -3940,20 +4112,17 @@ var Reranker = class {
3940
4112
  }
3941
4113
  try {
3942
4114
  const topN = matches.slice(0, 10);
3943
- const prompt = `You are a relevance ranking expert.
3944
- Given the following user query and a list of retrieved document chunks, rank the chunks by relevance to the query.
3945
- Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant.
3946
- Use the indices provided in brackets like [0], [1], etc.
3947
- Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.
3948
-
3949
- Query: "${query}"
4115
+ const response = await llm.chat(
4116
+ [{ role: "user", content: `Query: "${query}"
3950
4117
 
3951
4118
  Documents:
3952
- ${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}`;
3953
- const response = await llm.chat(
3954
- [{ role: "user", content: prompt }],
4119
+ ${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}` }],
3955
4120
  "",
3956
- { temperature: 0, maxTokens: 50 }
4121
+ {
4122
+ 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.',
4123
+ temperature: 0,
4124
+ maxTokens: 50
4125
+ }
3957
4126
  );
3958
4127
  const cleanedResponse = response.trim().replace(/[\[\]\s]/g, "");
3959
4128
  const rankedIndices = cleanedResponse.split(",").map(Number).filter((n) => !isNaN(n));
@@ -4465,6 +4634,18 @@ var QueryProcessor = class {
4465
4634
  }, normalizedField ? { field: normalizedField } : {}));
4466
4635
  }
4467
4636
  };
4637
+ const activeValidFields = validFields.length > 0 ? validFields : ["brand", "category", "price", "rating", "stock", "stock_quantity", "status", "name", "title"];
4638
+ const resolveValidField = (fieldStr) => {
4639
+ 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();
4640
+ const comparable = (v) => v.toLowerCase().replace(/[^a-z0-9]/g, "");
4641
+ const cleanedComparable = comparable(cleaned);
4642
+ if (!cleanedComparable) return null;
4643
+ const matchedField = activeValidFields.find((fieldName) => {
4644
+ const candidate = comparable(fieldName);
4645
+ return candidate === cleanedComparable || candidate.includes(cleanedComparable) || cleanedComparable.includes(candidate);
4646
+ });
4647
+ return matchedField != null ? matchedField : null;
4648
+ };
4468
4649
  for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
4469
4650
  addHint(match[1]);
4470
4651
  }
@@ -4489,7 +4670,13 @@ var QueryProcessor = class {
4489
4670
  const field = match[1];
4490
4671
  const value = match[2];
4491
4672
  if (field && !this.isLikelyPromptPhrase(field)) {
4492
- addHint(value, field);
4673
+ const resolvedField = resolveValidField(field);
4674
+ if (resolvedField) {
4675
+ addHint(value, resolvedField);
4676
+ } else {
4677
+ addHint(value);
4678
+ addHint(field);
4679
+ }
4493
4680
  }
4494
4681
  }
4495
4682
  if (validFields.length > 0) {
@@ -4535,12 +4722,16 @@ var QueryProcessor = class {
4535
4722
  ];
4536
4723
  for (const pattern of fieldValuePatterns) {
4537
4724
  for (const match of question.matchAll(pattern)) {
4538
- const field = this.normalizeHintValue((_c = match[1]) != null ? _c : "");
4725
+ const field = (_c = match[1]) != null ? _c : "";
4539
4726
  const value = (_d = match[2]) != null ? _d : "";
4540
- if (field && !this.isLikelyPromptPhrase(field)) {
4541
- addHint(value, field);
4727
+ const resolvedField = resolveValidField(field);
4728
+ if (resolvedField) {
4729
+ addHint(value, resolvedField);
4542
4730
  } else {
4543
4731
  addHint(value);
4732
+ if (field && !this.isLikelyPromptPhrase(field)) {
4733
+ addHint(field);
4734
+ }
4544
4735
  }
4545
4736
  }
4546
4737
  }
@@ -4767,7 +4958,7 @@ var LLMRouter = class {
4767
4958
 
4768
4959
  // src/utils/UITransformer.ts
4769
4960
  init_synonyms();
4770
- var UITransformer = class {
4961
+ var UITransformer = class _UITransformer {
4771
4962
  // ─── Public Entry Points ─────────────────────────────────────────────────
4772
4963
  /**
4773
4964
  * Heuristic-only transform (no LLM required).
@@ -4829,7 +5020,7 @@ var UITransformer = class {
4829
5020
  static async analyzeAndDecide(query, sources, llm) {
4830
5021
  let intent;
4831
5022
  try {
4832
- intent = await this.detectIntent(query, llm);
5023
+ intent = await this.detectIntent(query, llm, sources);
4833
5024
  console.debug("[UITransformer] Detected intent:", intent);
4834
5025
  } catch (err) {
4835
5026
  console.warn("[UITransformer] Intent detection failed; using heuristic.", err);
@@ -4847,11 +5038,23 @@ var UITransformer = class {
4847
5038
  try {
4848
5039
  const context = this.buildContextSummary(sources);
4849
5040
  const systemPrompt = this.buildVisualizationSystemPrompt();
5041
+ const profile = this.profileData(sources);
5042
+ const schemaContext = `
5043
+ RETRIEVED DATA SCHEMA:
5044
+ - Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5045
+ - Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5046
+ - Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5047
+ - Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5048
+ - Text/Other fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
5049
+ `;
4850
5050
  const userPrompt = [
4851
5051
  `USER QUESTION: ${query}`,
4852
5052
  "",
4853
5053
  `DETECTED INTENT (JSON): ${JSON.stringify(intent)}`,
4854
5054
  "",
5055
+ `RETRIEVED DATA SCHEMA:`,
5056
+ schemaContext,
5057
+ "",
4855
5058
  "RETRIEVED DATA (JSON):",
4856
5059
  context
4857
5060
  ].join("\n");
@@ -4862,6 +5065,20 @@ var UITransformer = class {
4862
5065
  );
4863
5066
  const parsed = this.parseTransformationResponse(rawResponse);
4864
5067
  if (parsed) {
5068
+ let isCompatible = true;
5069
+ if (parsed.type === "line_chart" && (profile.dateFields.length === 0 || profile.numericFields.length === 0)) {
5070
+ isCompatible = false;
5071
+ } else if (["bar_chart", "horizontal_bar", "pie_chart", "donut_chart"].includes(parsed.type) && (profile.categoricalFields.length === 0 && profile.numericFields.length === 0)) {
5072
+ isCompatible = false;
5073
+ } else if (parsed.type === "scatter_plot" && profile.numericFields.length < 2) {
5074
+ isCompatible = false;
5075
+ } else if (parsed.type === "metric_card" && profile.numericFields.length === 0) {
5076
+ isCompatible = false;
5077
+ }
5078
+ if (!isCompatible) {
5079
+ console.warn(`[UITransformer] LLM chose incompatible visualization type "${parsed.type}" for retrieved schema. Falling back to heuristic.`);
5080
+ return this.transform(query, sources, void 0, void 0, intent);
5081
+ }
4865
5082
  const intentAllowsTable = intent.wantsExplicitTable || ["tabular", "table", "geographic"].includes(intent.visualizationHint);
4866
5083
  const intentWantsPieLikeChart = ["pie_chart", "donut_chart"].includes(intent.recommendedChart) || ["composition", "category_breakdown"].includes(intent.visualizationHint);
4867
5084
  if (parsed.type === "table" && !intentAllowsTable) {
@@ -4891,7 +5108,26 @@ var UITransformer = class {
4891
5108
  * - The intent object can be reused across both the heuristic and LLM paths.
4892
5109
  * - It is easy to unit-test intent detection in isolation.
4893
5110
  */
4894
- static async detectIntent(query, llm) {
5111
+ static async detectIntent(query, llm, sources) {
5112
+ let schemaProfileText = "";
5113
+ if (sources && sources.length > 0) {
5114
+ const profile = this.profileData(sources);
5115
+ const hasProducts = sources.some((item) => this.isProductData(item));
5116
+ schemaProfileText = `
5117
+ RETRIEVED DATA SCHEMA:
5118
+ - Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5119
+ - Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5120
+ - Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5121
+ - Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
5122
+ - Text fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
5123
+
5124
+ Please choose visualizationHint and recommendedChart dynamically based on the available schema.
5125
+ - Do NOT recommend "trend" or "line_chart" if there are no Date fields.
5126
+ - 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.
5127
+ - If there are multiple metadata fields but no clear categories or numbers, "tabular"/"table" is a highly accurate default.
5128
+ ${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".` : ""}
5129
+ `;
5130
+ }
4895
5131
  const systemPrompt = `You are an intent classifier for a product-search RAG system.
4896
5132
  Given a user query, return ONLY a valid JSON object with this exact shape \u2014 no prose, no markdown:
4897
5133
 
@@ -4927,8 +5163,11 @@ RULES:
4927
5163
  - isTemporal: true if time words appear (trend, historical, over time, last year, monthly, etc.)
4928
5164
  - isComparison: true if user compares, ranks, or contrasts entities or categories.
4929
5165
  - language: detect from the query text itself; default "en" if uncertain.`;
5166
+ const userPrompt = `QUERY: ${query}${schemaProfileText ? `
5167
+
5168
+ ${schemaProfileText}` : ""}`;
4930
5169
  const rawResponse = await llm.chat(
4931
- [{ role: "user", content: `QUERY: ${query}` }],
5170
+ [{ role: "user", content: userPrompt }],
4932
5171
  "",
4933
5172
  { systemPrompt, temperature: 0 }
4934
5173
  );
@@ -5371,10 +5610,21 @@ RULES:
5371
5610
  const hasEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
5372
5611
  return asksForRecords && (hasNumericPredicate || hasEntityTerms);
5373
5612
  }
5613
+ /** @internal kept private for transform() internal logic */
5374
5614
  static isProductQuery(query) {
5615
+ return _UITransformer._productQueryTest(query);
5616
+ }
5617
+ /**
5618
+ * Public entry-point for external callers (e.g. Pipeline)
5619
+ * to check whether a query maps to product_browse without triggering an LLM call.
5620
+ */
5621
+ static isProductQueryPublic(query) {
5622
+ return _UITransformer._productQueryTest(query);
5623
+ }
5624
+ static _productQueryTest(query) {
5375
5625
  const q = query.toLowerCase();
5376
5626
  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);
5377
- const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list)\b/.test(q);
5627
+ const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list|explore|exploring)\b/.test(q);
5378
5628
  const nonProductEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
5379
5629
  return productTerms && productAction && !nonProductEntityTerms;
5380
5630
  }
@@ -6147,23 +6397,19 @@ function estimateCostUsd(promptTokens, completionTokens, model) {
6147
6397
  async function scoreHallucination(llm, answer, context) {
6148
6398
  const maxContextChars = 3e3;
6149
6399
  const truncatedContext = context.length > maxContextChars ? context.slice(0, maxContextChars) + "\n...[truncated]" : context;
6150
- 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.
6151
-
6152
- CONTEXT:
6400
+ try {
6401
+ const raw = await llm.chat(
6402
+ [{ role: "user", content: `CONTEXT:
6153
6403
  ${truncatedContext}
6154
6404
 
6155
6405
  ANSWER:
6156
- ${answer}
6157
-
6158
- Return ONLY a valid JSON object with no markdown fences:
6159
- {"score": <float 0-1>, "reason": "<one sentence>"}
6160
-
6161
- Where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.`;
6162
- try {
6163
- const raw = await llm.chat(
6164
- [{ role: "user", content: prompt }],
6406
+ ${answer}` }],
6165
6407
  "",
6166
- { temperature: 0, maxTokens: 120 }
6408
+ {
6409
+ 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.',
6410
+ temperature: 0,
6411
+ maxTokens: 120
6412
+ }
6167
6413
  );
6168
6414
  const jsonMatch = raw.match(/\{[\s\S]*\}/);
6169
6415
  if (!jsonMatch) return void 0;
@@ -6223,7 +6469,10 @@ var Pipeline = class {
6223
6469
  - 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.
6224
6470
  - Do NOT try to format product lists as tables.
6225
6471
  `;
6226
- this.config.llm.systemPrompt = chartInstruction;
6472
+ const userPromptPrefix = this.config.llm.systemPrompt ? `${this.config.llm.systemPrompt}
6473
+
6474
+ ` : "";
6475
+ this.config.llm.systemPrompt = `${userPromptPrefix}${chartInstruction}`;
6227
6476
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
6228
6477
  this.llmRouter = new LLMRouter(this.config);
6229
6478
  const { llmProvider: resolvedLLM, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
@@ -6401,11 +6650,11 @@ var Pipeline = class {
6401
6650
  */
6402
6651
  askStream(_0) {
6403
6652
  return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
6404
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
6653
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
6405
6654
  yield new __await(this.initialize());
6406
6655
  const ns = namespace != null ? namespace : this.config.projectId;
6407
6656
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
6408
- const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
6657
+ const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0.3;
6409
6658
  const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
6410
6659
  const requestStart = performance.now();
6411
6660
  try {
@@ -6424,12 +6673,13 @@ var Pipeline = class {
6424
6673
  const embedStart = performance.now();
6425
6674
  const cacheKey = `${ns}::${searchQuery}`;
6426
6675
  const cachedVector = this.embeddingCache.get(cacheKey);
6676
+ const useGraph = this.graphDB && ((_h = this.config.rag) == null ? void 0 : _h.useGraphRetrieval);
6427
6677
  const [strategyResult, embeddedVector] = yield new __await(Promise.all([
6428
6678
  QueryProcessor.determineRetrievalStrategy(
6429
6679
  searchQuery,
6430
- this.llmRouter.get("fast"),
6431
- (_h = this.config.rag) == null ? void 0 : _h.graphKeywords,
6432
- (_i = this.config.rag) == null ? void 0 : _i.vectorKeywords
6680
+ useGraph ? this.llmRouter.get("fast") : void 0,
6681
+ (_i = this.config.rag) == null ? void 0 : _i.graphKeywords,
6682
+ (_j = this.config.rag) == null ? void 0 : _j.vectorKeywords
6433
6683
  ),
6434
6684
  // Embed immediately regardless of strategy — costs nothing if cached.
6435
6685
  // If strategy turns out to be 'graph'-only we just won't use the vector.
@@ -6439,7 +6689,7 @@ var Pipeline = class {
6439
6689
  if (!cachedVector && queryVector.length > 0) {
6440
6690
  this.embeddingCache.set(cacheKey, queryVector);
6441
6691
  }
6442
- 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;
6692
+ 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;
6443
6693
  const hasMetadataFilter = filter.metadata && Object.keys(filter.metadata).length > 0;
6444
6694
  const hasNumericPredicates = Array.isArray(filter.__numericPredicates) && filter.__numericPredicates.length > 0;
6445
6695
  const wantsExhaustiveList = hasNumericPredicates || hasMetadataFilter || /\b(list|all|show|provide|give|get|browse|find|search|display)\b/i.test(question);
@@ -6452,7 +6702,7 @@ var Pipeline = class {
6452
6702
  const structuredSources = this.applyStructuredFilters(rawSources, filter);
6453
6703
  let fullSources = hasNumericPredicates ? structuredSources : structuredSources.filter((m) => m.score >= scoreThreshold);
6454
6704
  const rerankLimit = wantsExhaustiveList ? retrievalLimit : topK;
6455
- if (!hasNumericPredicates && ((_k = this.config.rag) == null ? void 0 : _k.useReranking)) {
6705
+ if (!hasNumericPredicates && ((_l = this.config.rag) == null ? void 0 : _l.useReranking)) {
6456
6706
  fullSources = yield new __await(this.reranker.rerank(fullSources, question, rerankLimit));
6457
6707
  } else if (!wantsExhaustiveList) {
6458
6708
  fullSources = fullSources.slice(0, topK);
@@ -6464,7 +6714,8 @@ ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
6464
6714
  if (hasMetadataFilter) {
6465
6715
  displayCount = fullSources.length;
6466
6716
  } else {
6467
- const highlyRelevant = fullSources.filter((m) => m.score >= 0.4);
6717
+ const baseThreshold = Math.max(scoreThreshold, 0.4);
6718
+ const highlyRelevant = fullSources.filter((m) => m.score >= baseThreshold);
6468
6719
  displayCount = Math.max(highlyRelevant.length, topK);
6469
6720
  if (displayCount > 15) {
6470
6721
  displayCount = 15;
@@ -6482,34 +6733,117 @@ VECTOR CONTEXT:
6482
6733
  ${context}`;
6483
6734
  }
6484
6735
  const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
6485
- const trainedSchemaPromise = allMetadataKeys.length > 0 ? SchemaMapper.train(this.llmProvider, ns, allMetadataKeys).catch(() => void 0) : Promise.resolve(void 0);
6486
- const uiTransformationPromise = trainedSchemaPromise.then(
6487
- (trainedSchema) => this.generateUiTransformation(
6736
+ const cachedSchema = allMetadataKeys.length > 0 ? SchemaMapper.getCached(ns, allMetadataKeys) : void 0;
6737
+ if (allMetadataKeys.length > 0 && !cachedSchema) {
6738
+ SchemaMapper.train(this.llmProvider, ns, allMetadataKeys).catch(() => {
6739
+ });
6740
+ }
6741
+ const isProductQ = UITransformer.isProductQueryPublic(question);
6742
+ const uiTransformationPromise = isProductQ ? Promise.resolve(
6743
+ UITransformer.transform(
6488
6744
  question,
6489
6745
  sources,
6490
- trainedSchema,
6491
- hasNumericPredicates
6746
+ this.config,
6747
+ cachedSchema,
6748
+ { visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
6492
6749
  )
6750
+ ) : this.generateUiTransformation(
6751
+ question,
6752
+ sources,
6753
+ cachedSchema,
6754
+ hasNumericPredicates
6493
6755
  ).catch((uiError) => {
6494
6756
  console.warn("[Pipeline] UI transformation failed concurrently:", uiError);
6495
- return UITransformer.transform(question, sources, this.config);
6757
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6496
6758
  });
6759
+ let hallucinationScoringPromise = Promise.resolve(void 0);
6497
6760
  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.)";
6761
+ const isClaude37 = this.config.llm.model.includes("claude-3-7-sonnet");
6762
+ 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);
6763
+ const modelNameLower = this.config.llm.model.toLowerCase();
6764
+ const isReasoningModel = modelNameLower.includes("-r1") || modelNameLower.includes("deepseek-r1") || modelNameLower.includes("reasoning") || modelNameLower.includes("thinking");
6765
+ 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);
6766
+ let finalRestrictionSuffix = restrictionSuffix;
6767
+ if (isSimulatedThinking) {
6768
+ 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.`)";
6769
+ }
6498
6770
  const hardenedHistory = [...history];
6499
- const userQuestion = { role: "user", content: question + restrictionSuffix };
6771
+ const userQuestion = { role: "user", content: question + finalRestrictionSuffix };
6500
6772
  const messages = [...hardenedHistory, userQuestion];
6501
- const systemPrompt = (_l = this.config.llm.systemPrompt) != null ? _l : "";
6773
+ const systemPrompt = (_s = this.config.llm.systemPrompt) != null ? _s : "";
6502
6774
  const userPrompt = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
6503
6775
  let fullReply = "";
6776
+ let thinkingText = "";
6777
+ let thinkingStartMs = performance.now();
6778
+ let thinkingDurationMs = 0;
6504
6779
  const generateStart = performance.now();
6505
6780
  if (this.llmProvider.chatStream) {
6506
6781
  const stream = this.llmProvider.chatStream(messages, context);
6507
6782
  if (!stream) throw new Error(`[Pipeline] ${this.config.llm.provider} chatStream returned undefined`);
6783
+ let inThinking = false;
6784
+ let textBuffer = "";
6508
6785
  try {
6509
6786
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
6510
6787
  const chunk = temp.value;
6511
- fullReply += chunk;
6512
- yield chunk;
6788
+ if (chunk.startsWith("\0THINKING\0")) {
6789
+ const thinkingDelta = chunk.slice("\0THINKING\0".length);
6790
+ thinkingText += thinkingDelta;
6791
+ thinkingDurationMs = performance.now() - thinkingStartMs;
6792
+ yield { type: "thinking", text: thinkingDelta };
6793
+ continue;
6794
+ }
6795
+ if (isSimulatedThinking) {
6796
+ textBuffer += chunk;
6797
+ while (textBuffer.length > 0) {
6798
+ if (!inThinking) {
6799
+ const thinkIndex = textBuffer.indexOf("<think>");
6800
+ if (thinkIndex !== -1) {
6801
+ const before = textBuffer.slice(0, thinkIndex);
6802
+ if (before) {
6803
+ fullReply += before;
6804
+ yield before;
6805
+ }
6806
+ inThinking = true;
6807
+ thinkingStartMs = performance.now();
6808
+ textBuffer = textBuffer.slice(thinkIndex + "<think>".length);
6809
+ } else {
6810
+ const maxSafeLength = textBuffer.length - "<think>".length;
6811
+ if (maxSafeLength > 0) {
6812
+ const safeText = textBuffer.slice(0, maxSafeLength);
6813
+ fullReply += safeText;
6814
+ yield safeText;
6815
+ textBuffer = textBuffer.slice(maxSafeLength);
6816
+ }
6817
+ break;
6818
+ }
6819
+ } else {
6820
+ const endThinkIndex = textBuffer.indexOf("</think>");
6821
+ if (endThinkIndex !== -1) {
6822
+ const thinkingDelta = textBuffer.slice(0, endThinkIndex);
6823
+ if (thinkingDelta) {
6824
+ thinkingText += thinkingDelta;
6825
+ thinkingDurationMs = performance.now() - thinkingStartMs;
6826
+ yield { type: "thinking", text: thinkingDelta };
6827
+ }
6828
+ inThinking = false;
6829
+ textBuffer = textBuffer.slice(endThinkIndex + "</think>".length);
6830
+ } else {
6831
+ const maxSafeLength = textBuffer.length - "</think>".length;
6832
+ if (maxSafeLength > 0) {
6833
+ const thinkingDelta = textBuffer.slice(0, maxSafeLength);
6834
+ thinkingText += thinkingDelta;
6835
+ thinkingDurationMs = performance.now() - thinkingStartMs;
6836
+ yield { type: "thinking", text: thinkingDelta };
6837
+ textBuffer = textBuffer.slice(maxSafeLength);
6838
+ }
6839
+ break;
6840
+ }
6841
+ }
6842
+ }
6843
+ } else {
6844
+ fullReply += chunk;
6845
+ yield chunk;
6846
+ }
6513
6847
  }
6514
6848
  } catch (temp) {
6515
6849
  error = [temp];
@@ -6521,17 +6855,42 @@ ${context}`;
6521
6855
  throw error[0];
6522
6856
  }
6523
6857
  }
6858
+ if (isSimulatedThinking && textBuffer.length > 0) {
6859
+ if (inThinking) {
6860
+ thinkingText += textBuffer;
6861
+ yield { type: "thinking", text: textBuffer };
6862
+ } else {
6863
+ fullReply += textBuffer;
6864
+ yield textBuffer;
6865
+ }
6866
+ }
6524
6867
  } else {
6525
6868
  const reply = yield new __await(this.llmProvider.chat(messages, context));
6526
- fullReply = reply;
6527
- yield reply;
6869
+ if (isSimulatedThinking) {
6870
+ const thinkStart = reply.indexOf("<think>");
6871
+ const thinkEnd = reply.indexOf("</think>");
6872
+ if (thinkStart !== -1 && thinkEnd !== -1 && thinkEnd > thinkStart) {
6873
+ thinkingText = reply.slice(thinkStart + "<think>".length, thinkEnd);
6874
+ fullReply = reply.slice(0, thinkStart) + reply.slice(thinkEnd + "</think>".length);
6875
+ thinkingDurationMs = 100;
6876
+ } else {
6877
+ fullReply = reply;
6878
+ }
6879
+ } else {
6880
+ fullReply = reply;
6881
+ }
6882
+ yield fullReply;
6883
+ }
6884
+ 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;
6885
+ if (runHallucination) {
6886
+ hallucinationScoringPromise = scoreHallucination(this.llmProvider, fullReply, context).catch(() => void 0);
6528
6887
  }
6529
6888
  const generateMs = performance.now() - generateStart;
6530
6889
  const totalMs = performance.now() - requestStart;
6531
6890
  const latency = {
6532
6891
  embedMs: Math.round(embedMs),
6533
6892
  retrieveMs: Math.round(retrieveMs),
6534
- rerankMs: ((_m = this.config.rag) == null ? void 0 : _m.useReranking) ? Math.round(rerankMs) : void 0,
6893
+ rerankMs: ((_v = this.config.rag) == null ? void 0 : _v.useReranking) ? Math.round(rerankMs) : void 0,
6535
6894
  generateMs: Math.round(generateMs),
6536
6895
  totalMs: Math.round(totalMs)
6537
6896
  };
@@ -6544,12 +6903,16 @@ ${context}`;
6544
6903
  totalTokens: promptTokens + completionTokens,
6545
6904
  estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model)
6546
6905
  };
6547
- const trace = {
6906
+ const [uiTransformation, hallucinationResult] = yield new __await(Promise.all([
6907
+ uiTransformationPromise,
6908
+ hallucinationScoringPromise
6909
+ ]));
6910
+ const trace = __spreadValues({
6548
6911
  requestId,
6549
6912
  query: question,
6550
6913
  rewrittenQuery,
6551
6914
  systemPrompt,
6552
- userPrompt: question + restrictionSuffix,
6915
+ userPrompt: question + finalRestrictionSuffix,
6553
6916
  chunks: sources.map((s) => {
6554
6917
  var _a2;
6555
6918
  return {
@@ -6563,22 +6926,19 @@ ${context}`;
6563
6926
  latency,
6564
6927
  tokens,
6565
6928
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6566
- };
6567
- const uiTransformation = yield new __await(uiTransformationPromise);
6929
+ }, hallucinationResult ? {
6930
+ hallucinationScore: hallucinationResult.score,
6931
+ hallucinationReason: hallucinationResult.reason
6932
+ } : {});
6568
6933
  yield {
6569
6934
  reply: "",
6570
6935
  sources,
6571
6936
  graphData,
6572
6937
  ui_transformation: uiTransformation,
6573
- trace
6938
+ trace,
6939
+ thinking: thinkingText || void 0,
6940
+ thinkingMs: thinkingDurationMs > 0 ? Math.round(thinkingDurationMs) : void 0
6574
6941
  };
6575
- scoreHallucination(this.llmProvider, fullReply, context).then((hallucinationResult) => {
6576
- if (hallucinationResult) {
6577
- trace.hallucinationScore = hallucinationResult.score;
6578
- trace.hallucinationReason = hallucinationResult.reason;
6579
- }
6580
- }).catch(() => {
6581
- });
6582
6942
  } catch (error2) {
6583
6943
  throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
6584
6944
  }
@@ -6588,18 +6948,30 @@ ${context}`;
6588
6948
  * Universal retrieval method combining all enabled providers.
6589
6949
  * Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
6590
6950
  */
6591
- async generateUiTransformation(question, sources, trainedSchema, forceDeterministic = false) {
6951
+ async generateUiTransformation(question, sources, cachedSchema, forceDeterministic = false) {
6952
+ var _a;
6592
6953
  if (!sources || sources.length === 0) {
6593
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6954
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6594
6955
  }
6595
- if (forceDeterministic) {
6596
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6956
+ if (UITransformer.isProductQueryPublic(question)) {
6957
+ return UITransformer.transform(
6958
+ question,
6959
+ sources,
6960
+ this.config,
6961
+ cachedSchema,
6962
+ { visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
6963
+ );
6964
+ }
6965
+ const isLocalProvider = this.config.llm.provider === "ollama";
6966
+ const disableLlmUiTransform = ((_a = this.config.llm.options) == null ? void 0 : _a.disableLlmUiTransform) === true;
6967
+ if (forceDeterministic || isLocalProvider || disableLlmUiTransform) {
6968
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6597
6969
  }
6598
6970
  try {
6599
6971
  return await UITransformer.analyzeAndDecide(question, sources, this.llmRouter.get("fast"));
6600
6972
  } catch (err) {
6601
6973
  console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
6602
- return UITransformer.transform(question, sources, this.config, trainedSchema);
6974
+ return UITransformer.transform(question, sources, this.config, cachedSchema);
6603
6975
  }
6604
6976
  }
6605
6977
  applyStructuredFilters(sources, filter) {
@@ -6683,25 +7055,29 @@ ${context}`;
6683
7055
  return Number.isFinite(numeric) ? numeric : null;
6684
7056
  }
6685
7057
  async retrieve(query, options) {
6686
- var _a, _b, _c, _d, _e, _f;
7058
+ var _a, _b, _c, _d, _e, _f, _g;
6687
7059
  const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
6688
7060
  const topK = (_b = options.topK) != null ? _b : 5;
6689
7061
  const cacheKey = `${ns}::${query}`;
6690
7062
  let queryVector = this.embeddingCache.get(cacheKey);
6691
- const strategy = await QueryProcessor.determineRetrievalStrategy(query, this.llmRouter.get("fast"));
7063
+ const useGraph = this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval);
7064
+ const strategy = await QueryProcessor.determineRetrievalStrategy(
7065
+ query,
7066
+ useGraph ? this.llmRouter.get("fast") : void 0
7067
+ );
6692
7068
  console.debug(`[Pipeline] Determined retrieval strategy: ${strategy}`);
6693
7069
  const [retrievedVector, graphData] = await Promise.all([
6694
7070
  // Only embed if we need vector search (strategy is 'vector' or 'both')
6695
7071
  strategy === "vector" || strategy === "both" ? queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }) : Promise.resolve([]),
6696
7072
  // Only query graph if we need graph search (strategy is 'graph' or 'both')
6697
- (strategy === "graph" || strategy === "both") && this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
7073
+ (strategy === "graph" || strategy === "both") && this.graphDB && ((_d = this.config.rag) == null ? void 0 : _d.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
6698
7074
  ]);
6699
7075
  if ((strategy === "vector" || strategy === "both") && !queryVector && retrievedVector && retrievedVector.length > 0) {
6700
7076
  this.embeddingCache.set(cacheKey, retrievedVector);
6701
7077
  queryVector = retrievedVector;
6702
7078
  }
6703
- const baseFilter = __spreadProps(__spreadValues({}, (_d = options.filter) != null ? _d : {}), { queryText: query });
6704
- const numericPredicates = QueryProcessor.extractNumericPredicates(query, (_e = this.config.rag) == null ? void 0 : _e.filterableFields);
7079
+ const baseFilter = __spreadProps(__spreadValues({}, (_e = options.filter) != null ? _e : {}), { queryText: query });
7080
+ const numericPredicates = QueryProcessor.extractNumericPredicates(query, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
6705
7081
  if (numericPredicates.length > 0) {
6706
7082
  baseFilter.__numericPredicates = numericPredicates;
6707
7083
  }
@@ -6712,7 +7088,7 @@ ${context}`;
6712
7088
  ) : [];
6713
7089
  const resolvedSources = [];
6714
7090
  for (const source of sources) {
6715
- const parentId = (_f = source.metadata) == null ? void 0 : _f.parent_id;
7091
+ const parentId = (_g = source.metadata) == null ? void 0 : _g.parent_id;
6716
7092
  if (parentId) {
6717
7093
  console.log(`[Pipeline] Multi-Vector: Found child chunk. Parent ID: ${parentId}`);
6718
7094
  resolvedSources.push(source);
@@ -6735,10 +7111,13 @@ New Question: ${question}
6735
7111
  Optimized Search Query:`;
6736
7112
  const rewrite = await this.llmProvider.chat(
6737
7113
  [
6738
- { role: "system", content: "You are an assistant that optimizes search queries for RAG systems." },
6739
7114
  { role: "user", content: prompt }
6740
7115
  ],
6741
- ""
7116
+ "",
7117
+ {
7118
+ 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.",
7119
+ temperature: 0
7120
+ }
6742
7121
  );
6743
7122
  return rewrite.trim() || question;
6744
7123
  }
@@ -6762,10 +7141,13 @@ ${context}
6762
7141
  Suggestions:`;
6763
7142
  const response = await this.llmProvider.chat(
6764
7143
  [
6765
- { role: "system", content: "You are a helpful assistant that generates search suggestions." },
6766
7144
  { role: "user", content: prompt }
6767
7145
  ],
6768
- ""
7146
+ "",
7147
+ {
7148
+ 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.",
7149
+ temperature: 0
7150
+ }
6769
7151
  );
6770
7152
  const match = response.match(/\[[\s\S]*\]/);
6771
7153
  if (match) {
@@ -6950,6 +7332,30 @@ var VectorPlugin = class {
6950
7332
  }
6951
7333
  };
6952
7334
 
7335
+ // src/core/Retrivora.ts
7336
+ var Retrivora = class {
7337
+ constructor(config) {
7338
+ this.config = ConfigResolver.resolveUniversal(config);
7339
+ this.pipeline = new Pipeline(this.config);
7340
+ }
7341
+ async initialize() {
7342
+ await ConfigValidator.validateAndThrow(this.config);
7343
+ await this.pipeline.initialize();
7344
+ }
7345
+ async ingest(documents, namespace) {
7346
+ return this.pipeline.ingest(documents, namespace);
7347
+ }
7348
+ async ask(question, history = [], namespace) {
7349
+ return this.pipeline.ask(question, history, namespace);
7350
+ }
7351
+ askStream(question, history = [], namespace) {
7352
+ return this.pipeline.askStream(question, history, namespace);
7353
+ }
7354
+ getPipeline() {
7355
+ return this.pipeline;
7356
+ }
7357
+ };
7358
+
6953
7359
  // src/config/ConfigBuilder.ts
6954
7360
  var ConfigBuilder = class {
6955
7361
  /**
@@ -7318,6 +7724,10 @@ function createStreamHandler(configOrPlugin) {
7318
7724
  if (!isActive) break;
7319
7725
  if (typeof chunk === "string") {
7320
7726
  enqueue(sseTextFrame(chunk));
7727
+ } else if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "thinking") {
7728
+ enqueue(`data: ${JSON.stringify(chunk)}
7729
+
7730
+ `);
7321
7731
  } else {
7322
7732
  enqueue(sseMetaFrame(chunk));
7323
7733
  const responseChunk = chunk;
@@ -7492,14 +7902,17 @@ function createUploadHandler(configOrPlugin) {
7492
7902
  // Annotate the CommonJS export names for ESM import in node:
7493
7903
  0 && (module.exports = {
7494
7904
  AnthropicProvider,
7905
+ AuthenticationException,
7495
7906
  BaseVectorProvider,
7496
7907
  BatchProcessor,
7497
7908
  ChromaDBProvider,
7498
7909
  ConfigBuilder,
7499
7910
  ConfigResolver,
7500
7911
  ConfigValidator,
7912
+ ConfigurationException,
7501
7913
  DocumentChunker,
7502
7914
  DocumentParser,
7915
+ EmbeddingFailedException,
7503
7916
  EmbeddingStrategy,
7504
7917
  EmbeddingStrategyResolver,
7505
7918
  LLMFactory,
@@ -7514,9 +7927,14 @@ function createUploadHandler(configOrPlugin) {
7514
7927
  Pipeline,
7515
7928
  PostgreSQLProvider,
7516
7929
  ProviderHealthCheck,
7930
+ ProviderNotFoundException,
7517
7931
  ProviderRegistry,
7518
7932
  QdrantProvider,
7933
+ RateLimitException,
7519
7934
  RedisProvider,
7935
+ RetrievalException,
7936
+ Retrivora,
7937
+ RetrivoraError,
7520
7938
  UniversalLLMAdapter,
7521
7939
  UniversalVectorProvider,
7522
7940
  VECTOR_PROFILES,