@smythos/sre 1.7.20 → 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.
- package/dist/index.js +134 -89
- package/dist/index.js.map +1 -1
- package/dist/types/Components/AgentPlugin.class.d.ts +1 -1
- package/dist/types/Components/DataSourceIndexer.class.d.ts +4 -12
- package/dist/types/Components/GenAILLM.class.d.ts +5 -5
- package/dist/types/Components/RAG/DataSourceCleaner.class.d.ts +4 -4
- package/dist/types/Components/RAG/DataSourceComponent.class.d.ts +5 -1
- package/dist/types/Components/index.d.ts +3 -3
- package/dist/types/config.d.ts +1 -0
- package/dist/types/helpers/Conversation.helper.d.ts +10 -13
- package/dist/types/helpers/TemplateString.helper.d.ts +1 -1
- package/dist/types/index.d.ts +4 -3
- package/dist/types/subsystems/IO/VectorDB.service/VectorDBConnector.d.ts +1 -0
- package/dist/types/subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class.d.ts +1 -0
- package/dist/types/subsystems/IO/VectorDB.service/connectors/PineconeVectorDB.class.d.ts +11 -4
- package/dist/types/subsystems/IO/VectorDB.service/embed/index.d.ts +5 -0
- package/dist/types/subsystems/LLMManager/LLM.helper.d.ts +19 -0
- package/dist/types/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.d.ts +15 -10
- package/dist/types/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.d.ts +35 -0
- package/dist/types/subsystems/Security/Account.service/AccountConnector.d.ts +2 -2
- package/dist/types/subsystems/Security/Vault.service/connectors/SecretsManager.class.d.ts +2 -3
- package/dist/types/types/LLM.types.d.ts +23 -0
- package/dist/types/types/VectorDB.types.d.ts +4 -0
- package/dist/types/utils/string.utils.d.ts +1 -0
- package/package.json +3 -3
- package/src/Components/APIEndpoint.class.ts +1 -6
- package/src/Components/AgentPlugin.class.ts +20 -3
- package/src/Components/Classifier.class.ts +79 -16
- package/src/Components/Component.class.ts +14 -1
- package/src/Components/ForEach.class.ts +34 -6
- package/src/Components/GenAILLM.class.ts +75 -34
- package/src/Components/LLMAssistant.class.ts +56 -21
- package/src/Components/RAG/DataSourceCleaner.class.ts +180 -0
- package/src/Components/RAG/DataSourceComponent.class.ts +137 -0
- package/src/Components/RAG/DataSourceIndexer.class.ts +260 -0
- package/src/Components/{DataSourceLookup.class.ts → RAG/DataSourceLookup.class.ts} +96 -3
- package/src/Components/ScrapflyWebScrape.class.ts +7 -0
- package/src/Components/ServerlessCode.class.ts +1 -4
- package/src/Components/index.ts +3 -3
- package/src/config.ts +1 -0
- package/src/helpers/Conversation.helper.ts +112 -26
- package/src/helpers/S3Cache.helper.ts +2 -1
- package/src/helpers/TemplateString.helper.ts +6 -5
- package/src/index.ts +213 -212
- package/src/index.ts.bak +213 -212
- package/src/subsystems/IO/NKV.service/connectors/NKVRedis.class.ts +3 -1
- package/src/subsystems/IO/VectorDB.service/VectorDBConnector.ts +1 -0
- package/src/subsystems/IO/VectorDB.service/connectors/MilvusVectorDB.class.ts +145 -19
- package/src/subsystems/IO/VectorDB.service/connectors/PineconeVectorDB.class.ts +67 -22
- package/src/subsystems/IO/VectorDB.service/embed/GoogleEmbedding.ts +1 -0
- package/src/subsystems/IO/VectorDB.service/embed/OpenAIEmbedding.ts +2 -1
- package/src/subsystems/IO/VectorDB.service/embed/index.ts +16 -0
- package/src/subsystems/LLMManager/LLM.helper.ts +25 -0
- package/src/subsystems/LLMManager/LLM.service/LLMConnector.ts +1 -1
- package/src/subsystems/LLMManager/LLM.service/connectors/Anthropic.class.ts +35 -10
- package/src/subsystems/LLMManager/LLM.service/connectors/Bedrock.class.ts +12 -4
- package/src/subsystems/LLMManager/LLM.service/connectors/Echo.class.ts +4 -4
- package/src/subsystems/LLMManager/LLM.service/connectors/GoogleAI.class.ts +192 -139
- package/src/subsystems/LLMManager/LLM.service/connectors/Groq.class.ts +17 -5
- package/src/subsystems/LLMManager/LLM.service/connectors/Ollama.class.ts +18 -3
- package/src/subsystems/LLMManager/LLM.service/connectors/Perplexity.class.ts +14 -5
- package/src/subsystems/LLMManager/LLM.service/connectors/VertexAI.class.ts +6 -4
- package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ChatCompletionsApiInterface.ts +5 -5
- package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/ResponsesApiInterface.ts +8 -3
- package/src/subsystems/LLMManager/LLM.service/connectors/openai/apiInterfaces/utils.ts +1 -1
- package/src/subsystems/LLMManager/LLM.service/connectors/xAI.class.ts +9 -8
- package/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts +92 -1
- package/src/subsystems/ObservabilityManager/Telemetry.service/connectors/OTel/OTel.class.ts +260 -17
- package/src/subsystems/Security/Account.service/AccountConnector.ts +3 -3
- package/src/subsystems/Security/Vault.service/connectors/SecretsManager.class.ts +8 -63
- package/src/types/LLM.types.ts +24 -0
- package/src/types/VectorDB.types.ts +4 -0
- package/src/utils/array.utils.ts +11 -0
- package/src/utils/base64.utils.ts +1 -1
- package/src/utils/data.utils.ts +6 -4
- package/src/utils/string.utils.ts +3 -192
- package/src/Components/DataSourceCleaner.class.ts +0 -92
- package/src/Components/DataSourceIndexer.class.ts +0 -181
|
@@ -63,11 +63,8 @@ const VALID_MIME_TYPES = [
|
|
|
63
63
|
type UsageMetadataWithThoughtsToken = GenerateContentResponseUsageMetadata & { thoughtsTokenCount?: number; cost?: number };
|
|
64
64
|
|
|
65
65
|
const IMAGE_GEN_FIXED_PRICING = {
|
|
66
|
-
'imagen-3.0-generate-001': 0.04, // Fixed cost per image
|
|
67
|
-
'imagen-4.0-generate-001': 0.04, // Fixed cost per image
|
|
68
66
|
'imagen-4': 0.04, // Standard Imagen 4
|
|
69
67
|
'imagen-4-ultra': 0.06, // Imagen 4 Ultra
|
|
70
|
-
'gemini-2.5-flash-image': 0.039,
|
|
71
68
|
};
|
|
72
69
|
|
|
73
70
|
export class GoogleAIConnector extends LLMConnector {
|
|
@@ -129,9 +126,19 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
129
126
|
let useTool = false;
|
|
130
127
|
|
|
131
128
|
if (toolCalls && toolCalls.length > 0) {
|
|
129
|
+
// Extract the thoughtSignature from the first tool call (Google AI only attaches it to the first one)
|
|
130
|
+
const sharedThoughtSignature = (toolCalls[0] as any).thoughtSignature;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Unique ID per streamRequest call to prevent tool ID collisions.
|
|
134
|
+
* Without unique IDs, each call would generate "tool-0", causing UI merge conflicts.
|
|
135
|
+
* Example: tool-ABC123-0, tool-DEF456-0, tool-GHI789-0 (instead of all "tool-0")
|
|
136
|
+
*/
|
|
137
|
+
const requestId = uid();
|
|
138
|
+
|
|
132
139
|
toolsData = toolCalls.map((toolCall, index) => ({
|
|
133
140
|
index,
|
|
134
|
-
id: `tool-${index}`,
|
|
141
|
+
id: `tool-${requestId}-${index}`,
|
|
135
142
|
type: 'function',
|
|
136
143
|
name: toolCall.functionCall?.name,
|
|
137
144
|
arguments:
|
|
@@ -139,7 +146,8 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
139
146
|
? toolCall.functionCall?.args
|
|
140
147
|
: JSON.stringify(toolCall.functionCall?.args ?? {}),
|
|
141
148
|
role: TLLMMessageRole.Assistant,
|
|
142
|
-
|
|
149
|
+
// All parallel tool calls share the same thoughtSignature from the first one
|
|
150
|
+
thoughtSignature: (toolCall as any).thoughtSignature || sharedThoughtSignature,
|
|
143
151
|
}));
|
|
144
152
|
useTool = true;
|
|
145
153
|
}
|
|
@@ -182,54 +190,92 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
182
190
|
|
|
183
191
|
let toolsData: ToolData[] = [];
|
|
184
192
|
let usage: UsageMetadataWithThoughtsToken | undefined;
|
|
193
|
+
let streamThoughtSignature: string | undefined; // Track signature across streaming chunks
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Unique ID per streamRequest call to prevent tool ID collisions.
|
|
197
|
+
* Without unique IDs, each call would generate "tool-0", causing UI merge conflicts.
|
|
198
|
+
* Example: tool-ABC123-0, tool-DEF456-0, tool-GHI789-0 (instead of all "tool-0")
|
|
199
|
+
*/
|
|
200
|
+
const requestId = uid();
|
|
201
|
+
|
|
202
|
+
// Defer async processing to next tick to ensure event listeners are attached first
|
|
203
|
+
// This prevents race condition where fast tool calls emit events before listeners are ready
|
|
204
|
+
setImmediate(() => {
|
|
205
|
+
(async () => {
|
|
206
|
+
try {
|
|
207
|
+
for await (const chunk of stream) {
|
|
208
|
+
emitter.emit(TLLMEvent.Data, chunk);
|
|
209
|
+
|
|
210
|
+
const parts = chunk.candidates?.[0]?.content?.parts || [];
|
|
211
|
+
// Extract text from parts, filtering out non-text parts and ensuring type safety
|
|
212
|
+
const textParts = parts
|
|
213
|
+
.map((part) => part?.text)
|
|
214
|
+
.filter((text): text is string => typeof text === 'string')
|
|
215
|
+
.join('');
|
|
216
|
+
if (textParts) {
|
|
217
|
+
emitter.emit(TLLMEvent.Content, textParts);
|
|
218
|
+
}
|
|
185
219
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
220
|
+
const toolCalls = chunk.candidates?.[0]?.content?.parts?.filter((part) => part.functionCall);
|
|
221
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
222
|
+
// Capture thoughtSignature from the first tool call chunk if we haven't already
|
|
223
|
+
if (!streamThoughtSignature) {
|
|
224
|
+
streamThoughtSignature = (toolCalls[0] as any).thoughtSignature;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// For streaming, replace toolsData with the latest chunk (chunks contain cumulative tool calls)
|
|
228
|
+
// All tool calls in this request share the same requestId for uniqueness
|
|
229
|
+
toolsData = toolCalls.map((toolCall, index) => ({
|
|
230
|
+
index,
|
|
231
|
+
id: `tool-${requestId}-${index}`,
|
|
232
|
+
type: 'function' as const,
|
|
233
|
+
name: toolCall.functionCall?.name,
|
|
234
|
+
arguments:
|
|
235
|
+
typeof toolCall.functionCall?.args === 'string'
|
|
236
|
+
? toolCall.functionCall?.args
|
|
237
|
+
: JSON.stringify(toolCall.functionCall?.args ?? {}),
|
|
238
|
+
role: TLLMMessageRole.Assistant as any,
|
|
239
|
+
// All tool calls share the thoughtSignature from the first chunk
|
|
240
|
+
thoughtSignature: (toolCall as any).thoughtSignature || streamThoughtSignature,
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (chunk.usageMetadata) {
|
|
245
|
+
usage = chunk.usageMetadata as UsageMetadataWithThoughtsToken;
|
|
246
|
+
}
|
|
192
247
|
}
|
|
193
248
|
|
|
194
|
-
|
|
195
|
-
if (
|
|
196
|
-
toolsData = toolCalls.map((toolCall, index) => ({
|
|
197
|
-
index,
|
|
198
|
-
id: `tool-${index}`,
|
|
199
|
-
type: 'function',
|
|
200
|
-
name: toolCall.functionCall?.name,
|
|
201
|
-
arguments:
|
|
202
|
-
typeof toolCall.functionCall?.args === 'string'
|
|
203
|
-
? toolCall.functionCall?.args
|
|
204
|
-
: JSON.stringify(toolCall.functionCall?.args ?? {}),
|
|
205
|
-
role: TLLMMessageRole.Assistant,
|
|
206
|
-
thoughtSignature: (toolCall as any).thoughtSignature, // Preserve Google AI's reasoning context
|
|
207
|
-
}));
|
|
249
|
+
// Emit ToolInfo once after all chunks are processed (similar to Anthropic's finalMessage pattern)
|
|
250
|
+
if (toolsData.length > 0) {
|
|
208
251
|
emitter.emit(TLLMEvent.ToolInfo, toolsData);
|
|
209
252
|
}
|
|
210
253
|
|
|
211
|
-
|
|
212
|
-
|
|
254
|
+
const finishReason = 'stop'; // GoogleAI doesn't provide finishReason in streaming
|
|
255
|
+
const reportedUsage: any[] = [];
|
|
256
|
+
|
|
257
|
+
if (usage) {
|
|
258
|
+
const reported = this.reportUsage(usage, {
|
|
259
|
+
modelEntryName: context.modelEntryName,
|
|
260
|
+
keySource: context.isUserKey ? APIKeySource.User : APIKeySource.Smyth,
|
|
261
|
+
agentId: context.agentId,
|
|
262
|
+
teamId: context.teamId,
|
|
263
|
+
});
|
|
264
|
+
reportedUsage.push(reported);
|
|
213
265
|
}
|
|
214
|
-
}
|
|
215
266
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
modelEntryName: context.modelEntryName,
|
|
219
|
-
keySource: context.isUserKey ? APIKeySource.User : APIKeySource.Smyth,
|
|
220
|
-
agentId: context.agentId,
|
|
221
|
-
teamId: context.teamId,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
267
|
+
// Note: GoogleAI stream doesn't provide explicit finish reasons
|
|
268
|
+
// If we had a non-stop finish reason, we would emit Interrupted here
|
|
224
269
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
emitter.emit(TLLMEvent.End, toolsData, reportedUsage, finishReason);
|
|
272
|
+
}, 100);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
logger.error(`streamRequest ${this.name}`, error, acRequest.candidate);
|
|
275
|
+
emitter.emit(TLLMEvent.Error, error);
|
|
276
|
+
}
|
|
277
|
+
})();
|
|
278
|
+
});
|
|
233
279
|
|
|
234
280
|
return emitter;
|
|
235
281
|
} catch (error: any) {
|
|
@@ -285,12 +331,11 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
285
331
|
// https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-flash-image-preview
|
|
286
332
|
const usageMetadata = response?.usageMetadata as UsageMetadataWithThoughtsToken;
|
|
287
333
|
|
|
288
|
-
this.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
context,
|
|
334
|
+
this.reportUsage(usageMetadata, {
|
|
335
|
+
modelEntryName: context.modelEntryName,
|
|
336
|
+
keySource: context.isUserKey ? APIKeySource.User : APIKeySource.Smyth,
|
|
337
|
+
agentId: context.agentId,
|
|
338
|
+
teamId: context.teamId,
|
|
294
339
|
});
|
|
295
340
|
|
|
296
341
|
if (imageData.length === 0) {
|
|
@@ -313,14 +358,23 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
313
358
|
// Report input tokens and image cost pricing based on the official pricing page:
|
|
314
359
|
// https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-flash-image-preview
|
|
315
360
|
const usageMetadata = response?.usageMetadata as UsageMetadataWithThoughtsToken;
|
|
316
|
-
|
|
317
|
-
|
|
361
|
+
|
|
362
|
+
const isImagen4 = modelName.startsWith('imagen-4');
|
|
363
|
+
|
|
364
|
+
if (isImagen4) {
|
|
365
|
+
this.reportImageCost({
|
|
318
366
|
cost: IMAGE_GEN_FIXED_PRICING[modelName],
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
367
|
+
numberOfImages: config.numberOfImages,
|
|
368
|
+
context,
|
|
369
|
+
});
|
|
370
|
+
} else {
|
|
371
|
+
this.reportUsage(usageMetadata, {
|
|
372
|
+
modelEntryName: context.modelEntryName,
|
|
373
|
+
keySource: context.isUserKey ? APIKeySource.User : APIKeySource.Smyth,
|
|
374
|
+
agentId: context.agentId,
|
|
375
|
+
teamId: context.teamId,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
324
378
|
|
|
325
379
|
return {
|
|
326
380
|
created: Math.floor(Date.now() / 1000),
|
|
@@ -347,7 +401,6 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
347
401
|
}
|
|
348
402
|
|
|
349
403
|
const ai = new GoogleGenAI({ apiKey });
|
|
350
|
-
const modelName = context.modelEntryName.replace(BUILT_IN_MODEL_PREFIX, '');
|
|
351
404
|
|
|
352
405
|
// Use the prepared body which already contains processed files and contents
|
|
353
406
|
const response = await ai.models.generateContent({
|
|
@@ -372,12 +425,11 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
372
425
|
// Report pricing for input tokens and image costs
|
|
373
426
|
const usageMetadata = response?.usageMetadata as UsageMetadataWithThoughtsToken;
|
|
374
427
|
|
|
375
|
-
this.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
context,
|
|
428
|
+
this.reportUsage(usageMetadata, {
|
|
429
|
+
modelEntryName: context.modelEntryName,
|
|
430
|
+
keySource: context.isUserKey ? APIKeySource.User : APIKeySource.Smyth,
|
|
431
|
+
agentId: context.agentId,
|
|
432
|
+
teamId: context.teamId,
|
|
381
433
|
});
|
|
382
434
|
|
|
383
435
|
return {
|
|
@@ -519,7 +571,7 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
519
571
|
let inputTokens = usage?.promptTokenCount || 0;
|
|
520
572
|
|
|
521
573
|
// The pricing is the same for output and thinking tokens, so we can add them together.
|
|
522
|
-
|
|
574
|
+
let outputTokens = (usage?.candidatesTokenCount || 0) + (usage?.thoughtsTokenCount || 0);
|
|
523
575
|
|
|
524
576
|
// If cached input tokens are available, we need to subtract them from the input tokens.
|
|
525
577
|
let cachedInputTokens = usage?.cachedContentTokenCount || 0;
|
|
@@ -535,15 +587,11 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
535
587
|
'gemini-3-pro': 200_000,
|
|
536
588
|
};
|
|
537
589
|
|
|
538
|
-
let
|
|
539
|
-
let outTier = '';
|
|
540
|
-
let crTier = '';
|
|
590
|
+
let tier = '';
|
|
541
591
|
|
|
542
592
|
const modelWithTier = Object.keys(tierThresholds).find((model) => modelName.includes(model));
|
|
543
593
|
if (modelWithTier) {
|
|
544
|
-
|
|
545
|
-
outTier = outputTokens <= tierThresholds[modelWithTier] ? 'tier1' : 'tier2';
|
|
546
|
-
crTier = cachedInputTokens <= tierThresholds[modelWithTier] ? 'tier1' : 'tier2';
|
|
594
|
+
tier = inputTokens <= tierThresholds[modelWithTier] ? 'tier1' : 'tier2';
|
|
547
595
|
}
|
|
548
596
|
// #endregion
|
|
549
597
|
|
|
@@ -551,7 +599,7 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
551
599
|
// Since Gemini 2.5 Flash has a different pricing model for audio input tokens, we need to report audio input tokens separately.
|
|
552
600
|
let audioInputTokens = 0;
|
|
553
601
|
let cachedAudioInputTokens = 0;
|
|
554
|
-
const isFlashModel =
|
|
602
|
+
const isFlashModel = modelName.includes('flash');
|
|
555
603
|
|
|
556
604
|
if (isFlashModel) {
|
|
557
605
|
// There is no concept of different pricing for Flash models based on token tiers (e.g., less than or greater than 200k),
|
|
@@ -569,10 +617,20 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
569
617
|
}
|
|
570
618
|
// #endregion
|
|
571
619
|
|
|
620
|
+
// #region Calculate image tokens
|
|
621
|
+
const imageOutputTokens = usage?.candidatesTokensDetails?.find((detail) => detail.modality === 'IMAGE')?.tokenCount || 0;
|
|
622
|
+
|
|
623
|
+
// Gemini models does not return output text tokens right now for Image Generation, so we need to subtract the output image tokens from the output tokens to get the output text tokens.
|
|
624
|
+
if (imageOutputTokens) {
|
|
625
|
+
outputTokens = outputTokens - imageOutputTokens;
|
|
626
|
+
}
|
|
627
|
+
// #endregion Calculate image tokens
|
|
628
|
+
|
|
572
629
|
const usageData = {
|
|
573
630
|
sourceId: `llm:${modelName}`,
|
|
574
631
|
input_tokens: inputTokens,
|
|
575
632
|
output_tokens: outputTokens,
|
|
633
|
+
output_tokens_image: imageOutputTokens,
|
|
576
634
|
input_tokens_audio: audioInputTokens,
|
|
577
635
|
input_tokens_cache_read: cachedInputTokens,
|
|
578
636
|
input_tokens_cache_read_audio: cachedAudioInputTokens,
|
|
@@ -581,9 +639,7 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
581
639
|
keySource: metadata.keySource,
|
|
582
640
|
agentId: metadata.agentId,
|
|
583
641
|
teamId: metadata.teamId,
|
|
584
|
-
|
|
585
|
-
outTier,
|
|
586
|
-
crTier,
|
|
642
|
+
tier,
|
|
587
643
|
};
|
|
588
644
|
SystemEvents.emit('USAGE:LLM', usageData);
|
|
589
645
|
|
|
@@ -600,32 +656,12 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
600
656
|
return { textTokens, imageTokens };
|
|
601
657
|
}
|
|
602
658
|
|
|
603
|
-
protected
|
|
604
|
-
usage,
|
|
605
|
-
context,
|
|
606
|
-
numberOfImages = 1,
|
|
607
|
-
}: {
|
|
608
|
-
usage: { cost?: number; usageMetadata?: UsageMetadataWithThoughtsToken };
|
|
609
|
-
context: ILLMRequestContext;
|
|
610
|
-
numberOfImages?: number;
|
|
611
|
-
}) {
|
|
612
|
-
// Extract text and image tokens from rawUsage if available
|
|
613
|
-
let input_tokens_txt = 0;
|
|
614
|
-
let input_tokens_img = 0;
|
|
615
|
-
|
|
616
|
-
if (usage.usageMetadata) {
|
|
617
|
-
const { textTokens, imageTokens } = this.extractTokenCounts(usage.usageMetadata);
|
|
618
|
-
input_tokens_txt = textTokens;
|
|
619
|
-
input_tokens_img = imageTokens;
|
|
620
|
-
}
|
|
621
|
-
|
|
659
|
+
protected reportImageCost({ cost, context, numberOfImages = 1 }) {
|
|
622
660
|
const imageUsageData = {
|
|
623
661
|
sourceId: `api:imagegen.smyth`,
|
|
624
662
|
keySource: context.isUserKey ? APIKeySource.User : APIKeySource.Smyth,
|
|
625
663
|
|
|
626
|
-
cost:
|
|
627
|
-
input_tokens_txt,
|
|
628
|
-
input_tokens_img,
|
|
664
|
+
cost: cost * numberOfImages,
|
|
629
665
|
|
|
630
666
|
agentId: context.agentId,
|
|
631
667
|
teamId: context.teamId,
|
|
@@ -633,6 +669,39 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
633
669
|
SystemEvents.emit('USAGE:API', imageUsageData);
|
|
634
670
|
}
|
|
635
671
|
|
|
672
|
+
/**
|
|
673
|
+
* Normalizes function response values to ensure they conform to Google AI's STRUCT requirement.
|
|
674
|
+
* Gemini expects functionResponse.response to be a STRUCT (JSON object format), not a list or scalar.
|
|
675
|
+
*/
|
|
676
|
+
private normalizeFunctionResponse(value: unknown): any {
|
|
677
|
+
// Return objects as-is (but not arrays, which are also objects in JS)
|
|
678
|
+
if (value !== null && value !== undefined && typeof value === 'object' && !Array.isArray(value)) {
|
|
679
|
+
return value;
|
|
680
|
+
}
|
|
681
|
+
// Wrap all other types (arrays, scalars, null, undefined) in result key
|
|
682
|
+
return { result: value ?? null };
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Parses and normalizes function response values, handling string JSON and various data types.
|
|
687
|
+
*/
|
|
688
|
+
private parseFunctionResponse(response: unknown): any {
|
|
689
|
+
if (typeof response === 'string') {
|
|
690
|
+
try {
|
|
691
|
+
const parsed = JSON.parse(response);
|
|
692
|
+
// If parsed result is still a string, try parsing again (handles double-stringified JSON)
|
|
693
|
+
if (typeof parsed === 'string' && parsed !== response) {
|
|
694
|
+
return this.parseFunctionResponse(parsed);
|
|
695
|
+
}
|
|
696
|
+
return this.normalizeFunctionResponse(parsed);
|
|
697
|
+
} catch (error) {
|
|
698
|
+
// If parsing fails, wrap the string in an object to satisfy Google AI's Struct requirement
|
|
699
|
+
return { result: response };
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return this.normalizeFunctionResponse(response);
|
|
703
|
+
}
|
|
704
|
+
|
|
636
705
|
public formatToolsConfig({ toolDefinitions, toolChoice = 'auto' }) {
|
|
637
706
|
const tools = toolDefinitions.map((tool) => {
|
|
638
707
|
const { name, description, properties, requiredFields } = tool;
|
|
@@ -686,23 +755,10 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
686
755
|
return args ?? {};
|
|
687
756
|
};
|
|
688
757
|
|
|
689
|
-
|
|
690
|
-
if (typeof response === 'string') {
|
|
691
|
-
try {
|
|
692
|
-
const parsed = JSON.parse(response);
|
|
693
|
-
if (typeof parsed === 'string' && parsed !== response) {
|
|
694
|
-
return parseFunctionResponse(parsed);
|
|
695
|
-
}
|
|
696
|
-
return parsed;
|
|
697
|
-
} catch {
|
|
698
|
-
return response;
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
return response ?? {};
|
|
702
|
-
};
|
|
703
|
-
|
|
758
|
+
//#region Function call parts
|
|
704
759
|
if (messageBlock) {
|
|
705
760
|
const content: any[] = [];
|
|
761
|
+
let partFunctionCallIndex = 0; // Track function calls within this message block
|
|
706
762
|
|
|
707
763
|
if (Array.isArray(messageBlock.parts) && messageBlock.parts.length > 0) {
|
|
708
764
|
for (const part of messageBlock.parts) {
|
|
@@ -720,11 +776,12 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
720
776
|
args: parseFunctionArgs(part.functionCall.args),
|
|
721
777
|
},
|
|
722
778
|
};
|
|
723
|
-
//
|
|
724
|
-
if ((part as any).thoughtSignature) {
|
|
779
|
+
// Only the first function call part should have the thoughtSignature (Google AI requirement)
|
|
780
|
+
if (partFunctionCallIndex === 0 && (part as any).thoughtSignature) {
|
|
725
781
|
functionCallPart.thoughtSignature = (part as any).thoughtSignature;
|
|
726
782
|
}
|
|
727
783
|
content.push(functionCallPart);
|
|
784
|
+
partFunctionCallIndex++;
|
|
728
785
|
continue;
|
|
729
786
|
}
|
|
730
787
|
|
|
@@ -732,7 +789,7 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
732
789
|
content.push({
|
|
733
790
|
functionResponse: {
|
|
734
791
|
name: part.functionResponse.name,
|
|
735
|
-
response: parseFunctionResponse(part.functionResponse.response),
|
|
792
|
+
response: this.parseFunctionResponse(part.functionResponse.response),
|
|
736
793
|
},
|
|
737
794
|
});
|
|
738
795
|
continue;
|
|
@@ -752,15 +809,15 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
752
809
|
|
|
753
810
|
const hasFunctionCall = content.some((part) => part.functionCall);
|
|
754
811
|
if (!hasFunctionCall && toolsData.length > 0) {
|
|
755
|
-
toolsData.forEach((toolCall) => {
|
|
812
|
+
toolsData.forEach((toolCall, index) => {
|
|
756
813
|
const functionCallPart: any = {
|
|
757
814
|
functionCall: {
|
|
758
815
|
name: toolCall.name,
|
|
759
816
|
args: parseFunctionArgs(toolCall.arguments),
|
|
760
817
|
},
|
|
761
818
|
};
|
|
762
|
-
//
|
|
763
|
-
if (toolCall.thoughtSignature) {
|
|
819
|
+
// Only the first function call part should have the thoughtSignature (Google AI requirement)
|
|
820
|
+
if (index === 0 && toolCall.thoughtSignature) {
|
|
764
821
|
functionCallPart.thoughtSignature = toolCall.thoughtSignature;
|
|
765
822
|
}
|
|
766
823
|
content.push(functionCallPart);
|
|
@@ -779,13 +836,15 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
779
836
|
});
|
|
780
837
|
}
|
|
781
838
|
}
|
|
839
|
+
//#endregion Function call parts
|
|
782
840
|
|
|
841
|
+
//#region Function response parts
|
|
783
842
|
const functionResponseParts = toolsData
|
|
784
843
|
.filter((toolData) => toolData.result !== undefined)
|
|
785
844
|
.map((toolData) => ({
|
|
786
845
|
functionResponse: {
|
|
787
846
|
name: toolData.name,
|
|
788
|
-
response: parseFunctionResponse(toolData.result),
|
|
847
|
+
response: this.parseFunctionResponse(toolData.result),
|
|
789
848
|
},
|
|
790
849
|
}));
|
|
791
850
|
|
|
@@ -795,6 +854,7 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
795
854
|
parts: functionResponseParts,
|
|
796
855
|
});
|
|
797
856
|
}
|
|
857
|
+
//#endregion Function response parts
|
|
798
858
|
|
|
799
859
|
return messageBlocks;
|
|
800
860
|
}
|
|
@@ -817,18 +877,6 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
817
877
|
return args ?? {};
|
|
818
878
|
};
|
|
819
879
|
|
|
820
|
-
const parseFunctionResponse = (response: unknown) => {
|
|
821
|
-
if (typeof response === 'string') {
|
|
822
|
-
try {
|
|
823
|
-
return JSON.parse(response);
|
|
824
|
-
} catch {
|
|
825
|
-
return response;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
return response;
|
|
830
|
-
};
|
|
831
|
-
|
|
832
880
|
const pushTextPart = (parts: any[], text?: string) => {
|
|
833
881
|
const value = typeof text === 'string' && text.trim() ? text : undefined;
|
|
834
882
|
if (value) {
|
|
@@ -837,6 +885,7 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
837
885
|
};
|
|
838
886
|
|
|
839
887
|
const normalizedParts: any[] = [];
|
|
888
|
+
let functionCallCount = 0; // Track function call parts for thoughtSignature handling
|
|
840
889
|
|
|
841
890
|
// Map roles to valid Google AI roles
|
|
842
891
|
switch (_message.role) {
|
|
@@ -870,16 +919,17 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
870
919
|
name: part.functionCall.name,
|
|
871
920
|
args: parseFunctionArgs(part.functionCall.args),
|
|
872
921
|
};
|
|
873
|
-
//
|
|
874
|
-
if ((part as any).thoughtSignature) {
|
|
922
|
+
// Only the first function call part should have the thoughtSignature (Google AI requirement)
|
|
923
|
+
if (functionCallCount === 0 && (part as any).thoughtSignature) {
|
|
875
924
|
normalizedPart.thoughtSignature = (part as any).thoughtSignature;
|
|
876
925
|
}
|
|
926
|
+
functionCallCount++;
|
|
877
927
|
}
|
|
878
928
|
|
|
879
929
|
if (part.functionResponse) {
|
|
880
930
|
normalizedPart.functionResponse = {
|
|
881
931
|
name: part.functionResponse.name,
|
|
882
|
-
response: parseFunctionResponse(part.functionResponse.response),
|
|
932
|
+
response: this.parseFunctionResponse(part.functionResponse.response),
|
|
883
933
|
};
|
|
884
934
|
}
|
|
885
935
|
|
|
@@ -908,17 +958,18 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
908
958
|
args: parseFunctionArgs(functionCallPart.args),
|
|
909
959
|
},
|
|
910
960
|
};
|
|
911
|
-
//
|
|
912
|
-
if ((contentPart as any).thoughtSignature) {
|
|
961
|
+
// Only the first function call part should have the thoughtSignature (Google AI requirement)
|
|
962
|
+
if (functionCallCount === 0 && (contentPart as any).thoughtSignature) {
|
|
913
963
|
normalizedFunctionCall.thoughtSignature = (contentPart as any).thoughtSignature;
|
|
914
964
|
}
|
|
915
965
|
normalizedParts.push(normalizedFunctionCall);
|
|
966
|
+
functionCallCount++;
|
|
916
967
|
} else if ('functionResponse' in contentPart && (contentPart as any).functionResponse) {
|
|
917
968
|
const functionResponsePart = (contentPart as any).functionResponse;
|
|
918
969
|
normalizedParts.push({
|
|
919
970
|
functionResponse: {
|
|
920
971
|
name: functionResponsePart.name,
|
|
921
|
-
response: parseFunctionResponse(functionResponsePart.response),
|
|
972
|
+
response: this.parseFunctionResponse(functionResponsePart.response),
|
|
922
973
|
},
|
|
923
974
|
});
|
|
924
975
|
} else {
|
|
@@ -947,6 +998,7 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
947
998
|
}
|
|
948
999
|
|
|
949
1000
|
if (Array.isArray(message?.tool_calls) && message.tool_calls.length > 0) {
|
|
1001
|
+
let functionCallIndex = 0;
|
|
950
1002
|
for (const toolCall of message.tool_calls) {
|
|
951
1003
|
if (!toolCall?.function?.name) continue;
|
|
952
1004
|
|
|
@@ -956,11 +1008,12 @@ export class GoogleAIConnector extends LLMConnector {
|
|
|
956
1008
|
args: parseFunctionArgs(toolCall.function.arguments),
|
|
957
1009
|
},
|
|
958
1010
|
};
|
|
959
|
-
//
|
|
960
|
-
if ((toolCall as any).thoughtSignature) {
|
|
1011
|
+
// Only the first function call part should have the thoughtSignature (Google AI requirement)
|
|
1012
|
+
if (functionCallIndex === 0 && (toolCall as any).thoughtSignature) {
|
|
961
1013
|
normalizedFunctionCall.thoughtSignature = (toolCall as any).thoughtSignature;
|
|
962
1014
|
}
|
|
963
1015
|
normalizedParts.push(normalizedFunctionCall);
|
|
1016
|
+
functionCallIndex++;
|
|
964
1017
|
}
|
|
965
1018
|
}
|
|
966
1019
|
|
|
@@ -108,6 +108,7 @@ export class GroqConnector extends LLMConnector {
|
|
|
108
108
|
const stream = await groq.chat.completions.create({ ...body, stream: true, stream_options: { include_usage: true } });
|
|
109
109
|
|
|
110
110
|
let toolsData: ToolData[] = [];
|
|
111
|
+
let finishReason = 'stop';
|
|
111
112
|
|
|
112
113
|
(async () => {
|
|
113
114
|
for await (const chunk of stream as any) {
|
|
@@ -117,10 +118,10 @@ export class GroqConnector extends LLMConnector {
|
|
|
117
118
|
if (usage) {
|
|
118
119
|
usage_data.push(usage);
|
|
119
120
|
}
|
|
120
|
-
emitter.emit(
|
|
121
|
+
emitter.emit(TLLMEvent.Data, delta);
|
|
121
122
|
|
|
122
123
|
if (delta?.content) {
|
|
123
|
-
emitter.emit(
|
|
124
|
+
emitter.emit(TLLMEvent.Content, delta.content);
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
if (delta?.tool_calls) {
|
|
@@ -139,24 +140,35 @@ export class GroqConnector extends LLMConnector {
|
|
|
139
140
|
}
|
|
140
141
|
});
|
|
141
142
|
}
|
|
143
|
+
|
|
144
|
+
// Capture finish reason
|
|
145
|
+
if (chunk.choices[0]?.finish_reason) {
|
|
146
|
+
finishReason = chunk.choices[0].finish_reason;
|
|
147
|
+
}
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
if (toolsData.length > 0) {
|
|
145
151
|
emitter.emit(TLLMEvent.ToolInfo, toolsData);
|
|
146
152
|
}
|
|
147
153
|
|
|
154
|
+
const reportedUsage: any[] = [];
|
|
148
155
|
usage_data.forEach((usage) => {
|
|
149
|
-
|
|
150
|
-
this.reportUsage(usage, {
|
|
156
|
+
const reported = this.reportUsage(usage, {
|
|
151
157
|
modelEntryName: context.modelEntryName,
|
|
152
158
|
keySource: context.isUserKey ? APIKeySource.User : APIKeySource.Smyth,
|
|
153
159
|
agentId: context.agentId,
|
|
154
160
|
teamId: context.teamId,
|
|
155
161
|
});
|
|
162
|
+
reportedUsage.push(reported);
|
|
156
163
|
});
|
|
157
164
|
|
|
165
|
+
// Emit interrupted event if finishReason is not 'stop'
|
|
166
|
+
if (finishReason !== 'stop') {
|
|
167
|
+
emitter.emit(TLLMEvent.Interrupted, finishReason);
|
|
168
|
+
}
|
|
169
|
+
|
|
158
170
|
setTimeout(() => {
|
|
159
|
-
emitter.emit(
|
|
171
|
+
emitter.emit(TLLMEvent.End, toolsData, reportedUsage, finishReason);
|
|
160
172
|
}, 100);
|
|
161
173
|
})();
|
|
162
174
|
|