@superatomai/sdk-node 0.0.61 → 0.0.63

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.
package/dist/index.js CHANGED
@@ -1963,6 +1963,7 @@ __export(index_exports, {
1963
1963
  llmUsageLogger: () => llmUsageLogger,
1964
1964
  logger: () => logger,
1965
1965
  openaiLLM: () => openaiLLM,
1966
+ queryCache: () => queryCache,
1966
1967
  rerankChromaResults: () => rerankChromaResults,
1967
1968
  rerankConversationResults: () => rerankConversationResults,
1968
1969
  userPromptErrorLogger: () => userPromptErrorLogger
@@ -3174,7 +3175,136 @@ var ThreadManager = class _ThreadManager {
3174
3175
  }
3175
3176
  };
3176
3177
 
3178
+ // src/utils/query-cache.ts
3179
+ init_logger();
3180
+ var QueryCache = class {
3181
+ constructor() {
3182
+ this.cache = /* @__PURE__ */ new Map();
3183
+ this.ttlMs = 5 * 60 * 1e3;
3184
+ // Default: 5 minutes
3185
+ this.cleanupInterval = null;
3186
+ this.startCleanup();
3187
+ }
3188
+ /**
3189
+ * Set the cache TTL (Time To Live)
3190
+ * @param minutes - TTL in minutes (default: 5)
3191
+ */
3192
+ setTTL(minutes) {
3193
+ this.ttlMs = minutes * 60 * 1e3;
3194
+ logger.info(`[QueryCache] TTL set to ${minutes} minutes`);
3195
+ }
3196
+ /**
3197
+ * Get the current TTL in minutes
3198
+ */
3199
+ getTTL() {
3200
+ return this.ttlMs / 60 / 1e3;
3201
+ }
3202
+ /**
3203
+ * Store query result in cache
3204
+ * Key is the exact query string (or JSON for parameterized queries)
3205
+ */
3206
+ set(query, data) {
3207
+ this.cache.set(query, {
3208
+ query,
3209
+ data,
3210
+ timestamp: Date.now()
3211
+ });
3212
+ logger.debug(`[QueryCache] Stored result for query (${query.substring(0, 50)}...)`);
3213
+ }
3214
+ /**
3215
+ * Get cached result if exists and not expired
3216
+ */
3217
+ get(query) {
3218
+ const entry = this.cache.get(query);
3219
+ if (!entry) {
3220
+ return null;
3221
+ }
3222
+ if (Date.now() - entry.timestamp > this.ttlMs) {
3223
+ this.cache.delete(query);
3224
+ logger.debug(`[QueryCache] Entry expired for query (${query.substring(0, 50)}...)`);
3225
+ return null;
3226
+ }
3227
+ logger.info(`[QueryCache] Cache HIT for query (${query.substring(0, 50)}...)`);
3228
+ return entry.data;
3229
+ }
3230
+ /**
3231
+ * Check if query exists in cache (not expired)
3232
+ */
3233
+ has(query) {
3234
+ return this.get(query) !== null;
3235
+ }
3236
+ /**
3237
+ * Remove a specific query from cache
3238
+ */
3239
+ delete(query) {
3240
+ this.cache.delete(query);
3241
+ }
3242
+ /**
3243
+ * Clear all cached entries
3244
+ */
3245
+ clear() {
3246
+ this.cache.clear();
3247
+ logger.info("[QueryCache] Cache cleared");
3248
+ }
3249
+ /**
3250
+ * Get cache statistics
3251
+ */
3252
+ getStats() {
3253
+ let oldestTimestamp = null;
3254
+ for (const entry of this.cache.values()) {
3255
+ if (oldestTimestamp === null || entry.timestamp < oldestTimestamp) {
3256
+ oldestTimestamp = entry.timestamp;
3257
+ }
3258
+ }
3259
+ return {
3260
+ size: this.cache.size,
3261
+ oldestEntryAge: oldestTimestamp ? Date.now() - oldestTimestamp : null
3262
+ };
3263
+ }
3264
+ /**
3265
+ * Start periodic cleanup of expired entries
3266
+ */
3267
+ startCleanup() {
3268
+ this.cleanupInterval = setInterval(() => {
3269
+ const now = Date.now();
3270
+ let expiredCount = 0;
3271
+ for (const [key, entry] of this.cache.entries()) {
3272
+ if (now - entry.timestamp > this.ttlMs) {
3273
+ this.cache.delete(key);
3274
+ expiredCount++;
3275
+ }
3276
+ }
3277
+ if (expiredCount > 0) {
3278
+ logger.debug(`[QueryCache] Cleaned up ${expiredCount} expired entries`);
3279
+ }
3280
+ }, 2 * 60 * 1e3);
3281
+ }
3282
+ /**
3283
+ * Stop cleanup interval (for graceful shutdown)
3284
+ */
3285
+ destroy() {
3286
+ if (this.cleanupInterval) {
3287
+ clearInterval(this.cleanupInterval);
3288
+ this.cleanupInterval = null;
3289
+ }
3290
+ this.cache.clear();
3291
+ }
3292
+ };
3293
+ var queryCache = new QueryCache();
3294
+
3177
3295
  // src/handlers/data-request.ts
3296
+ function getQueryCacheKey(query) {
3297
+ if (typeof query === "string") {
3298
+ return query;
3299
+ } else if (query?.sql) {
3300
+ const values = query.values || query.params;
3301
+ if (values && Object.keys(values).length > 0) {
3302
+ return JSON.stringify({ sql: query.sql, values });
3303
+ }
3304
+ return query.sql;
3305
+ }
3306
+ return "";
3307
+ }
3178
3308
  async function handleDataRequest(data, collections, sendMessage) {
3179
3309
  try {
3180
3310
  const dataRequest = DataRequestMessageSchema.parse(data);
@@ -3193,10 +3323,37 @@ async function handleDataRequest(data, collections, sendMessage) {
3193
3323
  return;
3194
3324
  }
3195
3325
  const startTime = performance.now();
3196
- const handler = collections[collection][op];
3197
- const result = await handler(params || {});
3326
+ let result;
3327
+ let fromCache = false;
3328
+ if (collection === "database" && op === "execute" && params?.sql) {
3329
+ const cacheKey = getQueryCacheKey(params.sql);
3330
+ if (cacheKey) {
3331
+ const cachedResult = queryCache.get(cacheKey);
3332
+ if (cachedResult !== null) {
3333
+ result = cachedResult;
3334
+ fromCache = true;
3335
+ logger.info(`[QueryCache] Returning cached result for database.execute`);
3336
+ }
3337
+ }
3338
+ }
3339
+ if (!fromCache) {
3340
+ const handler = collections[collection][op];
3341
+ let handlerParams = params || {};
3342
+ if (collection === "database" && op === "execute" && params?.sql && typeof params.sql !== "string") {
3343
+ const cacheKey = getQueryCacheKey(params.sql);
3344
+ handlerParams = { ...params, sql: cacheKey };
3345
+ logger.debug(`[data-request] Converted object query to JSON string for database handler`);
3346
+ }
3347
+ result = await handler(handlerParams);
3348
+ if (collection === "database" && op === "execute" && params?.sql && result) {
3349
+ const cacheKey = getQueryCacheKey(params.sql);
3350
+ if (cacheKey) {
3351
+ queryCache.set(cacheKey, result);
3352
+ }
3353
+ }
3354
+ }
3198
3355
  const executionMs = Math.round(performance.now() - startTime);
3199
- logger.info(`Executed ${collection}.${op} in ${executionMs}ms`);
3356
+ logger.info(`Executed ${collection}.${op} in ${executionMs}ms${fromCache ? " (from cache)" : ""}`);
3200
3357
  if (SA_RUNTIME && typeof SA_RUNTIME === "object" && "uiBlockId" in SA_RUNTIME) {
3201
3358
  const uiBlockId = SA_RUNTIME.uiBlockId;
3202
3359
  const threadId = SA_RUNTIME.threadId;
@@ -6010,6 +6167,7 @@ var BaseLLM = class {
6010
6167
  this.defaultLimit = config?.defaultLimit || 50;
6011
6168
  this.apiKey = config?.apiKey;
6012
6169
  this.modelStrategy = config?.modelStrategy || "fast";
6170
+ this.conversationSimilarityThreshold = config?.conversationSimilarityThreshold || 0.8;
6013
6171
  }
6014
6172
  /**
6015
6173
  * Get the appropriate model based on task type and model strategy
@@ -6042,6 +6200,26 @@ var BaseLLM = class {
6042
6200
  getModelStrategy() {
6043
6201
  return this.modelStrategy;
6044
6202
  }
6203
+ /**
6204
+ * Set the conversation similarity threshold at runtime
6205
+ * @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
6206
+ */
6207
+ setConversationSimilarityThreshold(threshold) {
6208
+ if (threshold < 0 || threshold > 1) {
6209
+ logger.warn(`[${this.getProviderName()}] Invalid threshold ${threshold}, must be between 0 and 1. Using default 0.8`);
6210
+ this.conversationSimilarityThreshold = 0.8;
6211
+ return;
6212
+ }
6213
+ this.conversationSimilarityThreshold = threshold;
6214
+ logger.info(`[${this.getProviderName()}] Conversation similarity threshold set to: ${threshold}`);
6215
+ }
6216
+ /**
6217
+ * Get the current conversation similarity threshold
6218
+ * @returns The current threshold value
6219
+ */
6220
+ getConversationSimilarityThreshold() {
6221
+ return this.conversationSimilarityThreshold;
6222
+ }
6045
6223
  /**
6046
6224
  * Get the API key (from instance, parameter, or environment)
6047
6225
  */
@@ -6068,6 +6246,96 @@ var BaseLLM = class {
6068
6246
  }
6069
6247
  return false;
6070
6248
  }
6249
+ /**
6250
+ * Get the cache key for a query (the exact sql param that would be sent to execute)
6251
+ * This ensures the cache key matches what the frontend will send
6252
+ * Used for both caching and internal deduplication
6253
+ */
6254
+ getQueryCacheKey(query) {
6255
+ if (typeof query === "string") {
6256
+ return query;
6257
+ } else if (query?.sql) {
6258
+ const values = query.values || query.params;
6259
+ if (values && Object.keys(values).length > 0) {
6260
+ return JSON.stringify({ sql: query.sql, values });
6261
+ } else {
6262
+ return query.sql;
6263
+ }
6264
+ }
6265
+ return "";
6266
+ }
6267
+ /**
6268
+ * Execute a query against the database for validation and caching
6269
+ * @param query - The SQL query to execute (string or object with sql/values)
6270
+ * @param collections - Collections object containing database execute function
6271
+ * @returns Object with result data and cache key
6272
+ * @throws Error if query execution fails
6273
+ */
6274
+ async executeQueryForValidation(query, collections) {
6275
+ const cacheKey = this.getQueryCacheKey(query);
6276
+ if (!cacheKey) {
6277
+ throw new Error("Invalid query format: expected string or object with sql property");
6278
+ }
6279
+ const result = await collections["database"]["execute"]({ sql: cacheKey });
6280
+ return { result, cacheKey };
6281
+ }
6282
+ /**
6283
+ * Request the LLM to fix a failed SQL query
6284
+ * @param failedQuery - The query that failed execution
6285
+ * @param errorMessage - The error message from the failed execution
6286
+ * @param componentContext - Context about the component (name, type, title)
6287
+ * @param apiKey - Optional API key
6288
+ * @returns Fixed query string
6289
+ */
6290
+ async requestQueryFix(failedQuery, errorMessage, componentContext, apiKey) {
6291
+ const schemaDoc = schema.generateSchemaDocumentation();
6292
+ const databaseRules = await promptLoader.loadDatabaseRules();
6293
+ const prompt = `You are a SQL expert. Fix the following SQL query that failed execution.
6294
+
6295
+ ## Database Schema
6296
+ ${schemaDoc}
6297
+
6298
+ ## Database-Specific SQL Rules
6299
+ ${databaseRules}
6300
+
6301
+ ## Component Context
6302
+ - Component Name: ${componentContext.name}
6303
+ - Component Type: ${componentContext.type}
6304
+ - Title: ${componentContext.title || "N/A"}
6305
+
6306
+ ## Failed Query
6307
+ \`\`\`sql
6308
+ ${failedQuery}
6309
+ \`\`\`
6310
+
6311
+ ## Error Message
6312
+ ${errorMessage}
6313
+
6314
+ ## Instructions
6315
+ 1. Analyze the error message and identify what caused the query to fail
6316
+ 2. Fix the query to resolve the error while preserving the original intent
6317
+ 3. Ensure the fixed query follows the database-specific SQL rules above
6318
+ 4. Return ONLY the fixed SQL query, no explanations or markdown
6319
+
6320
+ Fixed SQL query:`;
6321
+ const response = await LLM.text(
6322
+ {
6323
+ sys: "You are a SQL expert. Return only the fixed SQL query with no additional text, explanations, or markdown formatting.",
6324
+ user: prompt
6325
+ },
6326
+ {
6327
+ model: this.getModelForTask("simple"),
6328
+ maxTokens: 2048,
6329
+ temperature: 0.1,
6330
+ apiKey: this.getApiKey(apiKey)
6331
+ }
6332
+ );
6333
+ let fixedQuery = response.trim();
6334
+ fixedQuery = fixedQuery.replace(/^```sql\s*/i, "").replace(/\s*```$/i, "");
6335
+ fixedQuery = fixedQuery.replace(/^```\s*/i, "").replace(/\s*```$/i, "");
6336
+ const { query: validatedQuery } = validateAndFixSqlQuery(fixedQuery);
6337
+ return validatedQuery;
6338
+ }
6071
6339
  /**
6072
6340
  * Match components from text response suggestions and generate follow-up questions
6073
6341
  * Takes a text response with component suggestions (c1:type format) and matches with available components
@@ -6428,10 +6696,34 @@ ${executedToolsText}`);
6428
6696
  }
6429
6697
  };
6430
6698
  }).filter(Boolean);
6699
+ let validatedComponents = finalComponents;
6700
+ if (collections?.["database"]?.["execute"]) {
6701
+ logger.info(`[${this.getProviderName()}] Starting query validation for ${finalComponents.length} components...`);
6702
+ logCollector?.info(`Validating queries for ${finalComponents.length} components...`);
6703
+ try {
6704
+ const validationResult = await this.validateAndRetryComponentQueries(
6705
+ finalComponents,
6706
+ collections,
6707
+ apiKey,
6708
+ logCollector
6709
+ );
6710
+ validatedComponents = validationResult.components;
6711
+ const queriedComponents = finalComponents.filter((c) => c.props?.query);
6712
+ const validatedQueries = validatedComponents.filter((c) => c.props?.query);
6713
+ logger.info(`[${this.getProviderName()}] Query validation complete: ${validatedQueries.length}/${queriedComponents.length} queries validated`);
6714
+ logCollector?.info(`Query validation complete: ${validatedQueries.length}/${queriedComponents.length} queries validated`);
6715
+ } catch (validationError) {
6716
+ const validationErrorMsg = validationError instanceof Error ? validationError.message : String(validationError);
6717
+ logger.error(`[${this.getProviderName()}] Query validation error: ${validationErrorMsg}`);
6718
+ logCollector?.error(`Query validation error: ${validationErrorMsg}`);
6719
+ }
6720
+ } else {
6721
+ logger.debug(`[${this.getProviderName()}] Skipping query validation - database execute function not available`);
6722
+ }
6431
6723
  const methodDuration = Date.now() - methodStartTime;
6432
- logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | components: ${finalComponents.length} | actions: ${actions.length}`);
6724
+ logger.info(`[${this.getProviderName()}] [TIMING] DONE ${methodName} in ${methodDuration}ms | components: ${validatedComponents.length} | actions: ${actions.length}`);
6433
6725
  return {
6434
- components: finalComponents,
6726
+ components: validatedComponents,
6435
6727
  layoutTitle,
6436
6728
  layoutDescription,
6437
6729
  actions
@@ -6449,6 +6741,117 @@ ${executedToolsText}`);
6449
6741
  };
6450
6742
  }
6451
6743
  }
6744
+ /**
6745
+ * Validate component queries against the database and retry with LLM fixes if they fail
6746
+ * @param components - Array of components with potential queries
6747
+ * @param collections - Collections object containing database execute function
6748
+ * @param apiKey - Optional API key for LLM calls
6749
+ * @param logCollector - Optional log collector for logging
6750
+ * @returns Object with validated components and a map of query results
6751
+ */
6752
+ async validateAndRetryComponentQueries(components, collections, apiKey, logCollector) {
6753
+ const MAX_RETRIES = 3;
6754
+ const queryResults = /* @__PURE__ */ new Map();
6755
+ const validatedComponents = [];
6756
+ const queryAttempts = /* @__PURE__ */ new Map();
6757
+ const queryValidationStatus = /* @__PURE__ */ new Map();
6758
+ for (const component of components) {
6759
+ const query = component.props?.query;
6760
+ if (!query) {
6761
+ validatedComponents.push(component);
6762
+ continue;
6763
+ }
6764
+ const queryKey = this.getQueryCacheKey(query);
6765
+ const queryStr = typeof query === "string" ? query : query?.sql || "";
6766
+ if (queryValidationStatus.has(queryKey)) {
6767
+ if (queryValidationStatus.get(queryKey)) {
6768
+ validatedComponents.push(component);
6769
+ if (queryResults.has(queryKey)) {
6770
+ queryResults.set(`${component.id}:${queryKey}`, queryResults.get(queryKey));
6771
+ }
6772
+ } else {
6773
+ logger.warn(`[${this.getProviderName()}] Component ${component.name} has previously failed query, keeping as-is`);
6774
+ validatedComponents.push(component);
6775
+ }
6776
+ continue;
6777
+ }
6778
+ let attempts = queryAttempts.get(queryKey) || 0;
6779
+ let currentQuery = typeof query === "string" ? query : { sql: query?.sql || "", values: query?.values, params: query?.params };
6780
+ let currentQueryStr = queryStr;
6781
+ let validated = false;
6782
+ let lastError = "";
6783
+ logger.info(`[${this.getProviderName()}] Validating query for component: ${component.name} (${component.type})`);
6784
+ while (attempts < MAX_RETRIES && !validated) {
6785
+ attempts++;
6786
+ queryAttempts.set(queryKey, attempts);
6787
+ try {
6788
+ logger.debug(`[${this.getProviderName()}] Query validation attempt ${attempts}/${MAX_RETRIES} for ${component.name}`);
6789
+ const { result, cacheKey } = await this.executeQueryForValidation(currentQuery, collections);
6790
+ validated = true;
6791
+ queryValidationStatus.set(queryKey, true);
6792
+ queryResults.set(queryKey, result);
6793
+ queryResults.set(`${component.id}:${queryKey}`, result);
6794
+ queryCache.set(cacheKey, result);
6795
+ logger.info(`[${this.getProviderName()}] \u2713 Query validated for ${component.name} (attempt ${attempts}) - cached for frontend`);
6796
+ logCollector?.info(`\u2713 Query validated for ${component.name}`);
6797
+ if (currentQueryStr !== queryStr) {
6798
+ component.props = {
6799
+ ...component.props,
6800
+ query: typeof query === "string" ? currentQueryStr : { ...query, sql: currentQueryStr }
6801
+ };
6802
+ logger.info(`[${this.getProviderName()}] Updated ${component.name} with fixed query`);
6803
+ }
6804
+ } catch (error) {
6805
+ lastError = error instanceof Error ? error.message : String(error);
6806
+ logger.warn(`[${this.getProviderName()}] Query validation failed for ${component.name} (attempt ${attempts}/${MAX_RETRIES}): ${lastError}`);
6807
+ logCollector?.warn(`Query validation failed for ${component.name}: ${lastError}`);
6808
+ if (attempts >= MAX_RETRIES) {
6809
+ logger.error(`[${this.getProviderName()}] \u2717 Max retries reached for ${component.name}, keeping original component`);
6810
+ logCollector?.error(`Max retries reached for ${component.name}, query may fail at runtime`);
6811
+ queryValidationStatus.set(queryKey, false);
6812
+ break;
6813
+ }
6814
+ logger.info(`[${this.getProviderName()}] Requesting query fix from LLM for ${component.name}...`);
6815
+ logCollector?.info(`Requesting query fix for ${component.name}...`);
6816
+ try {
6817
+ const fixedQueryStr = await this.requestQueryFix(
6818
+ currentQueryStr,
6819
+ lastError,
6820
+ {
6821
+ name: component.name,
6822
+ type: component.type,
6823
+ title: component.props?.title
6824
+ },
6825
+ apiKey
6826
+ );
6827
+ if (fixedQueryStr && fixedQueryStr !== currentQueryStr) {
6828
+ logger.info(`[${this.getProviderName()}] Received fixed query for ${component.name}, retrying...`);
6829
+ currentQueryStr = fixedQueryStr;
6830
+ if (typeof currentQuery === "string") {
6831
+ currentQuery = fixedQueryStr;
6832
+ } else {
6833
+ currentQuery = { ...currentQuery, sql: fixedQueryStr };
6834
+ }
6835
+ } else {
6836
+ logger.warn(`[${this.getProviderName()}] LLM returned same or empty query, stopping retries`);
6837
+ queryValidationStatus.set(queryKey, false);
6838
+ break;
6839
+ }
6840
+ } catch (fixError) {
6841
+ const fixErrorMsg = fixError instanceof Error ? fixError.message : String(fixError);
6842
+ logger.error(`[${this.getProviderName()}] Failed to get query fix from LLM: ${fixErrorMsg}`);
6843
+ queryValidationStatus.set(queryKey, false);
6844
+ break;
6845
+ }
6846
+ }
6847
+ }
6848
+ validatedComponents.push(component);
6849
+ }
6850
+ return {
6851
+ components: validatedComponents,
6852
+ queryResults
6853
+ };
6854
+ }
6452
6855
  /**
6453
6856
  * Classify user question into category and detect external tools needed
6454
6857
  * Determines if question is for data analysis, requires external tools, or needs text response
@@ -6630,12 +7033,6 @@ ${executedToolsText}`);
6630
7033
  * This provides conversational text responses instead of component generation
6631
7034
  * Supports tool calling for query execution with automatic retry on errors (max 3 attempts)
6632
7035
  * After generating text response, if components are provided, matches suggested components
6633
- * @param streamCallback - Optional callback function to receive text chunks as they stream
6634
- * @param collections - Collection registry for executing database queries via database.execute
6635
- * @param components - Optional list of available components for matching suggestions
6636
- * @param externalTools - Optional array of external tools (email, calendar, etc.) that can be called
6637
- * @param category - Question category ('data_analysis' | 'data_modification' | 'general'). For data_modification, answer component streaming is skipped. For general, component generation is skipped entirely.
6638
- * @param userId - Optional user ID for fetching user-specific knowledge base nodes
6639
7036
  */
6640
7037
  async generateTextResponse(userPrompt, apiKey, logCollector, conversationHistory, streamCallback, collections, components, externalTools, category, userId) {
6641
7038
  const methodStartTime = Date.now();
@@ -7325,11 +7722,6 @@ ${errorMsg}
7325
7722
  * - If match found → Adapt UI block parameters and return
7326
7723
  * 2. Category classification: Determine if data_analysis, requires_external_tools, or text_response
7327
7724
  * 3. Route appropriately based on category and response mode
7328
- *
7329
- * @param responseMode - 'component' for component generation (default), 'text' for text responses
7330
- * @param streamCallback - Optional callback function to receive text chunks as they stream (only for text mode)
7331
- * @param collections - Collection registry for executing database queries (required for text mode)
7332
- * @param externalTools - Optional array of external tools (email, calendar, etc.) that can be called (only for text mode)
7333
7725
  */
7334
7726
  async handleUserRequest(userPrompt, components, apiKey, logCollector, conversationHistory, responseMode = "text", streamCallback, collections, externalTools, userId) {
7335
7727
  const startTime = Date.now();
@@ -7343,8 +7735,7 @@ ${errorMsg}
7343
7735
  userPrompt,
7344
7736
  collections,
7345
7737
  userId,
7346
- similarityThreshold: 0.8
7347
- // 80% threshold
7738
+ similarityThreshold: 0.99
7348
7739
  });
7349
7740
  if (conversationMatch) {
7350
7741
  logger.info(`[${this.getProviderName()}] \u2713 Found matching conversation with ${(conversationMatch.similarity * 100).toFixed(2)}% similarity`);
@@ -7495,16 +7886,6 @@ ${errorMsg}
7495
7886
  };
7496
7887
  }) || [];
7497
7888
  }
7498
- if (categoryClassification.category === "general") {
7499
- logger.info(`[${this.getProviderName()}] Routing to general conversation (no database operations)`);
7500
- logCollector?.info("Routing to general conversation...");
7501
- } else if (categoryClassification.category === "data_analysis") {
7502
- logger.info(`[${this.getProviderName()}] Routing to data analysis (SELECT operations)`);
7503
- logCollector?.info("Routing to data analysis...");
7504
- } else if (categoryClassification.category === "data_modification") {
7505
- logger.info(`[${this.getProviderName()}] Routing to data modification (INSERT/UPDATE/DELETE operations)`);
7506
- logCollector?.info("Routing to data modification...");
7507
- }
7508
7889
  const textResponse = await this.generateTextResponse(
7509
7890
  userPrompt,
7510
7891
  apiKey,
@@ -12924,8 +13305,13 @@ var SuperatomSDK = class {
12924
13305
  this.llmProviders = config.LLM_PROVIDERS || getLLMProviders();
12925
13306
  this.databaseType = config.databaseType || "postgresql";
12926
13307
  this.modelStrategy = config.modelStrategy || "fast";
13308
+ this.conversationSimilarityThreshold = config.conversationSimilarityThreshold ?? 0.8;
12927
13309
  this.applyModelStrategy(this.modelStrategy);
12928
- logger.info(`Initializing Superatom SDK v${SDK_VERSION} for project ${this.projectId}, llm providers: ${this.llmProviders.join(", ")}, database type: ${this.databaseType}, model strategy: ${this.modelStrategy}`);
13310
+ this.applyConversationSimilarityThreshold(this.conversationSimilarityThreshold);
13311
+ if (config.queryCacheTTL !== void 0) {
13312
+ queryCache.setTTL(config.queryCacheTTL);
13313
+ }
13314
+ logger.info(`Initializing Superatom SDK v${SDK_VERSION} for project ${this.projectId}, llm providers: ${this.llmProviders.join(", ")}, database type: ${this.databaseType}, model strategy: ${this.modelStrategy}, conversation similarity threshold: ${this.conversationSimilarityThreshold}, query cache TTL: ${queryCache.getTTL()} minutes`);
12929
13315
  this.userManager = new UserManager(this.projectId, 5e3);
12930
13316
  this.dashboardManager = new DashboardManager(this.projectId);
12931
13317
  this.reportManager = new ReportManager(this.projectId);
@@ -13338,6 +13724,31 @@ var SuperatomSDK = class {
13338
13724
  getModelStrategy() {
13339
13725
  return this.modelStrategy;
13340
13726
  }
13727
+ /**
13728
+ * Apply conversation similarity threshold to all LLM provider singletons
13729
+ * @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
13730
+ */
13731
+ applyConversationSimilarityThreshold(threshold) {
13732
+ anthropicLLM.setConversationSimilarityThreshold(threshold);
13733
+ groqLLM.setConversationSimilarityThreshold(threshold);
13734
+ geminiLLM.setConversationSimilarityThreshold(threshold);
13735
+ openaiLLM.setConversationSimilarityThreshold(threshold);
13736
+ logger.info(`Conversation similarity threshold '${threshold}' applied to all LLM providers`);
13737
+ }
13738
+ /**
13739
+ * Set conversation similarity threshold at runtime
13740
+ * @param threshold - Value between 0 and 1 (e.g., 0.8 = 80% similarity required)
13741
+ */
13742
+ setConversationSimilarityThreshold(threshold) {
13743
+ this.conversationSimilarityThreshold = threshold;
13744
+ this.applyConversationSimilarityThreshold(threshold);
13745
+ }
13746
+ /**
13747
+ * Get current conversation similarity threshold
13748
+ */
13749
+ getConversationSimilarityThreshold() {
13750
+ return this.conversationSimilarityThreshold;
13751
+ }
13341
13752
  };
13342
13753
  // Annotate the CommonJS export names for ESM import in node:
13343
13754
  0 && (module.exports = {
@@ -13360,6 +13771,7 @@ var SuperatomSDK = class {
13360
13771
  llmUsageLogger,
13361
13772
  logger,
13362
13773
  openaiLLM,
13774
+ queryCache,
13363
13775
  rerankChromaResults,
13364
13776
  rerankConversationResults,
13365
13777
  userPromptErrorLogger