@smythos/sre 1.7.40 → 1.7.41

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 (40) hide show
  1. package/dist/index.js +49 -42
  2. package/dist/index.js.map +1 -1
  3. package/dist/types/Components/AgentPlugin.class.d.ts +1 -1
  4. package/dist/types/Components/RAG/DataSourceCleaner.class.d.ts +4 -4
  5. package/dist/types/Components/RAG/DataSourceComponent.class.d.ts +5 -1
  6. package/dist/types/config.d.ts +1 -0
  7. package/dist/types/helpers/Conversation.helper.d.ts +10 -13
  8. package/dist/types/helpers/TemplateString.helper.d.ts +1 -1
  9. package/dist/types/index.d.ts +1 -0
  10. package/dist/types/subsystems/IO/VectorDB.service/VectorDBConnector.d.ts +1 -0
  11. package/dist/types/subsystems/LLMManager/LLM.helper.d.ts +19 -0
  12. package/dist/types/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.d.ts +15 -10
  13. package/dist/types/types/LLM.types.d.ts +23 -0
  14. package/package.json +1 -1
  15. package/src/Components/AgentPlugin.class.ts +20 -3
  16. package/src/Components/Classifier.class.ts +79 -16
  17. package/src/Components/ForEach.class.ts +34 -6
  18. package/src/Components/GenAILLM.class.ts +54 -23
  19. package/src/Components/LLMAssistant.class.ts +56 -21
  20. package/src/Components/RAG/DataSourceCleaner.class.ts +13 -11
  21. package/src/Components/RAG/DataSourceComponent.class.ts +39 -13
  22. package/src/Components/RAG/DataSourceIndexer.class.ts +18 -12
  23. package/src/Components/RAG/DataSourceLookup.class.ts +14 -10
  24. package/src/Components/ScrapflyWebScrape.class.ts +7 -0
  25. package/src/config.ts +1 -0
  26. package/src/helpers/Conversation.helper.ts +112 -26
  27. package/src/helpers/TemplateString.helper.ts +6 -5
  28. package/src/index.ts +1 -0
  29. package/src/index.ts.bak +1 -0
  30. package/src/subsystems/IO/VectorDB.service/VectorDBConnector.ts +1 -0
  31. package/src/subsystems/IO/VectorDB.service/connectors/PineconeVectorDB.class.ts +11 -0
  32. package/src/subsystems/IO/VectorDB.service/embed/index.ts +9 -11
  33. package/src/subsystems/LLMManager/LLM.helper.ts +25 -0
  34. package/src/subsystems/LLMManager/LLM.service/LLMConnector.ts +1 -1
  35. package/src/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.ts +190 -146
  36. package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/utils.ts +1 -1
  37. package/src/subsystems/ObservabilityManager/Telemetry.service/connectors/OTel/OTel.class.ts +229 -12
  38. package/src/types/LLM.types.ts +24 -0
  39. package/src/utils/data.utils.ts +6 -4
  40. package/src/Components/DataSourceIndexer.class.ts +0 -295
@@ -4,7 +4,7 @@ import { Logger } from '@sre/helpers/Log.helper';
4
4
  import { LLMInference } from '@sre/LLMManager/LLM.inference';
5
5
  import { LLMContext } from '@sre/MemoryManager/LLMContext';
6
6
  import { TAgentProcessParams } from '@sre/types/Agent.types';
7
- import { ILLMContextStore, TLLMEvent, TLLMModel, ToolData } from '@sre/types/LLM.types';
7
+ import { IConversationSettings, ILLMContextStore, TLLMEvent, TLLMModel, ToolData } from '@sre/types/LLM.types';
8
8
  import { isUrl } from '@sre/utils/data.utils';
9
9
  import { processWithConcurrencyLimit, uid } from '@sre/utils/general.utils';
10
10
  import axios, { AxiosRequestConfig } from 'axios';
@@ -76,10 +76,24 @@ export class Conversation extends EventEmitter {
76
76
  return this._id;
77
77
  }
78
78
 
79
+ // Tool call limit tracking
80
+ private _toolCallCount: number = 0;
81
+ private _maxToolCallsPerSession: number = Infinity; // Default limit
82
+ private _disableToolsForNextCall: boolean = false;
83
+
79
84
  public get context() {
80
85
  return this._context;
81
86
  }
82
87
 
88
+ public get storeId() {
89
+ return this._llmContextStore?.id;
90
+ }
91
+
92
+ /**
93
+ * Headers to be added to all tool call requests
94
+ */
95
+ public headers: Record<string, string> = {};
96
+
83
97
  private _lastError;
84
98
  private _spec;
85
99
  private _customToolsDeclarations: FunctionDeclaration[] = [];
@@ -122,22 +136,7 @@ export class Conversation extends EventEmitter {
122
136
  return this._llmInference;
123
137
  }
124
138
 
125
- constructor(
126
- private _model: string | TLLMModel,
127
- private _specSource?: string | Record<string, any>,
128
- private _settings?: {
129
- maxContextSize?: number;
130
- maxOutputTokens?: number;
131
- systemPrompt?: string;
132
- toolChoice?: string;
133
- store?: ILLMContextStore;
134
- experimentalCache?: boolean;
135
- toolsStrategy?: (toolsConfig) => any;
136
- agentId?: string;
137
- agentVersion?: string;
138
- baseUrl?: string;
139
- }
140
- ) {
139
+ constructor(private _model: string | TLLMModel, private _specSource?: string | Record<string, any>, private _settings?: IConversationSettings) {
141
140
  //TODO: handle loading previous session (messages)
142
141
  super();
143
142
 
@@ -166,6 +165,10 @@ export class Conversation extends EventEmitter {
166
165
  this._llmContextStore = _settings.store;
167
166
  }
168
167
 
168
+ if (_settings?.maxToolCalls !== undefined) {
169
+ this._maxToolCallsPerSession = _settings.maxToolCalls;
170
+ }
171
+
169
172
  this._baseUrl = _settings?.baseUrl;
170
173
 
171
174
  this._agentVersion = _settings?.agentVersion;
@@ -305,6 +308,9 @@ export class Conversation extends EventEmitter {
305
308
  const baseUrl = this._baseUrl;
306
309
  const message_id = 'msg_' + randomUUID();
307
310
  const isDebugSession = toolHeaders['X-DEBUG'];
311
+ for (let [key, value] of Object.entries(this.headers)) {
312
+ toolHeaders[key] = value;
313
+ }
308
314
 
309
315
  /* ==================== STEP ENTRY ==================== */
310
316
  // console.debug('Request to LLM with the given model, messages and functions properties.', {
@@ -333,13 +339,17 @@ export class Conversation extends EventEmitter {
333
339
  requestId: llmReqUid,
334
340
  });
335
341
 
342
+ // Disable tools if we've reached the limit (for final synthesis call)
343
+ const effectiveToolsConfig = this._disableToolsForNextCall ? null : toolsConfig;
344
+ this._disableToolsForNextCall = false; // Reset flag after using it
345
+
336
346
  const eventEmitter: any = await this.llmInference
337
347
  .promptStream({
338
348
  contextWindow,
339
349
  files,
340
350
  params: {
341
351
  model: this.model,
342
- toolsConfig: this._settings?.toolsStrategy ? this._settings.toolsStrategy(toolsConfig) : toolsConfig,
352
+ toolsConfig: this._settings?.toolsStrategy ? this._settings.toolsStrategy(effectiveToolsConfig) : effectiveToolsConfig,
343
353
  maxTokens,
344
354
  cache: this._settings?.experimentalCache,
345
355
  agentId: this._agentId,
@@ -431,13 +441,44 @@ export class Conversation extends EventEmitter {
431
441
  llmMessage.thinkingBlocks = thinkingBlocks;
432
442
  }
433
443
 
444
+ // Check if we're at or over the tool call limit BEFORE processing this batch
445
+ const remainingToolCalls = this._maxToolCallsPerSession - this._toolCallCount;
446
+
447
+ if (remainingToolCalls <= 0) {
448
+ // Already at limit, don't execute any tools from this batch - all will be pending
449
+ const pendingToolNames = toolsData.map((t: ToolData) => t.name).join(', ');
450
+ const systemInstruction = `You have reached the maximum number of tool calls (${this._maxToolCallsPerSession}). The following tools were requested but marked as "pending": ${pendingToolNames}. Please provide a helpful response based on the information you've gathered so far. You may acknowledge these pending tools and suggest the user can continue in a follow-up request.`;
451
+ this._context.addUserMessage(systemInstruction, message_id, { internal: true });
452
+ this.emit(TLLMEvent.Interrupted, 'max_tool_calls', { requestId: llmReqUid });
453
+ this._disableToolsForNextCall = true;
454
+
455
+ // Continue to get final synthesis without executing tools
456
+ this.streamPrompt(null, toolHeaders, concurrentToolCalls, abortSignal).then(resolve).catch(reject);
457
+ return;
458
+ }
459
+
460
+ // If this batch would exceed the limit, truncate to only execute remaining quota
461
+ let actualToolsData = toolsData;
462
+ let skippedToolsData: ToolData[] = [];
463
+
464
+ if (toolsData.length > remainingToolCalls) {
465
+ actualToolsData = toolsData.slice(0, remainingToolCalls);
466
+ skippedToolsData = toolsData.slice(remainingToolCalls);
467
+
468
+ const skippedToolNames = skippedToolsData.map((t) => t.name).join(', ');
469
+ console.warn(
470
+ `Tool call limit will be reached. Executing only ${remainingToolCalls} of ${toolsData.length} requested tools. ` +
471
+ `Skipped tools: ${skippedToolNames}`
472
+ );
473
+ }
474
+
434
475
  //add tool status for every tool entry
435
- toolsData.forEach((tool) => {
476
+ actualToolsData.forEach((tool) => {
436
477
  tool.status = tool.name ? this._toolStatusMap?.[tool.name] : undefined;
437
478
  });
438
- toolsData.content = _content;
439
- toolsData.requestId = llmReqUid;
440
- toolsData.contextWindow = contextWindow;
479
+ actualToolsData.content = _content;
480
+ actualToolsData.requestId = llmReqUid;
481
+ actualToolsData.contextWindow = contextWindow;
441
482
 
442
483
  llmMessage.tool_calls = toolsData.map((tool) => {
443
484
  return {
@@ -452,7 +493,8 @@ export class Conversation extends EventEmitter {
452
493
 
453
494
  //if (llmMessage.tool_calls?.length <= 0) return;
454
495
 
455
- this.emit(TLLMEvent.ToolInfo, toolsData);
496
+ // Emit ToolInfo with only the tools we'll actually execute
497
+ this.emit(TLLMEvent.ToolInfo, actualToolsData);
456
498
 
457
499
  //initialize the agent callback logic
458
500
  const _agentCallback = (data) => {
@@ -487,7 +529,8 @@ export class Conversation extends EventEmitter {
487
529
  //eventEmitter.emit('content', data);
488
530
  };
489
531
 
490
- const toolProcessingTasks = toolsData.map(
532
+ // Only process tools up to the limit
533
+ const toolProcessingTasks = actualToolsData.map(
491
534
  (tool: { index: number; name: string; type: string; arguments: Record<string, any> }) => async () => {
492
535
  const endpoint = endpoints?.get(tool?.name) || tool?.name;
493
536
  // Sometimes we have object response from the LLM such as Anthropic
@@ -532,22 +575,47 @@ export class Conversation extends EventEmitter {
532
575
  this.emit('afterToolCall', { tool, args }, functionResponse); // Deprecated
533
576
  this.emit(TLLMEvent.ToolResult, { tool, result, requestId: llmReqUid });
534
577
 
578
+ // Increment tool call counter
579
+ this._toolCallCount++;
580
+
535
581
  return { ...tool, result: functionResponse };
536
582
  }
537
583
  );
538
584
 
539
585
  const processedToolsData = await processWithConcurrencyLimit<ToolData>(toolProcessingTasks, concurrentToolCalls);
540
586
 
587
+ // Add skipped tools with pending status (not errors - they can be executed in next request)
588
+ const skippedToolsWithPendingStatus = skippedToolsData.map((tool) => ({
589
+ ...tool,
590
+ result: JSON.stringify({
591
+ status: 'pending',
592
+ message: `Tool execution deferred - maximum tool call limit (${this._maxToolCallsPerSession}) reached for this request. This tool can be executed in a follow-up request.`,
593
+ pending: true,
594
+ }),
595
+ }));
596
+
597
+ // Combine executed tools and pending tools for context
598
+ const allToolsData = [...processedToolsData, ...skippedToolsWithPendingStatus];
599
+
600
+ // Emit pending status for skipped tools (not errors - these are valid requests)
601
+ skippedToolsWithPendingStatus.forEach((tool) => {
602
+ this.emit(TLLMEvent.ToolResult, {
603
+ tool,
604
+ result: { status: 'pending', message: 'Tool execution deferred - limit reached', pending: true },
605
+ requestId: llmReqUid,
606
+ });
607
+ });
608
+
541
609
  //if (!passThroughContent) {
542
610
 
543
611
  if (!passThroughContent) {
544
- this._context.addToolMessage(llmMessage, processedToolsData, message_id);
612
+ this._context.addToolMessage(llmMessage, allToolsData, message_id);
545
613
  //delete toolHeaders['x-passthrough'];
546
614
  } else {
547
615
  //this._context.addAssistantMessage(passThroughContent, message_id);
548
616
 
549
617
  //llmMessage.content += '\n' + passThroughContent;
550
- this._context.addToolMessage(llmMessage, processedToolsData, message_id, { passThrough: true });
618
+ this._context.addToolMessage(llmMessage, allToolsData, message_id, { passThrough: true });
551
619
 
552
620
  //this._context.addAssistantMessage(passThroughContent, message_id, { passthrough: true });
553
621
  //this should not be stored in the persistent conversation store
@@ -556,6 +624,24 @@ export class Conversation extends EventEmitter {
556
624
  //toolHeaders['x-passthrough'] = 'true';
557
625
  }
558
626
 
627
+ // Check if tool call limit has been reached AFTER processing this batch
628
+ const limitReached = this._toolCallCount >= this._maxToolCallsPerSession;
629
+ const hasPendingTools = skippedToolsWithPendingStatus.length > 0;
630
+
631
+ if (limitReached) {
632
+ // Disable tools for the next (final) call to prevent infinite loops
633
+ this._disableToolsForNextCall = true;
634
+
635
+ if (hasPendingTools) {
636
+ // Only add system instruction if there are pending tools
637
+ // If no pending tools, LLM completed naturally - don't confuse it with limit messages
638
+ const systemInstruction = `You have reached the maximum number of tool calls (${this._maxToolCallsPerSession}) for this request. Some tools are marked as "pending" and were not executed. Please provide a helpful response based on the information you've gathered so far. You may acknowledge these pending tools and suggest the user can continue in a follow-up request.`;
639
+
640
+ this._context.addUserMessage(systemInstruction, message_id, { internal: true });
641
+ this.emit(TLLMEvent.Interrupted, 'max_tool_calls', { requestId: llmReqUid });
642
+ }
643
+ }
644
+
559
645
  this.streamPrompt(null, toolHeaders, concurrentToolCalls, abortSignal).then(resolve).catch(reject);
560
646
 
561
647
  //} else {
@@ -103,7 +103,7 @@ export class TemplateStringHelper {
103
103
  * unmatched placeholders will be left as is
104
104
  * Recursively resolves nested template variables until no more variables are found
105
105
  */
106
- public parse(data: Record<string, string>, regex: TemplateStringMatch = Match.default, maxDepth: number = 5) {
106
+ public parse(data: Record<string, unknown>, regex: TemplateStringMatch = Match.default, maxDepth: number = 5) {
107
107
  if (typeof this._current !== 'string' || typeof data !== 'object') return this;
108
108
 
109
109
  // Keep parsing until no more template variables are resolved or max depth is reached
@@ -114,12 +114,13 @@ export class TemplateStringHelper {
114
114
  this._current = this._current.replace(regex, (match, token) => {
115
115
  let val = data?.[token] ?? match; // Use nullish coalescing to preserve falsy values (0, '', false)
116
116
 
117
- //if no exact match, try to parse the token as a JSON expression
118
- if (!data?.[token]) {
119
- val = JSONExpression(data, token) || `{{${token}}}`; //if no match, use the token as is
117
+ // if no exact match, try to parse the token as a JSON expression
118
+ // * Nullish check: using `==` intentionally to match both null and undefined
119
+ if (data?.[token] == null) {
120
+ val = JSONExpression(data, token) ?? `{{${token}}}`; //if no match, use the token as is
120
121
  }
121
122
 
122
- return typeof val === 'object' ? JSON.stringify(val) : escapeJsonField(val);
123
+ return typeof val === 'object' ? JSON.stringify(val) : escapeJsonField(val as string);
123
124
  });
124
125
 
125
126
  // Break early if no changes were made : we parsed all the template variables
package/src/index.ts CHANGED
@@ -93,6 +93,7 @@ export * from './Components/APICall/parseUrl';
93
93
  export * from './Components/Image/imageSettings.config';
94
94
  export * from './Components/RAG/DataSourceCleaner.class';
95
95
  export * from './Components/RAG/DataSourceComponent.class';
96
+ export * from './Components/RAG/DataSourceIndexer.class';
96
97
  export * from './Components/RAG/DataSourceLookup.class';
97
98
  export * from './Components/Triggers/Gmail.trigger';
98
99
  export * from './Components/Triggers/JobScheduler.trigger';
package/src/index.ts.bak CHANGED
@@ -93,6 +93,7 @@ export * from './Components/APICall/parseUrl';
93
93
  export * from './Components/Image/imageSettings.config';
94
94
  export * from './Components/RAG/DataSourceCleaner.class';
95
95
  export * from './Components/RAG/DataSourceComponent.class';
96
+ export * from './Components/RAG/DataSourceIndexer.class';
96
97
  export * from './Components/RAG/DataSourceLookup.class';
97
98
  export * from './Components/Triggers/Gmail.trigger';
98
99
  export * from './Components/Triggers/JobScheduler.trigger';
@@ -35,6 +35,7 @@ export interface IVectorDBRequest {
35
35
 
36
36
  export abstract class VectorDBConnector extends SecureConnector<IVectorDBRequest> {
37
37
  protected readonly USER_METADATA_KEY = 'user_metadata';
38
+ protected readonly LEGACY_USER_METADATA_KEY = 'metadata';
38
39
 
39
40
  public abstract id: string;
40
41
  public abstract getResourceACL(resourceId: string, candidate: IAccessCandidate): Promise<ACL>;
@@ -201,10 +201,21 @@ export class PineconeVectorDB extends VectorDBConnector {
201
201
  for (const match of results.matches) {
202
202
  if (match.metadata?.isSkeletonVector) continue;
203
203
 
204
+ // priortize user metadata over the default flat metadata
204
205
  if (match.metadata?.[this.USER_METADATA_KEY]) {
205
206
  match.metadata[this.USER_METADATA_KEY] = JSONContentHelper.create(match.metadata[this.USER_METADATA_KEY].toString()).tryParse();
206
207
  }
207
208
 
209
+ // if legacy metadata is present, we add it to the fallback metadata obj
210
+ if (match.metadata?.[this.LEGACY_USER_METADATA_KEY]) {
211
+ const parsedMetadata = JSONContentHelper.create(match.metadata[this.LEGACY_USER_METADATA_KEY].toString()).tryParse();
212
+ match.metadata = {
213
+ ...match.metadata,
214
+ ...parsedMetadata,
215
+ };
216
+ delete match.metadata?.[this.LEGACY_USER_METADATA_KEY];
217
+ }
218
+
208
219
  const text = match.metadata?.text as string | undefined;
209
220
  delete match.metadata?.text; // delete the text metadata to avoid duplication in case we returned the default raw metadata
210
221
 
@@ -40,16 +40,14 @@ export class EmbeddingsFactory {
40
40
  }
41
41
 
42
42
  public static getModels() {
43
- return Object.keys(supportedProviders)
44
- .reduce((acc, provider) => {
45
- acc.push(
46
- ...supportedProviders[provider].models.map((model) => ({
47
- provider,
48
- model,
49
- }))
50
- );
51
- return acc;
52
- }, [] as { provider: SupportedProviders; model: SupportedModels[SupportedProviders] }[])
53
- .filter((item) => item.model !== 'text-embedding-ada-002'); //! SPECIAL case for ada-002, it doesn't support dimensions passing
43
+ return Object.keys(supportedProviders).reduce((acc, provider) => {
44
+ acc.push(
45
+ ...supportedProviders[provider].models.map((model) => ({
46
+ provider,
47
+ model,
48
+ }))
49
+ );
50
+ return acc;
51
+ }, [] as { provider: SupportedProviders; model: SupportedModels[SupportedProviders] }[]);
54
52
  }
55
53
  }
@@ -248,4 +248,29 @@ export class LLMHelper {
248
248
 
249
249
  return _messages;
250
250
  }
251
+
252
+ /**
253
+ * Checks if the given model is part of the Claude 4 family.
254
+ *
255
+ * @param {string} modelId - The model identifier to check.
256
+ * @returns {boolean} True if the model is Claude 4 family, false otherwise.
257
+ *
258
+ * @example
259
+ * const isClaude4 = LLMHelper.isClaude4Family('claude-sonnet-4-20250514');
260
+ * console.log(isClaude4); // true
261
+ *
262
+ * @example
263
+ * const isClaude4 = LLMHelper.isClaude4Family('claude-opus-4-5');
264
+ * console.log(isClaude4); // true
265
+ *
266
+ * @example
267
+ * const isClaude4 = LLMHelper.isClaude4Family('gpt-4-turbo');
268
+ * console.log(isClaude4); // false
269
+ */
270
+ public static isClaude4Family(modelId: string): boolean {
271
+ if (!modelId) return false;
272
+ // Match patterns like: claude-4-*, claude-{variant}-4-*, claude-{variant}-4
273
+ // Examples: claude-opus-4-5, claude-sonnet-4-20250514, claude-4-opus
274
+ return /claude-(?:\w+-)?4(?:-|$)/i.test(modelId);
275
+ }
251
276
  }
@@ -275,7 +275,7 @@ export abstract class LLMConnector extends Connector {
275
275
  const teamId = await this.getTeamId(candidate);
276
276
 
277
277
  // We need the model entry name for usage reporting
278
- _params.modelEntryName = typeof model === 'string' ? model : (model as TLLMModel).modelId;
278
+ _params.modelEntryName = typeof model === 'string' ? model : model?.modelEntryName || model?.modelId;
279
279
  _params.teamId = teamId;
280
280
 
281
281
  const modelProviderCandidate = modelsProvider.requester(candidate);