@smythos/sre 1.7.18 → 1.7.20
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 +21 -21
- package/dist/index.js.map +1 -1
- package/dist/types/Components/RAG/DataSourceCleaner.class.d.ts +37 -0
- package/dist/types/Components/RAG/DataSourceComponent.class.d.ts +30 -0
- package/dist/types/Components/RAG/DataSourceIndexer.class.d.ts +14 -0
- package/dist/types/Components/RAG/DataSourceLookup.class.d.ts +36 -0
- package/dist/types/helpers/Conversation.helper.d.ts +3 -0
- package/dist/types/subsystems/LLMManager/LLM.inference.d.ts +10 -3
- package/dist/types/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.d.ts +4 -2
- package/dist/types/subsystems/Security/ManagedVault.service/connectors/SecretManagerManagedVault.d.ts +10 -0
- package/dist/types/subsystems/Security/Vault.service/connectors/SecretsManager.class.d.ts +5 -0
- package/dist/types/types/LLM.types.d.ts +2 -0
- package/dist/types/utils/array.utils.d.ts +4 -0
- package/package.json +1 -1
- package/src/helpers/AWSLambdaCode.helper.ts +40 -45
- package/src/helpers/Conversation.helper.ts +14 -10
- package/src/subsystems/LLMManager/LLM.inference.ts +63 -47
- package/src/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.ts +92 -19
- package/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts +34 -27
- package/src/subsystems/ObservabilityManager/Telemetry.service/connectors/OTel/OTel.class.ts +6 -0
- package/src/subsystems/Security/ManagedVault.service/connectors/SecretManagerManagedVault.ts +111 -48
- package/src/subsystems/Security/Vault.service/connectors/SecretsManager.class.ts +92 -62
- package/src/types/LLM.types.ts +5 -0
|
@@ -13,14 +13,30 @@ import { TLLMChatResponse, TLLMMessageRole, TLLMModel, TLLMParams } from '@sre/t
|
|
|
13
13
|
import { LLMConnector } from './LLM.service/LLMConnector';
|
|
14
14
|
import { IModelsProviderRequest, ModelsProviderConnector } from './ModelsProvider.service/ModelsProviderConnector';
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const logger = Logger('LLMInference');
|
|
17
17
|
|
|
18
18
|
type TPromptParams = { query?: string; contextWindow?: any[]; files?: any[]; params: TLLMParams; onFallback?: (data: any) => void };
|
|
19
19
|
|
|
20
20
|
export class LLMInference {
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
private _model: string | TLLMModel;
|
|
22
|
+
public get model() {
|
|
23
|
+
return this._model;
|
|
24
|
+
}
|
|
25
|
+
public get modelId() {
|
|
26
|
+
return typeof this._model === 'string' ? this._model : this._model?.modelId;
|
|
27
|
+
}
|
|
28
|
+
private _llmConnector: LLMConnector;
|
|
29
|
+
public get llmConnector() {
|
|
30
|
+
return this._llmConnector;
|
|
31
|
+
}
|
|
32
|
+
private _modelProviderReq: IModelsProviderRequest;
|
|
33
|
+
public get modelProviderReq() {
|
|
34
|
+
return this._modelProviderReq;
|
|
35
|
+
}
|
|
36
|
+
private _llmProviderName: string;
|
|
37
|
+
public get llmProviderName() {
|
|
38
|
+
return this._llmProviderName;
|
|
39
|
+
}
|
|
24
40
|
public teamId?: string;
|
|
25
41
|
|
|
26
42
|
public static async getInstance(model: string | TLLMModel, candidate: AccessCandidate) {
|
|
@@ -34,18 +50,18 @@ export class LLMInference {
|
|
|
34
50
|
const llmInference = new LLMInference();
|
|
35
51
|
llmInference.teamId = teamId;
|
|
36
52
|
|
|
37
|
-
llmInference.
|
|
53
|
+
llmInference._modelProviderReq = modelsProvider.requester(candidate);
|
|
38
54
|
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
llmInference.
|
|
55
|
+
llmInference._llmProviderName = await llmInference._modelProviderReq.getProvider(model);
|
|
56
|
+
if (llmInference._llmProviderName) {
|
|
57
|
+
llmInference._llmConnector = ConnectorService.getLLMConnector(llmInference._llmProviderName);
|
|
42
58
|
}
|
|
43
59
|
|
|
44
|
-
if (!llmInference.
|
|
45
|
-
|
|
60
|
+
if (!llmInference._llmConnector) {
|
|
61
|
+
logger.warn(`Model ${model} unavailable for team ${teamId}`);
|
|
46
62
|
}
|
|
47
63
|
|
|
48
|
-
llmInference.
|
|
64
|
+
llmInference._model = model;
|
|
49
65
|
|
|
50
66
|
return llmInference;
|
|
51
67
|
}
|
|
@@ -53,32 +69,32 @@ export class LLMInference {
|
|
|
53
69
|
public static user(candidate: AccessCandidate): any {}
|
|
54
70
|
|
|
55
71
|
public get connector(): LLMConnector {
|
|
56
|
-
return this.
|
|
72
|
+
return this._llmConnector;
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
public async prompt({ query, contextWindow, files, params, onFallback = () => {} }: TPromptParams, isInFallback: boolean = false) {
|
|
60
76
|
let messages = contextWindow || [];
|
|
61
77
|
|
|
62
78
|
if (query) {
|
|
63
|
-
const content = this.
|
|
79
|
+
const content = this._llmConnector.enhancePrompt(query, params);
|
|
64
80
|
messages.push({ role: TLLMMessageRole.User, content });
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
// Reset the model, since the fallback model may change — especially when using user custom models.
|
|
68
|
-
params.model = this.
|
|
84
|
+
params.model = this._model;
|
|
69
85
|
|
|
70
86
|
params.messages = messages;
|
|
71
87
|
params.files = files;
|
|
72
88
|
|
|
73
89
|
// If a fallback model is used, trigger the onFallback callback to notify the caller.
|
|
74
90
|
if (isInFallback && typeof onFallback === 'function') {
|
|
75
|
-
onFallback({ model: this.
|
|
91
|
+
onFallback({ model: this._model });
|
|
76
92
|
}
|
|
77
93
|
|
|
78
94
|
try {
|
|
79
|
-
let response: TLLMChatResponse = await this.
|
|
95
|
+
let response: TLLMChatResponse = await this._llmConnector.requester(AccessCandidate.agent(params.agentId)).request(params);
|
|
80
96
|
|
|
81
|
-
const result = this.
|
|
97
|
+
const result = this._llmConnector.postProcess(response?.content);
|
|
82
98
|
if (result.error) {
|
|
83
99
|
// If the model stopped before completing the response, this is usually due to output token limit reached.
|
|
84
100
|
if (response.finishReason !== 'stop') {
|
|
@@ -102,12 +118,12 @@ export class LLMInference {
|
|
|
102
118
|
}
|
|
103
119
|
} catch (fallbackError) {
|
|
104
120
|
// If fallback also failed, log it but continue to throw original error
|
|
105
|
-
|
|
121
|
+
logger.warn('Fallback also failed:', fallbackError);
|
|
106
122
|
}
|
|
107
123
|
}
|
|
108
124
|
|
|
109
125
|
// If fallback was not attempted or failed, throw the original error
|
|
110
|
-
|
|
126
|
+
logger.error('Error in chatRequest: ', error);
|
|
111
127
|
throw error;
|
|
112
128
|
}
|
|
113
129
|
}
|
|
@@ -116,23 +132,23 @@ export class LLMInference {
|
|
|
116
132
|
let messages = contextWindow || [];
|
|
117
133
|
|
|
118
134
|
if (query) {
|
|
119
|
-
const content = this.
|
|
135
|
+
const content = this._llmConnector.enhancePrompt(query, params);
|
|
120
136
|
messages.push({ role: TLLMMessageRole.User, content });
|
|
121
137
|
}
|
|
122
138
|
|
|
123
139
|
// Reset the model, since the fallback model may change — especially when using user custom models.
|
|
124
|
-
params.model = this.
|
|
140
|
+
params.model = this._model;
|
|
125
141
|
|
|
126
142
|
params.messages = messages;
|
|
127
143
|
params.files = files;
|
|
128
144
|
|
|
129
145
|
// If a fallback model is used, trigger the onFallback callback to notify the caller.
|
|
130
146
|
if (isInFallback && typeof onFallback === 'function') {
|
|
131
|
-
onFallback({ model: this.
|
|
147
|
+
onFallback({ model: this._model });
|
|
132
148
|
}
|
|
133
149
|
|
|
134
150
|
try {
|
|
135
|
-
return await this.
|
|
151
|
+
return await this._llmConnector.user(AccessCandidate.agent(params.agentId)).streamRequest(params);
|
|
136
152
|
} catch (error) {
|
|
137
153
|
// Attempt fallback for custom models (only if not already in fallback)
|
|
138
154
|
if (!isInFallback) {
|
|
@@ -152,12 +168,12 @@ export class LLMInference {
|
|
|
152
168
|
}
|
|
153
169
|
} catch (fallbackError) {
|
|
154
170
|
// If fallback also failed, log it but continue to return error emitter
|
|
155
|
-
|
|
171
|
+
logger.warn('Fallback also failed:', fallbackError);
|
|
156
172
|
}
|
|
157
173
|
}
|
|
158
174
|
|
|
159
175
|
// If fallback was not attempted or failed, return error emitter
|
|
160
|
-
|
|
176
|
+
logger.error('Error in streamRequest:', error);
|
|
161
177
|
|
|
162
178
|
const dummyEmitter = new EventEmitter();
|
|
163
179
|
process.nextTick(() => {
|
|
@@ -224,22 +240,22 @@ export class LLMInference {
|
|
|
224
240
|
* @returns The result from the fallback execution, or null if fallback should not be attempted
|
|
225
241
|
*/
|
|
226
242
|
private async executeFallback(methodName: 'prompt' | 'promptStream', args: TPromptParams): Promise<any> {
|
|
227
|
-
const isCustomModel = await this.
|
|
228
|
-
const fallbackModel = await this.
|
|
243
|
+
const isCustomModel = await this._modelProviderReq.isUserCustomLLM(this._model);
|
|
244
|
+
const fallbackModel = await this._modelProviderReq.getFallbackLLM(this._model);
|
|
229
245
|
|
|
230
246
|
// Only execute fallback if it's a custom model with a configured fallback
|
|
231
247
|
if (!isCustomModel || !fallbackModel) {
|
|
232
248
|
return null;
|
|
233
249
|
}
|
|
234
250
|
|
|
235
|
-
|
|
251
|
+
logger.info(`Attempting fallback from ${this._model} to ${fallbackModel}`);
|
|
236
252
|
|
|
237
253
|
// Mutate the model and connector to use fallback
|
|
238
|
-
this.
|
|
254
|
+
this._model = fallbackModel;
|
|
239
255
|
|
|
240
|
-
const llmProvider = await this.
|
|
256
|
+
const llmProvider = await this._modelProviderReq.getProvider(fallbackModel);
|
|
241
257
|
if (llmProvider) {
|
|
242
|
-
this.
|
|
258
|
+
this._llmConnector = ConnectorService.getLLMConnector(llmProvider);
|
|
243
259
|
}
|
|
244
260
|
|
|
245
261
|
// Call the appropriate method with isInFallback=true to prevent further fallbacks
|
|
@@ -252,13 +268,13 @@ export class LLMInference {
|
|
|
252
268
|
|
|
253
269
|
public async imageGenRequest({ query, files, params }: TPromptParams) {
|
|
254
270
|
params.prompt = query;
|
|
255
|
-
return this.
|
|
271
|
+
return this._llmConnector.user(AccessCandidate.agent(params.agentId)).imageGenRequest(params);
|
|
256
272
|
}
|
|
257
273
|
|
|
258
274
|
public async imageEditRequest({ query, files, params }: TPromptParams) {
|
|
259
275
|
params.prompt = query;
|
|
260
276
|
params.files = files;
|
|
261
|
-
return this.
|
|
277
|
+
return this._llmConnector.user(AccessCandidate.agent(params.agentId)).imageEditRequest(params);
|
|
262
278
|
}
|
|
263
279
|
|
|
264
280
|
//@deprecated
|
|
@@ -269,11 +285,11 @@ export class LLMInference {
|
|
|
269
285
|
throw new Error('Input messages are required.');
|
|
270
286
|
}
|
|
271
287
|
|
|
272
|
-
const model = params.model || this.
|
|
288
|
+
const model = params.model || this._model;
|
|
273
289
|
|
|
274
|
-
return await this.
|
|
290
|
+
return await this._llmConnector.user(AccessCandidate.agent(agentId)).streamRequest({ ...params, model });
|
|
275
291
|
} catch (error) {
|
|
276
|
-
|
|
292
|
+
logger.error('Error in streamRequest:', error);
|
|
277
293
|
|
|
278
294
|
const dummyEmitter = new EventEmitter();
|
|
279
295
|
process.nextTick(() => {
|
|
@@ -306,11 +322,11 @@ export class LLMInference {
|
|
|
306
322
|
//FIXME we need to update the connector multimediaStreamRequest in order to ignore prompt param if not provided
|
|
307
323
|
const userMessage = Array.isArray(params.messages) ? params.messages.pop() : {};
|
|
308
324
|
const prompt = userMessage?.content || '';
|
|
309
|
-
const model = params.model || this.
|
|
325
|
+
const model = params.model || this._model;
|
|
310
326
|
|
|
311
|
-
return await this.
|
|
327
|
+
return await this._llmConnector.user(AccessCandidate.agent(agentId)).multimodalStreamRequest(prompt, { ...params, model });
|
|
312
328
|
} catch (error: any) {
|
|
313
|
-
|
|
329
|
+
logger.error('Error in multimodalRequest: ', error);
|
|
314
330
|
|
|
315
331
|
throw error;
|
|
316
332
|
}
|
|
@@ -337,12 +353,12 @@ export class LLMInference {
|
|
|
337
353
|
params.files = _files;
|
|
338
354
|
|
|
339
355
|
try {
|
|
340
|
-
prompt = this.
|
|
341
|
-
const model = params.model || this.
|
|
356
|
+
prompt = this._llmConnector.enhancePrompt(prompt, config);
|
|
357
|
+
const model = params.model || this._model;
|
|
342
358
|
|
|
343
|
-
return await this.
|
|
359
|
+
return await this._llmConnector.user(AccessCandidate.agent(agentId)).multimodalStreamRequest(prompt, { ...params, model });
|
|
344
360
|
} catch (error: any) {
|
|
345
|
-
|
|
361
|
+
logger.error('Error in multimodalRequest: ', error);
|
|
346
362
|
|
|
347
363
|
throw error;
|
|
348
364
|
}
|
|
@@ -376,7 +392,7 @@ export class LLMInference {
|
|
|
376
392
|
|
|
377
393
|
//#region get max model context
|
|
378
394
|
|
|
379
|
-
const modelInfo = await this.
|
|
395
|
+
const modelInfo = await this._modelProviderReq.getModelInfo(this._model, true);
|
|
380
396
|
let maxModelContext = modelInfo?.tokens;
|
|
381
397
|
let maxModelOutputTokens = modelInfo?.completionTokens || modelInfo?.tokens;
|
|
382
398
|
// const isStandardLLM = LLMRegistry.isStandardLLM(this.model);
|
|
@@ -399,10 +415,10 @@ export class LLMInference {
|
|
|
399
415
|
}
|
|
400
416
|
|
|
401
417
|
if (maxInputContext <= 0) {
|
|
402
|
-
|
|
418
|
+
logger.warn('Max input context is 0, returning empty context window, This usually indicates a wrong model configuration');
|
|
403
419
|
}
|
|
404
420
|
|
|
405
|
-
|
|
421
|
+
logger.debug(
|
|
406
422
|
`Context Window Configuration: Max Input Tokens: ${maxInputContext}, Max Output Tokens: ${maxOutputContext}, Max Model Tokens: ${maxModelContext}`
|
|
407
423
|
);
|
|
408
424
|
const systemMessage = { role: 'system', content: systemPrompt };
|
|
@@ -473,7 +489,7 @@ function countTokens(content: any, model: 'gpt-4o' | 'gpt-4o-mini' = 'gpt-4o') {
|
|
|
473
489
|
const tokens = encodeChat([{ role: 'user', content: _stringifiedContent } as ChatMessage], model);
|
|
474
490
|
return tokens.length;
|
|
475
491
|
} catch (error) {
|
|
476
|
-
|
|
492
|
+
logger.warn('Error in countTokens: ', error);
|
|
477
493
|
return 0;
|
|
478
494
|
}
|
|
479
495
|
}
|
|
@@ -139,6 +139,7 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
139
139
|
? toolCall.functionCall?.args
|
|
140
140
|
: JSON.stringify(toolCall.functionCall?.args ?? {}),
|
|
141
141
|
role: TLLMMessageRole.Assistant,
|
|
142
|
+
thoughtSignature: (toolCall as any).thoughtSignature, // Preserve Google AI's reasoning context
|
|
142
143
|
}));
|
|
143
144
|
useTool = true;
|
|
144
145
|
}
|
|
@@ -202,6 +203,7 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
202
203
|
? toolCall.functionCall?.args
|
|
203
204
|
: JSON.stringify(toolCall.functionCall?.args ?? {}),
|
|
204
205
|
role: TLLMMessageRole.Assistant,
|
|
206
|
+
thoughtSignature: (toolCall as any).thoughtSignature, // Preserve Google AI's reasoning context
|
|
205
207
|
}));
|
|
206
208
|
emitter.emit(TLLMEvent.ToolInfo, toolsData);
|
|
207
209
|
}
|
|
@@ -426,6 +428,13 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
426
428
|
if (params.stopSequences?.length) config.stopSequences = params.stopSequences;
|
|
427
429
|
if (responseMimeType) config.responseMimeType = responseMimeType;
|
|
428
430
|
|
|
431
|
+
// #region Gemini 3 specific fields
|
|
432
|
+
const isGemini3Model = params.modelEntryName?.includes('gemini-3');
|
|
433
|
+
|
|
434
|
+
if (isGemini3Model) {
|
|
435
|
+
if (params?.reasoningEffort) config.thinkingConfig = { thinkingLevel: params.reasoningEffort };
|
|
436
|
+
}
|
|
437
|
+
|
|
429
438
|
if (systemInstruction) body.systemInstruction = systemInstruction;
|
|
430
439
|
if (Object.keys(config).length > 0) {
|
|
431
440
|
body.generationConfig = config;
|
|
@@ -505,36 +514,76 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
505
514
|
) {
|
|
506
515
|
// SmythOS (built-in) models have a prefix, so we need to remove it to get the model name
|
|
507
516
|
const modelName = metadata.modelEntryName.replace(BUILT_IN_MODEL_PREFIX, '');
|
|
508
|
-
|
|
517
|
+
|
|
518
|
+
// Initially, all input tokens – such as text, audio, image, video, document, etc. – were included in promptTokenCount.
|
|
519
|
+
let inputTokens = usage?.promptTokenCount || 0;
|
|
520
|
+
|
|
521
|
+
// The pricing is the same for output and thinking tokens, so we can add them together.
|
|
522
|
+
const outputTokens = (usage?.candidatesTokenCount || 0) + (usage?.thoughtsTokenCount || 0);
|
|
523
|
+
|
|
524
|
+
// If cached input tokens are available, we need to subtract them from the input tokens.
|
|
525
|
+
let cachedInputTokens = usage?.cachedContentTokenCount || 0;
|
|
526
|
+
|
|
527
|
+
if (cachedInputTokens) {
|
|
528
|
+
inputTokens = inputTokens - cachedInputTokens;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// #region Find matching model and set tier based on threshold
|
|
509
532
|
const tierThresholds = {
|
|
510
533
|
'gemini-1.5-pro': 128_000,
|
|
511
534
|
'gemini-2.5-pro': 200_000,
|
|
535
|
+
'gemini-3-pro': 200_000,
|
|
512
536
|
};
|
|
513
537
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
538
|
+
let inTier = '';
|
|
539
|
+
let outTier = '';
|
|
540
|
+
let crTier = '';
|
|
517
541
|
|
|
518
|
-
// Find matching model and set tier based on threshold
|
|
519
542
|
const modelWithTier = Object.keys(tierThresholds).find((model) => modelName.includes(model));
|
|
520
543
|
if (modelWithTier) {
|
|
521
|
-
|
|
544
|
+
inTier = inputTokens <= tierThresholds[modelWithTier] ? 'tier1' : 'tier2';
|
|
545
|
+
outTier = outputTokens <= tierThresholds[modelWithTier] ? 'tier1' : 'tier2';
|
|
546
|
+
crTier = cachedInputTokens <= tierThresholds[modelWithTier] ? 'tier1' : 'tier2';
|
|
522
547
|
}
|
|
548
|
+
// #endregion
|
|
523
549
|
|
|
550
|
+
// #region Calculate audio input tokens
|
|
551
|
+
// Since Gemini 2.5 Flash has a different pricing model for audio input tokens, we need to report audio input tokens separately.
|
|
552
|
+
let audioInputTokens = 0;
|
|
553
|
+
let cachedAudioInputTokens = 0;
|
|
554
|
+
const isFlashModel = ['gemini-2.5-flash'].includes(modelName);
|
|
555
|
+
|
|
556
|
+
if (isFlashModel) {
|
|
557
|
+
// There is no concept of different pricing for Flash models based on token tiers (e.g., less than or greater than 200k),
|
|
558
|
+
// so we don't need to provide tier information for audio input tokens.
|
|
559
|
+
audioInputTokens = usage?.promptTokensDetails?.find((detail) => detail.modality === 'AUDIO')?.tokenCount || 0;
|
|
560
|
+
|
|
561
|
+
// subtract the audio cached input tokens from the audio input tokens and total cached input tokens.
|
|
562
|
+
cachedAudioInputTokens = usage?.cacheTokensDetails?.find((detail) => detail.modality === 'AUDIO')?.tokenCount || 0;
|
|
563
|
+
if (cachedAudioInputTokens) {
|
|
564
|
+
audioInputTokens = audioInputTokens - cachedAudioInputTokens;
|
|
565
|
+
cachedInputTokens = cachedInputTokens - cachedAudioInputTokens;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
inputTokens = inputTokens - audioInputTokens;
|
|
569
|
+
}
|
|
524
570
|
// #endregion
|
|
525
571
|
|
|
526
572
|
const usageData = {
|
|
527
573
|
sourceId: `llm:${modelName}`,
|
|
528
|
-
input_tokens:
|
|
529
|
-
output_tokens:
|
|
574
|
+
input_tokens: inputTokens,
|
|
575
|
+
output_tokens: outputTokens,
|
|
530
576
|
input_tokens_audio: audioInputTokens,
|
|
531
|
-
input_tokens_cache_read:
|
|
577
|
+
input_tokens_cache_read: cachedInputTokens,
|
|
578
|
+
input_tokens_cache_read_audio: cachedAudioInputTokens,
|
|
532
579
|
input_tokens_cache_write: 0,
|
|
533
|
-
reasoning_tokens: usage?.thoughtsTokenCount,
|
|
580
|
+
// reasoning_tokens: usage?.thoughtsTokenCount, // * reasoning tokens are included in the output tokens.
|
|
534
581
|
keySource: metadata.keySource,
|
|
535
582
|
agentId: metadata.agentId,
|
|
536
583
|
teamId: metadata.teamId,
|
|
537
|
-
|
|
584
|
+
inTier,
|
|
585
|
+
outTier,
|
|
586
|
+
crTier,
|
|
538
587
|
};
|
|
539
588
|
SystemEvents.emit('USAGE:LLM', usageData);
|
|
540
589
|
|
|
@@ -665,12 +714,17 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
665
714
|
}
|
|
666
715
|
|
|
667
716
|
if (part.functionCall) {
|
|
668
|
-
|
|
717
|
+
const functionCallPart: any = {
|
|
669
718
|
functionCall: {
|
|
670
719
|
name: part.functionCall.name,
|
|
671
720
|
args: parseFunctionArgs(part.functionCall.args),
|
|
672
721
|
},
|
|
673
|
-
}
|
|
722
|
+
};
|
|
723
|
+
// Preserve thoughtSignature if present for Google AI reasoning context
|
|
724
|
+
if ((part as any).thoughtSignature) {
|
|
725
|
+
functionCallPart.thoughtSignature = (part as any).thoughtSignature;
|
|
726
|
+
}
|
|
727
|
+
content.push(functionCallPart);
|
|
674
728
|
continue;
|
|
675
729
|
}
|
|
676
730
|
|
|
@@ -699,12 +753,17 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
699
753
|
const hasFunctionCall = content.some((part) => part.functionCall);
|
|
700
754
|
if (!hasFunctionCall && toolsData.length > 0) {
|
|
701
755
|
toolsData.forEach((toolCall) => {
|
|
702
|
-
|
|
756
|
+
const functionCallPart: any = {
|
|
703
757
|
functionCall: {
|
|
704
758
|
name: toolCall.name,
|
|
705
759
|
args: parseFunctionArgs(toolCall.arguments),
|
|
706
760
|
},
|
|
707
|
-
}
|
|
761
|
+
};
|
|
762
|
+
// Preserve thoughtSignature if present for Google AI reasoning context
|
|
763
|
+
if (toolCall.thoughtSignature) {
|
|
764
|
+
functionCallPart.thoughtSignature = toolCall.thoughtSignature;
|
|
765
|
+
}
|
|
766
|
+
content.push(functionCallPart);
|
|
708
767
|
});
|
|
709
768
|
}
|
|
710
769
|
|
|
@@ -811,6 +870,10 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
811
870
|
name: part.functionCall.name,
|
|
812
871
|
args: parseFunctionArgs(part.functionCall.args),
|
|
813
872
|
};
|
|
873
|
+
// Preserve thoughtSignature if present for Google AI reasoning context
|
|
874
|
+
if ((part as any).thoughtSignature) {
|
|
875
|
+
normalizedPart.thoughtSignature = (part as any).thoughtSignature;
|
|
876
|
+
}
|
|
814
877
|
}
|
|
815
878
|
|
|
816
879
|
if (part.functionResponse) {
|
|
@@ -839,12 +902,17 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
839
902
|
pushTextPart(normalizedParts, contentPart.text);
|
|
840
903
|
} else if ('functionCall' in contentPart && (contentPart as any).functionCall) {
|
|
841
904
|
const functionCallPart = (contentPart as any).functionCall;
|
|
842
|
-
|
|
905
|
+
const normalizedFunctionCall: any = {
|
|
843
906
|
functionCall: {
|
|
844
907
|
name: functionCallPart.name,
|
|
845
908
|
args: parseFunctionArgs(functionCallPart.args),
|
|
846
909
|
},
|
|
847
|
-
}
|
|
910
|
+
};
|
|
911
|
+
// Preserve thoughtSignature if present for Google AI reasoning context
|
|
912
|
+
if ((contentPart as any).thoughtSignature) {
|
|
913
|
+
normalizedFunctionCall.thoughtSignature = (contentPart as any).thoughtSignature;
|
|
914
|
+
}
|
|
915
|
+
normalizedParts.push(normalizedFunctionCall);
|
|
848
916
|
} else if ('functionResponse' in contentPart && (contentPart as any).functionResponse) {
|
|
849
917
|
const functionResponsePart = (contentPart as any).functionResponse;
|
|
850
918
|
normalizedParts.push({
|
|
@@ -882,12 +950,17 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
882
950
|
for (const toolCall of message.tool_calls) {
|
|
883
951
|
if (!toolCall?.function?.name) continue;
|
|
884
952
|
|
|
885
|
-
|
|
953
|
+
const normalizedFunctionCall: any = {
|
|
886
954
|
functionCall: {
|
|
887
955
|
name: toolCall.function.name,
|
|
888
956
|
args: parseFunctionArgs(toolCall.function.arguments),
|
|
889
957
|
},
|
|
890
|
-
}
|
|
958
|
+
};
|
|
959
|
+
// Preserve thoughtSignature if present for Google AI reasoning context
|
|
960
|
+
if ((toolCall as any).thoughtSignature) {
|
|
961
|
+
normalizedFunctionCall.thoughtSignature = (toolCall as any).thoughtSignature;
|
|
962
|
+
}
|
|
963
|
+
normalizedParts.push(normalizedFunctionCall);
|
|
891
964
|
}
|
|
892
965
|
}
|
|
893
966
|
|
package/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts
CHANGED
|
@@ -16,7 +16,7 @@ import fsSync from 'fs';
|
|
|
16
16
|
import path from 'path';
|
|
17
17
|
import { findSmythPath } from '@sre/helpers/Sysconfig.helper';
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const logger = Logger('SmythModelsProvider');
|
|
20
20
|
|
|
21
21
|
type SmythModelsProviderConfig = {
|
|
22
22
|
/**
|
|
@@ -58,7 +58,7 @@ export class JSONModelsProvider extends ModelsProviderConnector {
|
|
|
58
58
|
this._settings.mode = 'merge'; //Force merge mode if using models from .smyth folder
|
|
59
59
|
this.initDirWatcher(modelsFolder); //this.started will be set to true when the watcher is ready
|
|
60
60
|
} else {
|
|
61
|
-
|
|
61
|
+
logger.warn('No models folder found ... falling back to built-in models only');
|
|
62
62
|
this.started = true;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -71,7 +71,7 @@ export class JSONModelsProvider extends ModelsProviderConnector {
|
|
|
71
71
|
const _modelsFolder = findSmythPath('models');
|
|
72
72
|
|
|
73
73
|
if (fsSync.existsSync(_modelsFolder)) {
|
|
74
|
-
|
|
74
|
+
logger.warn('Using default models folder : ', _modelsFolder);
|
|
75
75
|
return _modelsFolder;
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -106,7 +106,7 @@ export class JSONModelsProvider extends ModelsProviderConnector {
|
|
|
106
106
|
|
|
107
107
|
private async reindexModels(dir: string) {
|
|
108
108
|
try {
|
|
109
|
-
|
|
109
|
+
logger.debug(`Reindexing models from directory: ${dir}`);
|
|
110
110
|
|
|
111
111
|
// Scan directory for models and get them as an object
|
|
112
112
|
const scannedModels = await this.scanDirectoryForModels(dir);
|
|
@@ -121,9 +121,9 @@ export class JSONModelsProvider extends ModelsProviderConnector {
|
|
|
121
121
|
|
|
122
122
|
JSONModelsProvider.localCache.clear();
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
logger.debug(`Successfully reindexed models. Total models: ${Object.keys(this.models).length}`);
|
|
125
125
|
} catch (error) {
|
|
126
|
-
|
|
126
|
+
logger.error(`Error reindexing models from directory "${dir}":`, error);
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -141,15 +141,21 @@ export class JSONModelsProvider extends ModelsProviderConnector {
|
|
|
141
141
|
const subDirModels = await this.scanDirectoryForModels(fullPath);
|
|
142
142
|
Object.assign(scannedModels, subDirModels);
|
|
143
143
|
} else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
try {
|
|
145
|
+
// Process JSON files and merge results
|
|
146
|
+
|
|
147
|
+
const fileContent = await fs.readFile(fullPath, 'utf-8');
|
|
148
|
+
const modelData = JSON.parse(fileContent);
|
|
149
|
+
const validModels = await this.getValidModels(modelData);
|
|
150
|
+
Object.assign(scannedModels, validModels);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error(`Error parsing model data from file "${fullPath}"`);
|
|
153
|
+
logger.warn(`Error parsing model data from file "${fullPath}":`, error.message);
|
|
154
|
+
}
|
|
149
155
|
}
|
|
150
156
|
}
|
|
151
157
|
} catch (error) {
|
|
152
|
-
|
|
158
|
+
logger.warn(`Error scanning directory "${dir}":`, error);
|
|
153
159
|
}
|
|
154
160
|
|
|
155
161
|
return scannedModels;
|
|
@@ -164,9 +170,9 @@ export class JSONModelsProvider extends ModelsProviderConnector {
|
|
|
164
170
|
// Single model case
|
|
165
171
|
if (this.isValidSingleModel(modelData)) {
|
|
166
172
|
validModels[modelData.modelId] = modelData as TLLMModel;
|
|
167
|
-
|
|
173
|
+
logger.debug(`Loaded model: ${modelData.modelId}`);
|
|
168
174
|
} else {
|
|
169
|
-
|
|
175
|
+
logger.warn(`Invalid model format`, modelData);
|
|
170
176
|
}
|
|
171
177
|
} else if (typeof modelData === 'object' && !Array.isArray(modelData)) {
|
|
172
178
|
// Object of models case
|
|
@@ -178,19 +184,19 @@ export class JSONModelsProvider extends ModelsProviderConnector {
|
|
|
178
184
|
//console.debug(`Loaded model: ${modelId}`);
|
|
179
185
|
models += `${modelId} `;
|
|
180
186
|
} else {
|
|
181
|
-
|
|
187
|
+
logger.warn(`Invalid model format for model "${modelId}"`);
|
|
182
188
|
}
|
|
183
189
|
} catch (error) {
|
|
184
|
-
|
|
190
|
+
logger.warn(`Error processing model "${modelId}":`, error);
|
|
185
191
|
// Continue processing other models instead of failing the whole file
|
|
186
192
|
}
|
|
187
193
|
}
|
|
188
|
-
|
|
194
|
+
logger.debug(`Loaded models: ${models}`);
|
|
189
195
|
} else {
|
|
190
|
-
|
|
196
|
+
logger.warn(`Invalid format (not a model or object of models)`);
|
|
191
197
|
}
|
|
192
198
|
} catch (error) {
|
|
193
|
-
|
|
199
|
+
logger.warn(`Error loading model:`, error);
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
return validModels;
|
|
@@ -212,7 +218,7 @@ export class JSONModelsProvider extends ModelsProviderConnector {
|
|
|
212
218
|
const stats = fsSync.statSync(dir);
|
|
213
219
|
|
|
214
220
|
if (!stats.isDirectory() && !stats.isFile()) {
|
|
215
|
-
|
|
221
|
+
logger.warn(`Path "${dir}" is neither a file nor a directory ... skipping models watcher and falling back to built-in models only`);
|
|
216
222
|
this.started = true;
|
|
217
223
|
return;
|
|
218
224
|
}
|
|
@@ -230,17 +236,18 @@ export class JSONModelsProvider extends ModelsProviderConnector {
|
|
|
230
236
|
if (this._settings?.mode === 'merge') this.models = { ...this.models, ...modelData };
|
|
231
237
|
else this.models = modelData;
|
|
232
238
|
} catch (error) {
|
|
233
|
-
console.error(`Error parsing model data from file "${dir}"
|
|
239
|
+
console.error(`Error parsing model data from file "${dir}":`);
|
|
240
|
+
logger.warn(`Error parsing model data from file "${dir}":`, error.message);
|
|
234
241
|
}
|
|
235
242
|
this.started = true;
|
|
236
243
|
return;
|
|
237
244
|
}
|
|
238
245
|
|
|
239
|
-
|
|
246
|
+
logger.warn(`Path "${dir}" is neither a file nor a directory`);
|
|
240
247
|
return;
|
|
241
248
|
}
|
|
242
249
|
} catch (error) {
|
|
243
|
-
|
|
250
|
+
logger.warn(`Path "${dir}" does not exist or cannot be accessed:`, error.message);
|
|
244
251
|
return;
|
|
245
252
|
}
|
|
246
253
|
|
|
@@ -262,19 +269,19 @@ export class JSONModelsProvider extends ModelsProviderConnector {
|
|
|
262
269
|
|
|
263
270
|
watcher
|
|
264
271
|
.on('add', (path) => {
|
|
265
|
-
|
|
272
|
+
logger.debug(`File ${path} has been added`);
|
|
266
273
|
debouncedReindex();
|
|
267
274
|
})
|
|
268
275
|
.on('change', (path) => {
|
|
269
|
-
|
|
276
|
+
logger.debug(`File ${path} has been changed`);
|
|
270
277
|
debouncedReindex();
|
|
271
278
|
})
|
|
272
279
|
.on('unlink', (path) => {
|
|
273
|
-
|
|
280
|
+
logger.debug(`File ${path} has been removed`);
|
|
274
281
|
debouncedReindex();
|
|
275
282
|
})
|
|
276
283
|
.on('ready', async () => {
|
|
277
|
-
|
|
284
|
+
logger.debug(`Watcher ready. Performing initial scan of ${dir}`);
|
|
278
285
|
// Do initial scan once when watcher is ready
|
|
279
286
|
await this.reindexModels(dir);
|
|
280
287
|
this.started = true;
|